/* Manage sets of pixel buffers on an image. * * 30/10/06 * - from window.c * 2/2/07 * - speed up the search, use our own lock (thanks Christian) * 5/2/07 * - split to many buffer lists per image * 11/2/07 * - split to a buffer hash per thread * - reuse buffer mallocs when we can * 20/2/07 * - add im_buffer_cache_list_t and we can avoid some hash ops on * done/undone */ /* 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 */ /* #define DEBUG #define DEBUG_CREATE */ #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ #include #include #include #include #include #include #include #ifdef WITH_DMALLOC #include #endif /*WITH_DMALLOC*/ #ifdef DEBUG /* Track all regions here for debugging. */ static GSList *im__buffers_all = NULL; #endif /*DEBUG*/ #ifdef DEBUG_CREATE static int buffer_cache_n = 0; #endif /*DEBUG_CREATE*/ #ifdef HAVE_THREADS static GPrivate *thread_buffer_cache_key = NULL; #else /*!HAVE_THREADS*/ static im_buffer_cache_t *thread_buffer_cache = NULL; #endif /*HAVE_THREADS*/ /* Only need this if we're threading and need to do a lot of start/stop. */ #ifdef HAVE_THREADS static void buffer_cache_free( im_buffer_cache_t *cache ) { #ifdef DEBUG_CREATE buffer_cache_n -= 1; printf( "buffer_cache_free: freeing cache %p on thread %p\n", cache, g_thread_self() ); printf( "\t(%d cachees left)\n", buffer_cache_n ); #endif /*DEBUG_CREATE*/ IM_FREEF( g_hash_table_destroy, cache->hash ); IM_FREE( cache ); } #endif /*HAVE_THREADS*/ static void buffer_cache_list_free( im_buffer_cache_list_t *cache_list ) { GSList *p; /* Need to mark undone so we don't try and take them off this hash on * unref. */ for( p = cache_list->buffers; p; p = p->next ) { im_buffer_t *buffer = (im_buffer_t *) p->data; buffer->done = FALSE; } g_slist_free( cache_list->buffers ); im_free( cache_list ); } static im_buffer_cache_list_t * buffer_cache_list_new( im_buffer_cache_t *cache, IMAGE *im ) { im_buffer_cache_list_t *cache_list; if( !(cache_list = IM_NEW( NULL, im_buffer_cache_list_t )) ) return( NULL ); cache_list->buffers = NULL; cache_list->thread = g_thread_self(); cache_list->cache = cache; cache_list->im = im; #ifdef DEBUG_CREATE printf( "buffer_cache_list_new: new cache %p for thread %p\n", cache, g_thread_self() ); printf( "\t(%d cachees now)\n", buffer_cache_n ); #endif /*DEBUG_CREATE*/ return( cache_list ); } static im_buffer_cache_t * buffer_cache_new( void ) { im_buffer_cache_t *cache; if( !(cache = IM_NEW( NULL, im_buffer_cache_t )) ) return( NULL ); cache->hash = g_hash_table_new_full( g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) buffer_cache_list_free ); cache->thread = g_thread_self(); #ifdef DEBUG_CREATE buffer_cache_n += 1; printf( "buffer_cache_new: new cache %p for thread %p\n", cache, g_thread_self() ); printf( "\t(%d cachees now)\n", buffer_cache_n ); #endif /*DEBUG_CREATE*/ return( cache ); } /* Get the buffer cache. */ static im_buffer_cache_t * buffer_cache_get( void ) { im_buffer_cache_t *cache; #ifdef HAVE_THREADS if( !(cache = g_private_get( thread_buffer_cache_key )) ) { cache = buffer_cache_new(); g_private_set( thread_buffer_cache_key, cache ); } #else /*!HAVE_THREADS*/ if( !thread_buffer_cache ) thread_buffer_cache = buffer_cache_new(); cache = thread_buffer_cache; #endif /*HAVE_THREADS*/ return( cache ); } /* Pixels have been calculated: publish for other parts of this thread to see. */ void im_buffer_done( im_buffer_t *buffer ) { if( !buffer->done ) { IMAGE *im = buffer->im; im_buffer_cache_t *cache = buffer_cache_get(); im_buffer_cache_list_t *cache_list; #ifdef DEBUG printf( "im_buffer_done: thread %p adding " "buffer %p to cache %p\n", g_thread_self(), buffer, cache ); #endif /*DEBUG*/ /* Look up and update the buffer list. */ if( !(cache_list = g_hash_table_lookup( cache->hash, im )) ) { cache_list = buffer_cache_list_new( cache, im ); g_hash_table_insert( cache->hash, im, cache_list ); } assert( !g_slist_find( cache_list->buffers, buffer ) ); assert( cache_list->thread == cache->thread ); cache_list->buffers = g_slist_prepend( cache_list->buffers, buffer ); buffer->done = TRUE; buffer->cache = cache; } } /* Take off the public 'done' list. */ void im_buffer_undone( im_buffer_t *buffer ) { if( buffer->done ) { IMAGE *im = buffer->im; im_buffer_cache_t *cache = buffer->cache; im_buffer_cache_list_t *cache_list; #ifdef DEBUG printf( "im_buffer_undone: thread %p removing " "buffer %p from cache %p\n", g_thread_self(), buffer, cache ); #endif /*DEBUG*/ assert( cache->thread == g_thread_self() ); cache_list = g_hash_table_lookup( cache->hash, im ); assert( cache_list ); assert( cache_list->thread == cache->thread ); assert( g_slist_find( cache_list->buffers, buffer ) ); cache_list->buffers = g_slist_remove( cache_list->buffers, buffer ); buffer->done = FALSE; buffer->cache = NULL; #ifdef DEBUG printf( "im_buffer_undone: %d buffers left\n", g_slist_length( buffers ) ); #endif /*DEBUG*/ } } void im_buffer_unref( im_buffer_t *buffer ) { #ifdef DEBUG printf( "** im_buffer_unref: left = %d, top = %d, " "width = %d, height = %d (%p)\n", buffer->area.left, buffer->area.top, buffer->area.width, buffer->area.height, buffer ); #endif /*DEBUG*/ assert( buffer->ref_count > 0 ); buffer->ref_count -= 1; if( buffer->ref_count == 0 ) { #ifdef DEBUG if( !buffer->done ) printf( "im_buffer_unref: buffer was not done\n" ); #endif /*DEBUG*/ im_buffer_undone( buffer ); buffer->im = NULL; IM_FREE( buffer->buf ); buffer->bsize = 0; im_free( buffer ); #ifdef DEBUG g_mutex_lock( im__global_lock ); assert( g_slist_find( im__buffers_all, buffer ) ); im__buffers_all = g_slist_remove( im__buffers_all, buffer ); printf( "%d buffers in vips\n", g_slist_length( im__buffers_all ) ); g_mutex_unlock( im__global_lock ); #endif /*DEBUG*/ } } /* Make a new buffer. */ static im_buffer_t * buffer_new( IMAGE *im, Rect *area ) { im_buffer_t *buffer; if( !(buffer = IM_NEW( NULL, im_buffer_t )) ) return( NULL ); buffer->ref_count = 1; buffer->im = im; buffer->area = *area; buffer->done = FALSE; buffer->cache = NULL; buffer->invalid = FALSE; buffer->bsize = (size_t) IM_IMAGE_SIZEOF_PEL( im ) * area->width * area->height; if( !(buffer->buf = im_malloc( NULL, buffer->bsize )) ) { im_buffer_unref( buffer ); return( NULL ); } #ifdef DEBUG printf( "** buffer_new: left = %d, top = %d, " "width = %d, height = %d (%p)\n", buffer->area.left, buffer->area.top, buffer->area.width, buffer->area.height, buffer ); #endif /*DEBUG*/ #ifdef DEBUG g_mutex_lock( im__global_lock ); im__buffers_all = g_slist_prepend( im__buffers_all, buffer ); printf( "%d buffers in vips\n", g_slist_length( im__buffers_all ) ); g_mutex_unlock( im__global_lock ); #endif /*DEBUG*/ return( buffer ); } static int buffer_move( im_buffer_t *buffer, Rect *area ) { IMAGE *im = buffer->im; size_t new_bsize; assert( buffer->ref_count == 1 ); buffer->area = *area; im_buffer_undone( buffer ); assert( !buffer->done ); buffer->invalid = FALSE; new_bsize = (size_t) IM_IMAGE_SIZEOF_PEL( im ) * area->width * area->height; if( buffer->bsize < new_bsize ) { buffer->bsize = new_bsize; IM_FREE( buffer->buf ); if( !(buffer->buf = im_malloc( NULL, buffer->bsize )) ) return( -1 ); } return( 0 ); } /* Find an existing buffer that encloses area and return a ref. */ static im_buffer_t * buffer_find( IMAGE *im, Rect *r ) { im_buffer_cache_t *cache = buffer_cache_get(); im_buffer_cache_list_t *cache_list; im_buffer_t *buffer; GSList *p; Rect *area; cache_list = g_hash_table_lookup( cache->hash, im ); p = cache_list ? cache_list->buffers : NULL; /* This needs to be quick :-( don't use * im_slist_map2()/im_rect_includesrect(), do the search inline. */ for( ; p; p = p->next ) { buffer = (im_buffer_t *) p->data; area = &buffer->area; if( area->left <= r->left && area->top <= r->top && area->left + area->width >= r->left + r->width && area->top + area->height >= r->top + r->height ) { buffer->ref_count += 1; #ifdef DEBUG printf( "im_buffer_find: left = %d, top = %d, " "width = %d, height = %d, count = %d (%p)\n", buffer->area.left, buffer->area.top, buffer->area.width, buffer->area.height, buffer->ref_count, buffer ); #endif /*DEBUG*/ break; } } if( p ) return( buffer ); else return( NULL ); } /* Return a ref to a buffer that encloses area. */ im_buffer_t * im_buffer_ref( IMAGE *im, Rect *area ) { im_buffer_t *buffer; if( !(buffer = buffer_find( im, area )) ) /* No existing buffer ... make a new one. */ if( !(buffer = buffer_new( im, area )) ) return( NULL ); return( buffer ); } /* Unref old, ref new, in a single operation. Move the buffer if we can. */ im_buffer_t * im_buffer_unref_ref( im_buffer_t *old_buffer, IMAGE *im, Rect *area ) { im_buffer_t *buffer; assert( !old_buffer || old_buffer->im == im ); if( (buffer = buffer_find( im, area )) && !buffer->invalid ) { /* The new area has an OK buffer already: use that. */ IM_FREEF( im_buffer_unref, old_buffer ); } else if( old_buffer && old_buffer->ref_count == 1 ) { /* The old buffer is not shared ... we can reuse it. */ buffer = old_buffer; if( buffer_move( buffer, area ) ) { im_buffer_unref( buffer ); return( NULL ); } } else { /* Old buffer in use ... need another. */ IM_FREEF( im_buffer_unref, old_buffer ); if( !(buffer = buffer_new( im, area )) ) return( NULL ); } return( buffer ); } void im_buffer_print( im_buffer_t *buffer ) { printf( "im_buffer_t: %p ref_count = %d, ", buffer, buffer->ref_count ); printf( "im = %p, ", buffer->im ); printf( "area.left = %d, ", buffer->area.left ); printf( "area.top = %d, ", buffer->area.top ); printf( "area.width = %d, ", buffer->area.width ); printf( "area.height = %d, ", buffer->area.height ); printf( "done = %d, ", buffer->done ); printf( "invalid = %d, ", buffer->invalid ); printf( "buf = %p, ", buffer->buf ); printf( "bsize = %zd\n", buffer->bsize ); } /* Make a parent/child link. child is one of parent's inputs. */ void im__link_make( IMAGE *parent, IMAGE *child ) { assert( parent ); assert( child ); parent->children = g_slist_prepend( parent->children, child ); child->parents = g_slist_prepend( child->parents, parent ); /* Propogate the progress indicator. */ if( child->progress && !parent->progress ) parent->progress = child->progress; } /* Break link. child is one of parent's inputs. */ static void * im__link_break( IMAGE *parent, IMAGE *child ) { assert( parent ); assert( child ); assert( g_slist_find( parent->children, child ) ); assert( g_slist_find( child->parents, parent ) ); parent->children = g_slist_remove( parent->children, child ); child->parents = g_slist_remove( child->parents, parent ); /* Unlink the progress chain. */ if( parent->progress && parent->progress == child->progress ) parent->progress = NULL; return( NULL ); } static void * im__link_break_rev( IMAGE *child, IMAGE *parent ) { return( im__link_break( parent, child ) ); } /* An IMAGE is going ... break all links. */ void im__link_break_all( IMAGE *im ) { im_slist_map2( im->parents, (VSListMap2Fn) im__link_break, im, NULL ); im_slist_map2( im->children, (VSListMap2Fn) im__link_break_rev, im, NULL ); } static void * im__link_mapp( IMAGE *im, VSListMap2Fn fn, int *serial, void *a, void *b ) { void *res; /* Loop? */ if( im->serial == *serial ) return( NULL ); im->serial = *serial; if( (res = fn( im, a, b )) ) return( res ); return( im_slist_map4( im->parents, (VSListMap4Fn) im__link_mapp, fn, serial, a, b ) ); } /* Apply a function to an image and all it's parents, direct and indirect. */ void * im__link_map( IMAGE *im, VSListMap2Fn fn, void *a, void *b ) { static int serial = 0; serial += 1; return( im__link_mapp( im, fn, &serial, a, b ) ); } static void * im_invalidate_region( REGION *reg ) { if( reg->buffer ) reg->buffer->invalid = TRUE; return( NULL ); } static void * im_invalidate_image( IMAGE *im ) { (void) im_slist_map2( im->regions, (VSListMap2Fn) im_invalidate_region, NULL, NULL ); if( im__trigger_callbacks( im->invalidatefns ) ) return( im ); return( NULL ); } /* Invalidate all pixel caches on an IMAGE and any parents. */ void im_invalidate( IMAGE *im ) { (void) im__link_map( im, (VSListMap2Fn) im_invalidate_image, NULL, NULL ); } /* Init the buffer cache system. */ void im__buffer_init( void ) { #ifdef HAVE_THREADS if( !thread_buffer_cache_key ) thread_buffer_cache_key = g_private_new( (GDestroyNotify) buffer_cache_free ); #endif /*HAVE_THREADS*/ }