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