stuff
This commit is contained in:
parent
fb965a7136
commit
7ca1fb4909
14
TODO
14
TODO
@ -1,9 +1,23 @@
|
||||
|
||||
- vips_threadpool_run() needs to quit if another thread tries to free the
|
||||
image
|
||||
|
||||
look for im->kill?
|
||||
|
||||
|
||||
- VIPS_DEBUG_MSG() needs the wo/while thing so we can do:
|
||||
|
||||
if( tile )
|
||||
VIPS_DEBUG_MSG( "reusing dirty %p\n", tile );
|
||||
return( 0 );
|
||||
|
||||
and not have the return() move into the if() if debug is off
|
||||
|
||||
|
||||
- im_prepare_thread() can go? I think it was only used by im_render()
|
||||
|
||||
wtf, im_vips2tiff.c seems to be using it, what's wrong with im_wbuffer()?
|
||||
|
||||
|
||||
|
||||
- expose more of the tone funcs in nip2
|
||||
|
@ -63,34 +63,16 @@ static const int have_threads = 1;
|
||||
static const int have_threads = 0;
|
||||
#endif /*HAVE_THREADS*/
|
||||
|
||||
/* The states a tile can be in.
|
||||
*/
|
||||
typedef enum {
|
||||
/* On the dirty list .. contains no pixels
|
||||
*/
|
||||
TILE_DIRTY,
|
||||
|
||||
/* Valid pixels, on the painted hash..
|
||||
*/
|
||||
TILE_PAINTED,
|
||||
|
||||
/* Currently being worked on .. not on the dirty list, but contains
|
||||
* no valid pixels.
|
||||
*/
|
||||
TILE_WORKING
|
||||
} TileState;
|
||||
|
||||
/* A tile in our cache.
|
||||
*/
|
||||
typedef struct {
|
||||
struct _Render *render;
|
||||
|
||||
TileState state;
|
||||
|
||||
Rect area; /* Place here (unclipped) */
|
||||
gboolean painted; /* Tile contains valid pixels (ie. not dirty) */
|
||||
REGION *region; /* REGION with the pixels */
|
||||
|
||||
int access_ticks; /* Time of last use for LRU flush */
|
||||
int ticks; /* Time of last use, for LRU flush */
|
||||
} Tile;
|
||||
|
||||
/* Per-call state.
|
||||
@ -115,28 +97,42 @@ typedef struct _Render {
|
||||
VipsSinkNotify notify; /* Tell caller about paints here */
|
||||
void *a;
|
||||
|
||||
/* Make readers single thread with this. No point allowing
|
||||
* multi-thread read.
|
||||
/* Lock here before reading or modifying the tile structure.
|
||||
*/
|
||||
GMutex *read_lock;
|
||||
GMutex *lock;
|
||||
|
||||
/* Tile cache.
|
||||
*/
|
||||
GSList *cache; /* All our tiles */
|
||||
GSList *all; /* All our tiles */
|
||||
int ntiles; /* Number of tiles */
|
||||
int access_ticks; /* Inc. on each access ... used for LRU */
|
||||
int ticks; /* Inc. on each access ... used for LRU */
|
||||
|
||||
/* List of dirty tiles. Most recent at the front.
|
||||
*/
|
||||
GMutex *dirty_lock; /* Lock before we read/write the dirty list */
|
||||
GSList *dirty;
|
||||
|
||||
/* Hash of painted tiles. Look up by x/y position.
|
||||
/* Hash of tiles with positions. Tiles can be dirty or painted.
|
||||
*/
|
||||
GMutex *painted_lock; /* Lock before we read/write the painted hash */
|
||||
GHashTable *painted;
|
||||
GHashTable *tiles;
|
||||
} Render;
|
||||
|
||||
/* Our per-thread state.
|
||||
*/
|
||||
typedef struct _RenderThreadState {
|
||||
VipsThreadState parent_object;
|
||||
|
||||
/* The tile that should be calculated.
|
||||
*/
|
||||
Tile *tile;
|
||||
} RenderThreadState;
|
||||
|
||||
typedef struct _RenderThreadStateClass {
|
||||
VipsThreadStateClass parent_class;
|
||||
|
||||
} RenderThreadStateClass;
|
||||
|
||||
G_DEFINE_TYPE( RenderThreadState, render_thread_state, VIPS_TYPE_THREAD_STATE );
|
||||
|
||||
/* The BG thread which sits waiting to do some rendering.
|
||||
*/
|
||||
static GThread *render_thread = NULL;
|
||||
@ -150,6 +146,29 @@ static im_semaphore_t render_dirty_sem;
|
||||
static GMutex *render_dirty_lock = NULL;
|
||||
static GSList *render_dirty_all = NULL;
|
||||
|
||||
static void
|
||||
render_thread_state_class_init( RenderThreadStateClass *class )
|
||||
{
|
||||
VipsObjectClass *object_class = VIPS_OBJECT_CLASS( class );
|
||||
|
||||
object_class->nickname = "renderthreadstate";
|
||||
object_class->description = _( "per-thread state for render" );
|
||||
}
|
||||
|
||||
static void
|
||||
render_thread_state_init( RenderThreadState *state )
|
||||
{
|
||||
state->tile = NULL;
|
||||
}
|
||||
|
||||
static VipsThreadState *
|
||||
render_thread_state_new( VipsImage *im, void *a )
|
||||
{
|
||||
return( VIPS_THREAD_STATE( vips_object_new(
|
||||
render_thread_state_get_type(),
|
||||
vips_thread_state_set, im, a ) ) );
|
||||
}
|
||||
|
||||
static void
|
||||
render_dirty_remove( Render *render )
|
||||
{
|
||||
@ -175,18 +194,14 @@ render_free( Render *render )
|
||||
|
||||
g_mutex_free( render->ref_count_lock );
|
||||
|
||||
g_mutex_free( render->read_lock );
|
||||
g_mutex_free( render->lock );
|
||||
|
||||
im_slist_map2( render->cache,
|
||||
im_slist_map2( render->all,
|
||||
(VSListMap2Fn) tile_free, NULL, NULL );
|
||||
IM_FREEF( g_slist_free, render->cache );
|
||||
IM_FREEF( g_slist_free, render->all );
|
||||
render->ntiles = 0;
|
||||
|
||||
IM_FREEF( g_slist_free, render->dirty );
|
||||
g_mutex_free( render->dirty_lock );
|
||||
|
||||
IM_FREEF( g_hash_table_destroy, render->painted );
|
||||
g_mutex_free( render->painted_lock );
|
||||
IM_FREEF( g_hash_table_destroy, render->tiles );
|
||||
|
||||
im_free( render );
|
||||
|
||||
@ -223,7 +238,7 @@ render_unref( Render *render )
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/* Wait for a render with dirty tiles.
|
||||
/* Wait for a render with dirty tiles.
|
||||
*/
|
||||
static Render *
|
||||
render_dirty_get( void )
|
||||
@ -264,12 +279,25 @@ render_allocate( VipsThreadState *state, void *a, gboolean *stop )
|
||||
{
|
||||
Render *render = (Render *) a;
|
||||
RenderThreadState *rstate = (RenderThreadState *) state;
|
||||
Tile *tile;
|
||||
|
||||
if( render_rescheule ) {
|
||||
g_mutex_lock( render->lock );
|
||||
|
||||
if( render_rescheule || !render->dirty )
|
||||
*stop = TRUE;
|
||||
rstate->tile = NULL;
|
||||
g_mutex_unlock( render->lock );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
tile = (Tile *) render->dirty->data;
|
||||
render->dirty = g_slist_remove( render->dirty, tile );
|
||||
|
||||
g_mutex_unlock( render->lock );
|
||||
|
||||
rstate->tile = tile;
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
@ -278,6 +306,19 @@ render_work( VipsThreadState *state, void *a )
|
||||
{
|
||||
Render *render = (Render *) a;
|
||||
RenderThreadState *rstate = (RenderThreadState *) state;
|
||||
Tile *tile = rstate->tile;
|
||||
|
||||
g_assert( tile );
|
||||
g_assert( !tile->painted );
|
||||
|
||||
if( im_prepare( tile->region, &tile->area ) )
|
||||
return( -1 );
|
||||
tile->painted = TRUE;
|
||||
|
||||
/* Now clients can update.
|
||||
*/
|
||||
if( render->notify )
|
||||
render->notify( render->out, &tile->area, render->a );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
@ -288,98 +329,12 @@ render_work( VipsThreadState *state, void *a )
|
||||
static int
|
||||
render_process( Render *render )
|
||||
{
|
||||
render_reschedule = FALSE;
|
||||
|
||||
vips_threadpool_run( render->im,
|
||||
sink_thread_state_new,
|
||||
return( vips_threadpool_run( render->im,
|
||||
render_thread_state_new,
|
||||
render_allocate,
|
||||
render_work,
|
||||
NULL,
|
||||
render );
|
||||
|
||||
render_reschedule = FALSE;
|
||||
}
|
||||
|
||||
/* Do a single tile. Take a dirty tile from the dirty list and fill with
|
||||
* pixels.
|
||||
*/
|
||||
static void
|
||||
render_dirty_process( Render *render )
|
||||
{
|
||||
Tile *tile;
|
||||
|
||||
/* Take a tile off the dirty list.
|
||||
*/
|
||||
g_mutex_lock( render->dirty_lock );
|
||||
if( render->dirty ) {
|
||||
tile = (Tile *) render->dirty->data;
|
||||
g_assert( tile->state == TILE_DIRTY );
|
||||
render->dirty = g_slist_remove( render->dirty, tile );
|
||||
tile->state = TILE_WORKING;
|
||||
}
|
||||
else
|
||||
tile = NULL;
|
||||
g_mutex_unlock( render->dirty_lock );
|
||||
|
||||
if( tile ) {
|
||||
int result;
|
||||
|
||||
result = -1;
|
||||
if( !render->tg ) {
|
||||
render->tg = im_threadgroup_create( render->in );
|
||||
|
||||
#ifdef DEBUG_TG
|
||||
printf( "render_paint_tile: "
|
||||
"%p starting threadgroup\n",
|
||||
render );
|
||||
threadgroup_count += 1;
|
||||
printf( "render_paint_tile: %d\n", threadgroup_count );
|
||||
threadgroup_active += 1;
|
||||
printf( "render_dirty_put: %d active\n",
|
||||
threadgroup_active );
|
||||
#endif /*DEBUG_TG*/
|
||||
}
|
||||
|
||||
if( render->tg ) {
|
||||
#ifdef DEBUG_PAINT
|
||||
printf( "render_fill_tile: "
|
||||
"%p paint of tile %dx%d\n",
|
||||
render,
|
||||
tile->area.left, tile->area.top );
|
||||
#endif /*DEBUG_PAINT*/
|
||||
|
||||
/* We're writing to the tile region, but we didn't
|
||||
* make it.
|
||||
*/
|
||||
im__region_take_ownership( tile->region );
|
||||
|
||||
result = im_prepare_thread( render->tg,
|
||||
tile->region, &tile->area );
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
FIXME ... nice if we did something with the error return
|
||||
|
||||
*/
|
||||
#ifdef DEBUG_PAINT
|
||||
if( result )
|
||||
printf( "render_fill_tile: "
|
||||
"im_prepare_thread() failed!\n\t%s\n",
|
||||
im_error_buffer() );
|
||||
#endif /*DEBUG_PAINT*/
|
||||
|
||||
/* All done.
|
||||
*/
|
||||
tile->state = TILE_PAINTED;
|
||||
im__region_no_ownership( tile->region );
|
||||
|
||||
/* Now clients can update.
|
||||
*/
|
||||
if( render->notify )
|
||||
render->notify( render->out,
|
||||
&tile->area, render->client );
|
||||
}
|
||||
render ) );
|
||||
}
|
||||
|
||||
static int
|
||||
@ -395,7 +350,7 @@ render_dirty_put( Render *render )
|
||||
{
|
||||
g_mutex_lock( render_dirty_lock );
|
||||
|
||||
if( render->dirty && !render->render_kill ) {
|
||||
if( render->dirty ) {
|
||||
if( !g_slist_find( render_dirty_all, render ) ) {
|
||||
render_dirty_all = g_slist_prepend( render_dirty_all,
|
||||
render );
|
||||
@ -404,16 +359,6 @@ render_dirty_put( Render *render )
|
||||
im_semaphore_up( &render_dirty_sem );
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Coming off the jobs list ... shut down the render pipeline.
|
||||
*/
|
||||
#ifdef DEBUG_TG
|
||||
printf( "render_dirty_put: %p stopping threadgroup\n", render );
|
||||
threadgroup_active -= 1;
|
||||
printf( "render_dirty_put: %d active\n", threadgroup_active );
|
||||
#endif /*DEBUG_TG*/
|
||||
IM_FREEF( im_threadgroup_free, render->tg );
|
||||
}
|
||||
|
||||
g_mutex_unlock( render_dirty_lock );
|
||||
}
|
||||
@ -423,23 +368,19 @@ render_dirty_put( Render *render )
|
||||
static void *
|
||||
render_thread_main( void *client )
|
||||
{
|
||||
/* Could use this if we want per-thread state in the future.
|
||||
RenderThread *thread = (RenderThread *) client;
|
||||
*/
|
||||
|
||||
for(;;) {
|
||||
Render *render;
|
||||
|
||||
if( (render = render_dirty_get()) ) {
|
||||
/* Loop here if this is the only dirty render, rather
|
||||
* than bouncing back to _put()/_get(). We don't
|
||||
* lock before testing since this is just a trivial
|
||||
* optimisation and does not affect integrity.
|
||||
/* Ignore errors, I'm not sure what we'd do with them
|
||||
* anyway.
|
||||
*/
|
||||
do {
|
||||
render_dirty_process( render );
|
||||
} while( !render_dirty_all && render->dirty );
|
||||
render_reschedule = FALSE;
|
||||
(void) render_process( render );
|
||||
render_reschedule = FALSE;
|
||||
|
||||
/* Add back to the jobs list, if we need to.
|
||||
*/
|
||||
render_dirty_put( render );
|
||||
|
||||
g_assert( render->ref_count == 1 ||
|
||||
@ -461,86 +402,27 @@ render_thread_main( void *client )
|
||||
static int
|
||||
render_thread_create( void )
|
||||
{
|
||||
int len = g_slist_length( render_thread_all );
|
||||
int i;
|
||||
|
||||
if( !have_threads )
|
||||
return( 0 );
|
||||
|
||||
/* 1st time through only.
|
||||
*/
|
||||
if( !render_dirty_lock ) {
|
||||
render_dirty_lock = g_mutex_new();
|
||||
im_semaphore_init( &render_dirty_sem, 0, "render_dirty_sem" );
|
||||
}
|
||||
|
||||
for( i = len; i < render_thread_max; i++ ) {
|
||||
RenderThread *thread = IM_NEW( NULL, RenderThread );
|
||||
|
||||
thread->gthread = NULL;
|
||||
thread->render = NULL;
|
||||
|
||||
if( !(thread->gthread = g_thread_create_full(
|
||||
render_thread_main, thread,
|
||||
if( !render_thread && have_threads )
|
||||
if( !(render_thread = g_thread_create_full(
|
||||
render_thread_main, NULL,
|
||||
IM__DEFAULT_STACK_SIZE, TRUE, FALSE,
|
||||
G_THREAD_PRIORITY_NORMAL, NULL )) ) {
|
||||
im_free( thread );
|
||||
im_error( "im_render",
|
||||
"%s", _( "unable to create thread" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
render_thread_all = g_slist_prepend( render_thread_all,
|
||||
thread );
|
||||
}
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
static void *
|
||||
tile_test_clean_ticks( Tile *this, Tile **best )
|
||||
{
|
||||
if( this->state == TILE_PAINTED )
|
||||
if( !*best || this->access_ticks < (*best)->access_ticks )
|
||||
*best = this;
|
||||
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
/* Pick a painted tile to reuse. Search for LRU (slow!).
|
||||
*/
|
||||
static Tile *
|
||||
render_tile_get_painted( Render *render )
|
||||
{
|
||||
Tile *tile;
|
||||
|
||||
tile = NULL;
|
||||
im_slist_map2( render->cache,
|
||||
(VSListMap2Fn) tile_test_clean_ticks, &tile, NULL );
|
||||
|
||||
if( tile ) {
|
||||
g_assert( tile->state == TILE_PAINTED );
|
||||
|
||||
#ifdef DEBUG_REUSE
|
||||
printf( "render_tile_get_painted: reusing painted %p\n", tile );
|
||||
|
||||
g_mutex_lock( render->dirty_lock );
|
||||
g_assert( !g_slist_find( render->dirty, tile ) );
|
||||
g_mutex_unlock( render->dirty_lock );
|
||||
#endif /*DEBUG_REUSE*/
|
||||
|
||||
tile->state = TILE_WORKING;
|
||||
}
|
||||
|
||||
return( tile );
|
||||
}
|
||||
|
||||
static guint
|
||||
tile_hash( gconstpointer key )
|
||||
{
|
||||
Tile *tile = (Tile *) key;
|
||||
int x = tile->area.left / tile->render->tile_width;
|
||||
int y = tile->area.top / tile->render->tile_height;
|
||||
Rect *rect = (Rect *) key;
|
||||
|
||||
int x = rect->left / rect->width;
|
||||
int y = rect->top / rect->height;
|
||||
|
||||
return( x << 16 ^ y );
|
||||
}
|
||||
@ -548,11 +430,11 @@ tile_hash( gconstpointer key )
|
||||
static gboolean
|
||||
tile_equal( gconstpointer a, gconstpointer b )
|
||||
{
|
||||
Tile *tile1 = (Tile *) a;
|
||||
Tile *tile2 = (Tile *) b;
|
||||
Rect *rect1 = (Rect *) a;
|
||||
Rect *rect2 = (Rect *) b;
|
||||
|
||||
return( tile1->area.left == tile2->area.left &&
|
||||
tile1->area.top == tile2->area.top );
|
||||
return( rect1->left == rect2->left &&
|
||||
rect1->top == rect2->top );
|
||||
}
|
||||
|
||||
static void *
|
||||
@ -593,14 +475,14 @@ render_new( IMAGE *in, IMAGE *out, IMAGE *mask,
|
||||
render->notify = notify;
|
||||
render->client = client;
|
||||
|
||||
render->read_lock = g_mutex_new();
|
||||
render->lock = g_mutex_new();
|
||||
|
||||
render->cache = g_hash_table_new_full( tile_hash, tile_equal,
|
||||
tile_free, NULL );
|
||||
render->all = NULL
|
||||
render->ntiles = 0;
|
||||
render->access_ticks = 0;
|
||||
render->ticks = 0;
|
||||
|
||||
render->tiles = g_hash_table_new( tile_hash, tile_equal );
|
||||
|
||||
render->dirty_lock = g_mutex_new();
|
||||
render->dirty = NULL;
|
||||
|
||||
if( im_add_close_callback( out,
|
||||
@ -635,72 +517,54 @@ tile_new( Render *render )
|
||||
tile->area.top = 0;
|
||||
tile->area.width = 0;
|
||||
tile->area.height = 0;
|
||||
tile->access_ticks = render->access_ticks;
|
||||
tile->state = TILE_WORKING;
|
||||
tile->ticks = render->ticks;
|
||||
|
||||
if( !(tile->region = im_region_create( render->in )) ) {
|
||||
(void) tile_free( tile );
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
render->cache = g_slist_prepend( render->cache, tile );
|
||||
render->all = g_slist_prepend( render->all, tile );
|
||||
render->ntiles += 1;
|
||||
|
||||
return( tile );
|
||||
}
|
||||
|
||||
static void *
|
||||
tile_test_area( Tile *tile, Rect *area )
|
||||
{
|
||||
if( im_rect_equalsrect( &tile->area, area ) )
|
||||
return( tile );
|
||||
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
/* Search the cache for a tile, NULL if not there. Could be *much* faster.
|
||||
*
|
||||
* This is always called from the downstream thread, and upstream never adds
|
||||
* or positions tiles, so no need to lock.
|
||||
/* Search the cache for a tile by position.
|
||||
*/
|
||||
static Tile *
|
||||
render_tile_lookup( Render *render, Rect *area )
|
||||
{
|
||||
Tile *tile;
|
||||
|
||||
tile = (Tile *) im_slist_map2( render->cache,
|
||||
(VSListMap2Fn) tile_test_area, area, NULL );
|
||||
|
||||
return( tile );
|
||||
return( (Tile *) g_hash_table_lookup( render->tiles, area ) );
|
||||
}
|
||||
|
||||
/* We've looked at a tile ... bump to end of LRU and front of dirty.
|
||||
*/
|
||||
static void
|
||||
render_tile_touch( Tile *tile )
|
||||
tile_touch( Tile *tile )
|
||||
{
|
||||
Render *render = tile->render;
|
||||
|
||||
tile->access_ticks = render->access_ticks;
|
||||
render->access_ticks += 1;
|
||||
tile->ticks = render->ticks;
|
||||
render->ticks += 1;
|
||||
|
||||
g_mutex_lock( render->dirty_lock );
|
||||
if( tile->state == TILE_DIRTY ) {
|
||||
if( !tile->painted ) {
|
||||
#ifdef DEBUG
|
||||
printf( "tile_bump_dirty: bumping tile %dx%d\n",
|
||||
tile->area.left, tile->area.top );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
render->dirty = g_slist_remove( render->dirty, tile );
|
||||
render->dirty = g_slist_prepend( render->dirty, tile );
|
||||
if( g_slist_find( render->dirty, tile ) ) {
|
||||
render->dirty = g_slist_remove( render->dirty, tile );
|
||||
render->dirty = g_slist_prepend( render->dirty, tile );
|
||||
}
|
||||
}
|
||||
g_mutex_unlock( render->dirty_lock );
|
||||
}
|
||||
|
||||
/* Add a tile to the dirty list.
|
||||
/* Queue a tile for calculation. It might need moving too.
|
||||
*/
|
||||
static void
|
||||
tile_set_dirty( Tile *tile, Rect *area )
|
||||
tile_queue( Tile *tile, Rect *area )
|
||||
{
|
||||
Render *render = tile->render;
|
||||
|
||||
@ -709,33 +573,27 @@ tile_set_dirty( Tile *tile, Rect *area )
|
||||
area->left, area->top );
|
||||
#endif /*DEBUG_PAINT*/
|
||||
|
||||
g_assert( tile->state == TILE_WORKING );
|
||||
|
||||
/* Touch the ticks ... we want to make sure this tile will not be
|
||||
* reused too soon, so it gets a chance to get painted.
|
||||
*/
|
||||
tile->access_ticks = render->access_ticks;
|
||||
render->access_ticks += 1;
|
||||
tile->ticks = render->ticks;
|
||||
render->ticks += 1;
|
||||
|
||||
g_mutex_lock( render->dirty_lock );
|
||||
|
||||
tile->state = TILE_DIRTY;
|
||||
/* It might not be in the tile hash, but remove anyway.
|
||||
*/
|
||||
g_hash_table_remove( render->tiles, &tile->area );
|
||||
tile->painted = FALSE;
|
||||
tile->area = *area;
|
||||
render->dirty = g_slist_prepend( render->dirty, tile );
|
||||
g_hash_table_insert( render->tiles, &tile->area, tile );
|
||||
|
||||
/* Someone else will write to it now.
|
||||
*/
|
||||
im__region_no_ownership( tile->region );
|
||||
|
||||
/* Can't unlock render->dirty_lock here, we need to render_dirty_put()
|
||||
* before the tile is processed.
|
||||
*/
|
||||
|
||||
if( render->notify && have_threads )
|
||||
/* Add to the list of renders with dirty tiles. One of our bg
|
||||
* threads will pick it up and paint it.
|
||||
if( render->notify && have_threads ) {
|
||||
/* Add to the list of renders with dirty tiles. The bg
|
||||
* thread will pick it up and paint it.
|
||||
*/
|
||||
im__region_no_ownership( tile->region );
|
||||
render->dirty = g_slist_prepend( render->dirty, tile );
|
||||
render_dirty_put( render );
|
||||
}
|
||||
else {
|
||||
/* No threads, or no notify ... paint the tile ourselves,
|
||||
* sychronously. No need to notify the client since they'll
|
||||
@ -746,84 +604,86 @@ tile_set_dirty( Tile *tile, Rect *area )
|
||||
area->left, area->top );
|
||||
#endif /*DEBUG_PAINT*/
|
||||
|
||||
if( !render->tg )
|
||||
render->tg = im_threadgroup_create( render->in );
|
||||
|
||||
/* We're writing to the tile region, but we didn't make it.
|
||||
*/
|
||||
im__region_take_ownership( tile->region );
|
||||
|
||||
if( render->tg )
|
||||
im_prepare_thread( render->tg,
|
||||
tile->region, &tile->area );
|
||||
|
||||
tile->state = TILE_PAINTED;
|
||||
render->dirty = g_slist_remove( render->dirty, tile );
|
||||
im_prepare( tile->region, &tile->area );
|
||||
tile->painted = TRUE;
|
||||
}
|
||||
|
||||
g_mutex_unlock( render->dirty_lock );
|
||||
}
|
||||
|
||||
/* Take a tile off the end of the dirty list.
|
||||
static void *
|
||||
tile_test_clean_ticks( Rect *key, Tile *value, Tile **best )
|
||||
{
|
||||
if( value->painted )
|
||||
if( !*best || value->ticks < (*best)->ticks )
|
||||
*best = value;
|
||||
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
/* Pick a painted tile to reuse. Search for LRU (slow!).
|
||||
*/
|
||||
static Tile *
|
||||
render_tile_get_painted( Render *render )
|
||||
{
|
||||
Tile *tile;
|
||||
|
||||
tile = NULL;
|
||||
g_hash_table_foreach( render->tiles,
|
||||
(GHRFunc) tile_test_clean_ticks, &tile );
|
||||
|
||||
if( tile ) {
|
||||
g_assert( tile->painted );
|
||||
|
||||
#ifdef DEBUG_REUSE
|
||||
printf( "render_tile_get_painted: reusing painted %p\n", tile );
|
||||
#endif /*DEBUG_REUSE*/
|
||||
}
|
||||
|
||||
return( tile );
|
||||
}
|
||||
|
||||
/* Pick a dirty tile to reuse.
|
||||
*/
|
||||
static Tile *
|
||||
render_tile_get_dirty( Render *render )
|
||||
{
|
||||
Tile *tile;
|
||||
|
||||
g_mutex_lock( render->dirty_lock );
|
||||
if( !render->dirty )
|
||||
tile = NULL;
|
||||
else {
|
||||
tile = (Tile *) g_slist_last( render->dirty )->data;
|
||||
render->dirty = g_slist_remove( render->dirty, tile );
|
||||
tile->state = TILE_WORKING;
|
||||
}
|
||||
g_mutex_unlock( render->dirty_lock );
|
||||
|
||||
#ifdef DEBUG_REUSE
|
||||
if( tile )
|
||||
printf( "render_tile_get_dirty: reusing dirty %p\n", tile );
|
||||
#endif /*DEBUG_REUSE*/
|
||||
|
||||
return( tile );
|
||||
}
|
||||
|
||||
/* Ask for an area of calculated pixels. Get from cache, request calculation,
|
||||
* or if we've no threads or no notify, calculate immediately.
|
||||
*/
|
||||
static Tile *
|
||||
render_tile_get( Render *render, Rect *area )
|
||||
tile_request( Render *render, Rect *area )
|
||||
{
|
||||
Tile *tile;
|
||||
|
||||
/* Got this tile already?
|
||||
*/
|
||||
if( (tile = render_tile_lookup( render, area )) ) {
|
||||
#ifdef DEBUG_PAINT
|
||||
printf( "render_tile_get: found %dx%d in cache\n",
|
||||
area->left, area->top );
|
||||
#endif /*DEBUG_PAINT*/
|
||||
|
||||
/* If the tile is painted but invalid, send it for
|
||||
* calculation.
|
||||
*
|
||||
* Otherwise just touch it to keep it in cache a little
|
||||
* longer.
|
||||
/* We already have a tile at this position. Either it's all
|
||||
* OK, in which case we're using it so we need to update the
|
||||
* ticks to keep it in cache a little longer, or it's invalid,
|
||||
* in which case we need to recalculate.
|
||||
*/
|
||||
if( tile->state == TILE_PAINTED &&
|
||||
tile->region->invalid ) {
|
||||
tile->state = TILE_WORKING;
|
||||
tile_set_dirty( tile, area );
|
||||
}
|
||||
if( tile->painted && !tile->region->invalid )
|
||||
tile_touch( tile );
|
||||
else
|
||||
render_tile_touch( tile );
|
||||
|
||||
return( tile );
|
||||
tile_queue( tile, area );
|
||||
}
|
||||
|
||||
/* Have we fewer tiles than teh max? Can just make a new tile.
|
||||
*/
|
||||
if( render->ntiles < render->max || render->max == -1 ) {
|
||||
else if( render->ntiles < render->max || render->max == -1 ) {
|
||||
/* We fewer tiles than teh max. We can just make a new tile.
|
||||
*/
|
||||
if( !(tile = tile_new( render )) )
|
||||
return( NULL );
|
||||
|
||||
tile_queue( tile, area );
|
||||
}
|
||||
else {
|
||||
/* Need to reuse a tile. Try for an old painted tile first,
|
||||
@ -838,15 +698,10 @@ render_tile_get( Render *render, Rect *area )
|
||||
tile->area.left, tile->area.top,
|
||||
area->left, area->top );
|
||||
#endif /*DEBUG_REUSE*/
|
||||
|
||||
tile_queue( tile, area );
|
||||
}
|
||||
|
||||
#ifdef DEBUG_PAINT
|
||||
printf( "render_tile_get: sending %dx%d for calc\n",
|
||||
area->left, area->top );
|
||||
#endif /*DEBUG_PAINT*/
|
||||
|
||||
tile_set_dirty( tile, area );
|
||||
|
||||
return( tile );
|
||||
}
|
||||
|
||||
@ -866,8 +721,7 @@ tile_copy( Tile *tile, REGION *to )
|
||||
/* If the tile is painted, copy over the pixels. Otherwise, fill with
|
||||
* zero.
|
||||
*/
|
||||
if( tile->state == TILE_PAINTED &&
|
||||
!tile->region->invalid ) {
|
||||
if( tile->painted && !tile->region->invalid ) {
|
||||
int len = IM_IMAGE_SIZEOF_PEL( to->im ) * ovlap.width;
|
||||
|
||||
#ifdef DEBUG_PAINT
|
||||
@ -903,27 +757,25 @@ region_fill( REGION *out, void *seq, void *a, void *b )
|
||||
|
||||
/* Find top left of tiles we need.
|
||||
*/
|
||||
int xs = (r->left / render->width) * render->width;
|
||||
int ys = (r->top / render->height) * render->height;
|
||||
int xs = (r->left / render->tile_width) * render->tile_width;
|
||||
int ys = (r->top / render->tile_height) * render->tile_height;
|
||||
|
||||
#ifdef DEBUG_PAINT
|
||||
printf( "region_fill: left = %d, top = %d, width = %d, height = %d\n",
|
||||
r->left, r->top, r->width, r->height );
|
||||
#endif /*DEBUG_PAINT*/
|
||||
|
||||
/* Only allow one reader. No point threading this, calculation is
|
||||
* decoupled anyway.
|
||||
*/
|
||||
g_mutex_lock( render->read_lock );
|
||||
g_mutex_lock( render->lock );
|
||||
|
||||
/*
|
||||
|
||||
FIXME ... if r fits inside a single tile, could skip the copy.
|
||||
FIXME ... if r fits inside a single tile, we could skip the
|
||||
copy.
|
||||
|
||||
*/
|
||||
|
||||
for( y = ys; y < IM_RECT_BOTTOM( r ); y += render->height )
|
||||
for( x = xs; x < IM_RECT_RIGHT( r ); x += render->width ) {
|
||||
for( y = ys; y < IM_RECT_BOTTOM( r ); y += render->tile_height )
|
||||
for( x = xs; x < IM_RECT_RIGHT( r ); x += render->tile_width ) {
|
||||
Rect area;
|
||||
Tile *tile;
|
||||
|
||||
@ -932,11 +784,12 @@ region_fill( REGION *out, void *seq, void *a, void *b )
|
||||
area.width = render->width;
|
||||
area.height = render->height;
|
||||
|
||||
if( (tile = render_tile_get( render, &area )) )
|
||||
tile = tile_request( render, &area );
|
||||
if( tile )
|
||||
tile_copy( tile, out );
|
||||
}
|
||||
|
||||
g_mutex_unlock( render->read_lock );
|
||||
g_mutex_unlock( render->lock );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
@ -952,25 +805,25 @@ mask_fill( REGION *out, void *seq, void *a, void *b )
|
||||
|
||||
/* Find top left of tiles we need.
|
||||
*/
|
||||
int xs = (r->left / render->width) * render->width;
|
||||
int ys = (r->top / render->height) * render->height;
|
||||
int xs = (r->left / render->tile_width) * render->tile_width;
|
||||
int ys = (r->top / render->tile_height) * render->tile_height;
|
||||
|
||||
#ifdef DEBUG_PAINT
|
||||
printf( "mask_fill: left = %d, top = %d, width = %d, height = %d\n",
|
||||
r->left, r->top, r->width, r->height );
|
||||
#endif /*DEBUG_PAINT*/
|
||||
|
||||
g_mutex_lock( render->read_lock );
|
||||
g_mutex_lock( render->lock );
|
||||
|
||||
for( y = ys; y < IM_RECT_BOTTOM( r ); y += render->height )
|
||||
for( x = xs; x < IM_RECT_RIGHT( r ); x += render->width ) {
|
||||
for( y = ys; y < IM_RECT_BOTTOM( r ); y += render->tile_height )
|
||||
for( x = xs; x < IM_RECT_RIGHT( r ); x += render->tile_width ) {
|
||||
Rect area;
|
||||
Tile *tile;
|
||||
|
||||
area.left = x;
|
||||
area.top = y;
|
||||
area.width = render->width;
|
||||
area.height = render->height;
|
||||
area.width = render->tile_width;
|
||||
area.height = render->tile_height;
|
||||
|
||||
tile = render_tile_lookup( render, &area );
|
||||
|
||||
@ -978,17 +831,17 @@ mask_fill( REGION *out, void *seq, void *a, void *b )
|
||||
*/
|
||||
im_region_paint( out, &area,
|
||||
(tile &&
|
||||
tile->state == TILE_PAINTED &&
|
||||
tile->painted &&
|
||||
!tile->region->invalid) ? 255 : 0 );
|
||||
}
|
||||
|
||||
g_mutex_unlock( render->read_lock );
|
||||
g_mutex_unlock( render->lock );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* im_render_priority:
|
||||
* vips_sink_screen:
|
||||
* @in: input image
|
||||
* @out: output image
|
||||
* @mask: mask image indicating valid pixels
|
||||
@ -997,7 +850,7 @@ mask_fill( REGION *out, void *seq, void *a, void *b )
|
||||
* @max: maximum tiles to cache
|
||||
* @priority: rendering priority
|
||||
* @notify: pixels are ready notification callback
|
||||
* @client: client data for callback
|
||||
* @a: client data for callback
|
||||
*
|
||||
* This operation renders @in in the background, making pixels available on
|
||||
* @out as they are calculated. The @notify callback is run every time a new
|
||||
@ -1034,10 +887,10 @@ mask_fill( REGION *out, void *seq, void *a, void *b )
|
||||
* Returns: 0 on sucess, -1 on error.
|
||||
*/
|
||||
int
|
||||
im_render_priority( IMAGE *in, IMAGE *out, IMAGE *mask,
|
||||
vips_sink_screen( VipsImage *in, VipsImage *out, VipsImage *mask,
|
||||
int width, int height, int max,
|
||||
int priority,
|
||||
notify_fn notify, void *client )
|
||||
VipsSinkNotify notify, void *a )
|
||||
{
|
||||
Render *render;
|
||||
|
||||
@ -1049,14 +902,18 @@ im_render_priority( IMAGE *in, IMAGE *out, IMAGE *mask,
|
||||
if( width <= 0 ||
|
||||
height <= 0 ||
|
||||
max < -1 ) {
|
||||
im_error( "im_render", "%s", _( "bad parameters" ) );
|
||||
im_error( "vips_sink_screen", "%s", _( "bad parameters" ) );
|
||||
return( -1 );
|
||||
}
|
||||
if( im_piocheck( in, out ) )
|
||||
|
||||
if( im_piocheck( in, out ) ||
|
||||
im_cp_desc( out, in ) ||
|
||||
im_demand_hint( out, IM_SMALLTILE, in, NULL ) )
|
||||
return( -1 );
|
||||
if( mask ) {
|
||||
if( im_poutcheck( mask ) ||
|
||||
im_cp_desc( mask, in ) )
|
||||
im_cp_desc( mask, in ) ||
|
||||
im_demand_hint( mask, IM_SMALLTILE, in, NULL ) )
|
||||
return( -1 );
|
||||
|
||||
mask->Bands = 1;
|
||||
@ -1064,55 +921,21 @@ im_render_priority( IMAGE *in, IMAGE *out, IMAGE *mask,
|
||||
mask->Type = IM_TYPE_B_W;
|
||||
mask->Coding = IM_CODING_NONE;
|
||||
}
|
||||
if( im_cp_desc( out, in ) )
|
||||
return( -1 );
|
||||
if( im_demand_hint( out, IM_SMALLTILE, in, NULL ) )
|
||||
return( -1 );
|
||||
if( mask &&
|
||||
im_demand_hint( mask, IM_SMALLTILE, in, NULL ) )
|
||||
return( -1 );
|
||||
|
||||
if( !(render = render_new( in, out, mask,
|
||||
width, height, max, priority, notify, client )) )
|
||||
width, height, max, priority, notify, a )) )
|
||||
return( -1 );
|
||||
|
||||
#ifdef DEBUG_MAKE
|
||||
printf( "im_render: max = %d, %p\n", max, render );
|
||||
printf( "vips_sink_screen: max = %d, %p\n", max, render );
|
||||
#endif /*DEBUG_MAKE*/
|
||||
|
||||
if( im_generate( out, NULL, region_fill, NULL,
|
||||
render, NULL ) )
|
||||
if( im_generate( out, NULL, region_fill, NULL, render, NULL ) )
|
||||
return( -1 );
|
||||
if( mask &&
|
||||
im_generate( mask, NULL, mask_fill, NULL,
|
||||
render, NULL ) )
|
||||
im_generate( mask, NULL, mask_fill, NULL, render, NULL ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* im_cache:
|
||||
* @in: input image
|
||||
* @out: output image
|
||||
* @width: tile width
|
||||
* @height: tile height
|
||||
* @max: maximum tiles to cache
|
||||
*
|
||||
* im_cache() works exactly as im_copy(), except that calculated pixels are
|
||||
* kept in a cache. If @in is the result of a large computation and you are
|
||||
* expecting to reuse the result in a number of places, im_cache() can save a
|
||||
* lot of time.
|
||||
*
|
||||
* im_cache() is a convenience function over im_render_priority().
|
||||
*
|
||||
* See also: im_render_priority(), im_copy(), im_prepare_thread().
|
||||
*/
|
||||
int
|
||||
im_cache( IMAGE *in, IMAGE *out, int width, int height, int max )
|
||||
{
|
||||
return( im_render_priority( in, out, NULL,
|
||||
width, height, max,
|
||||
0,
|
||||
NULL, NULL ) );
|
||||
}
|
||||
|
@ -38,8 +38,8 @@
|
||||
/*
|
||||
#define TIME_THREAD
|
||||
#define VIPS_DEBUG_RED
|
||||
*/
|
||||
#define VIPS_DEBUG
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
|
Loading…
Reference in New Issue
Block a user