blob: 74f34ed47df0f5fea751a8d948b76d80aa82d16c [file] [log] [blame]
/* Tile tile cache from tiff2vips ... broken out so it can be shared with
* openexr read.
*
* This isn't the same as im_cache(): we don't sub-divide, and we
* single-thread our callee.
*
* 23/8/06
* - take ownership of reused tiles in case the cache is being shared
* 13/2/07
* - release ownership after fillng with pixels in case we read across
* threads
* 4/2/10
* - gtkdoc
*/
/*
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
*/
/* Turn on debugging output.
#define DEBUG
*/
#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 <assert.h>
#include <vips/vips.h>
#include <vips/thread.h>
#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/
/* Lower and upper bounds for tile cache size. Choose an exact number based on
* tile size.
*/
#define IM_MAX_TILE_CACHE (250)
#define IM_MIN_TILE_CACHE (5)
/* A tile in our cache.
*/
typedef struct {
struct _Read *read;
REGION *region; /* REGION with private mem for data */
int time; /* Time of last use for flush */
int x; /* xy pos in VIPS image cods */
int y;
} Tile;
/* Stuff we track during a read.
*/
typedef struct _Read {
/* Parameters.
*/
IMAGE *in;
IMAGE *out;
int tile_width;
int tile_height;
int max_tiles;
/* Cache.
*/
int time; /* Update ticks for LRU here */
int ntiles; /* Current cache size */
GMutex *lock; /* Lock everything here */
GSList *cache; /* List of tiles */
} Read;
static void
tile_destroy( Tile *tile )
{
Read *read = tile->read;
read->cache = g_slist_remove( read->cache, tile );
read->ntiles -= 1;
assert( read->ntiles >= 0 );
tile->read = NULL;
IM_FREEF( im_region_free, tile->region );
im_free( tile );
}
static void
read_destroy( Read *read )
{
IM_FREEF( g_mutex_free, read->lock );
while( read->cache ) {
Tile *tile = (Tile *) read->cache->data;
tile_destroy( tile );
}
im_free( read );
}
static Read *
read_new( IMAGE *in, IMAGE *out,
int tile_width, int tile_height, int max_tiles )
{
Read *read;
if( !(read = IM_NEW( NULL, Read )) )
return( NULL );
read->in = in;
read->out = out;
read->tile_width = tile_width;
read->tile_height = tile_height;
read->max_tiles = max_tiles;
read->time = 0;
read->ntiles = 0;
read->lock = g_mutex_new();
read->cache = NULL;
if( im_add_close_callback( out,
(im_callback_fn) read_destroy, read, NULL ) ) {
read_destroy( read );
return( NULL );
}
return( read );
}
static Tile *
tile_new( Read *read )
{
Tile *tile;
if( !(tile = IM_NEW( NULL, Tile )) )
return( NULL );
tile->read = read;
tile->region = NULL;
tile->time = read->time;
tile->x = -1;
tile->y = -1;
read->cache = g_slist_prepend( read->cache, tile );
assert( read->ntiles >= 0 );
read->ntiles += 1;
if( !(tile->region = im_region_create( read->in )) ) {
tile_destroy( tile );
return( NULL );
}
return( tile );
}
/* Do we have a tile in the cache?
*/
static Tile *
tile_search( Read *read, int x, int y )
{
GSList *p;
for( p = read->cache; p; p = p->next ) {
Tile *tile = (Tile *) p->data;
if( tile->x == x && tile->y == y )
return( tile );
}
return( NULL );
}
static void
tile_touch( Tile *tile )
{
assert( tile->read->ntiles >= 0 );
tile->time = tile->read->time++;
}
/* Fill a tile with pixels.
*/
static int
tile_fill( Tile *tile, int x, int y )
{
Rect area;
tile->x = x;
tile->y = y;
#ifdef DEBUG
printf( "im_tile_cache: filling tile %d x %d\n", tile->x, tile->y );
#endif /*DEBUG*/
area.left = x;
area.top = y;
area.width = tile->read->tile_width;
area.height = tile->read->tile_height;
if( im_prepare( tile->region, &area ) )
return( -1 );
/* Make sure these pixels aren't part of this thread's buffer cache
* ... they may be read out by another thread.
*/
im__region_no_ownership( tile->region );
tile_touch( tile );
return( 0 );
}
/* Find existing tile, make a new tile, or if we have a full set of tiles,
* reuse LRU.
*/
static Tile *
tile_find( Read *read, int x, int y )
{
Tile *tile;
int oldest;
GSList *p;
/* In cache already?
*/
if( (tile = tile_search( read, x, y )) ) {
tile_touch( tile );
return( tile );
}
/* Cache not full?
*/
if( read->max_tiles == -1 ||
read->ntiles < read->max_tiles ) {
if( !(tile = tile_new( read )) ||
tile_fill( tile, x, y ) )
return( NULL );
return( tile );
}
/* Reuse an old one.
*/
oldest = read->time;
tile = NULL;
for( p = read->cache; p; p = p->next ) {
Tile *t = (Tile *) p->data;
if( t->time < oldest ) {
oldest = t->time;
tile = t;
}
}
assert( tile );
/* The tile may have been created by another thread if we are sharing
* the tile cache between several readers. Take ownership of the tile
* to stop assert() failures in im_prepare(). This is safe, since we
* are in a mutex.
*/
im__region_take_ownership( tile->region );
#ifdef DEBUG
printf( "im_tile_cache: reusing tile %d x %d\n", tile->x, tile->y );
#endif /*DEBUG*/
if( tile_fill( tile, x, y ) )
return( NULL );
return( tile );
}
/* Copy rect from from to to.
*/
static void
copy_region( REGION *from, REGION *to, Rect *area )
{
int y;
/* Area should be inside both from and to.
*/
assert( im_rect_includesrect( &from->valid, area ) );
assert( im_rect_includesrect( &to->valid, area ) );
/* Loop down common area, copying.
*/
for( y = area->top; y < IM_RECT_BOTTOM( area ); y++ ) {
PEL *p = (PEL *) IM_REGION_ADDR( from, area->left, y );
PEL *q = (PEL *) IM_REGION_ADDR( to, area->left, y );
memcpy( q, p, IM_IMAGE_SIZEOF_PEL( from->im ) * area->width );
}
}
/* Loop over the output region, filling with data from cache.
*/
static int
fill_region( REGION *out, void *seq, void *a, void *b )
{
Read *read = (Read *) a;
const int tw = read->tile_width;
const int th = read->tile_height;
Rect *r = &out->valid;
/* Find top left of tiles we need.
*/
int xs = (r->left / tw) * tw;
int ys = (r->top / th) * th;
int x, y;
g_mutex_lock( read->lock );
for( y = ys; y < IM_RECT_BOTTOM( r ); y += th )
for( x = xs; x < IM_RECT_RIGHT( r ); x += tw ) {
Tile *tile;
Rect tarea;
Rect hit;
if( !(tile = tile_find( read, x, y )) ) {
g_mutex_unlock( read->lock );
return( -1 );
}
/* The area of the tile.
*/
tarea.left = x;
tarea.top = y;
tarea.width = tw;
tarea.height = th;
/* The part of the tile that we need.
*/
im_rect_intersectrect( &tarea, r, &hit );
copy_region( tile->region, out, &hit );
}
g_mutex_unlock( read->lock );
return( 0 );
}
/**
* im_tile_cache:
* @in: input image
* @out: output image
* @tile_width: tile width
* @tile_height: tile height
* @max_tiles: maximum number of tiles to cache
*
* This operation behaves rather like im_copy() between images
* @in and @out, except that it keeps a cache of computed pixels.
* This cache is made of up to @max_tiles tiles (a value of -1 for
* means any number of tiles), and each tile is of size @tile_width
* by @tile_height pixels. Each cache tile is made with a single call to
* im_prepare().
*
* This is a lower-level operation than im_cache() since it does no
* subdivision. It is suitable for caching the output of operations like
* im_exr2vips() on tiled images.
*
* See also: im_cache().
*
* Returns: 0 on success, -1 on error.
*/
int
im_tile_cache( IMAGE *in, IMAGE *out,
int tile_width, int tile_height, int max_tiles )
{
Read *read;
if( tile_width <= 0 || tile_height <= 0 || max_tiles < -1 ) {
im_error( "im_tile_cache", "%s", _( "bad parameters" ) );
return( -1 );
}
if( im_piocheck( in, out ) ||
im_cp_desc( out, in ) ||
im_demand_hint( out, IM_SMALLTILE, in, NULL ) ||
!(read = read_new( in, out,
tile_width, tile_height, max_tiles )) ||
im_generate( out,
NULL, fill_region, NULL, read, NULL ) )
return( -1 );
return( 0 );
}