/* This file is part of GEGL. * * 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 * Lesser 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 . * * Copyright 2006,2007 Øyvind Kolås */ #include "config.h" #include #include #include "gegl.h" #include "gegl-types-internal.h" #include "gegl-config.h" #include "gegl-buffer.h" #include "gegl-buffer-private.h" #include "gegl-tile.h" #include "gegl-tile-handler-cache.h" #include "gegl-tile-storage.h" #include "gegl-debug.h" #include "gegl-buffer-cl-cache.h" /* #define GEGL_DEBUG_CACHE_HITS */ typedef struct CacheItem { GeglTileHandlerCache *handler; /* The specific handler that cached this item*/ GeglTile *tile; /* The tile */ gint x; /* The coordinates this tile was cached for */ gint y; gint z; } CacheItem; static void gegl_tile_handler_cache_dispose (GObject *object); static gboolean gegl_tile_handler_cache_wash (GeglTileHandlerCache *cache); static gpointer gegl_tile_handler_cache_command (GeglTileSource *tile_store, GeglTileCommand command, gint x, gint y, gint z, gpointer data); static GeglTile * gegl_tile_handler_cache_get_tile (GeglTileHandlerCache *cache, gint x, gint y, gint z); static gboolean gegl_tile_handler_cache_has_tile (GeglTileHandlerCache *cache, gint x, gint y, gint z); void gegl_tile_handler_cache_insert (GeglTileHandlerCache *cache, GeglTile *tile, gint x, gint y, gint z); static void gegl_tile_handler_cache_void (GeglTileHandlerCache *cache, gint x, gint y, gint z); static void gegl_tile_handler_cache_invalidate (GeglTileHandlerCache *cache, gint x, gint y, gint z); static GStaticMutex mutex = G_STATIC_MUTEX_INIT; static GQueue *cache_queue = NULL; static GHashTable *cache_ht = NULL; static gint cache_wash_percentage = 20; static gint cache_total = 0; /* approximate amount of bytes stored */ #ifdef GEGL_DEBUG_CACHE_HITS static gint cache_hits = 0; static gint cache_misses = 0; #endif G_DEFINE_TYPE (GeglTileHandlerCache, gegl_tile_handler_cache, GEGL_TYPE_TILE_HANDLER) static void gegl_tile_handler_cache_class_init (GeglTileHandlerCacheClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); gobject_class->dispose = gegl_tile_handler_cache_dispose; } static void gegl_tile_handler_cache_init (GeglTileHandlerCache *cache) { ((GeglTileSource*)cache)->command = gegl_tile_handler_cache_command; gegl_tile_cache_init (); } static void gegl_tile_handler_cache_reinit_buffer_tiles (gpointer itm, gpointer userdata) { CacheItem *item; item = itm; if (item->handler == userdata) { GeglTileHandlerCache *cache = userdata; cache->free_list = g_slist_prepend (cache->free_list, item); } } static void gegl_tile_handler_cache_reinit (GeglTileHandlerCache *cache) { CacheItem *item; GSList *iter; if (cache->tile_storage->hot_tile) { gegl_tile_unref (cache->tile_storage->hot_tile); cache->tile_storage->hot_tile = NULL; } if (!cache->count) return; g_static_mutex_lock (&mutex); /* only throw out items belonging to this cache instance */ cache->free_list = NULL; g_queue_foreach (cache_queue, gegl_tile_handler_cache_reinit_buffer_tiles, cache); for (iter = cache->free_list; iter; iter = g_slist_next (iter)) { item = iter->data; if (item->tile) { cache_total -= item->tile->size; gegl_tile_mark_as_stored (item->tile); /* to avoid saving */ gegl_tile_unref (item->tile); cache->count--; } g_queue_remove (cache_queue, item); g_hash_table_remove (cache_ht, item); g_slice_free (CacheItem, item); } g_slist_free (cache->free_list); cache->free_list = NULL; g_static_mutex_unlock (&mutex); } static void gegl_tile_handler_cache_dispose_buffer_tiles (gpointer itm, gpointer userdata) { CacheItem *item; item = itm; if (item->handler == userdata) { GeglTileHandlerCache *cache = userdata; cache->free_list = g_slist_prepend (cache->free_list, item); } } static void gegl_tile_handler_cache_dispose (GObject *object) { GeglTileHandlerCache *cache; CacheItem *item; GSList *iter; cache = GEGL_TILE_HANDLER_CACHE (object); /* only throw out items belonging to this cache instance */ cache->free_list = NULL; /* XXX: for optimization this could be delayed,. or collected among multiple * buffer destructions, to avoid the overhead of walking the full queue for * every tiny buffer being destroyed. */ if (cache->count) { g_static_mutex_lock (&mutex); g_queue_foreach (cache_queue, gegl_tile_handler_cache_dispose_buffer_tiles, cache); for (iter = cache->free_list; iter; iter = g_slist_next (iter)) { item = iter->data; if (item->tile) { cache_total -= item->tile->size; gegl_tile_unref (item->tile); cache->count--; } g_queue_remove (cache_queue, item); g_hash_table_remove (cache_ht, item); g_slice_free (CacheItem, item); } g_slist_free (cache->free_list); cache->free_list = NULL; g_static_mutex_unlock (&mutex); } if (cache->count != 0) { g_warning ("cache-handler tile balance not zero: %i\n", cache->count); } G_OBJECT_CLASS (gegl_tile_handler_cache_parent_class)->dispose (object); } static GeglTile * gegl_tile_handler_cache_get_tile_command (GeglTileSource *tile_store, gint x, gint y, gint z) { GeglTileHandlerCache *cache = GEGL_TILE_HANDLER_CACHE (tile_store); GeglTileSource *source = GEGL_TILE_HANDLER (tile_store)->source; GeglTile *tile = NULL; if (gegl_cl_is_accelerated ()) gegl_buffer_cl_cache_flush2 (cache, NULL); tile = gegl_tile_handler_cache_get_tile (cache, x, y, z); if (tile) { #ifdef GEGL_DEBUG_CACHE_HITS cache_hits++; #endif return tile; } #ifdef GEGL_DEBUG_CACHE_HITS cache_misses++; #endif if (source) tile = gegl_tile_source_get_tile (source, x, y, z); if (tile) gegl_tile_handler_cache_insert (cache, tile, x, y, z); return tile; } static gpointer gegl_tile_handler_cache_command (GeglTileSource *tile_store, GeglTileCommand command, gint x, gint y, gint z, gpointer data) { GeglTileHandler *handler = GEGL_TILE_HANDLER (tile_store); GeglTileHandlerCache *cache = GEGL_TILE_HANDLER_CACHE (handler); switch (command) { case GEGL_TILE_FLUSH: { GList *link; if (gegl_cl_is_accelerated ()) gegl_buffer_cl_cache_flush2 (GEGL_TILE_HANDLER_CACHE (tile_store), NULL); if (cache->count) { for (link = g_queue_peek_head_link (cache_queue); link; link = link->next) { CacheItem *item = link->data; GeglTile *tile = item->tile; if (tile != NULL && item->handler == cache) { gegl_tile_store (tile); } } } } break; case GEGL_TILE_GET: /* XXX: we should perhaps store a NIL result, and place the empty * generator after the cache, this would have to be possible to disable * to work in sync operation with backend. */ return gegl_tile_handler_cache_get_tile_command (tile_store, x, y, z); case GEGL_TILE_IS_CACHED: return GINT_TO_POINTER(gegl_tile_handler_cache_has_tile (cache, x, y, z)); case GEGL_TILE_EXIST: { gboolean exist = gegl_tile_handler_cache_has_tile (cache, x, y, z); if (exist) return (gpointer)TRUE; } break; case GEGL_TILE_IDLE: { gboolean action = gegl_tile_handler_cache_wash (cache); if (action) return GINT_TO_POINTER(action); /* with no action, we chain up to lower levels */ break; } case GEGL_TILE_REFETCH: gegl_tile_handler_cache_invalidate (cache, x, y, z); break; case GEGL_TILE_VOID: gegl_tile_handler_cache_void (cache, x, y, z); break; case GEGL_TILE_REINIT: gegl_tile_handler_cache_reinit (cache); break; default: break; } return gegl_tile_handler_source_command (handler, command, x, y, z, data); } /* write the least recently used dirty tile to disk if it * is in the wash_percentage (20%) least recently used tiles, * calling this function in an idle handler distributes the * tile flushing overhead over time. */ gboolean gegl_tile_handler_cache_wash (GeglTileHandlerCache *cache) { GeglTile *last_dirty = NULL; guint count = 0; gint length = g_queue_get_length (cache_queue); gint wash_tiles = cache_wash_percentage * length / 100; GList *link; for (link = g_queue_peek_head_link (cache_queue); link; link = link->next) { CacheItem *item = link->data; GeglTile *tile = item->tile; count++; if (!gegl_tile_is_stored (tile)) if (count > length - wash_tiles) last_dirty = tile; } if (last_dirty != NULL) { gegl_tile_store (last_dirty); return TRUE; } return FALSE; } /* returns the requested Tile if it is in the cache, NULL otherwize. */ static GeglTile * gegl_tile_handler_cache_get_tile (GeglTileHandlerCache *cache, gint x, gint y, gint z) { CacheItem *result; CacheItem pin; if (cache->count == 0) return NULL; pin.x = x; pin.y = y; pin.z = z; pin.handler = cache; g_static_mutex_lock (&mutex); result = g_hash_table_lookup (cache_ht, &pin); if (result) { g_queue_remove (cache_queue, result); g_queue_push_head (cache_queue, result); g_static_mutex_unlock (&mutex); return gegl_tile_ref (result->tile); } g_static_mutex_unlock (&mutex); return NULL; } static gboolean gegl_tile_handler_cache_has_tile (GeglTileHandlerCache *cache, gint x, gint y, gint z) { GeglTile *tile = gegl_tile_handler_cache_get_tile (cache, x, y, z); if (tile) { gegl_tile_unref (tile); return TRUE; } return FALSE; } static gboolean gegl_tile_handler_cache_trim (GeglTileHandlerCache *cache) { CacheItem *last_writable; last_writable = g_queue_pop_tail (cache_queue); if (last_writable != NULL) { g_hash_table_remove (cache_ht, last_writable); cache_total -= last_writable->tile->size; gegl_tile_unref (last_writable->tile); g_slice_free (CacheItem, last_writable); return TRUE; } return FALSE; } static void gegl_tile_handler_cache_invalidate (GeglTileHandlerCache *cache, gint x, gint y, gint z) { GList *link; g_static_mutex_lock (&mutex); for (link = g_queue_peek_head_link (cache_queue); link; link = link->next) { CacheItem *item = link->data; GeglTile *tile = item->tile; if (tile != NULL && item->x == x && item->y == y && item->z == z && item->handler == cache) { cache_total -= item->tile->size; tile->tile_storage = NULL; gegl_tile_mark_as_stored (tile); /* to cheat it out of being stored */ gegl_tile_unref (tile); g_hash_table_remove (cache_ht, item); g_slice_free (CacheItem, item); g_queue_delete_link (cache_queue, link); g_static_mutex_unlock (&mutex); return; } } g_static_mutex_unlock (&mutex); } static void gegl_tile_handler_cache_void (GeglTileHandlerCache *cache, gint x, gint y, gint z) { GList *link; if (!cache_queue) return; g_static_mutex_lock (&mutex); for (link = g_queue_peek_head_link (cache_queue); link; link = link->next) { CacheItem *item = link->data; GeglTile *tile = item->tile; if (tile != NULL && item->x == x && item->y == y && item->z == z && item->handler == cache) { gegl_tile_void (tile); cache_total -= item->tile->size; gegl_tile_unref (tile); g_hash_table_remove (cache_ht, item); g_slice_free (CacheItem, item); g_queue_delete_link (cache_queue, link); g_static_mutex_unlock (&mutex); return; } } g_static_mutex_unlock (&mutex); } void gegl_tile_handler_cache_insert (GeglTileHandlerCache *cache, GeglTile *tile, gint x, gint y, gint z) { CacheItem *item = g_slice_new (CacheItem); item->handler = cache; item->tile = gegl_tile_ref (tile); item->x = x; item->y = y; item->z = z; g_static_mutex_lock (&mutex); cache_total += item->tile->size; g_queue_push_head (cache_queue, item); cache->count ++; g_hash_table_insert (cache_ht, item, item); while (cache_total > gegl_config()->cache_size) { #ifdef GEGL_DEBUG_CACHE_HITS GEGL_NOTE(GEGL_DEBUG_CACHE, "cache_total:%i > cache_size:%i", cache_total, gegl_config()->cache_size); GEGL_NOTE(GEGL_DEBUG_CACHE, "%f%% hit:%i miss:%i %i]", cache_hits*100.0/(cache_hits+cache_misses), cache_hits, cache_misses, g_queue_get_length (cache_queue)); #endif gegl_tile_handler_cache_trim (cache); } g_static_mutex_unlock (&mutex); } GeglTileHandlerCache * gegl_tile_handler_cache_new (void) { return g_object_new (GEGL_TYPE_TILE_HANDLER_CACHE, NULL); } static guint gegl_tile_handler_cache_hashfunc (gconstpointer key) { const CacheItem *e = key; guint hash; gint i; gint srcA = e->x; gint srcB = e->y; gint srcC = e->z; /* interleave the 10 least significant bits of all coordinates, * this gives us Z-order / morton order of the space and should * work well as a hash */ hash = 0; for (i = 9; i >= 0; i--) { #define ADD_BIT(bit) do { hash |= (((bit) != 0) ? 1 : 0); hash <<= 1; } while (0) ADD_BIT (srcA & (1 << i)); ADD_BIT (srcB & (1 << i)); ADD_BIT (srcC & (1 << i)); #undef ADD_BIT } return hash ^ GPOINTER_TO_INT (e->handler); } static gboolean gegl_tile_handler_cache_equalfunc (gconstpointer a, gconstpointer b) { const CacheItem *ea = a; const CacheItem *eb = b; if (ea->x == eb->x && ea->y == eb->y && ea->z == eb->z && ea->handler == eb->handler) return TRUE; return FALSE; } void gegl_tile_cache_init (void) { if (cache_queue == NULL) cache_queue = g_queue_new (); if (cache_ht == NULL) cache_ht = g_hash_table_new (gegl_tile_handler_cache_hashfunc, gegl_tile_handler_cache_equalfunc); } void gegl_tile_cache_destroy (void) { if (cache_queue) g_queue_free (cache_queue); if (cache_ht) g_hash_table_destroy (cache_ht); cache_queue = NULL; cache_ht = NULL; }