| /* aynchronous screen sink |
| * |
| * 1/1/10 |
| * - from im_render.c |
| */ |
| |
| /* |
| |
| 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 |
| |
| */ |
| |
| /* Verbose debugging output. |
| #define VIPS_DEBUG |
| */ |
| |
| /* Trace allocate/free. |
| #define VIPS_DEBUG_AMBER |
| */ |
| |
| /* Trace reschedule |
| #define VIPS_DEBUG_GREEN |
| */ |
| |
| /* Trace serious problems. |
| #define VIPS_DEBUG_RED |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif /*HAVE_CONFIG_H*/ |
| #include <vips/intl.h> |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <math.h> |
| #ifdef HAVE_UNISTD_H |
| #include <unistd.h> |
| #endif /*HAVE_UNISTD_H*/ |
| |
| #include <vips/vips.h> |
| #include <vips/thread.h> |
| #include <vips/debug.h> |
| |
| #ifdef WITH_DMALLOC |
| #include <dmalloc.h> |
| #endif /*WITH_DMALLOC*/ |
| |
| /* A have-threads we can test in if(). |
| */ |
| #ifdef HAVE_THREADS |
| static const int have_threads = 1; |
| #else /*!HAVE_THREADS*/ |
| static const int have_threads = 0; |
| #endif /*HAVE_THREADS*/ |
| |
| #ifdef VIPS_DEBUG_AMBER |
| static int render_num_renders = 0; |
| #endif /*VIPS_DEBUG_AMBER*/ |
| |
| /* A tile in our cache. |
| */ |
| typedef struct { |
| struct _Render *render; |
| |
| Rect area; /* Place here (unclipped) */ |
| REGION *region; /* REGION with the pixels */ |
| |
| /* The tile contains calculated pixels. Though the region may have been |
| * invalidated behind our backs: we have to check that too. |
| */ |
| gboolean painted; |
| |
| /* The tile is on the dirty list. This saves us having to search the |
| * dirty list all the time. |
| */ |
| gboolean dirty; |
| |
| /* Time of last use, for LRU flush |
| */ |
| int ticks; |
| } Tile; |
| |
| /* Per-call state. |
| */ |
| typedef struct _Render { |
| /* Reference count this, since we use these things from several |
| * threads. We can't easily use the gobject ref count system since we |
| * need a lock around operations. |
| */ |
| int ref_count; |
| GMutex *ref_count_lock; |
| |
| /* Parameters. |
| */ |
| VipsImage *in; /* Image we render */ |
| VipsImage *out; /* Write tiles here on demand */ |
| VipsImage *mask; /* Set valid pixels here */ |
| int tile_width; /* Tile size */ |
| int tile_height; |
| int max_tiles; /* Maximum number of tiles */ |
| int priority; /* Larger numbers done sooner */ |
| VipsSinkNotify notify; /* Tell caller about paints here */ |
| void *a; |
| |
| /* Lock here before reading or modifying the tile structure. |
| */ |
| GMutex *lock; |
| |
| /* Tile cache. |
| */ |
| GSList *all; /* All our tiles */ |
| int ntiles; /* Number of tiles */ |
| int ticks; /* Inc. on each access ... used for LRU */ |
| |
| /* List of dirty tiles. Most recent at the front. |
| */ |
| GSList *dirty; |
| |
| /* Hash of tiles with positions. Tiles can be dirty or 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 calculations. |
| */ |
| static GThread *render_thread = NULL; |
| |
| /* Number of renders with dirty tiles. render_thread queues up on this. |
| */ |
| static im_semaphore_t render_dirty_sem; |
| |
| /* All the renders with dirty tiles. |
| */ |
| static GMutex *render_dirty_lock = NULL; |
| static GSList *render_dirty_all = NULL; |
| |
| /* Set this to make the bg thread stop and reschedule. |
| */ |
| static gboolean render_reschedule = FALSE; |
| |
| 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 * |
| tile_free( Tile *tile ) |
| { |
| VIPS_DEBUG_MSG_AMBER( "tile_free\n" ); |
| |
| IM_FREEF( im_region_free, tile->region ); |
| im_free( tile ); |
| |
| return( NULL ); |
| } |
| |
| static int |
| render_free( Render *render ) |
| { |
| VIPS_DEBUG_MSG_AMBER( "render_free: %p\n", render ); |
| |
| g_assert( render->ref_count == 0 ); |
| |
| g_mutex_lock( render_dirty_lock ); |
| if( g_slist_find( render_dirty_all, render ) ) { |
| render_dirty_all = g_slist_remove( render_dirty_all, render ); |
| |
| /* We could im_semaphore_upn( &render_dirty_sem, -1 ), but |
| * what's the point. We'd just wake up the bg thread |
| * for no reason. |
| */ |
| } |
| g_mutex_unlock( render_dirty_lock ); |
| |
| g_mutex_free( render->ref_count_lock ); |
| g_mutex_free( render->lock ); |
| |
| im_slist_map2( render->all, (VSListMap2Fn) tile_free, NULL, NULL ); |
| IM_FREEF( g_slist_free, render->all ); |
| render->ntiles = 0; |
| IM_FREEF( g_slist_free, render->dirty ); |
| IM_FREEF( g_hash_table_destroy, render->tiles ); |
| |
| im_free( render ); |
| |
| #ifdef VIPS_DEBUG_AMBER |
| render_num_renders -= 1; |
| #endif /*VIPS_DEBUG_AMBER*/ |
| |
| return( 0 ); |
| } |
| |
| /* Ref and unref a Render ... free on last unref. |
| */ |
| static int |
| render_ref( Render *render ) |
| { |
| g_mutex_lock( render->ref_count_lock ); |
| g_assert( render->ref_count != 0 ); |
| render->ref_count += 1; |
| g_mutex_unlock( render->ref_count_lock ); |
| |
| return( 0 ); |
| } |
| |
| static int |
| render_unref( Render *render ) |
| { |
| int kill; |
| |
| g_mutex_lock( render->ref_count_lock ); |
| g_assert( render->ref_count > 0 ); |
| render->ref_count -= 1; |
| kill = render->ref_count == 0; |
| g_mutex_unlock( render->ref_count_lock ); |
| |
| if( kill ) |
| render_free( render ); |
| |
| return( 0 ); |
| } |
| |
| /* Wait for a render with dirty tiles. |
| */ |
| static Render * |
| render_dirty_get( void ) |
| { |
| Render *render; |
| |
| /* Wait for a render with dirty tiles. |
| */ |
| im_semaphore_down( &render_dirty_sem ); |
| |
| g_mutex_lock( render_dirty_lock ); |
| |
| /* Just take the head of the jobs list ... we sort when we add. If |
| * render_free() is called between our semaphore letting us in |
| * and the _lock(), render_dirty_all can be NULL. |
| */ |
| render = NULL; |
| if( render_dirty_all ) { |
| render = (Render *) render_dirty_all->data; |
| |
| /* Ref the render to make sure it can't die while we're |
| * working on it. |
| */ |
| render_ref( render ); |
| |
| render_dirty_all = g_slist_remove( render_dirty_all, render ); |
| } |
| |
| g_mutex_unlock( render_dirty_lock ); |
| |
| return( render ); |
| } |
| |
| /* Get the next tile to paint off the dirty list. |
| */ |
| static Tile * |
| render_tile_dirty_get( Render *render ) |
| { |
| Tile *tile; |
| |
| if( !render->dirty ) |
| tile = NULL; |
| else { |
| tile = (Tile *) render->dirty->data; |
| g_assert( tile->dirty ); |
| render->dirty = g_slist_remove( render->dirty, tile ); |
| tile->dirty = FALSE; |
| } |
| |
| return( tile ); |
| } |
| |
| /* Pick a dirty tile to reuse. We could potentially get the tile that |
| * render_work() is working on in the background :-( but I don't think we'll |
| * get a crash, just a mis-paint. It should be vanishingly impossible anyway. |
| */ |
| static Tile * |
| render_tile_dirty_reuse( Render *render ) |
| { |
| Tile *tile; |
| |
| if( !render->dirty ) |
| tile = NULL; |
| else { |
| tile = (Tile *) g_slist_last( render->dirty )->data; |
| render->dirty = g_slist_remove( render->dirty, tile ); |
| g_assert( tile->dirty ); |
| tile->dirty = FALSE; |
| |
| VIPS_DEBUG_MSG( "render_tile_get_dirty_reuse: " |
| "reusing dirty %p\n", tile ); |
| } |
| |
| return( tile ); |
| } |
| |
| /* Add a tile to the dirty list. |
| */ |
| static void |
| tile_dirty_set( Tile *tile ) |
| { |
| Render *render = tile->render; |
| |
| if( !tile->dirty ) { |
| g_assert( !g_slist_find( render->dirty, tile ) ); |
| render->dirty = g_slist_prepend( render->dirty, tile ); |
| tile->dirty = TRUE; |
| tile->painted = FALSE; |
| } |
| else |
| g_assert( g_slist_find( render->dirty, tile ) ); |
| } |
| |
| /* Bump a tile to the front of the dirty list, if it's there. |
| */ |
| static void |
| tile_dirty_bump( Tile *tile ) |
| { |
| Render *render = tile->render; |
| |
| if( tile->dirty ) { |
| g_assert( g_slist_find( render->dirty, tile ) ); |
| |
| render->dirty = g_slist_remove( render->dirty, tile ); |
| render->dirty = g_slist_prepend( render->dirty, tile ); |
| } |
| else |
| g_assert( !g_slist_find( render->dirty, tile ) ); |
| } |
| |
| static int |
| render_allocate( VipsThreadState *state, void *a, gboolean *stop ) |
| { |
| Render *render = (Render *) a; |
| RenderThreadState *rstate = (RenderThreadState *) state; |
| Tile *tile; |
| |
| g_mutex_lock( render->lock ); |
| |
| if( render_reschedule || |
| !(tile = render_tile_dirty_get( render )) ) { |
| VIPS_DEBUG_MSG_GREEN( "render_allocate: stopping\n" ); |
| *stop = TRUE; |
| rstate->tile = NULL; |
| } |
| else |
| rstate->tile = tile; |
| |
| g_mutex_unlock( render->lock ); |
| |
| return( 0 ); |
| } |
| |
| static int |
| render_work( VipsThreadState *state, void *a ) |
| { |
| Render *render = (Render *) a; |
| RenderThreadState *rstate = (RenderThreadState *) state; |
| Tile *tile = rstate->tile; |
| |
| g_assert( tile ); |
| |
| VIPS_DEBUG_MSG( "calculating tile %p %dx%d\n", |
| tile, tile->area.left, tile->area.top ); |
| |
| if( im_prepare_to( state->reg, tile->region, |
| &tile->area, tile->area.left, tile->area.top ) ) { |
| VIPS_DEBUG_MSG_RED( "render_work: im_prepare_to() failed\n" ); |
| return( -1 ); |
| } |
| tile->painted = TRUE; |
| |
| /* Now clients can update. |
| */ |
| if( render->notify ) |
| render->notify( render->out, &tile->area, render->a ); |
| |
| return( 0 ); |
| } |
| |
| static int |
| render_dirty_sort( Render *a, Render *b ) |
| { |
| return( b->priority - a->priority ); |
| } |
| |
| /* Add to the jobs list, if it has work to be done. |
| */ |
| static void |
| render_dirty_put( Render *render ) |
| { |
| g_mutex_lock( render_dirty_lock ); |
| |
| if( render->dirty ) { |
| if( !g_slist_find( render_dirty_all, render ) ) { |
| render_dirty_all = g_slist_prepend( render_dirty_all, |
| render ); |
| render_dirty_all = g_slist_sort( render_dirty_all, |
| (GCompareFunc) render_dirty_sort ); |
| |
| /* Ask the bg thread to stop and reschedule, if it's |
| * running. |
| */ |
| VIPS_DEBUG_MSG_GREEN( "render_dirty_put: " |
| "reschedule\n" ); |
| render_reschedule = TRUE; |
| |
| im_semaphore_up( &render_dirty_sem ); |
| } |
| } |
| |
| g_mutex_unlock( render_dirty_lock ); |
| } |
| |
| /* Main loop for RenderThreads. |
| */ |
| static void * |
| render_thread_main( void *client ) |
| { |
| for(;;) { |
| Render *render; |
| |
| if( (render = render_dirty_get()) ) { |
| /* Ignore errors, I'm not sure what we'd do with them |
| * anyway. |
| */ |
| VIPS_DEBUG_MSG_GREEN( "render_thread_main: " |
| "threadpool start\n" ); |
| |
| render_reschedule = FALSE; |
| if( vips_threadpool_run( render->in, |
| render_thread_state_new, |
| render_allocate, |
| render_work, |
| NULL, |
| render ) ) |
| VIPS_DEBUG_MSG_RED( "render_thread_main: " |
| "threadpool_run failed\n" ); |
| |
| VIPS_DEBUG_MSG_GREEN( "render_thread_main: " |
| "threadpool return\n" ); |
| |
| /* Add back to the jobs list, if we need to. |
| */ |
| render_dirty_put( render ); |
| |
| /* _get() does a ref to make sure we keep the render |
| * alive during processing ... unref before we loop. |
| * This can kill off the render. |
| */ |
| render_unref( render ); |
| } |
| } |
| |
| return( NULL ); |
| } |
| |
| /* Create our set of RenderThread. Assume we're single-threaded here. |
| */ |
| static int |
| render_thread_create( void ) |
| { |
| if( !have_threads ) |
| return( 0 ); |
| |
| if( !render_dirty_lock ) { |
| render_dirty_lock = g_mutex_new(); |
| im_semaphore_init( &render_dirty_sem, 0, "render_dirty_sem" ); |
| } |
| |
| 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_error( "im_render", |
| "%s", _( "unable to create thread" ) ); |
| return( -1 ); |
| } |
| } |
| |
| return( 0 ); |
| } |
| |
| static guint |
| tile_hash( gconstpointer key ) |
| { |
| Rect *rect = (Rect *) key; |
| |
| int x = rect->left / rect->width; |
| int y = rect->top / rect->height; |
| |
| return( x << 16 ^ y ); |
| } |
| |
| static gboolean |
| tile_equal( gconstpointer a, gconstpointer b ) |
| { |
| Rect *rect1 = (Rect *) a; |
| Rect *rect2 = (Rect *) b; |
| |
| return( rect1->left == rect2->left && |
| rect1->top == rect2->top ); |
| } |
| |
| static int |
| render_close_cb( Render *render ) |
| { |
| VIPS_DEBUG_MSG_AMBER( "render_close_cb\n" ); |
| |
| render_unref( render ); |
| |
| /* If this render is being worked on, we want to jog the bg thread, |
| * make it drop it's ref and think again. |
| */ |
| VIPS_DEBUG_MSG_GREEN( "render_close_cb: reschedule\n" ); |
| render_reschedule = TRUE; |
| |
| return( 0 ); |
| } |
| |
| static Render * |
| render_new( VipsImage *in, VipsImage *out, VipsImage *mask, |
| int tile_width, int tile_height, |
| int max_tiles, |
| int priority, |
| VipsSinkNotify notify, void *a ) |
| { |
| Render *render; |
| |
| /* Don't use auto-free for render, we do our own lifetime management |
| * with _ref() and _unref(). |
| */ |
| if( !(render = IM_NEW( NULL, Render )) ) |
| return( NULL ); |
| |
| render->ref_count = 1; |
| render->ref_count_lock = g_mutex_new(); |
| |
| render->in = in; |
| render->out = out; |
| render->mask = mask; |
| render->tile_width = tile_width; |
| render->tile_height = tile_height; |
| render->max_tiles = max_tiles; |
| render->priority = priority; |
| render->notify = notify; |
| render->a = a; |
| |
| render->lock = g_mutex_new(); |
| |
| render->all = NULL; |
| render->ntiles = 0; |
| render->ticks = 0; |
| |
| render->tiles = g_hash_table_new( tile_hash, tile_equal ); |
| |
| render->dirty = NULL; |
| |
| /* Both out and mask must close before we can free the render. |
| */ |
| |
| if( im_add_close_callback( out, |
| (im_callback_fn) render_close_cb, render, NULL ) ) { |
| (void) render_unref( render ); |
| return( NULL ); |
| } |
| |
| if( mask ) { |
| if( im_add_close_callback( mask, |
| (im_callback_fn) render_close_cb, render, NULL ) ) { |
| (void) render_unref( render ); |
| return( NULL ); |
| } |
| render_ref( render ); |
| } |
| |
| VIPS_DEBUG_MSG_AMBER( "render_new: %p\n", render ); |
| |
| #ifdef VIPS_DEBUG_AMBER |
| render_num_renders += 1; |
| #endif /*VIPS_DEBUG_AMBER*/ |
| |
| return( render ); |
| } |
| |
| /* Make a Tile. |
| */ |
| static Tile * |
| tile_new( Render *render ) |
| { |
| Tile *tile; |
| |
| VIPS_DEBUG_MSG_AMBER( "tile_new\n" ); |
| |
| /* Don't use auto-free: we need to make sure we free the tile after |
| * Render. |
| */ |
| if( !(tile = IM_NEW( NULL, Tile )) ) |
| return( NULL ); |
| |
| tile->render = render; |
| tile->area.left = 0; |
| tile->area.top = 0; |
| tile->area.width = render->tile_width; |
| tile->area.height = render->tile_height; |
| tile->region = NULL; |
| tile->painted = FALSE; |
| tile->dirty = FALSE; |
| tile->ticks = render->ticks; |
| |
| if( !(tile->region = im_region_create( render->in )) ) { |
| (void) tile_free( tile ); |
| return( NULL ); |
| } |
| |
| render->all = g_slist_prepend( render->all, tile ); |
| render->ntiles += 1; |
| |
| return( tile ); |
| } |
| |
| /* Search the cache for a tile by position. |
| */ |
| static Tile * |
| render_tile_lookup( Render *render, Rect *area ) |
| { |
| return( (Tile *) g_hash_table_lookup( render->tiles, area ) ); |
| } |
| |
| /* Add a new tile to the table. |
| */ |
| static void |
| render_tile_add( Tile *tile, Rect *area ) |
| { |
| Render *render = tile->render; |
| |
| g_assert( !render_tile_lookup( render, area ) ); |
| |
| tile->area = *area; |
| tile->painted = FALSE; |
| |
| /* Ignore buffer allocate errors, there's not much we could do with |
| * them. |
| */ |
| if( im_region_buffer( tile->region, &tile->area ) ) |
| VIPS_DEBUG_MSG_RED( "render_tile_add: " |
| "buffer allocate failed\n" ); |
| |
| g_hash_table_insert( render->tiles, &tile->area, tile ); |
| } |
| |
| /* Move a tile to a new position. |
| */ |
| static void |
| render_tile_move( Tile *tile, Rect *area ) |
| { |
| Render *render = tile->render; |
| |
| g_assert( render_tile_lookup( render, &tile->area ) ); |
| |
| if( tile->area.left != area->left || |
| tile->area.top != area->top ) { |
| g_assert( !render_tile_lookup( render, area ) ); |
| |
| g_hash_table_remove( render->tiles, &tile->area ); |
| render_tile_add( tile, area ); |
| } |
| } |
| |
| /* We've looked at a tile ... bump to end of LRU and front of dirty. |
| */ |
| static void |
| tile_touch( Tile *tile ) |
| { |
| Render *render = tile->render; |
| |
| tile->ticks = render->ticks; |
| render->ticks += 1; |
| tile_dirty_bump( tile ); |
| } |
| |
| /* Queue a tile for calculation. |
| */ |
| static void |
| tile_queue( Tile *tile ) |
| { |
| Render *render = tile->render; |
| |
| VIPS_DEBUG_MSG( "tile_queue: adding tile %p %dx%d to dirty\n", |
| tile, tile->area.left, tile->area.top ); |
| |
| tile->painted = FALSE; |
| tile_touch( tile ); |
| |
| if( render->notify && have_threads ) { |
| /* Add to the list of renders with dirty tiles. The bg |
| * thread will pick it up and paint it. It can be already on |
| * the dirty list. |
| */ |
| tile_dirty_set( 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 |
| * never see black tiles. |
| */ |
| VIPS_DEBUG_MSG( "tile_queue: " |
| "painting tile %p %dx%d synchronously\n", |
| tile, tile->area.left, tile->area.top ); |
| |
| if( im_prepare( tile->region, &tile->area ) ) |
| VIPS_DEBUG_MSG_RED( "tile_queue: prepare failed\n" ); |
| |
| tile->painted = TRUE; |
| } |
| } |
| |
| static void |
| tile_test_clean_ticks( Rect *key, Tile *value, Tile **best ) |
| { |
| if( value->painted ) |
| if( !*best || value->ticks < (*best)->ticks ) |
| *best = value; |
| } |
| |
| /* 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, |
| (GHFunc) tile_test_clean_ticks, &tile ); |
| |
| if( tile ) { |
| VIPS_DEBUG_MSG( "render_tile_get_painted: " |
| "reusing painted %p\n", tile ); |
| } |
| |
| 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_request( Render *render, Rect *area ) |
| { |
| Tile *tile; |
| |
| VIPS_DEBUG_MSG( "render_tile_request: asking for %dx%d\n", |
| area->left, area->top ); |
| |
| if( (tile = render_tile_lookup( render, area )) ) { |
| /* We already have a tile at this position. If it's invalid, |
| * ask for a repaint. |
| */ |
| if( tile->region->invalid ) |
| tile_queue( tile ); |
| else |
| tile_touch( tile ); |
| } |
| else if( render->ntiles < render->max_tiles || |
| render->max_tiles == -1 ) { |
| /* We have fewer tiles than teh max. We can just make a new |
| * tile. |
| */ |
| if( !(tile = tile_new( render )) ) |
| return( NULL ); |
| |
| render_tile_add( tile, area ); |
| |
| tile_queue( tile ); |
| } |
| else { |
| /* Need to reuse a tile. Try for an old painted tile first, |
| * then if that fails, reuse a dirty tile. |
| */ |
| if( !(tile = render_tile_get_painted( render )) && |
| !(tile = render_tile_dirty_reuse( render )) ) { |
| VIPS_DEBUG_MSG( "render_tile_request: " |
| "no tiles to reuse\n" ); |
| return( NULL ); |
| } |
| |
| render_tile_move( tile, area ); |
| |
| tile_queue( tile ); |
| } |
| |
| return( tile ); |
| } |
| |
| /* Copy what we can from the tile into the region. |
| */ |
| static void |
| tile_copy( Tile *tile, REGION *to ) |
| { |
| Rect ovlap; |
| int y; |
| |
| /* Find common pixels. |
| */ |
| im_rect_intersectrect( &tile->area, &to->valid, &ovlap ); |
| g_assert( !im_rect_isempty( &ovlap ) ); |
| |
| /* If the tile is painted, copy over the pixels. Otherwise, fill with |
| * zero. |
| */ |
| if( tile->painted && !tile->region->invalid ) { |
| int len = IM_IMAGE_SIZEOF_PEL( to->im ) * ovlap.width; |
| |
| VIPS_DEBUG_MSG( "tile_copy: " |
| "copying calculated pixels for %p %dx%d\n", |
| tile, tile->area.left, tile->area.top ); |
| |
| for( y = ovlap.top; y < IM_RECT_BOTTOM( &ovlap ); y++ ) { |
| PEL *p = (PEL *) IM_REGION_ADDR( tile->region, |
| ovlap.left, y ); |
| PEL *q = (PEL *) IM_REGION_ADDR( to, ovlap.left, y ); |
| |
| memcpy( q, p, len ); |
| } |
| } |
| else { |
| VIPS_DEBUG_MSG( "tile_copy: zero filling for %p %dx%d\n", |
| tile, tile->area.left, tile->area.top ); |
| im_region_paint( to, &ovlap, 0 ); |
| } |
| } |
| |
| /* Loop over the output region, filling with data from cache. |
| */ |
| static int |
| region_fill( REGION *out, void *seq, void *a, void *b ) |
| { |
| Render *render = (Render *) a; |
| Rect *r = &out->valid; |
| int x, y; |
| |
| /* Find top left of tiles we need. |
| */ |
| int xs = (r->left / render->tile_width) * render->tile_width; |
| int ys = (r->top / render->tile_height) * render->tile_height; |
| |
| VIPS_DEBUG_MSG( "region_fill: left = %d, top = %d, " |
| "width = %d, height = %d\n", |
| r->left, r->top, r->width, r->height ); |
| |
| g_mutex_lock( render->lock ); |
| |
| /* |
| |
| FIXME ... if r fits inside a single tile, we could skip the |
| copy. |
| |
| */ |
| |
| 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->tile_width; |
| area.height = render->tile_height; |
| |
| tile = render_tile_request( render, &area ); |
| if( tile ) |
| tile_copy( tile, out ); |
| else |
| VIPS_DEBUG_MSG_RED( "region_fill: argh!\n" ); |
| } |
| |
| g_mutex_unlock( render->lock ); |
| |
| return( 0 ); |
| } |
| |
| /* The mask image is 255 / 0 for the state of painted for each tile. |
| */ |
| static int |
| mask_fill( REGION *out, void *seq, void *a, void *b ) |
| { |
| #ifdef HAVE_THREADS |
| Render *render = (Render *) a; |
| Rect *r = &out->valid; |
| int x, y; |
| |
| /* Find top left of tiles we need. |
| */ |
| int xs = (r->left / render->tile_width) * render->tile_width; |
| int ys = (r->top / render->tile_height) * render->tile_height; |
| |
| VIPS_DEBUG_MSG( "mask_fill: left = %d, top = %d, " |
| "width = %d, height = %d\n", |
| r->left, r->top, r->width, r->height ); |
| |
| g_mutex_lock( render->lock ); |
| |
| 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; |
| int value; |
| |
| area.left = x; |
| area.top = y; |
| area.width = render->tile_width; |
| area.height = render->tile_height; |
| |
| tile = render_tile_lookup( render, &area ); |
| value = (tile && |
| tile->painted && |
| !tile->region->invalid) ? 255 : 0; |
| |
| /* Only mark painted tiles containing valid pixels. |
| */ |
| im_region_paint( out, &area, value ); |
| } |
| |
| g_mutex_unlock( render->lock ); |
| #else /*!HAVE_THREADS*/ |
| im_region_paint( out, &out->valid, 255 ); |
| #endif /*HAVE_THREADS*/ |
| |
| return( 0 ); |
| } |
| |
| /** |
| * vips_sink_screen: |
| * @in: input image |
| * @out: output image |
| * @mask: mask image indicating valid pixels |
| * @tile_width: tile width |
| * @tile_height: tile height |
| * @max_tiles: maximum tiles to cache |
| * @priority: rendering priority |
| * @notify: pixels are ready notification 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 |
| * set of pixels are available. Calculated pixels are kept in a cache with |
| * tiles sized @tile_width by @tile_height pixels and with at most @max_tiles |
| * tiles. |
| * If @max_tiles is -1, the cache is of unlimited size (up to the maximum image |
| * size). |
| * The @mask image is a one-band uchar image and has 255 for pixels which are |
| * currently in cache and 0 for uncalculated pixels. |
| * |
| * Only a single sink is calculated at any one time, though many may be |
| * alive. Use @priority to indicate which renders are more important: |
| * zero means normal |
| * priority, negative numbers are low priority, positive numbers high |
| * priority. |
| * |
| * Calls to im_prepare() on @out return immediately and hold whatever is |
| * currently in cache for that #Rect (check @mask to see which parts of the |
| * #Rect are valid). Any pixels in the #Rect which are not in cache are added |
| * to a queue, and the @notify callback will trigger when those pixels are |
| * ready. |
| * |
| * The @notify callback is run from one of the background threads. In the |
| * callback |
| * you need to somehow send a message to the main thread that the pixels are |
| * ready. In a glib-based application, this is easily done with g_idle_add(). |
| * |
| * If @notify is %NULL then im_render_priority() runs synchronously. |
| * im_prepare() on @out will always block until the pixels have been |
| * calculated. |
| * |
| * See also: im_cache(), im_prepare(), vips_sink_disc(), vips_sink(). |
| * |
| * Returns: 0 on sucess, -1 on error. |
| */ |
| int |
| vips_sink_screen( VipsImage *in, VipsImage *out, VipsImage *mask, |
| int tile_width, int tile_height, |
| int max_tiles, |
| int priority, |
| VipsSinkNotify notify, void *a ) |
| { |
| Render *render; |
| |
| /* Make sure the bg work threads are ready. |
| */ |
| if( render_thread_create() ) |
| return( -1 ); |
| |
| if( tile_width <= 0 || tile_height <= 0 || |
| max_tiles < -1 ) { |
| im_error( "vips_sink_screen", "%s", _( "bad parameters" ) ); |
| return( -1 ); |
| } |
| |
| 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_demand_hint( mask, IM_SMALLTILE, in, NULL ) ) |
| return( -1 ); |
| |
| mask->Bands = 1; |
| mask->BandFmt = IM_BANDFMT_UCHAR; |
| mask->Type = IM_TYPE_B_W; |
| mask->Coding = IM_CODING_NONE; |
| } |
| |
| if( !(render = render_new( in, out, mask, |
| tile_width, tile_height, max_tiles, priority, notify, a )) ) |
| return( -1 ); |
| |
| VIPS_DEBUG_MSG( "vips_sink_screen: max = %d, %p\n", max_tiles, render ); |
| |
| if( im_generate( out, NULL, region_fill, NULL, render, NULL ) ) |
| return( -1 ); |
| if( mask && |
| im_generate( mask, NULL, mask_fill, NULL, render, NULL ) ) |
| return( -1 ); |
| |
| return( 0 ); |
| } |
| |
| void |
| im__print_renders( void ) |
| { |
| #ifdef VIPS_DEBUG_AMBER |
| printf( "%d active renders\n", render_num_renders ); |
| #endif /*VIPS_DEBUG_AMBER*/ |
| printf( "%d dirty renders\n", g_slist_length( render_dirty_all ) ); |
| } |