2009-08-16 17:00:08 +02:00
|
|
|
/* Manage pipelines of partial images.
|
|
|
|
*
|
|
|
|
* J.Cupitt, 17/4/93.
|
|
|
|
* 1/7/93 JC
|
|
|
|
* - adapted for partial v2
|
|
|
|
* 9/5/94
|
|
|
|
* - new thread stuff added, with a define to turn it off
|
|
|
|
* 21/11/94 JC
|
|
|
|
* - pw and ph wrong way round!
|
|
|
|
* 24/5/95 JC
|
|
|
|
* - redone, now works in pipelines!
|
|
|
|
* 30/8/96 JC
|
|
|
|
* - more sharing with im_generate()
|
|
|
|
* 2/3/98 JC
|
|
|
|
* - IM_ANY added
|
|
|
|
* 19/1/99 JC
|
|
|
|
* - oops, threads were broken :(
|
|
|
|
* 30/7/99 RP JC
|
|
|
|
* - threads reorganised for POSIX
|
|
|
|
* 25/10/03 JC
|
|
|
|
* - read via a buffer image so we work with mmap window images
|
|
|
|
* 27/11/06
|
|
|
|
* - merge threadgroup stuff
|
|
|
|
* 7/11/07
|
|
|
|
* - new eval start/progress/end system
|
2009-10-07 23:15:29 +02:00
|
|
|
* 7/10/09
|
|
|
|
* - gtkdoc comments
|
2009-10-15 16:22:23 +02:00
|
|
|
* 15/10/09
|
|
|
|
* - call start and stop functions from the worker threads
|
|
|
|
* - reworked a bit
|
2009-08-16 17:00:08 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
This file is part of VIPS.
|
|
|
|
|
|
|
|
VIPS is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
2009-10-15 16:22:23 +02:00
|
|
|
#define DEBUG
|
2009-08-16 17:00:08 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include <config.h>
|
|
|
|
#endif /*HAVE_CONFIG_H*/
|
|
|
|
#include <vips/intl.h>
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#include <vips/vips.h>
|
2010-03-05 21:21:06 +01:00
|
|
|
#include <vips/thread.h>
|
2009-08-16 17:00:08 +02:00
|
|
|
#include <vips/internal.h>
|
2009-10-03 16:15:24 +02:00
|
|
|
#include <vips/debug.h>
|
2009-08-16 17:00:08 +02:00
|
|
|
|
|
|
|
#ifdef WITH_DMALLOC
|
|
|
|
#include <dmalloc.h>
|
|
|
|
#endif /*WITH_DMALLOC*/
|
|
|
|
|
2009-10-14 12:25:18 +02:00
|
|
|
/* Track this stuff during an im_iterate().
|
|
|
|
*/
|
|
|
|
typedef struct _Iterate {
|
|
|
|
IMAGE *im;
|
|
|
|
|
|
|
|
/* We need a temp "p" image between the source image and us to
|
|
|
|
* make sure we can't damage the original.
|
|
|
|
*/
|
|
|
|
IMAGE *t;
|
|
|
|
|
|
|
|
/* Store our sequence values in tg->thr[i]->a. The seq values in the
|
|
|
|
* regions are used by the im_copy() to t.
|
|
|
|
*/
|
|
|
|
im_threadgroup_t *tg;
|
|
|
|
|
|
|
|
im_start_fn start;
|
|
|
|
im_generate_fn generate;
|
|
|
|
im_stop_fn stop;
|
|
|
|
void *b;
|
|
|
|
void *c;
|
2009-10-15 16:22:23 +02:00
|
|
|
|
|
|
|
/* Set this to signal to workers that we want to shut down sequences.
|
|
|
|
*/
|
|
|
|
gboolean shutdown;
|
2009-10-14 12:25:18 +02:00
|
|
|
} Iterate;
|
|
|
|
|
2009-10-15 16:22:23 +02:00
|
|
|
/* Call a thread's stop function.
|
2009-10-14 12:25:18 +02:00
|
|
|
*/
|
|
|
|
static int
|
2009-10-15 16:22:23 +02:00
|
|
|
iterate_call_stop( Iterate *iter, im_thread_t *thr )
|
2009-10-14 12:25:18 +02:00
|
|
|
{
|
2009-10-15 16:22:23 +02:00
|
|
|
#ifdef DEBUG
|
|
|
|
printf( "iterate_call_stop: thr = %p\n", thr );
|
|
|
|
#endif /*DEBUG*/
|
2009-10-14 12:25:18 +02:00
|
|
|
|
2009-10-15 16:22:23 +02:00
|
|
|
if( thr->a && iter->stop ) {
|
|
|
|
if( iter->stop( thr->a, iter->b, iter->c ) ) {
|
|
|
|
im_error( "im_iterate",
|
|
|
|
_( "stop function failed "
|
|
|
|
"for image \"%s\"" ),
|
|
|
|
iter->im->filename );
|
|
|
|
return( -1 );
|
2009-10-14 12:25:18 +02:00
|
|
|
}
|
2009-10-15 16:22:23 +02:00
|
|
|
|
|
|
|
thr->a = NULL;
|
2009-10-14 12:25:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|
|
|
2009-10-15 16:22:23 +02:00
|
|
|
static void
|
|
|
|
iterate_call_all_stop( Iterate *iter )
|
|
|
|
{
|
|
|
|
im_threadgroup_t *tg = iter->tg;
|
|
|
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
printf( "iterate_call_all_stop: start\n" );
|
|
|
|
#endif /*DEBUG*/
|
|
|
|
|
|
|
|
/* Wait for all threads to hit 'go'.
|
|
|
|
*/
|
|
|
|
im_threadgroup_wait( tg );
|
|
|
|
|
|
|
|
/* Get all threads off idle and waiting for work.
|
|
|
|
*/
|
|
|
|
for( i = 0; i < tg->nthr; i++ )
|
|
|
|
(void) im_threadgroup_get( tg );
|
|
|
|
|
|
|
|
/* Now run all threads one more time and ask them to junk their
|
|
|
|
* sequence value. 'shutdown' changes the behaviour of the work
|
|
|
|
* function, see below.
|
|
|
|
*/
|
|
|
|
iter->shutdown = TRUE;
|
|
|
|
|
|
|
|
for( i = 0; i < tg->nthr; i++ ) {
|
|
|
|
/* The work fn will need this set if it's not been run
|
|
|
|
* before.
|
|
|
|
*/
|
|
|
|
tg->thr[i]->b = iter;
|
|
|
|
im_threadgroup_trigger( tg->thr[i] );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* And wait for them to idle again.
|
|
|
|
*/
|
|
|
|
im_threadgroup_wait( tg );
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
|
|
printf( "iterate_call_all_stop: done\n" );
|
|
|
|
#endif /*DEBUG*/
|
|
|
|
}
|
|
|
|
|
2009-10-14 12:25:18 +02:00
|
|
|
static void
|
|
|
|
iterate_free( Iterate *iter )
|
|
|
|
{
|
2009-10-15 16:22:23 +02:00
|
|
|
/* All threads should have junked their sequence values.
|
2009-10-14 12:25:18 +02:00
|
|
|
*/
|
|
|
|
if( iter->tg ) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for( i = 0; i < iter->tg->nthr; i++ )
|
|
|
|
g_assert( !iter->tg->thr[i]->a );
|
|
|
|
}
|
|
|
|
|
|
|
|
IM_FREEF( im_threadgroup_free, iter->tg );
|
|
|
|
IM_FREEF( im_close, iter->t );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Call the start function for this thread, if necessary.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
iterate_call_start( Iterate *iter, im_thread_t *thr )
|
|
|
|
{
|
|
|
|
if( !thr->a && iter->start ) {
|
|
|
|
g_mutex_lock( iter->t->sslock );
|
|
|
|
thr->a = iter->start( iter->t, iter->b, iter->c );
|
|
|
|
g_mutex_unlock( iter->t->sslock );
|
|
|
|
|
|
|
|
if( !thr->a ) {
|
|
|
|
im_error( "im_iterate",
|
|
|
|
_( "start function failed for image \"%s\"" ),
|
|
|
|
iter->im->filename );
|
|
|
|
return( -1 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|
|
|
2009-10-15 16:22:23 +02:00
|
|
|
/* Our work function. This is complicated because we need to make sure we
|
|
|
|
* all call our start and stop functions from the worker thread, so that any
|
|
|
|
* regions created are owned by the worker and not the main thread.
|
2009-10-14 12:25:18 +02:00
|
|
|
*/
|
|
|
|
static int
|
2009-10-15 16:22:23 +02:00
|
|
|
iterate_work( im_thread_t *thr, REGION *reg, void *seq, void *a, void *b )
|
2009-10-14 12:25:18 +02:00
|
|
|
{
|
|
|
|
Iterate *iter = (Iterate *) a;
|
|
|
|
|
2009-10-15 16:22:23 +02:00
|
|
|
/* 'shutdown' is set at the end of computation to ask workers to free
|
|
|
|
* context.
|
2009-10-14 12:25:18 +02:00
|
|
|
*/
|
2009-10-15 16:22:23 +02:00
|
|
|
if( iter->shutdown ) {
|
|
|
|
if( iterate_call_stop( iter, thr ) )
|
|
|
|
return( -1 );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* Make sure the start function has run and we have the
|
|
|
|
* sequence value set.
|
|
|
|
*/
|
|
|
|
iterate_call_start( iter, thr );
|
|
|
|
seq = thr->a;
|
|
|
|
|
|
|
|
/* thr pos needs to be set before coming here ... check.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
Rect image;
|
|
|
|
|
|
|
|
image.left = 0;
|
|
|
|
image.top = 0;
|
|
|
|
image.width = thr->tg->im->Xsize;
|
|
|
|
image.height = thr->tg->im->Ysize;
|
|
|
|
|
|
|
|
g_assert( im_rect_includesrect( &image, &thr->pos ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Generate pixels for the user.
|
|
|
|
*/
|
|
|
|
if( im_prepare( reg, &thr->pos ) )
|
|
|
|
return( -1 );
|
|
|
|
|
|
|
|
/* Run the user code on this area.
|
|
|
|
*/
|
|
|
|
if( iter->generate( reg, seq, iter->b, iter->c ) )
|
|
|
|
return( -1 );
|
|
|
|
}
|
2009-10-14 12:25:18 +02:00
|
|
|
|
2009-10-15 16:22:23 +02:00
|
|
|
return( 0 );
|
2009-10-14 12:25:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
iterate_init( Iterate *iter,
|
|
|
|
IMAGE *im,
|
|
|
|
im_start_fn start, im_generate_fn generate, im_stop_fn stop,
|
|
|
|
void *b, void *c )
|
|
|
|
{
|
|
|
|
iter->im = im;
|
|
|
|
iter->t = NULL;
|
|
|
|
iter->tg = NULL;
|
|
|
|
iter->start = start;
|
|
|
|
iter->generate = generate;
|
|
|
|
iter->stop = stop;
|
|
|
|
iter->b = b;
|
|
|
|
iter->c = c;
|
2009-10-15 16:22:23 +02:00
|
|
|
iter->shutdown = FALSE;
|
2009-10-14 12:25:18 +02:00
|
|
|
|
|
|
|
if( !(iter->t = im_open( "iterate", "p" )) ||
|
|
|
|
im_copy( iter->im, iter->t ) ||
|
|
|
|
!(iter->tg = im_threadgroup_create( iter->t )) ) {
|
|
|
|
iterate_free( iter );
|
|
|
|
return( -1 );
|
|
|
|
}
|
|
|
|
|
2009-10-15 16:22:23 +02:00
|
|
|
iter->tg->work = iterate_work;
|
2009-10-14 12:25:18 +02:00
|
|
|
|
2009-10-15 16:22:23 +02:00
|
|
|
#ifdef DEBUG
|
|
|
|
im_diag( "im_iterate", "using %d threads", iter->tg->nthr );
|
|
|
|
#endif /*DEBUG*/
|
2009-10-14 12:25:18 +02:00
|
|
|
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|
|
|
2009-08-16 17:00:08 +02:00
|
|
|
/* Loop over an image, preparing in parts with threads.
|
|
|
|
*/
|
|
|
|
static int
|
2009-10-14 12:25:18 +02:00
|
|
|
iterate_loop( Iterate *iter, im_threadgroup_t *tg, IMAGE *t )
|
2009-08-16 17:00:08 +02:00
|
|
|
{
|
|
|
|
int x, y;
|
|
|
|
Rect image;
|
|
|
|
|
|
|
|
image.left = 0;
|
|
|
|
image.top = 0;
|
2009-10-14 12:25:18 +02:00
|
|
|
image.width = t->Xsize;
|
|
|
|
image.height = t->Ysize;
|
2009-08-16 17:00:08 +02:00
|
|
|
|
|
|
|
/* Loop over or, attaching to all sub-parts in turn.
|
|
|
|
*/
|
2009-10-14 12:25:18 +02:00
|
|
|
for( y = 0; y < t->Ysize; y += tg->ph )
|
|
|
|
for( x = 0; x < t->Xsize; x += tg->pw ) {
|
2009-08-16 17:00:08 +02:00
|
|
|
im_thread_t *thr;
|
|
|
|
Rect pos;
|
|
|
|
Rect clipped;
|
|
|
|
|
|
|
|
/* thrs appear on idle when the child thread does
|
|
|
|
* threadgroup_idle_add and hits the 'go' semaphore.
|
|
|
|
*/
|
|
|
|
thr = im_threadgroup_get( tg );
|
|
|
|
|
|
|
|
/* Set the position we want to generate with this
|
|
|
|
* thread.
|
|
|
|
*/
|
|
|
|
pos.left = x;
|
|
|
|
pos.top = y;
|
|
|
|
pos.width = tg->pw;
|
|
|
|
pos.height = tg->ph;
|
|
|
|
im_rect_intersectrect( &image, &pos, &clipped );
|
|
|
|
|
|
|
|
thr->pos = clipped;
|
|
|
|
|
2009-10-15 16:22:23 +02:00
|
|
|
/* Other stuff we want passed to iterate_work().
|
2009-10-14 12:25:18 +02:00
|
|
|
*/
|
|
|
|
thr->b = iter;
|
|
|
|
|
2009-08-16 17:00:08 +02:00
|
|
|
/* Start worker going.
|
|
|
|
*/
|
|
|
|
im_threadgroup_trigger( thr );
|
|
|
|
|
|
|
|
/* Trigger any eval callbacks on our source image,
|
|
|
|
* check for errors.
|
|
|
|
*/
|
2009-10-14 12:25:18 +02:00
|
|
|
if( im__handle_eval( t, tg->pw, tg->ph ) ||
|
2009-08-16 17:00:08 +02:00
|
|
|
im_threadgroup_iserror( tg ) ) {
|
|
|
|
/* Don't kill threads yet ... we may want to
|
|
|
|
* get some error stuff out of them.
|
|
|
|
*/
|
|
|
|
im_threadgroup_wait( tg );
|
|
|
|
return( -1 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-10-15 16:22:23 +02:00
|
|
|
/* Make sure all processing is done.
|
2009-08-16 17:00:08 +02:00
|
|
|
*/
|
|
|
|
im_threadgroup_wait( tg );
|
|
|
|
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|
|
|
2009-10-08 16:02:38 +02:00
|
|
|
/**
|
|
|
|
* im_iterate:
|
|
|
|
* @im: scan over this image
|
|
|
|
* @start: start sequences with this function
|
|
|
|
* @generate: generate pixels with this function
|
|
|
|
* @stop: stop sequences with this function
|
|
|
|
* @a: user data
|
|
|
|
* @b: user data
|
|
|
|
*
|
|
|
|
* Loops over an image. @generate is called for every pixel in the image, with
|
|
|
|
* the @reg argument being a region of pixels for processing. im_iterate() is
|
|
|
|
* used to implement operations like im_avg() which have no image output.
|
|
|
|
*
|
|
|
|
* See also: im_generate(), im_open().
|
|
|
|
*
|
|
|
|
* Returns: 0 on success, or -1 on error.
|
2009-08-16 17:00:08 +02:00
|
|
|
*/
|
|
|
|
int
|
|
|
|
im_iterate( IMAGE *im,
|
|
|
|
im_start_fn start, im_generate_fn generate, im_stop_fn stop,
|
|
|
|
void *b, void *c )
|
|
|
|
{
|
2009-10-14 12:25:18 +02:00
|
|
|
Iterate iter;
|
2009-08-16 17:00:08 +02:00
|
|
|
int result;
|
|
|
|
|
2010-04-09 18:51:45 +02:00
|
|
|
if( im__wbuffer2 )
|
|
|
|
return( vips_sink( im, start, generate, stop, b, c ) );
|
|
|
|
|
2009-08-16 17:00:08 +02:00
|
|
|
g_assert( !im_image_sanity( im ) );
|
|
|
|
|
2009-11-07 22:33:07 +01:00
|
|
|
/* We don't use this, but make sure it's set in case any old binaries
|
|
|
|
* are expectiing it.
|
|
|
|
*/
|
|
|
|
im->Bbits = im_bits_of_fmt( im->BandFmt );
|
|
|
|
|
2009-10-14 12:25:18 +02:00
|
|
|
if( iterate_init( &iter, im, start, generate, stop, b, c ) )
|
2009-08-16 17:00:08 +02:00
|
|
|
return( -1 );
|
|
|
|
|
|
|
|
/* Signal start of eval.
|
|
|
|
*/
|
2009-10-14 12:25:18 +02:00
|
|
|
if( im__start_eval( iter.t ) ) {
|
|
|
|
iterate_free( &iter );
|
2009-08-16 17:00:08 +02:00
|
|
|
return( -1 );
|
2009-10-14 12:25:18 +02:00
|
|
|
}
|
2009-08-16 17:00:08 +02:00
|
|
|
|
2009-10-14 12:25:18 +02:00
|
|
|
/* Loop and generate multi-thread.
|
|
|
|
*/
|
|
|
|
result = iterate_loop( &iter, iter.tg, iter.t );
|
2009-08-16 17:00:08 +02:00
|
|
|
|
|
|
|
/* Signal end of eval.
|
|
|
|
*/
|
2009-10-14 12:25:18 +02:00
|
|
|
result |= im__end_eval( iter.t );
|
2009-10-15 16:22:23 +02:00
|
|
|
|
|
|
|
/* Shut down and test for any errors.
|
|
|
|
*/
|
|
|
|
iterate_call_all_stop( &iter );
|
|
|
|
result |= im_threadgroup_iserror( iter.tg );
|
2009-10-14 12:25:18 +02:00
|
|
|
iterate_free( &iter );
|
2009-08-16 17:00:08 +02:00
|
|
|
|
|
|
|
return( result );
|
|
|
|
}
|