1 /* This file is part of GEGL.
2  *
3  * This library is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU Lesser General Public
5  * License as published by the Free Software Foundation; either
6  * version 3 of the License, or (at your option) any later version.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with this library; if not, see <https://www.gnu.org/licenses/>.
15  *
16  * Copyright 2006,2007 Øyvind Kolås <pippin@gimp.org>
17  */
18 
19 #include "config.h"
20 
21 #include <glib.h>
22 #include <glib-object.h>
23 
24 #include "gegl-buffer-config.h"
25 #include "gegl-buffer.h"
26 #include "gegl-buffer-private.h"
27 #include "gegl-tile.h"
28 #include "gegl-tile-handler-cache.h"
29 #include "gegl-tile-storage.h"
30 #include "gegl-debug.h"
31 
32 /*
33 #define GEGL_DEBUG_CACHE_HITS
34 */
35 
36 #define GEGL_CACHE_TRIM_INTERVAL   100000 /* microseconds */
37 #define GEGL_CACHE_TRIM_RATIO_MIN  0.01
38 #define GEGL_CACHE_TRIM_RATIO_MAX  0.50
39 #define GEGL_CACHE_TRIM_RATIO_RATE 2.0
40 
41 typedef struct CacheItem
42 {
43   GeglTile *tile; /* The tile */
44   GList     link; /*  Link in the cache queue, to avoid
45                    *  queue lookups involving g_list_find() */
46 
47   gint      x;    /* The coordinates this tile was cached for */
48   gint      y;
49   gint      z;
50 } CacheItem;
51 
52 #define LINK_GET_CACHE(l) \
53         ((GeglTileHandlerCache *) ((guchar *) l - G_STRUCT_OFFSET (GeglTileHandlerCache, link)))
54 #define LINK_GET_ITEM(l) \
55         ((CacheItem *) ((guchar *) l - G_STRUCT_OFFSET (CacheItem, link)))
56 
57 
58 static gboolean   gegl_tile_handler_cache_equalfunc  (gconstpointer             a,
59                                                       gconstpointer             b);
60 static guint      gegl_tile_handler_cache_hashfunc   (gconstpointer             key);
61 static void       gegl_tile_handler_cache_dispose    (GObject                  *object);
62 static gboolean   gegl_tile_handler_cache_wash       (GeglTileHandlerCache     *cache);
63 static gpointer   gegl_tile_handler_cache_command    (GeglTileSource           *tile_store,
64                                                       GeglTileCommand           command,
65                                                       gint                      x,
66                                                       gint                      y,
67                                                       gint                      z,
68                                                       gpointer                  data);
69 static gboolean   gegl_tile_handler_cache_has_tile   (GeglTileHandlerCache     *cache,
70                                                       gint                      x,
71                                                       gint                      y,
72                                                       gint                      z);
73 static void       gegl_tile_handler_cache_void       (GeglTileHandlerCache     *cache,
74                                                       gint                      x,
75                                                       gint                      y,
76                                                       gint                      z,
77                                                       guint64                   damage);
78 static void       gegl_tile_handler_cache_invalidate (GeglTileHandlerCache     *cache,
79                                                       gint                      x,
80                                                       gint                      y,
81                                                       gint                      z);
82 static gboolean   gegl_tile_handler_cache_copy       (GeglTileHandlerCache     *cache,
83                                                       gint                      x,
84                                                       gint                      y,
85                                                       gint                      z,
86                                                       const GeglTileCopyParams *params);
87 
88 
89 static GMutex             mutex                 = { 0, };
90 static GQueue             cache_queue           = G_QUEUE_INIT;
91 static gint               cache_wash_percentage = 20;
92 static volatile guintptr  cache_total           = 0; /* approximate amount of bytes stored */
93 static guintptr           cache_total_max       = 0; /* maximal value of cache_total */
94 static volatile guintptr  cache_total_uncloned  = 0; /* approximate amount of uncloned bytes stored */
95 static gint               cache_hits            = 0;
96 static gint               cache_misses          = 0;
97 static guintptr           cache_time            = 0;
98 
99 
G_DEFINE_TYPE(GeglTileHandlerCache,gegl_tile_handler_cache,GEGL_TYPE_TILE_HANDLER)100 G_DEFINE_TYPE (GeglTileHandlerCache, gegl_tile_handler_cache, GEGL_TYPE_TILE_HANDLER)
101 
102 
103 static void
104 gegl_tile_handler_cache_class_init (GeglTileHandlerCacheClass *class)
105 {
106   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
107 
108   gobject_class->dispose = gegl_tile_handler_cache_dispose;
109 }
110 
111 static void
gegl_tile_handler_cache_init(GeglTileHandlerCache * cache)112 gegl_tile_handler_cache_init (GeglTileHandlerCache *cache)
113 {
114   ((GeglTileSource*)cache)->command = gegl_tile_handler_cache_command;
115   cache->items = g_hash_table_new (gegl_tile_handler_cache_hashfunc, gegl_tile_handler_cache_equalfunc);
116   g_queue_init (&cache->queue);
117 
118   gegl_tile_handler_cache_connect (cache);
119 }
120 
121 static void
drop_hot_tile(GeglTile * tile)122 drop_hot_tile (GeglTile *tile)
123 {
124   GeglTileStorage *storage = tile->tile_storage;
125 
126   if (storage)
127     {
128       tile = gegl_tile_storage_try_steal_hot_tile (storage, tile);
129 
130       if (tile)
131         gegl_tile_unref (tile);
132     }
133 }
134 
135 static void
gegl_tile_handler_cache_reinit(GeglTileHandlerCache * cache)136 gegl_tile_handler_cache_reinit (GeglTileHandlerCache *cache)
137 {
138   CacheItem *item;
139   GList     *link;
140 
141   cache->time = cache->stamp = 0;
142 
143   if (cache->tile_storage->hot_tile)
144     {
145       gegl_tile_unref (cache->tile_storage->hot_tile);
146       cache->tile_storage->hot_tile = NULL;
147     }
148 
149   g_hash_table_remove_all (cache->items);
150 
151   while ((link = g_queue_pop_head_link (&cache->queue)))
152     {
153       item = LINK_GET_ITEM (link);
154       if (item->tile)
155         {
156           if (g_atomic_int_dec_and_test (gegl_tile_n_cached_clones (item->tile)))
157             g_atomic_pointer_add (&cache_total, -item->tile->size);
158           g_atomic_pointer_add (&cache_total_uncloned, -item->tile->size);
159           drop_hot_tile (item->tile);
160           gegl_tile_mark_as_stored (item->tile); // to avoid saving
161           item->tile->tile_storage = NULL;
162           gegl_tile_unref (item->tile);
163         }
164       g_slice_free (CacheItem, item);
165     }
166 }
167 
168 static void
gegl_tile_handler_cache_dispose(GObject * object)169 gegl_tile_handler_cache_dispose (GObject *object)
170 {
171   GeglTileHandlerCache *cache = GEGL_TILE_HANDLER_CACHE (object);
172 
173   gegl_tile_handler_cache_disconnect (cache);
174 
175   gegl_tile_handler_cache_reinit (cache);
176 
177   g_hash_table_destroy (cache->items);
178   G_OBJECT_CLASS (gegl_tile_handler_cache_parent_class)->dispose (object);
179 }
180 
181 static GeglTile *
gegl_tile_handler_cache_get_tile_command(GeglTileSource * tile_store,gint x,gint y,gint z)182 gegl_tile_handler_cache_get_tile_command (GeglTileSource *tile_store,
183                                           gint        x,
184                                           gint        y,
185                                           gint        z)
186 {
187   GeglTileHandlerCache *cache    = (GeglTileHandlerCache*) (tile_store);
188   GeglTileSource       *source   = ((GeglTileHandler*) (tile_store))->source;
189   GeglTile             *tile     = NULL;
190 
191   if (gegl_tile_handler_cache_ext_flush)
192     gegl_tile_handler_cache_ext_flush (cache, NULL);
193 
194   tile = gegl_tile_handler_cache_get_tile (cache, x, y, z);
195   if (tile)
196     {
197       /* we don't bother making cache_{hits,misses} atomic, since they're only
198        * needed for GeglStats.
199        */
200       cache_hits++;
201       return tile;
202     }
203   cache_misses++;
204 
205   if (source)
206     tile = gegl_tile_source_get_tile (source, x, y, z);
207 
208   if (tile)
209     gegl_tile_handler_cache_insert (cache, tile, x, y, z);
210 
211   return tile;
212 }
213 
214 static gpointer
gegl_tile_handler_cache_command(GeglTileSource * tile_store,GeglTileCommand command,gint x,gint y,gint z,gpointer data)215 gegl_tile_handler_cache_command (GeglTileSource  *tile_store,
216                                  GeglTileCommand  command,
217                                  gint             x,
218                                  gint             y,
219                                  gint             z,
220                                  gpointer         data)
221 {
222   GeglTileHandler      *handler = (GeglTileHandler*) (tile_store);
223   GeglTileHandlerCache *cache   = (GeglTileHandlerCache*) (handler);
224 
225   switch (command)
226     {
227       case GEGL_TILE_FLUSH:
228         {
229           GList *link;
230 
231           if (gegl_tile_handler_cache_ext_flush)
232             gegl_tile_handler_cache_ext_flush (cache, NULL);
233 
234           for (link = g_queue_peek_head_link (&cache->queue);
235                link;
236                link = g_list_next (link))
237             {
238               CacheItem *item = LINK_GET_ITEM (link);
239 
240               if (item->tile)
241                 gegl_tile_store (item->tile);
242             }
243         }
244         break;
245       case GEGL_TILE_GET:
246         /* XXX: we should perhaps store a NIL result, and place the empty
247          * generator after the cache, this would have to be possible to disable
248          * to work in sync operation with backend.
249          */
250         return gegl_tile_handler_cache_get_tile_command (tile_store, x, y, z);
251       case GEGL_TILE_IS_CACHED:
252         return GINT_TO_POINTER(gegl_tile_handler_cache_has_tile (cache, x, y, z));
253       case GEGL_TILE_EXIST:
254         {
255           gboolean exist = gegl_tile_handler_cache_has_tile (cache, x, y, z);
256           if (exist)
257             return (gpointer)TRUE;
258         }
259         break;
260       case GEGL_TILE_IDLE:
261         {
262           gboolean action = gegl_tile_handler_cache_wash (cache);
263           if (action)
264             return GINT_TO_POINTER(action);
265           /* with no action, we chain up to lower levels */
266           break;
267         }
268       case GEGL_TILE_REFETCH:
269         gegl_tile_handler_cache_invalidate (cache, x, y, z);
270         break;
271       case GEGL_TILE_VOID:
272         gegl_tile_handler_cache_void (cache, x, y, z,
273                                       data ? *(const guint64 *) data :
274                                              ~(guint64) 0);
275         break;
276       case GEGL_TILE_REINIT:
277         gegl_tile_handler_cache_reinit (cache);
278         break;
279       case GEGL_TILE_COPY:
280         /* we potentially chain up in gegl_tile_handler_cache_copy(), because
281          * its logic is interleaved with that of the backend.
282          */
283         return GINT_TO_POINTER (gegl_tile_handler_cache_copy (cache,
284                                                               x, y, z, data));
285       default:
286         break;
287     }
288 
289   return gegl_tile_handler_source_command (handler, command, x, y, z, data);
290 }
291 
292 /* find the oldest (least-recently used) nonempty cache, after prev_cache (if
293  * not NULL).  passing the previous result of this function as prev_cache
294  * allows iterating over the caches in chronological order.
295  *
296  * if most caches haven't been accessed since the last call to this function,
297  * it should be rather cheap (approaching O(1)).
298  *
299  * the global cache mutex must be held while calling this function, however,
300  * individual caches may be accessed concurrently.  as a result, there is a
301  * race between modifying the caches' last-access time during access, and
302  * inspecting the time by this function.  this isn't critical, but it does mean
303  * that the result might not always be accurate.
304  */
305 static GeglTileHandlerCache *
gegl_tile_handler_cache_find_oldest_cache(GeglTileHandlerCache * prev_cache)306 gegl_tile_handler_cache_find_oldest_cache (GeglTileHandlerCache *prev_cache)
307 {
308   GList                *link;
309   GeglTileHandlerCache *oldest_cache = NULL;
310   guintptr              oldest_time  = 0;
311 
312   /* find the oldest cache, after prev_cache */
313   for (link = prev_cache ? g_list_next (&prev_cache->link) :
314                            g_queue_peek_head_link (&cache_queue);
315        link;
316        link = g_list_next (link))
317     {
318       GeglTileHandlerCache *cache = LINK_GET_CACHE (link);
319       guintptr              time  = cache->time;
320       guintptr              stamp = cache->stamp;
321 
322       /* the cache is empty */
323       if (! time)
324         continue;
325 
326       /* the cache is being disconnected */
327       if (! cache->link.data)
328         continue;
329 
330       if (time == stamp)
331         {
332           oldest_cache = cache;
333           oldest_time  = time;
334 
335           /* the cache is still stamped, so it must be the oldest of the
336            * remaining caches, and we can break early
337            */
338           break;
339         }
340       else if (! oldest_time || time < oldest_time)
341         {
342           oldest_cache = cache;
343           oldest_time  = time;
344         }
345     }
346 
347   if (oldest_cache)
348     {
349       /* stamp the cache */
350       oldest_cache->stamp = oldest_time;
351 
352       /* ... and move it after prev_cache */
353       g_queue_unlink (&cache_queue, &oldest_cache->link);
354 
355       if (prev_cache)
356         {
357           if (prev_cache->link.next)
358             {
359               oldest_cache->link.prev = &prev_cache->link;
360               oldest_cache->link.next = prev_cache->link.next;
361 
362               oldest_cache->link.prev->next = &oldest_cache->link;
363               oldest_cache->link.next->prev = &oldest_cache->link;
364 
365               cache_queue.length++;
366             }
367           else
368             {
369               g_queue_push_tail_link (&cache_queue, &oldest_cache->link);
370             }
371         }
372       else
373         {
374           g_queue_push_head_link (&cache_queue, &oldest_cache->link);
375         }
376     }
377 
378   return oldest_cache;
379 }
380 
381 /* write the least recently used dirty tile to disk if it
382  * is in the wash_percentage (20%) least recently used tiles,
383  * calling this function in an idle handler distributes the
384  * tile flushing overhead over time.
385  */
386 gboolean
gegl_tile_handler_cache_wash(GeglTileHandlerCache * cache)387 gegl_tile_handler_cache_wash (GeglTileHandlerCache *cache)
388 {
389   GeglTile  *last_dirty = NULL;
390   guintptr   size       = 0;
391   guintptr   wash_size;
392 
393   g_mutex_lock (&mutex);
394 
395   wash_size = (gdouble) cache_total_uncloned *
396               cache_wash_percentage / 100.0 + 0.5;
397 
398   cache = NULL;
399 
400   while (size < wash_size)
401     {
402       GList *link;
403 
404       cache = gegl_tile_handler_cache_find_oldest_cache (cache);
405 
406       if (cache == NULL)
407         break;
408 
409       if (! g_rec_mutex_trylock (&cache->tile_storage->mutex))
410         {
411           continue;
412         }
413 
414       for (link = g_queue_peek_tail_link (&cache->queue);
415            link && size < wash_size;
416            link = g_list_previous (link))
417         {
418           CacheItem *item = LINK_GET_ITEM (link);
419           GeglTile  *tile = item->tile;
420 
421           if (tile->tile_storage && ! gegl_tile_is_stored (tile))
422             {
423               last_dirty = tile;
424               g_object_ref (last_dirty->tile_storage);
425               gegl_tile_ref (last_dirty);
426 
427               size = wash_size;
428               break;
429             }
430 
431           size += tile->size;
432         }
433 
434       g_rec_mutex_unlock (&cache->tile_storage->mutex);
435     }
436 
437   g_mutex_unlock (&mutex);
438 
439   if (last_dirty != NULL)
440     {
441       gegl_tile_store (last_dirty);
442       g_object_unref (last_dirty->tile_storage);
443       gegl_tile_unref (last_dirty);
444       return TRUE;
445     }
446   return FALSE;
447 }
448 
449 static inline CacheItem *
cache_lookup(GeglTileHandlerCache * cache,gint x,gint y,gint z)450 cache_lookup (GeglTileHandlerCache *cache,
451               gint                  x,
452               gint                  y,
453               gint                  z)
454 {
455   CacheItem key;
456 
457   key.x = x;
458   key.y = y;
459   key.z = z;
460 
461   return g_hash_table_lookup (cache->items, &key);
462 }
463 
464 /* returns the requested Tile if it is in the cache, NULL otherwize.
465  */
466 GeglTile *
gegl_tile_handler_cache_get_tile(GeglTileHandlerCache * cache,gint x,gint y,gint z)467 gegl_tile_handler_cache_get_tile (GeglTileHandlerCache *cache,
468                                   gint                  x,
469                                   gint                  y,
470                                   gint                  z)
471 {
472   CacheItem *result;
473 
474   if (g_queue_is_empty (&cache->queue))
475     return NULL;
476 
477   result = cache_lookup (cache, x, y, z);
478   if (result)
479     {
480       g_queue_unlink (&cache->queue, &result->link);
481       g_queue_push_head_link (&cache->queue, &result->link);
482       cache->time = ++cache_time;
483       if (result->tile == NULL)
484       {
485         g_printerr ("NULL tile in %s %p %i %i %i %p\n", __FUNCTION__, result, result->x, result->y, result->z,
486                 result->tile);
487         return NULL;
488       }
489       gegl_tile_ref (result->tile);
490       return result->tile;
491     }
492   return NULL;
493 }
494 
495 static gboolean
gegl_tile_handler_cache_has_tile(GeglTileHandlerCache * cache,gint x,gint y,gint z)496 gegl_tile_handler_cache_has_tile (GeglTileHandlerCache *cache,
497                                   gint                  x,
498                                   gint                  y,
499                                   gint                  z)
500 {
501   GeglTile *tile = gegl_tile_handler_cache_get_tile (cache, x, y, z);
502 
503   if (tile)
504     {
505       gegl_tile_unref (tile);
506       return TRUE;
507     }
508 
509   return FALSE;
510 }
511 
512 static gboolean
gegl_tile_handler_cache_trim(GeglTileHandlerCache * cache)513 gegl_tile_handler_cache_trim (GeglTileHandlerCache *cache)
514 {
515   GList          *link;
516   gint64          time;
517   static gint64   last_time;
518   static gdouble  ratio  = GEGL_CACHE_TRIM_RATIO_MIN;
519   guint64         target_size;
520   static guint    counter;
521 
522   cache = NULL;
523   link  = NULL;
524 
525   g_mutex_lock (&mutex);
526 
527   target_size = gegl_buffer_config ()->tile_cache_size;
528 
529   if ((guintptr) g_atomic_pointer_get (&cache_total) <= target_size)
530     {
531       g_mutex_unlock (&mutex);
532 
533       return TRUE;
534     }
535 
536   time = g_get_monotonic_time ();
537 
538   if (time - last_time < GEGL_CACHE_TRIM_INTERVAL)
539     {
540       ratio = MIN (ratio * GEGL_CACHE_TRIM_RATIO_RATE,
541                    GEGL_CACHE_TRIM_RATIO_MAX);
542     }
543   else if (time - last_time >= 2 * GEGL_CACHE_TRIM_INTERVAL)
544     {
545       ratio = GEGL_CACHE_TRIM_RATIO_MIN;
546     }
547 
548   target_size -= target_size * ratio;
549 
550   g_mutex_unlock (&mutex);
551 
552   while ((guintptr) g_atomic_pointer_get (&cache_total) > target_size)
553     {
554       CacheItem *last_writable;
555       GeglTile  *tile;
556       GList     *prev_link;
557 
558 #ifdef GEGL_DEBUG_CACHE_HITS
559       GEGL_NOTE(GEGL_DEBUG_CACHE, "cache_total:"G_GUINT64_FORMAT" > cache_size:"G_GUINT64_FORMAT, cache_total, gegl_buffer_config()->tile_cache_size);
560       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));
561 #endif
562 
563       if (! link)
564         {
565           if (cache)
566             g_rec_mutex_unlock (&cache->tile_storage->mutex);
567 
568           g_mutex_lock (&mutex);
569 
570           do
571             {
572               cache = gegl_tile_handler_cache_find_oldest_cache (cache);
573             }
574           while (cache &&
575                  /* XXX:  when trimming a dirty tile, gegl_tile_unref() will
576                   * try to store it, acquiring the cache's storage mutex in the
577                   * process.  this can lead to a deadlock if another thread is
578                   * already holding that mutex, and is waiting on the global
579                   * cache mutex, or on a tile-storage mutex held by the current
580                   * thread.  try locking the cache's storage mutex here, and
581                   * skip the cache if it fails.
582                   */
583                  ! g_rec_mutex_trylock (&cache->tile_storage->mutex));
584 
585           g_mutex_unlock (&mutex);
586 
587           if (! cache)
588             break;
589 
590           link = g_queue_peek_tail_link (&cache->queue);
591         }
592 
593       for (; link; link = g_list_previous (link))
594         {
595           last_writable = LINK_GET_ITEM (link);
596           tile          = last_writable->tile;
597 
598           /* if the tile's ref-count is greater than one, then someone is still
599            * using the tile, and we must keep it in the cache, so that we can
600            * return the same tile object upon request; otherwise, we would end
601            * up with two different tile objects referring to the same tile.
602            */
603           if (tile->ref_count > 1)
604             continue;
605 
606           /* if we need to maintain the tile's data-pointer identity we can't
607            * remove it from the cache, since the storage might copy the data
608            * and throw the tile away.
609            */
610           if (tile->keep_identity)
611             continue;
612 
613           /* a set of cloned tiles is only counted once toward the total cache
614            * size, so the entire set has to be removed from the cache in order
615            * to reclaim the memory of a single tile.  in other words, in a set
616            * of n cloned tiles, we can assume that each individual tile
617            * contributes only 1/n of its size to the total cache size.  on the
618            * other hand, storing a cloned tile is as expensive as storing an
619            * uncloned tile.  therefore, if the tile needs to be stored, we only
620            * remove it with a probability of 1/n.
621            */
622           if (gegl_tile_needs_store (tile) &&
623               counter++ % *gegl_tile_n_cached_clones (tile))
624             {
625               continue;
626             }
627 
628           break;
629         }
630 
631       /* the cache is being disconnected */
632       if (! cache->link.data)
633         link = NULL;
634 
635       if (! link)
636         continue;
637 
638       prev_link = g_list_previous (link);
639       g_queue_unlink (&cache->queue, link);
640       g_hash_table_remove (cache->items, last_writable);
641       if (g_queue_is_empty (&cache->queue))
642         cache->time = cache->stamp = 0;
643       if (g_atomic_int_dec_and_test (gegl_tile_n_cached_clones (tile)))
644         g_atomic_pointer_add (&cache_total, -tile->size);
645       g_atomic_pointer_add (&cache_total_uncloned, -tile->size);
646       /* drop_hot_tile (tile); */ /* XXX:  no use in trying to drop the hot
647                                    * tile, since this tile can't be it --
648                                    * the hot tile will have a ref-count of
649                                    * at least two.
650                                    */
651       gegl_tile_store (tile);
652       tile->tile_storage = NULL;
653       gegl_tile_unref (tile);
654 
655       g_slice_free (CacheItem, last_writable);
656       link = prev_link;
657     }
658 
659   if (cache)
660     g_rec_mutex_unlock (&cache->tile_storage->mutex);
661 
662   g_mutex_lock (&mutex);
663 
664   last_time = g_get_monotonic_time ();
665 
666   g_mutex_unlock (&mutex);
667 
668   return cache != NULL;
669 }
670 
671 static void
gegl_tile_handler_cache_invalidate(GeglTileHandlerCache * cache,gint x,gint y,gint z)672 gegl_tile_handler_cache_invalidate (GeglTileHandlerCache *cache,
673                                     gint                  x,
674                                     gint                  y,
675                                     gint                  z)
676 {
677   CacheItem *item;
678 
679   item = cache_lookup (cache, x, y, z);
680   if (item)
681     {
682       if (g_atomic_int_dec_and_test (gegl_tile_n_cached_clones (item->tile)))
683         g_atomic_pointer_add (&cache_total, -item->tile->size);
684       g_atomic_pointer_add (&cache_total_uncloned, -item->tile->size);
685 
686       g_queue_unlink (&cache->queue, &item->link);
687       g_hash_table_remove (cache->items, item);
688 
689       if (g_queue_is_empty (&cache->queue))
690         cache->time = cache->stamp = 0;
691 
692       drop_hot_tile (item->tile);
693       gegl_tile_mark_as_stored (item->tile); /* to cheat it out of being stored */
694       item->tile->tile_storage = NULL;
695       gegl_tile_unref (item->tile);
696 
697       g_slice_free (CacheItem, item);
698     }
699 }
700 
701 static gboolean
gegl_tile_handler_cache_copy(GeglTileHandlerCache * cache,gint x,gint y,gint z,const GeglTileCopyParams * params)702 gegl_tile_handler_cache_copy (GeglTileHandlerCache     *cache,
703                               gint                      x,
704                               gint                      y,
705                               gint                      z,
706                               const GeglTileCopyParams *params)
707 {
708   GeglTile *tile;
709   GeglTile *dst_tile = NULL;
710   gboolean  success  = FALSE;
711 
712   if (gegl_tile_handler_cache_ext_flush)
713     gegl_tile_handler_cache_ext_flush (cache, NULL);
714 
715   tile = gegl_tile_handler_cache_get_tile (cache, x, y, z);
716 
717   /* if the tile is not fully valid, bail, so that the copy happens using a
718    * TILE_GET commands, validating the tile in the process.
719    */
720   if (tile && tile->damage)
721     {
722       gegl_tile_unref (tile);
723 
724       return FALSE;
725     }
726 
727   /* otherwise, if we have the tile and it's valid, copy it directly to the
728    * destination first, so that the copied tile will already be present in the
729    * destination cache.
730    */
731   if (tile)
732     {
733       GeglTileHandler *dst_handler;
734 
735       if (params->dst_buffer)
736         dst_handler = GEGL_TILE_HANDLER (params->dst_buffer->tile_storage);
737       else
738         dst_handler = GEGL_TILE_HANDLER (cache);
739 
740       dst_tile = gegl_tile_handler_dup_tile (dst_handler,
741                                              tile,
742                                              params->dst_x,
743                                              params->dst_y,
744                                              params->dst_z);
745 
746       success = TRUE;
747     }
748   /* otherwise, if we don't have the tile, remove the corresponding tile from
749    * the destination cache, so that, if the backend copies the tile to the
750    * destination backend below, the destination cache doesn't hold an outdated
751    * tile.  we do this *before* letting the backend copy the tile, since the
752    * backend might insert the copied tile to the destination cache as well, as
753    * is the case for GeglTileBackendBuffer.
754    */
755   else
756     {
757       GeglTileHandlerCache *dst_cache;
758 
759       if (params->dst_buffer)
760         dst_cache = params->dst_buffer->tile_storage->cache;
761       else
762         dst_cache = cache;
763 
764       if (dst_cache)
765         {
766           gegl_tile_handler_cache_remove (dst_cache,
767                                           params->dst_x,
768                                           params->dst_y,
769                                           params->dst_z);
770         }
771     }
772 
773   /* then, if we don't have the tile, or if the tile is stored, iow, if the
774    * tile might exist in the backend in an up-to-date state ...
775    */
776   if (! tile || gegl_tile_is_stored (tile))
777     {
778       /* ... try letting the backend copy the tile, so that the copied tile
779        * will already be stored in the destination backend.
780        */
781       if (gegl_tile_handler_source_command (GEGL_TILE_HANDLER (cache),
782                                             GEGL_TILE_COPY, x, y, z,
783                                             (gpointer) params))
784         {
785           /* if the backend copied the tile, we can mark the copied tile, if
786            * exists, as stored.
787            */
788           if (dst_tile)
789             gegl_tile_mark_as_stored (dst_tile);
790 
791           success = TRUE;
792         }
793     }
794 
795   if (dst_tile)
796     gegl_tile_unref (dst_tile);
797 
798   if (tile)
799     gegl_tile_unref (tile);
800 
801   /* the copy is considered successful if either we or the backend (or both)
802    * copied the tile.
803    */
804   return success;
805 }
806 
807 
808 static void
gegl_tile_handler_cache_remove_item(GeglTileHandlerCache * cache,CacheItem * item)809 gegl_tile_handler_cache_remove_item (GeglTileHandlerCache *cache,
810                                      CacheItem            *item)
811 {
812   if (g_atomic_int_dec_and_test (gegl_tile_n_cached_clones (item->tile)))
813     g_atomic_pointer_add (&cache_total, -item->tile->size);
814   g_atomic_pointer_add (&cache_total_uncloned, -item->tile->size);
815 
816   g_queue_unlink (&cache->queue, &item->link);
817   g_hash_table_remove (cache->items, item);
818 
819   if (g_queue_is_empty (&cache->queue))
820     cache->time = cache->stamp = 0;
821 
822   item->tile->tile_storage = NULL;
823   gegl_tile_unref (item->tile);
824 
825   g_slice_free (CacheItem, item);
826 }
827 
828 void
gegl_tile_handler_cache_remove(GeglTileHandlerCache * cache,gint x,gint y,gint z)829 gegl_tile_handler_cache_remove (GeglTileHandlerCache *cache,
830                                 gint                  x,
831                                 gint                  y,
832                                 gint                  z)
833 {
834   CacheItem *item;
835 
836   item = cache_lookup (cache, x, y, z);
837 
838   if (item)
839     {
840       drop_hot_tile (item->tile);
841 
842       gegl_tile_handler_cache_remove_item (cache, item);
843     }
844 }
845 
846 static void
gegl_tile_handler_cache_void(GeglTileHandlerCache * cache,gint x,gint y,gint z,guint64 damage)847 gegl_tile_handler_cache_void (GeglTileHandlerCache *cache,
848                               gint                  x,
849                               gint                  y,
850                               gint                  z,
851                               guint64               damage)
852 {
853   CacheItem *item;
854 
855   item = cache_lookup (cache, x, y, z);
856   if (item)
857     {
858       drop_hot_tile (item->tile);
859 
860       if (gegl_tile_damage (item->tile, damage))
861         gegl_tile_handler_cache_remove_item (cache, item);
862     }
863   else if (z == 0 && damage)
864     {
865       gegl_tile_handler_damage_tile (GEGL_TILE_HANDLER (cache),
866                                      x, y, z, damage);
867     }
868 }
869 
870 void
gegl_tile_handler_cache_insert(GeglTileHandlerCache * cache,GeglTile * tile,gint x,gint y,gint z)871 gegl_tile_handler_cache_insert (GeglTileHandlerCache *cache,
872                                 GeglTile             *tile,
873                                 gint                  x,
874                                 gint                  y,
875                                 gint                  z)
876 {
877   CacheItem *item = g_slice_new (CacheItem);
878   guintptr   total;
879 
880   item->tile      = gegl_tile_ref (tile);
881   item->link.data = item;
882   item->link.next = NULL;
883   item->link.prev = NULL;
884   item->x         = x;
885   item->y         = y;
886   item->z         = z;
887 
888   // XXX : remove entry if it already exists
889   gegl_tile_handler_cache_remove (cache, x, y, z);
890 
891   tile->x = x;
892   tile->y = y;
893   tile->z = z;
894   tile->tile_storage = cache->tile_storage;
895 
896   /* XXX: this is a window when the tile is a zero tile during update */
897 
898   cache->time = ++cache_time;
899 
900   if (g_atomic_int_add (gegl_tile_n_cached_clones (tile), 1) == 0)
901     total = g_atomic_pointer_add (&cache_total, tile->size) + tile->size;
902   else
903     total = (guintptr) g_atomic_pointer_get (&cache_total);
904   g_atomic_pointer_add (&cache_total_uncloned, tile->size);
905   g_hash_table_add (cache->items, item);
906   g_queue_push_head_link (&cache->queue, &item->link);
907 
908   if (total > gegl_buffer_config ()->tile_cache_size)
909     gegl_tile_handler_cache_trim (cache);
910 
911   /* there's a race between this assignment, and the one at the bottom of
912    * gegl_tile_handler_cache_tile_uncloned().  this is acceptable, though,
913    * since we only need cache_total_max for GeglStats, so its accuracy is not
914    * ciritical.
915    */
916   cache_total_max = MAX (cache_total_max, cache_total);
917 }
918 
919 void
gegl_tile_handler_cache_tile_uncloned(GeglTileHandlerCache * cache,GeglTile * tile)920 gegl_tile_handler_cache_tile_uncloned (GeglTileHandlerCache *cache,
921                                        GeglTile             *tile)
922 {
923   guintptr total;
924 
925   total = (guintptr) g_atomic_pointer_add (&cache_total, tile->size) +
926           tile->size;
927 
928   if (total > gegl_buffer_config ()->tile_cache_size)
929     gegl_tile_handler_cache_trim (cache);
930 
931   cache_total_max = MAX (cache_total_max, total);
932 }
933 
934 GeglTileHandler *
gegl_tile_handler_cache_new(void)935 gegl_tile_handler_cache_new (void)
936 {
937   return g_object_new (GEGL_TYPE_TILE_HANDLER_CACHE, NULL);
938 }
939 
940 void
gegl_tile_handler_cache_connect(GeglTileHandlerCache * cache)941 gegl_tile_handler_cache_connect (GeglTileHandlerCache *cache)
942 {
943   /* join the global cache queue */
944   if (! cache->link.data)
945     {
946       cache->link.data = cache;
947 
948       g_mutex_lock (&mutex);
949       g_queue_push_tail_link (&cache_queue, &cache->link);
950       g_mutex_unlock (&mutex);
951     }
952 }
953 
954 void
gegl_tile_handler_cache_disconnect(GeglTileHandlerCache * cache)955 gegl_tile_handler_cache_disconnect (GeglTileHandlerCache *cache)
956 {
957   /* leave the global cache queue */
958   if (cache->link.data)
959     {
960       cache->link.data = NULL;
961 
962       g_rec_mutex_lock (&cache->tile_storage->mutex);
963 
964       g_mutex_lock (&mutex);
965       g_queue_unlink (&cache_queue, &cache->link);
966       g_mutex_unlock (&mutex);
967 
968       g_rec_mutex_unlock (&cache->tile_storage->mutex);
969     }
970 }
971 
972 gsize
gegl_tile_handler_cache_get_total(void)973 gegl_tile_handler_cache_get_total (void)
974 {
975   return cache_total;
976 }
977 
978 gsize
gegl_tile_handler_cache_get_total_max(void)979 gegl_tile_handler_cache_get_total_max (void)
980 {
981   return cache_total_max;
982 }
983 
984 gsize
gegl_tile_handler_cache_get_total_uncompressed(void)985 gegl_tile_handler_cache_get_total_uncompressed (void)
986 {
987   return cache_total_uncloned;
988 }
989 
990 gint
gegl_tile_handler_cache_get_hits(void)991 gegl_tile_handler_cache_get_hits (void)
992 {
993   return cache_hits;
994 }
995 
996 gint
gegl_tile_handler_cache_get_misses(void)997 gegl_tile_handler_cache_get_misses (void)
998 {
999   return cache_misses;
1000 }
1001 
1002 void
gegl_tile_handler_cache_reset_stats(void)1003 gegl_tile_handler_cache_reset_stats (void)
1004 {
1005   cache_total_max = cache_total;
1006   cache_hits      = 0;
1007   cache_misses    = 0;
1008 }
1009 
1010 
1011 static guint
gegl_tile_handler_cache_hashfunc(gconstpointer key)1012 gegl_tile_handler_cache_hashfunc (gconstpointer key)
1013 {
1014   const CacheItem *e = key;
1015   guint           hash;
1016   gint            i;
1017   gint            srcA = e->x;
1018   gint            srcB = e->y;
1019   gint            srcC = e->z;
1020 
1021   /* interleave the 10 least significant bits of all coordinates,
1022    * this gives us Z-order / morton order of the space and should
1023    * work well as a hash
1024    */
1025   hash = 0;
1026   for (i = 9; i >= 0; i--)
1027     {
1028 #define ADD_BIT(bit)    do { hash |= (((bit) != 0) ? 1 : 0); hash <<= 1; } while (0)
1029       ADD_BIT (srcA & (1 << i));
1030       ADD_BIT (srcB & (1 << i));
1031       ADD_BIT (srcC & (1 << i));
1032 #undef ADD_BIT
1033     }
1034   return hash;
1035 }
1036 
1037 static gboolean
gegl_tile_handler_cache_equalfunc(gconstpointer a,gconstpointer b)1038 gegl_tile_handler_cache_equalfunc (gconstpointer a,
1039                                    gconstpointer b)
1040 {
1041   const CacheItem *ea = a;
1042   const CacheItem *eb = b;
1043 
1044   if (ea->x == eb->x &&
1045       ea->y == eb->y &&
1046       ea->z == eb->z)
1047     return TRUE;
1048   return FALSE;
1049 }
1050 
1051 static void
gegl_buffer_config_tile_cache_size_notify(GObject * gobject,GParamSpec * pspec,gpointer user_data)1052 gegl_buffer_config_tile_cache_size_notify (GObject    *gobject,
1053                                            GParamSpec *pspec,
1054                                            gpointer    user_data)
1055 {
1056   if ((guintptr) g_atomic_pointer_get (&cache_total) >
1057       gegl_buffer_config () ->tile_cache_size)
1058     {
1059       gegl_tile_handler_cache_trim (NULL);
1060     }
1061 }
1062 
1063 void
gegl_tile_cache_init(void)1064 gegl_tile_cache_init (void)
1065 {
1066   g_signal_connect (gegl_buffer_config (), "notify::tile-cache-size",
1067                     G_CALLBACK (gegl_buffer_config_tile_cache_size_notify), NULL);
1068 }
1069 
1070 void
gegl_tile_cache_destroy(void)1071 gegl_tile_cache_destroy (void)
1072 {
1073   g_signal_handlers_disconnect_by_func (gegl_buffer_config(),
1074                                         gegl_buffer_config_tile_cache_size_notify,
1075                                         NULL);
1076   g_warn_if_fail (g_queue_is_empty (&cache_queue));
1077 
1078 
1079   if (g_queue_is_empty (&cache_queue))
1080     {
1081       g_queue_clear (&cache_queue);
1082     }
1083   else
1084     {
1085      /* we leak portions of the GQueue data structure when it is not empty,
1086         permitting leaked tiles to still be unreffed correctly */
1087     }
1088 }
1089