/* LIBGIMP - The GIMP Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * gimptile.c * * This library 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 3 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include "config.h" #include #include #define GIMP_DISABLE_DEPRECATION_WARNINGS #include "libgimpbase/gimpbase.h" #include "libgimpbase/gimpprotocol.h" #include "libgimpbase/gimpwire.h" #include "gimp.h" /** * SECTION: gimptile * @title: gimptile * @short_description: Functions for working with tiles. * * Functions for working with tiles. **/ /* This is the percentage of the maximum cache size that * should be cleared from the cache when an eviction is * necessary. */ #define FREE_QUANTUM 0.1 void gimp_read_expect_msg (GimpWireMessage *msg, gint type); static void gimp_tile_get (GimpTile *tile); static void gimp_tile_put (GimpTile *tile); static void gimp_tile_cache_insert (GimpTile *tile); static void gimp_tile_cache_flush (GimpTile *tile); /* private variables */ static GHashTable * tile_hash_table = NULL; static GList * tile_list_head = NULL; static GList * tile_list_tail = NULL; static gulong max_tile_size = 0; static gulong cur_cache_size = 0; static gulong max_cache_size = 0; /* public functions */ void gimp_tile_ref (GimpTile *tile) { g_return_if_fail (tile != NULL); tile->ref_count++; if (tile->ref_count == 1) { gimp_tile_get (tile); tile->dirty = FALSE; } gimp_tile_cache_insert (tile); } void gimp_tile_ref_zero (GimpTile *tile) { g_return_if_fail (tile != NULL); tile->ref_count++; if (tile->ref_count == 1) tile->data = g_new0 (guchar, tile->ewidth * tile->eheight * tile->bpp); gimp_tile_cache_insert (tile); } void _gimp_tile_ref_nocache (GimpTile *tile, gboolean init) { g_return_if_fail (tile != NULL); tile->ref_count++; if (tile->ref_count == 1) { if (init) { gimp_tile_get (tile); tile->dirty = FALSE; } else { tile->data = g_new (guchar, tile->ewidth * tile->eheight * tile->bpp); } } } void gimp_tile_unref (GimpTile *tile, gboolean dirty) { g_return_if_fail (tile != NULL); g_return_if_fail (tile->ref_count > 0); tile->ref_count--; tile->dirty |= dirty; if (tile->ref_count == 0) { gimp_tile_flush (tile); g_free (tile->data); tile->data = NULL; } } void gimp_tile_flush (GimpTile *tile) { g_return_if_fail (tile != NULL); if (tile->data && tile->dirty) { gimp_tile_put (tile); tile->dirty = FALSE; } } /** * gimp_tile_cache_size: * @kilobytes: new cache size in kilobytes * * Sets the size of the tile cache on the plug-in side. The tile cache * is used to reduce the number of tiles exchanged between the GIMP core * and the plug-in. See also gimp_tile_cache_ntiles(). **/ void gimp_tile_cache_size (gulong kilobytes) { max_cache_size = kilobytes * 1024; } /** * gimp_tile_cache_ntiles: * @ntiles: number of tiles that should fit into the cache * * Sets the size of the tile cache on the plug-in side. This function * is similar to gimp_tile_cache_size() but supports specifying the * number of tiles directly. * * If your plug-in access pixels tile-by-tile, it doesn't need a tile * cache at all. If however the plug-in accesses drawable pixel data * row-by-row, it should set the tile cache large enough to hold the * number of tiles per row. Double this size if your plug-in uses * shadow tiles. **/ void gimp_tile_cache_ntiles (gulong ntiles) { gimp_tile_cache_size ((ntiles * gimp_tile_width () * gimp_tile_height () * 4 + 1023) / 1024); } void _gimp_tile_cache_flush_drawable (GimpDrawable *drawable) { GList *list; g_return_if_fail (drawable != NULL); list = tile_list_head; while (list) { GimpTile *tile = list->data; list = list->next; if (tile->drawable == drawable) gimp_tile_cache_flush (tile); } } /* private functions */ static void gimp_tile_get (GimpTile *tile) { extern GIOChannel *_writechannel; GPTileReq tile_req; GPTileData *tile_data; GimpWireMessage msg; tile_req.drawable_ID = tile->drawable->drawable_id; tile_req.tile_num = tile->tile_num; tile_req.shadow = tile->shadow; if (! gp_tile_req_write (_writechannel, &tile_req, NULL)) gimp_quit (); gimp_read_expect_msg (&msg, GP_TILE_DATA); tile_data = msg.data; if (tile_data->drawable_ID != tile->drawable->drawable_id || tile_data->tile_num != tile->tile_num || tile_data->shadow != tile->shadow || tile_data->width != tile->ewidth || tile_data->height != tile->eheight || tile_data->bpp != tile->bpp) { g_message ("received tile info did not match computed tile info"); gimp_quit (); } if (tile_data->use_shm) { tile->data = g_memdup (gimp_shm_addr (), tile->ewidth * tile->eheight * tile->bpp); } else { tile->data = tile_data->data; tile_data->data = NULL; } if (! gp_tile_ack_write (_writechannel, NULL)) gimp_quit (); gimp_wire_destroy (&msg); } static void gimp_tile_put (GimpTile *tile) { extern GIOChannel *_writechannel; GPTileReq tile_req; GPTileData tile_data; GPTileData *tile_info; GimpWireMessage msg; tile_req.drawable_ID = -1; tile_req.tile_num = 0; tile_req.shadow = 0; if (! gp_tile_req_write (_writechannel, &tile_req, NULL)) gimp_quit (); gimp_read_expect_msg (&msg, GP_TILE_DATA); tile_info = msg.data; tile_data.drawable_ID = tile->drawable->drawable_id; tile_data.tile_num = tile->tile_num; tile_data.shadow = tile->shadow; tile_data.bpp = tile->bpp; tile_data.width = tile->ewidth; tile_data.height = tile->eheight; tile_data.use_shm = tile_info->use_shm; tile_data.data = NULL; if (tile_info->use_shm) memcpy (gimp_shm_addr (), tile->data, tile->ewidth * tile->eheight * tile->bpp); else tile_data.data = tile->data; if (! gp_tile_data_write (_writechannel, &tile_data, NULL)) gimp_quit (); if (! tile_info->use_shm) tile_data.data = NULL; gimp_wire_destroy (&msg); gimp_read_expect_msg (&msg, GP_TILE_ACK); gimp_wire_destroy (&msg); } /* This function is nearly identical to the function 'tile_cache_insert' * in the file 'tile_cache.c' which is part of the main gimp application. */ static void gimp_tile_cache_insert (GimpTile *tile) { GList *list; if (!tile_hash_table) { tile_hash_table = g_hash_table_new (g_direct_hash, NULL); max_tile_size = gimp_tile_width () * gimp_tile_height () * 4; } /* First check and see if the tile is already * in the cache. In that case we will simply place * it at the end of the tile list to indicate that * it was the most recently accessed tile. */ list = g_hash_table_lookup (tile_hash_table, tile); if (list) { /* The tile was already in the cache. Place it at * the end of the tile list. */ /* If the tile is already at the end of the list, we are done */ if (list == tile_list_tail) return; /* At this point we have at least two elements in our list */ g_assert (tile_list_head != tile_list_tail); tile_list_head = g_list_remove_link (tile_list_head, list); tile_list_tail = g_list_last (g_list_concat (tile_list_tail, list)); } else { /* The tile was not in the cache. First check and see * if there is room in the cache. If not then we'll have * to make room first. Note: it might be the case that the * cache is smaller than the size of a tile in which case * it won't be possible to put it in the cache. */ if ((cur_cache_size + max_tile_size) > max_cache_size) { while (tile_list_head && (cur_cache_size + max_cache_size * FREE_QUANTUM) > max_cache_size) { gimp_tile_cache_flush ((GimpTile *) tile_list_head->data); } if ((cur_cache_size + max_tile_size) > max_cache_size) return; } /* Place the tile at the end of the tile list. */ tile_list_tail = g_list_append (tile_list_tail, tile); if (! tile_list_head) tile_list_head = tile_list_tail; tile_list_tail = g_list_last (tile_list_tail); /* Add the tiles list node to the tile hash table. */ g_hash_table_insert (tile_hash_table, tile, tile_list_tail); /* Note the increase in the number of bytes the cache * is referencing. */ cur_cache_size += max_tile_size; /* Reference the tile so that it won't be returned to * the main gimp application immediately. */ tile->ref_count++; } } static void gimp_tile_cache_flush (GimpTile *tile) { GList *list; if (! tile_hash_table) return; /* Find where the tile is in the cache. */ list = g_hash_table_lookup (tile_hash_table, tile); if (list) { /* If the tile is in the cache, then remove it from the * tile list. */ if (list == tile_list_tail) tile_list_tail = tile_list_tail->prev; tile_list_head = g_list_remove_link (tile_list_head, list); if (! tile_list_head) tile_list_tail = NULL; /* Remove the tile from the tile hash table. */ g_hash_table_remove (tile_hash_table, tile); g_list_free (list); /* Note the decrease in the number of bytes the cache * is referencing. */ cur_cache_size -= max_tile_size; /* Unreference the tile. */ gimp_tile_unref (tile, FALSE); } }