Gimp/app/base/tile-manager.c
Michael Natterer 68ee4a5d6c base: add a proper version of the code that tracked down the tile manager leak
For GIMP_UNSTABLE, keep around a list of allocated tile managers and
have a function tile_manager_exit() which complains about them and
unrefs them. This is infinitely more helpful than the tile cache and
swap complaining about not being empty, because there is absolutely
nothing wrong with swap and cache when we simply leaked tile managers.
2010-02-10 12:33:30 +01:00

870 lines
20 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <glib-object.h>
#include "base-types.h"
#include "tile.h"
#include "tile-cache.h"
#include "tile-manager.h"
#include "tile-manager-private.h"
#include "tile-rowhints.h"
#include "tile-swap.h"
#include "tile-private.h"
static void tile_manager_allocate_tiles (TileManager *tm);
#ifdef TILE_PROFILING
extern gint tile_exist_peak;
extern gint tile_exist_count;
#endif
#ifdef GIMP_UNSTABLE
GList *tile_managers = NULL;
#endif
GType
gimp_tile_manager_get_type (void)
{
static GType type = 0;
if (! type)
type = g_boxed_type_register_static ("TileManager",
(GBoxedCopyFunc) tile_manager_ref,
(GBoxedFreeFunc) tile_manager_unref);
return type;
}
#ifdef GIMP_UNSTABLE
void
tile_manager_exit (void)
{
if (tile_managers)
{
g_warning ("%d tile managers leaked", g_list_length (tile_managers));
while (tile_managers)
{
g_printerr ("unref tile manager %p (%d x %d)\n",
tile_managers->data,
tile_manager_width (tile_managers->data),
tile_manager_height (tile_managers->data));
tile_manager_unref (tile_managers->data);
}
}
}
#endif
static inline gint
tile_manager_get_tile_num (TileManager *tm,
gint xpixel,
gint ypixel)
{
if ((xpixel < 0) || (xpixel >= tm->width) ||
(ypixel < 0) || (ypixel >= tm->height))
return -1;
return (ypixel / TILE_HEIGHT) * tm->ntile_cols + (xpixel / TILE_WIDTH);
}
TileManager *
tile_manager_new (gint width,
gint height,
gint bpp)
{
TileManager *tm;
g_return_val_if_fail (width > 0 && height > 0, NULL);
g_return_val_if_fail (bpp > 0 && bpp <= 4, NULL);
tm = g_slice_new0 (TileManager);
tm->ref_count = 1;
tm->width = width;
tm->height = height;
tm->bpp = bpp;
tm->ntile_rows = (height + TILE_HEIGHT - 1) / TILE_HEIGHT;
tm->ntile_cols = (width + TILE_WIDTH - 1) / TILE_WIDTH;
tm->cached_num = -1;
#ifdef GIMP_UNSTABLE
tile_managers = g_list_prepend (tile_managers, tm);
#endif
return tm;
}
TileManager *
tile_manager_ref (TileManager *tm)
{
g_return_val_if_fail (tm != NULL, NULL);
tm->ref_count++;
return tm;
}
void
tile_manager_unref (TileManager *tm)
{
g_return_if_fail (tm != NULL);
tm->ref_count--;
if (tm->ref_count < 1)
{
#ifdef GIMP_UNSTABLE
tile_managers = g_list_remove (tile_managers, tm);
#endif
if (tm->cached_tile)
tile_release (tm->cached_tile, FALSE);
if (tm->tiles)
{
gint ntiles = tm->ntile_rows * tm->ntile_cols;
gint i;
for (i = 0; i < ntiles; i++)
tile_detach (tm->tiles[i], tm, i);
g_free (tm->tiles);
}
g_slice_free (TileManager, tm);
}
}
void
tile_manager_set_validate_proc (TileManager *tm,
TileValidateProc proc,
gpointer user_data)
{
g_return_if_fail (tm != NULL);
tm->validate_proc = proc;
tm->user_data = user_data;
}
Tile *
tile_manager_get_tile (TileManager *tm,
gint xpixel,
gint ypixel,
gboolean wantread,
gboolean wantwrite)
{
g_return_val_if_fail (tm != NULL, NULL);
return tile_manager_get (tm,
tile_manager_get_tile_num (tm, xpixel, ypixel),
wantread, wantwrite);
}
Tile *
tile_manager_get (TileManager *tm,
gint tile_num,
gboolean wantread,
gboolean wantwrite)
{
Tile *tile;
gint ntiles;
g_return_val_if_fail (tm != NULL, NULL);
ntiles = tm->ntile_rows * tm->ntile_cols;
if ((tile_num < 0) || (tile_num >= ntiles))
return NULL;
if (! tm->tiles)
tile_manager_allocate_tiles (tm);
tile = tm->tiles[tile_num];
if (G_UNLIKELY (wantwrite && ! wantread))
g_warning ("WRITE-ONLY TILE... UNTESTED!");
#ifdef DEBUG_TILE_MANAGER
if (G_UNLIKELY (tile->share_count && tile->write_count))
g_printerr (">> MEEPITY %d,%d <<\n", tile->share_count, tile->write_count);
#endif
if (wantread)
{
if (wantwrite)
{
if (tile_num == tm->cached_num)
{
tile_release (tm->cached_tile, FALSE);
tm->cached_tile = NULL;
tm->cached_num = -1;
}
if (tile->share_count > 1)
{
/* Copy-on-write required */
Tile *new = tile_new (tile->bpp);
new->ewidth = tile->ewidth;
new->eheight = tile->eheight;
new->valid = tile->valid;
new->size = new->ewidth * new->eheight * new->bpp;
new->data = g_new (guchar, new->size);
#ifdef TILE_PROFILING
tile_exist_count++;
if (tile_exist_count > tile_exist_peak)
tile_exist_peak = tile_exist_count;
#endif
if (tile->rowhint)
{
tile_allocate_rowhints (new);
memcpy (new->rowhint, tile->rowhint,
new->eheight * sizeof (TileRowHint));
}
if (tile->data)
{
memcpy (new->data, tile->data, new->size);
}
else
{
tile_lock (tile);
memcpy (new->data, tile->data, new->size);
tile_release (tile, FALSE);
}
tile_detach (tile, tm, tile_num);
tile_attach (new, tm, tile_num);
tile = new;
tm->tiles[tile_num] = tile;
}
/* must lock before marking dirty */
tile_lock (tile);
tile->write_count++;
tile->dirty = TRUE;
}
else
{
#ifdef DEBUG_TILE_MANAGER
if (G_UNLIKELY (tile->write_count))
g_printerr ("STINK! r/o on r/w tile (%d)\n", tile->write_count);
#endif
tile_lock (tile);
}
}
return tile;
}
Tile *
tile_manager_get_at (TileManager *tm,
gint tile_col,
gint tile_row,
gboolean wantread,
gboolean wantwrite)
{
g_return_val_if_fail (tm != NULL, NULL);
if (tile_col < 0 || tile_col >= tm->ntile_cols ||
tile_row < 0 || tile_row >= tm->ntile_rows)
return NULL;
return tile_manager_get (tm,
tile_row * tm->ntile_cols + tile_col,
wantread, wantwrite);
}
void
tile_manager_validate_tile (TileManager *tm,
Tile *tile)
{
g_return_if_fail (tm != NULL);
g_return_if_fail (tile != NULL);
tile->valid = TRUE;
if (tm->validate_proc)
{
(* tm->validate_proc) (tm, tile, tm->user_data);
}
else
{
/* Set the contents of the tile to empty */
memset (tile->data, 0, tile_size (tile));
}
#ifdef DEBUG_TILE_MANAGER
g_printerr ("%c", tm->user_data ? 'V' : 'v');
#endif
}
static void
tile_manager_allocate_tiles (TileManager *tm)
{
Tile **tiles;
const gint nrows = tm->ntile_rows;
const gint ncols = tm->ntile_cols;
const gint right_tile = tm->width - ((ncols - 1) * TILE_WIDTH);
const gint bottom_tile = tm->height - ((nrows - 1) * TILE_HEIGHT);
gint i, j, k;
g_assert (tm->tiles == NULL);
tiles = g_new (Tile *, nrows * ncols);
for (i = 0, k = 0; i < nrows; i++)
{
for (j = 0; j < ncols; j++, k++)
{
Tile *new = tile_new (tm->bpp);
tile_attach (new, tm, k);
if (j == (ncols - 1))
new->ewidth = right_tile;
if (i == (nrows - 1))
new->eheight = bottom_tile;
new->size = new->ewidth * new->eheight * new->bpp;
tiles[k] = new;
}
}
tm->tiles = tiles;
}
static void
tile_manager_invalidate_tile (TileManager *tm,
gint tile_num)
{
Tile *tile = tm->tiles[tile_num];
if (! tile->valid)
return;
if (tile_num == tm->cached_num)
{
tile_release (tm->cached_tile, FALSE);
tm->cached_tile = NULL;
tm->cached_num = -1;
}
if (tile->cached)
tile_cache_flush (tile);
if (G_UNLIKELY (tile->share_count > 1))
{
/* This tile is shared. Replace it with a new invalid tile. */
Tile *new = tile_new (tile->bpp);
new->ewidth = tile->ewidth;
new->eheight = tile->eheight;
new->size = tile->size;
tile_detach (tile, tm, tile_num);
tile_attach (new, tm, tile_num);
tile = new;
tm->tiles[tile_num] = tile;
}
tile->valid = FALSE;
if (tile->data)
{
g_free (tile->data);
tile->data = NULL;
#ifdef TILE_PROFILING
tile_exist_count--;
#endif
}
if (tile->swap_offset != -1)
{
/* If the tile is on disk, then delete its
* presence there.
*/
tile_swap_delete (tile);
}
}
static void
tile_manager_invalidate_pixel (TileManager *tm,
gint xpixel,
gint ypixel)
{
gint num = tile_manager_get_tile_num (tm, xpixel, ypixel);
if (num < 0)
return;
tile_manager_invalidate_tile (tm, num);
}
void
tile_manager_map_tile (TileManager *tm,
gint xpixel,
gint ypixel,
Tile *srctile)
{
g_return_if_fail (tm != NULL);
g_return_if_fail (srctile != NULL);
tile_manager_map (tm,
tile_manager_get_tile_num (tm, xpixel, ypixel),
srctile);
}
void
tile_manager_map (TileManager *tm,
gint tile_num,
Tile *srctile)
{
Tile *tile;
g_return_if_fail (tm != NULL);
g_return_if_fail (srctile != NULL);
g_return_if_fail (tile_num >= 0);
g_return_if_fail (tile_num < tm->ntile_rows * tm->ntile_cols);
if (G_UNLIKELY (! tm->tiles))
{
g_warning ("%s: empty tile level - initializing", G_STRLOC);
tile_manager_allocate_tiles (tm);
}
tile = tm->tiles[tile_num];
#ifdef DEBUG_TILE_MANAGER
g_printerr (")");
#endif
if (G_UNLIKELY (! srctile->valid))
g_warning("%s: srctile not validated yet! please report", G_STRLOC);
if (G_UNLIKELY (tile->ewidth != srctile->ewidth ||
tile->eheight != srctile->eheight ||
tile->bpp != srctile->bpp))
{
g_warning ("%s: nonconformant map (%p -> %p)",
G_STRLOC, srctile, tile);
}
tile_detach (tile, tm, tile_num);
#ifdef DEBUG_TILE_MANAGER
g_printerr (">");
#endif
#ifdef DEBUG_TILE_MANAGER
g_printerr (" [src:%p tm:%p tn:%d] ", srctile, tm, tile_num);
#endif
tile_attach (srctile, tm, tile_num);
tm->tiles[tile_num] = srctile;
#ifdef DEBUG_TILE_MANAGER
g_printerr ("}\n");
#endif
}
void
tile_manager_invalidate_area (TileManager *tm,
gint x,
gint y,
gint w,
gint h)
{
gint i;
gint j;
/* if no tiles have been allocated, there's no need to invalidate any */
if (! tm->tiles)
return;
for (i = y; i < (y + h); i += (TILE_HEIGHT - (i % TILE_HEIGHT)))
for (j = x; j < (x + w); j += (TILE_WIDTH - (j % TILE_WIDTH)))
{
tile_manager_invalidate_pixel (tm, j, i);
}
}
gint
tile_manager_width (const TileManager *tm)
{
g_return_val_if_fail (tm != NULL, 0);
return tm->width;
}
gint
tile_manager_height (const TileManager *tm)
{
g_return_val_if_fail (tm != NULL, 0);
return tm->height;
}
gint
tile_manager_bpp (const TileManager *tm)
{
g_return_val_if_fail (tm != NULL, 0);
return tm->bpp;
}
gint
tile_manager_tiles_per_col (const TileManager *tm)
{
g_return_val_if_fail (tm != NULL, 0);
return tm->ntile_cols;
}
gint
tile_manager_tiles_per_row (const TileManager *tm)
{
g_return_val_if_fail (tm != NULL, 0);
return tm->ntile_rows;
}
void
tile_manager_get_offsets (const TileManager *tm,
gint *x,
gint *y)
{
g_return_if_fail (tm != NULL);
g_return_if_fail (x != NULL && y != NULL);
*x = tm->x;
*y = tm->y;
}
void
tile_manager_set_offsets (TileManager *tm,
gint x,
gint y)
{
g_return_if_fail (tm != NULL);
tm->x = x;
tm->y = y;
}
gint64
tile_manager_get_memsize (const TileManager *tm,
gboolean sparse)
{
/* the tile manager itself */
gint64 memsize = sizeof (TileManager);
if (! tm)
return 0;
/* the array of tiles */
memsize += (gint64) tm->ntile_rows * tm->ntile_cols * (sizeof (Tile) +
sizeof (gpointer));
/* the memory allocated for the tiles */
if (sparse)
{
if (tm->tiles)
{
Tile **tiles = tm->tiles;
gint64 size = TILE_WIDTH * TILE_HEIGHT * tm->bpp;
gint i, j;
for (i = 0; i < tm->ntile_rows; i++)
for (j = 0; j < tm->ntile_cols; j++, tiles++)
{
if (tile_is_valid (*tiles))
memsize += size;
}
}
}
else
{
memsize += (gint64) tm->width * tm->height * tm->bpp;
}
return memsize;
}
static inline gint
tile_manager_locate_tile (TileManager *tm,
Tile *tile)
{
TileLink *tl;
for (tl = tile->tlink; tl; tl = tl->next)
{
if (tl->tm == tm)
break;
}
if (G_UNLIKELY (tl == NULL))
{
g_warning ("%s: tile not attached to manager", G_STRLOC);
return 0;
}
return tl->tile_num;
}
void
tile_manager_get_tile_col_row (TileManager *tm,
Tile *tile,
gint *tile_col,
gint *tile_row)
{
gint tile_num;
g_return_if_fail (tm != NULL);
g_return_if_fail (tile != NULL);
g_return_if_fail (tile_col != NULL && tile_row != NULL);
tile_num = tile_manager_locate_tile (tm, tile);
*tile_col = tile_num % tm->ntile_cols;
*tile_row = tile_num / tm->ntile_cols;
}
void
tile_manager_get_tile_coordinates (TileManager *tm,
Tile *tile,
gint *x,
gint *y)
{
gint tile_col;
gint tile_row;
g_return_if_fail (tm != NULL);
g_return_if_fail (tile != NULL);
g_return_if_fail (x != NULL && y != NULL);
tile_manager_get_tile_col_row (tm, tile, &tile_col, &tile_row);
*x = TILE_WIDTH * tile_col;
*y = TILE_HEIGHT * tile_row;
}
void
tile_manager_map_over_tile (TileManager *tm,
Tile *tile,
Tile *srctile)
{
TileLink *tl;
g_return_if_fail (tm != NULL);
g_return_if_fail (tile != NULL);
g_return_if_fail (srctile != NULL);
for (tl = tile->tlink; tl; tl = tl->next)
{
if (tl->tm == tm)
break;
}
if (G_UNLIKELY (tl == NULL))
{
g_warning ("%s: tile not attached to manager", G_STRLOC);
return;
}
tile_manager_map (tm, tl->tile_num, srctile);
}
void
read_pixel_data (TileManager *tm,
gint x1,
gint y1,
gint x2,
gint y2,
guchar *buffer,
guint stride)
{
guint x, y;
for (y = y1; y <= y2; y += TILE_HEIGHT - (y % TILE_HEIGHT))
for (x = x1; x <= x2; x += TILE_WIDTH - (x % TILE_WIDTH))
{
Tile *tile = tile_manager_get_tile (tm, x, y, TRUE, FALSE);
const guchar *s = TILE_DATA_POINTER (tile, x, y);
guchar *d = buffer + stride * (y - y1) + tm->bpp * (x - x1);
guint rows, cols;
guint srcstride;
rows = tile->eheight - y % TILE_HEIGHT;
if (rows > (y2 - y + 1))
rows = y2 - y + 1;
cols = tile->ewidth - x % TILE_WIDTH;
if (cols > (x2 - x + 1))
cols = x2 - x + 1;
srcstride = tile->ewidth * tile->bpp;
while (rows--)
{
memcpy (d, s, cols * tm->bpp);
s += srcstride;
d += stride;
}
tile_release (tile, FALSE);
}
}
void
write_pixel_data (TileManager *tm,
gint x1,
gint y1,
gint x2,
gint y2,
const guchar *buffer,
guint stride)
{
guint x, y;
for (y = y1; y <= y2; y += TILE_HEIGHT - (y % TILE_HEIGHT))
for (x = x1; x <= x2; x += TILE_WIDTH - (x % TILE_WIDTH))
{
Tile *tile = tile_manager_get_tile (tm, x, y, TRUE, TRUE);
const guchar *s = buffer + stride * (y - y1) + tm->bpp * (x - x1);
guchar *d = TILE_DATA_POINTER (tile, x, y);
guint rows, cols;
guint dststride;
rows = tile->eheight - y % TILE_HEIGHT;
if (rows > (y2 - y + 1))
rows = y2 - y + 1;
cols = tile->ewidth - x % TILE_WIDTH;
if (cols > (x2 - x + 1))
cols = x2 - x + 1;
dststride = tile->ewidth * tile->bpp;
while (rows--)
{
memcpy (d, s, cols * tm->bpp);
s += stride;
d += dststride;
}
tile_release (tile, TRUE);
}
}
void
read_pixel_data_1 (TileManager *tm,
gint x,
gint y,
guchar *buffer)
{
const gint num = tile_manager_get_tile_num (tm, x, y);
if (num < 0)
return;
if (num != tm->cached_num) /* must fetch a new tile */
{
Tile *tile;
if (tm->cached_tile)
tile_release (tm->cached_tile, FALSE);
tm->cached_num = -1;
tm->cached_tile = NULL;
/* use a temporary variable instead of assigning to
* tm->cached_tile directly to make sure tm->cached_num
* and tm->cached_tile are always in a consistent state.
* (the requested tile might be invalid and needs to be
* validated, which would call tile_manager_get() recursively,
* which in turn would clear the cached tile) See bug #472770.
*/
tile = tile_manager_get (tm, num, TRUE, FALSE);
tm->cached_num = num;
tm->cached_tile = tile;
}
{
const guchar *src = TILE_DATA_POINTER (tm->cached_tile, x, y);
switch (tm->bpp)
{
case 4:
*buffer++ = *src++;
case 3:
*buffer++ = *src++;
case 2:
*buffer++ = *src++;
case 1:
*buffer++ = *src++;
}
}
}
void
write_pixel_data_1 (TileManager *tm,
gint x,
gint y,
const guchar *buffer)
{
Tile *tile = tile_manager_get_tile (tm, x, y, TRUE, TRUE);
guchar *dest = TILE_DATA_POINTER (tile, x, y);
switch (tm->bpp)
{
case 4:
*dest++ = *buffer++;
case 3:
*dest++ = *buffer++;
case 2:
*dest++ = *buffer++;
case 1:
*dest++ = *buffer++;
}
tile_release (tile, TRUE);
}