Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
pixelpipe_cache.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2009-2012, 2015 johannes hanika.
4 Copyright (C) 2010-2011 Henrik Andersson.
5 Copyright (C) 2011 Robert Bieber.
6 Copyright (C) 2011 Rostyslav Pidgornyi.
7 Copyright (C) 2012 Richard Wonka.
8 Copyright (C) 2012-2014, 2016 Tobias Ellinghaus.
9 Copyright (C) 2013-2014, 2016 Roman Lebedev.
10 Copyright (C) 2014 Ulrich Pegelow.
11 Copyright (C) 2019, 2023-2026 Aurélien PIERRE.
12 Copyright (C) 2019-2021 Pascal Obry.
13 Copyright (C) 2020, 2022 Hanno Schwalm.
14 Copyright (C) 2020 Ralf Brown.
15 Copyright (C) 2021 Aldric Renaudin.
16 Copyright (C) 2021 Dan Torop.
17 Copyright (C) 2022 Martin Bařinka.
18 Copyright (C) 2023 lologor.
19 Copyright (C) 2024 Alynx Zhou.
20 Copyright (C) 2025-2026 Guillaume Stutin.
21 Copyright (C) 2025 Miguel Moquillon.
22
23 darktable is free software: you can redistribute it and/or modify
24 it under the terms of the GNU General Public License as published by
25 the Free Software Foundation, either version 3 of the License, or
26 (at your option) any later version.
27
28 darktable is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 GNU General Public License for more details.
32
33 You should have received a copy of the GNU General Public License
34 along with darktable. If not, see <http://www.gnu.org/licenses/>.
35*/
36
37#include <inttypes.h>
38#include <glib.h>
39#include <stdlib.h>
40#include <signal.h>
41#include <string.h>
42
43#include "control/control.h"
44#include "control/signal.h"
46#include "develop/pixelpipe.h"
47#include "develop/supervisor.h"
48#include "common/darktable.h"
49#include "common/debug.h"
50#include "common/opencl.h"
51#include "develop/format.h"
52
53static __thread const char *dt_pixelpipe_cache_current_module = NULL;
54
56 const uint64_t key);
57
58static inline const char *_cache_debug_module_name(void)
59{
61}
62
63static void _trace_exact_hit(const char *phase, const uint64_t hash, dt_pixel_cache_entry_t *cache_entry,
64 void *data, void *cl_mem_output, const int preferred_devid, const gboolean verbose)
65{
66 if(!(darktable.unmuted & DT_DEBUG_PIPECACHE)) return;
67 if(verbose && !(darktable.unmuted & DT_DEBUG_VERBOSE)) return;
68
70 "[pixelpipe_cache] exact-hit %s req=%" PRIu64 " entry=%" PRIu64 "/%" PRIu64
71 " data=%p cl=%p refs=%i auto=%i dev=%i module=%s name=%s\n",
72 phase, hash, cache_entry ? cache_entry->hash : DT_PIXELPIPE_CACHE_HASH_INVALID,
73 cache_entry ? cache_entry->serial : 0, data, cl_mem_output,
74 cache_entry ? dt_atomic_get_int(&cache_entry->refcount) : -1,
75 cache_entry ? cache_entry->auto_destroy : -1, preferred_devid, _cache_debug_module_name(),
76 (cache_entry && cache_entry->name) ? cache_entry->name : "-");
77}
78
79const char *dt_pixelpipe_cache_set_current_module(const char *module)
80{
81 const char *previous = dt_pixelpipe_cache_current_module;
83 return previous;
84}
85
86typedef struct dt_cache_clmem_t
87{
88 void *host_ptr;
89 void *mem;
90 int refs;
92
101
102
104 dt_pixel_cache_entry_t *cache_entry);
105static void _free_cache_entry(dt_pixel_cache_entry_t *cache_entry);
106static void _pixelpipe_cache_finalize_entry(dt_pixel_cache_entry_t *cache_entry, void **data,
107 const char *message);
108int _non_thread_safe_cache_remove(dt_dev_pixelpipe_cache_t *cache, const gboolean force,
109 dt_pixel_cache_entry_t *cache_entry, GHashTable *table);
110
112 const uint64_t hash, const size_t size,
113 const char *name, const int id);
114static dt_pixel_cache_entry_t *dt_pixel_cache_new_entry(const uint64_t hash, const size_t size,
115 const char *name, const int id,
116 dt_dev_pixelpipe_cache_t *cache, gboolean alloc,
117 GHashTable *table);
118static gboolean _cache_entry_clmem_flush_device(dt_pixel_cache_entry_t *entry, const int devid);
119static gboolean _cache_entry_materialize_host_data_locked(dt_pixel_cache_entry_t *entry, int preferred_devid,
120 gboolean prefer_device_payload);
122
123#ifdef HAVE_OPENCL
124static gboolean _cache_entry_clmem_flush_host_pinned_locked(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid);
125#endif
126
128{
129 if(IS_NULL_PTR(cache) || IS_NULL_PTR(host_ptr)) return NULL;
130
131 const uint64_t hash = (uint64_t)(uintptr_t)host_ptr;
133 if(entry && entry->external_alloc && entry->data == host_ptr) return entry;
134 return NULL;
135}
136
138 const uint64_t key)
139{
140 dt_pixel_cache_entry_t *entry = (dt_pixel_cache_entry_t *)g_hash_table_lookup(table, &key);
141 return entry;
142}
143
144
153
154
156{
157 if(!cache || !data) return NULL;
158
160
161 GHashTableIter iter;
162 gpointer key, value;
163
164 /* Search regular entries table */
165 g_hash_table_iter_init(&iter, cache->entries);
166 while(g_hash_table_iter_next(&iter, &key, &value))
167 {
169 if(entry && entry->data == data)
170 {
172 return entry;
173 }
174 }
175
176 /* Search external entries table */
177 g_hash_table_iter_init(&iter, cache->external_entries);
178 while(g_hash_table_iter_next(&iter, &key, &value))
179 {
181 if(entry && entry->data == data)
182 {
184 return entry;
185 }
186 }
187
189 return NULL;
190}
191
192
194{
195 return cache_entry->size / (1024 * 1024);
196}
197
198
199static void _pixel_cache_message(dt_pixel_cache_entry_t *cache_entry, const char *message, gboolean verbose)
200{
201 if(!(darktable.unmuted & DT_DEBUG_PIPECACHE)) return;
202 if(verbose && !(darktable.unmuted & DT_DEBUG_VERBOSE)) return;
204 "[pixelpipe] cache entry %" PRIu64 "/%" PRIu64 ": %s (data=%p - %" G_GSIZE_FORMAT " MiB - age %" PRId64
205 " - hits %i - refs %i - auto %i - ext %i - id %i - module %s) %s\n",
206 cache_entry->hash, cache_entry->serial,
207 cache_entry->name ? cache_entry->name : "-", cache_entry->data,
208 _pixel_cache_get_size(cache_entry), cache_entry->age, cache_entry->hits,
209 dt_atomic_get_int(&cache_entry->refcount), cache_entry->auto_destroy,
210 cache_entry->external_alloc, cache_entry->id, _cache_debug_module_name(), message);
211}
212
213static void _pixelpipe_cache_finalize_entry(dt_pixel_cache_entry_t *cache_entry, void **data,
214 const char *message)
215{
216 cache_entry->age = g_get_monotonic_time(); // Update MRU timestamp
217 if(data)
218 *data = cache_entry->data ? __builtin_assume_aligned(cache_entry->data, DT_CACHELINE_BYTES) : NULL;
219 _pixel_cache_message(cache_entry, message, FALSE);
220}
221
223 const uint64_t hash,
224 void **data,
226{
227 if(!IS_NULL_PTR(data)) *data = NULL;
228 if(!IS_NULL_PTR(entry)) *entry = NULL;
229 if(IS_NULL_PTR(cache) || hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
230
232 cache->queries++;
233
234 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, hash);
235 if(!IS_NULL_PTR(cache_entry) && !cache_entry->auto_destroy)
236 {
237 cache->hits++;
238 cache_entry->hits++;
239 _non_thread_safe_cache_ref_count_entry(cache, TRUE, cache_entry);
240 _pixelpipe_cache_finalize_entry(cache_entry, data, "ref-by-hash");
241 if(!IS_NULL_PTR(entry)) *entry = cache_entry;
242 }
243
244 const gboolean found = !IS_NULL_PTR(cache_entry) && !cache_entry->auto_destroy;
245 const size_t found_size = found ? cache_entry->size : 0;
247
248 if(found && dt_supervisor_active())
249 dt_supervisor_cacheline_read(hash, found_size);
250
251 return found;
252}
253
254
255// remove the cache entry with the given hash and update the cache memory usage
256// WARNING: not internally thread-safe, protect its calls with mutex lock
257// return 0 on success, 1 on error
259 dt_pixel_cache_entry_t *cache_entry, GHashTable *table)
260{
261 if(!IS_NULL_PTR(cache_entry))
262 {
263 // Returns 1 if the lock is captured by another thread
264 // 0 if WE capture the lock, and then need to release it
265 gboolean locked = dt_pthread_rwlock_trywrlock(&cache_entry->lock);
266 if(!locked) dt_pthread_rwlock_unlock(&cache_entry->lock);
267 gboolean used = dt_atomic_get_int(&cache_entry->refcount) > 0;
268
269 /* Force-removal may bypass caller lifecycle checks but must never destroy
270 * an entry that still has active readers/writers. Active users can still
271 * access cl_mem_list after this call (for example borrowed GPU payloads),
272 * so removing a referenced entry here would create dangling pointers. */
273 if(!used && (!locked || force))
274 {
275 // Note: the free callback takes care of flushing OpenCL buffers too
276 g_hash_table_remove(table, &cache_entry->hash);
277 return 0;
278 }
279 else if(used)
280 _pixel_cache_message(cache_entry, "cannot remove: used", TRUE);
281 else if(locked)
282 _pixel_cache_message(cache_entry, "cannot remove: locked", TRUE);
283 }
284 else
285 {
286 dt_print(DT_DEBUG_PIPECACHE, "[pixelpipe] cache entry not found, will not be removed\n");
287 }
288 return 1;
289}
290
291
293 dt_pixel_cache_entry_t *cache_entry)
294{
296 int error = _non_thread_safe_cache_remove(cache, force, cache_entry, cache->entries);
298 return error;
299}
300
301#ifdef HAVE_OPENCL
302static gboolean _cache_entry_materialize_host_data_locked(dt_pixel_cache_entry_t *entry, int preferred_devid,
303 gboolean prefer_device_payload)
304{
305 dt_cache_clmem_t *source = NULL;
306 gboolean ok = FALSE;
308
309 /* We materialize RAM from the most authoritative cached payload in one pass instead of
310 * walking the list multiple times with slightly different predicates:
311 * - when RAM existed before, prefer pinned host-backed payloads first because they should
312 * already alias the cacheline or be the cheapest path back to host,
313 * - when RAM has just been allocated for a GPU-only cacheline, prefer device payloads first,
314 * - if a preferred OpenCL device is known, rank payloads from that device ahead of the rest.
315 * This keeps the fallback order explicit without scattering it over six loops. */
317 for(GList *l = g_list_first(entry->cl_mem_list); l; l = g_list_next(l))
318 {
319 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
320 if(IS_NULL_PTR(c) || IS_NULL_PTR(c->mem)) continue;
321
322 /* We are looking for one authoritative cached payload to materialize back to RAM.
323 * Only consider records whose live OpenCL context still matches the recorded device.
324 * Other cached payloads may belong to a different pipeline/device and must stay untouched. */
325 const int mem_devid = dt_opencl_get_mem_context_id((cl_mem)c->mem);
326 if(mem_devid != preferred_devid) continue;
327
328 const gboolean host_backed = (c->host_ptr == entry->data);
329 const gboolean device_only = (IS_NULL_PTR(c->host_ptr));
330 if(!host_backed && !device_only) continue;
331
333 if(!prefer_device_payload)
334 {
335 if(host_backed && (preferred_devid < 0 || mem_devid == preferred_devid))
337 else if(device_only && preferred_devid >= 0 && mem_devid == preferred_devid)
339 else if(device_only)
341 else if(host_backed)
343 }
344 else
345 {
346 if(device_only && preferred_devid >= 0 && mem_devid == preferred_devid)
348 else if(device_only)
350 else if(host_backed && (preferred_devid < 0 || mem_devid == preferred_devid))
352 else if(host_backed)
354 }
355
356 if(rank > best_rank)
357 {
358 best_rank = rank;
359 source = c;
361 }
362 }
363
364 if(source)
365 {
366
367 const int devid = dt_opencl_get_mem_context_id(source->mem);
368 const int width = dt_opencl_get_image_width(source->mem);
369 const int height = dt_opencl_get_image_height(source->mem);
370 const int bpp = dt_opencl_get_image_element_size(source->mem);
371
372 if(dt_opencl_is_pinned_memory((cl_mem)source->mem) && source->host_ptr == entry->data)
373 {
374 void *mapped = dt_opencl_map_image(devid, (cl_mem)source->mem, TRUE, CL_MAP_READ,
375 width, height, bpp);
376 ok = (dt_opencl_unmap_mem_object(devid, (cl_mem)source->mem, mapped) == CL_SUCCESS);
377 }
378 if(!ok)
379 {
380 ok = (dt_opencl_read_host_from_device(devid, entry->data, source->mem,
381 width, height, bpp) == CL_SUCCESS);
382 }
383 }
384
386 return ok;
387}
388#else
389static gboolean _cache_entry_materialize_host_data_locked(dt_pixel_cache_entry_t *entry, int preferred_devid,
390 gboolean prefer_device_payload)
391{
392 (void)preferred_devid;
393 (void)prefer_device_payload;
394 return entry && !IS_NULL_PTR(entry->data);
395}
396#endif
397
398static gboolean _cache_entry_materialize_host_data(dt_dev_pixelpipe_cache_t *cache, int preferred_devid,
400{
401 if(IS_NULL_PTR(cache) || IS_NULL_PTR(entry)) return FALSE;
402 if(preferred_devid < 0 && dt_pixel_cache_entry_get_data(entry) == NULL) return FALSE;
403
405 gboolean use_host_ptr = TRUE;
407 {
408 dt_pixel_cache_alloc(cache, entry);
409 use_host_ptr = FALSE;
410 }
411 const gboolean ok = _cache_entry_materialize_host_data_locked(entry, preferred_devid, use_host_ptr);
413
414 return ok;
415}
416
417#ifdef HAVE_OPENCL
418static gboolean _cache_entry_clmem_has_host_pinned_locked(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid)
419{
420 if(IS_NULL_PTR(entry) || IS_NULL_PTR(host_ptr)) return FALSE;
421
422 gboolean found = FALSE;
424 for(GList *l = g_list_first(entry->cl_mem_list); l; l = g_list_next(l))
425 {
426 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
427 if(IS_NULL_PTR(c) || IS_NULL_PTR(c->mem)) continue;
428
429 if(c->refs == 0 && devid == dt_opencl_get_mem_context_id((cl_mem)c->mem))
430 {
431 found = TRUE;
432 break;
433 }
434 }
436
437 return found;
438}
439
440static gboolean _cache_entry_clmem_flush_host_pinned_locked(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid)
441{
442 // If host_ptr is NULL, we don't have RAM cache for this buffer,
443 // so we can't flush the vRAM cache or we would loose it forever.
444 if(IS_NULL_PTR(entry) || IS_NULL_PTR(host_ptr)) return FALSE;
445
446 gboolean flushed = FALSE;
447
449 for(GList *l = g_list_first(entry->cl_mem_list); l;)
450 {
451 GList *next = g_list_next(l);
452 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
453 if(IS_NULL_PTR(c->mem))
454 {
455 // Current cacheline holds an empty buffer, no point keeping it
456 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
457 dt_free(c);
458 l = next;
459 continue;
460 }
461 if(dt_opencl_get_mem_context_id(c->mem) != devid)
462 {
463 // Current cacheline doesn't belong to current OpenCL devide: don't touch it
464 l = next;
465 continue;
466 }
467
468 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
470 dt_free(c);
471 flushed = TRUE;
472 l = next;
473 }
474
476
477 return flushed;
478}
479#endif
480
482{
483 // devid < 0 means the calling pipe never used OpenCL: it owns no device-side
484 // payload in the cache, so there is nothing of its own to release here.
485 //
486 // This used to also support devid == -1 as "drop cl_mem from every device,
487 // regardless of who else is using it", called from per-pipe cleanup. That ran
488 // without holding any dev[].lock, so it could race the eventlist/cl_mem
489 // bookkeeping of whichever OTHER pixelpipe was concurrently running on that
490 // device, corrupting it and crashing inside clGetEventInfo/clWaitForEvents
491 // (see #859 and #864). A global, all-devices teardown is never needed during
492 // normal operation: dt_cleanup() already finishes every device and
493 // dt_dev_pixelpipe_cache_cleanup() unconditionally releases all remaining
494 // cl_mem objects at application exit, when nothing else is running.
495 if(devid < 0) return;
496
497 // NOTE: the caller must hold darktable.opencl->dev[devid].lock -- either
498 // because it IS the pixelpipe currently running on that device (the lock
499 // dt_opencl_lock_device() handed it for the duration of its run), or because
500 // it explicitly took that lock to safely flush this device's cache entries
501 // after its own run finished (see dt_dev_pixelpipe_cache_flush_clmem_for_pipe()).
503
505 GHashTableIter iter;
506 gpointer key, value;
507 g_hash_table_iter_init(&iter, cache->entries);
508 while(g_hash_table_iter_next(&iter, &key, &value))
509 {
511
512 /* Only idle cachelines may have their vRAM reclaimed. An entry that is referenced or
513 * write-locked is somebody's live (or about-to-be-consumed) buffer: the recursion reserves
514 * an entry-level ref for the next consumer before that consumer borrows the cl_mem payload,
515 * so a payload can be unborrowed (per-payload refs == 0) yet still belong to an in-flight
516 * pipe. Honoring the same protection the LRU/removal paths use (refcount + non-blocking
517 * write-lock probe) keeps us from yanking the sole vRAM copy of another pipe's input out
518 * from under it -- which left a husk and produced skull thumbnails (issue #817). The
519 * trywrlock never waits, so this stays lightweight and cannot deadlock against renders that
520 * already hold entry locks. */
521 const gboolean used = dt_atomic_get_int(&entry->refcount) > 0;
522 gboolean locked = dt_pthread_rwlock_trywrlock(&entry->lock);
523 if(!locked) dt_pthread_rwlock_unlock(&entry->lock);
524 if(used || locked)
525 {
528 "[dt_dev_pixelpipe_cache_flush_clmem] entry %" PRIu64 " is in use (refcount=%i locked=%i), "
529 "keeping its vRAM\n", entry->hash, dt_atomic_get_int(&entry->refcount), locked);
530 continue;
531 }
532
535 "[dt_dev_pixelpipe_cache_flush_clmem] trying to flush vRAM for entry %" PRIu64 " on device %d...\n",
536 entry->hash, devid);
537
538 /* If reclaiming this device's vRAM leaves the entry with no buffer at all, delete it now
539 * instead of letting a payload-less husk persist as a cache hit. We hold cache->lock for the
540 * whole iteration, and lookups bump the consumer ref under that same lock, so no consumer can
541 * be mid-acquisition of this (refcount == 0) entry. iter_remove runs _free_cache_entry, which
542 * releases any remaining resources. */
543 if(_cache_entry_clmem_flush_device(entry, devid))
544 g_hash_table_iter_remove(&iter);
545 }
547}
548
549#ifdef HAVE_OPENCL
551{
552 // Like dt_dev_pixelpipe_cache_flush_clmem(), but for callers that do NOT
553 // currently hold darktable.opencl->dev[devid].lock -- typically a pipe's own
554 // cleanup, running after dt_dev_pixelpipe_process() already released that
555 // lock. Taking it here ensures we can't race the eventlist/cl_mem bookkeeping
556 // of whichever OTHER pixelpipe is now running on that device.
557 if(devid < 0 || IS_NULL_PTR(darktable.opencl) || !darktable.opencl->inited) return;
558
562}
563#else
565{
566 (void)cache;
567 (void)devid;
568}
569#endif
570
577
578
579// find the cache entry hash with the oldest use
580static void _cache_get_oldest(gpointer key, gpointer value, gpointer user_data)
581{
583 _cache_lru_t *lru = (_cache_lru_t *)user_data;
584
585 // Don't remove LRU entries that are still in use
586 // NOTE: with all the killswitches mechanisms and safety measures,
587 // we might have more things decreasing refcount than increasing it.
588 // It's no big deal though, as long as the (final output) backbuf
589 // is checked for NULL and not reused if pipeline is DIRTY.
590 if(cache_entry->age < lru->max_age)
591 {
592 // Returns 1 if the lock is captured by another thread
593 // 0 if WE capture the lock, and then need to release it
594 gboolean locked = dt_pthread_rwlock_trywrlock(&cache_entry->lock);
595 if(!locked) dt_pthread_rwlock_unlock(&cache_entry->lock);
596 gboolean used = dt_atomic_get_int(&cache_entry->refcount) > 0;
597
598 if(!locked && !used)
599 {
600 lru->max_age = cache_entry->age;
601 lru->hash = cache_entry->hash;
602 lru->cache_entry = cache_entry;
603 _pixel_cache_message(cache_entry, "candidate for deletion", TRUE);
604 }
605 else if(used)
606 _pixel_cache_message(cache_entry, "cannot be deleted: used", TRUE);
607 else if(locked)
608 _pixel_cache_message(cache_entry, "cannot be deleted: locked", TRUE);
609 }
610}
611
612static void _print_cache_lines(gpointer key, gpointer value, gpointer user_data)
613{
615 _pixel_cache_message(cache_entry, "", FALSE);
616}
617
618
619// remove the least used cache entry
620// return 0 on success, 1 on error
621// error is : we couldn't find a candidate for deletion because all entries are either locked or in use
622// or we found one but failed to remove it.
624{
625 _cache_lru_t *lru = (_cache_lru_t *)malloc(sizeof(_cache_lru_t));
626 lru->max_age = g_get_monotonic_time();
627 lru->hash = 0;
628 lru->cache_entry = NULL;
629 int error = 1;
630 g_hash_table_foreach(cache->entries, _cache_get_oldest, lru);
631
632 if(lru->hash > 0)
633 {
635 if(error)
636 dt_print(DT_DEBUG_PIPECACHE, "[pixelpipe] couldn't remove LRU %" PRIu64 "\n", lru->hash);
637 else
638 dt_print(DT_DEBUG_PIPECACHE, "[pixelpipe] LRU %" PRIu64 " removed. Total cache size: %" G_GSIZE_FORMAT " MiB\n",
639 lru->hash, cache->current_memory / (1024 * 1024));
640 }
641 else
642 {
643 dt_print(DT_DEBUG_PIPECACHE, "[pixelpipe] couldn't remove LRU, %i items and all are used\n", g_hash_table_size(cache->entries));
644 g_hash_table_foreach(cache->entries, _print_cache_lines, NULL);
645 }
646
647 dt_free(lru);
648 return error;
649}
650
651// return 0 on success 1 on error
659
660#ifdef HAVE_OPENCL
661static void *_pixel_cache_clmem_get(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid,
662 int width, int height, int bpp, int flags)
663{
665
668 "[_pixel_cache_clmem_get] %u output entries in %" PRIu64 "\n",
669 g_list_length(entry->cl_mem_list), entry->hash);
670
671 for(GList *l = g_list_first(entry->cl_mem_list); l;)
672 {
673 GList *next = g_list_next(l);
674 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
675 if(IS_NULL_PTR(c->mem))
676 {
677 // No point in keeping buffer-less cachelines
678 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
679 dt_free(c);
680 l = next;
681 continue;
682 }
683
684 // Buffer reuse must stay on the same OpenCL device and ensure proper size
685 if(dt_opencl_get_mem_context_id(c->mem) == devid
686 && dt_opencl_get_image_width(c->mem) == width
689 && c->refs == 0)
690 {
691 // Destroy the current OpenCL cacheline and return the buffer, the cacheline will be recreated
692 // when we are done consuming the buffer
693 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
694 void *mem = c->mem;
695 dt_free(c);
697 return mem;
698 }
699
700 l = next;
701 }
703
704 return NULL;
705}
706#endif
707
709 int width, int height, int bpp)
710{
711#ifdef HAVE_OPENCL
712
714
715 for(GList *l = g_list_first(entry->cl_mem_list); l; l = g_list_next(l))
716 {
717 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
718 if(dt_opencl_get_mem_context_id(c->mem) == devid
719 && dt_opencl_get_image_width(c->mem) == width
722 {
723 c->refs++;
724 void *mem = c->mem;
726 return mem;
727 }
728 }
730
731#endif
732
733 return NULL;
734}
735
737{
738#ifdef HAVE_OPENCL
739
740 if(IS_NULL_PTR(entry) || IS_NULL_PTR(mem)) return;
741
743 for(GList *l = entry->cl_mem_list; l; l = g_list_next(l))
744 {
745 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
746 if(c && c->mem == mem)
747 {
748 if(c->refs > 0) c->refs--;
749 break;
750 }
751 }
753
754#else
755 (void)entry;
756 (void)mem;
757#endif
758}
759
760#ifdef HAVE_OPENCL
770static int _pixel_cache_clmem_put(dt_pixel_cache_entry_t *entry, void *host_ptr, void *mem)
771{
772 cl_mem clmem = (cl_mem)mem;
773 const int devid = dt_opencl_get_mem_context_id(clmem);
774
776 for(GList *l = g_list_first(entry->cl_mem_list); l; l = g_list_next(l))
777 {
778 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
779 if(c->mem == mem)
780 {
782 return 3;
783 }
784 if(dt_opencl_get_mem_context_id(c->mem) == devid)
785 {
786 // We keep one GPU cacheline per GPU device per pipeline cache entry
787 // If refs > 0 here, we have a problem earlier.
788 if(c->refs > 0) continue;
789
790 void *old = c->mem;
791 c->mem = mem;
792 c->host_ptr = host_ptr;
795 return 2;
796 }
797 }
798
799 dt_cache_clmem_t *c = (dt_cache_clmem_t *)g_malloc0(sizeof(*c));
800 if(IS_NULL_PTR(c))
801 {
804 return 0;
805 }
806
807 c->host_ptr = host_ptr;
808 c->mem = mem;
809 entry->cl_mem_list = g_list_prepend(entry->cl_mem_list, c);
811 return 1;
812}
813
815{
816 if(IS_NULL_PTR(entry) || IS_NULL_PTR(mem)) return;
817
819 for(GList *l = entry->cl_mem_list; l;)
820 {
821 GList *next = g_list_next(l);
822 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
823 if(c && c->mem == mem)
824 {
825 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
826 dt_free(c);
827 }
828 l = next;
829 }
831}
832#endif
833
835{
837 for(GList *l = entry->cl_mem_list; l;)
838 {
839 GList *next = g_list_next(l);
840 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
841 if(c->refs > 0)
842 {
843 l = next;
844 continue;
845 }
846
847 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
849 dt_free(c);
850 l = next;
851 }
853}
854
855#ifdef HAVE_OPENCL
857 dt_pixel_cache_entry_t *entry_hint, int devid,
858 int width, int height, int bpp, int flags,
859 gboolean *out_reused)
860{
861 if(!IS_NULL_PTR(out_reused)) *out_reused = FALSE;
862 if(devid < 0 || width <= 0 || height <= 0 || bpp <= 0) return NULL;
863
864 // Pinning is enabled if the calling function requests it and if it is allowed by user for this device
865 gboolean use_pinned = dt_opencl_use_pinned_memory(devid) && (flags & CL_MEM_USE_HOST_PTR);
866
867 // If no pinning, remove the allocation flag now because pinning happens at vRAM alloc time
868 if(!use_pinned) flags &= ~CL_MEM_USE_HOST_PTR;
869
870 // Reuse the entry hint if available, else find the cache entry attached to the host_ptr
871 dt_pixel_cache_entry_t *entry = entry_hint;
872 if(IS_NULL_PTR(entry))
873 {
875 entry = _cache_entry_for_host_ptr_locked(cache, host_ptr);
877 }
878
879 // Reuse the vRAM buffer attached to the cache entry if any
880 void *mem = NULL;
881 if(entry)
882 {
883 mem = _pixel_cache_clmem_get(entry, host_ptr, devid, width, height, bpp, flags);
884 if(!IS_NULL_PTR(mem) && !IS_NULL_PTR(out_reused)) *out_reused = TRUE;
885 }
886
887 // If no vRAM buffer was found, allocate a new one, pinning the host_ptr memory if the option is enabled
888 if(IS_NULL_PTR(mem))
889 {
890 mem = dt_opencl_alloc_device_use_host_pointer(devid, width, height, bpp, use_pinned ? host_ptr : NULL, flags);
891 if(IS_NULL_PTR(mem)) return NULL;
892 }
893
894 gboolean synced = FALSE;
895
896 // Synchronize host_ptr with mem
898 {
899 // Zero-copy for pinned buffers : note that some drivers may still use non-zero-copy,
900 // in which case that degrades to basic memory copy.
901 void *mapped = dt_opencl_map_image(devid, mem, TRUE, CL_MAP_WRITE, width, height, bpp);
902 synced = (dt_opencl_unmap_mem_object(devid, mem, mapped) == CL_SUCCESS);
903 }
904
905 if(!synced)
906 {
907 // Zero-copy failed or pinned memory is disabled for this device : use plain memory transfer
908 if(dt_opencl_write_host_to_device(devid, host_ptr, mem, width, height, bpp) != CL_SUCCESS)
909 {
910 // Clean everything up on error and abort
911 if(entry) _pixel_cache_clmem_remove(entry, mem);
913 dt_print(DT_DEBUG_OPENCL, "[dt_dev_pixelpipe_cache_get_pinned_image] failed to synchronize\n");
914 return NULL;
915 }
916 else
917 {
918 dt_print(DT_DEBUG_OPENCL, "[dt_dev_pixelpipe_cache_get_pinned_image] synchronized with write_host_to_device\n");
919 }
920 }
921 else
922 {
923 dt_print(DT_DEBUG_OPENCL, "[dt_dev_pixelpipe_cache_get_pinned_image] synchronized with mapping/unmapping\n");
924 }
925
926 return mem;
927}
928
930 dt_pixel_cache_entry_t *entry_hint, void **mem)
931{
932 if(IS_NULL_PTR(mem) || IS_NULL_PTR(*mem) || IS_NULL_PTR(host_ptr)) return;
933 dt_pixel_cache_entry_t *entry = entry_hint;
934 if(IS_NULL_PTR(entry))
935 {
937 dt_print(DT_DEBUG_OPENCL, "[dt_dev_pixelpipe_cache_put_pinned_image] no cache entry to put the vRAM buffer\n");
938 return;
939 }
940
941 // FIXME: is it safe to cache non-pinned vRAM buffers (aka no CL_MEM_USE_HOST_PTR in flags) ?
942 const int state = _pixel_cache_clmem_put(entry, host_ptr, (cl_mem)*mem);
943 *mem = NULL;
945 dt_print(DT_DEBUG_OPENCL, "[dt_dev_pixelpipe_cache_put_pinned_image] cache entry put the vRAM buffer (state=%i) in %p\n", state, entry);
946}
947
949 dt_pixel_cache_entry_t *entry_hint, int devid)
950{
951 if(IS_NULL_PTR(cache) || IS_NULL_PTR(host_ptr)) return FALSE;
952
953 dt_pixel_cache_entry_t *entry = entry_hint;
954 if(IS_NULL_PTR(entry))
955 {
957 entry = _cache_entry_for_host_ptr_locked(cache, host_ptr);
959 }
960
961 if(IS_NULL_PTR(entry)) return FALSE;
962 if(!_cache_entry_clmem_has_host_pinned_locked(entry, host_ptr, devid)) return FALSE;
963
964 if(devid >= 0) dt_opencl_events_wait_for(devid);
966 const gboolean flushed = _cache_entry_clmem_flush_host_pinned_locked(entry, host_ptr, devid);
968 return flushed;
969}
970
971#else
972
974 dt_pixel_cache_entry_t *entry_hint, void **mem)
975{
976 (void)cache;
977 (void)host_ptr;
978 (void)entry_hint;
979 if(mem) *mem = NULL;
980}
981
983 dt_pixel_cache_entry_t *entry_hint, int devid)
984{
985 (void)cache;
986 (void)host_ptr;
987 (void)entry_hint;
988 (void)devid;
989 return FALSE;
990}
991
992void dt_dev_pixelpipe_cache_resync_host_pinned_image(dt_dev_pixelpipe_cache_t *cache, void *host_ptr,
993 dt_pixel_cache_entry_t *entry_hint, int devid)
994{
995 (void)cache;
996 (void)host_ptr;
997 (void)entry_hint;
998 (void)devid;
999}
1000#endif
1001
1002#ifdef HAVE_OPENCL
1003static inline gboolean _is_gamma_rgba8_output(const dt_iop_module_t *module, const size_t bpp,
1004 const char *message)
1005{
1006 return module && message && bpp == 4 * sizeof(uint8_t) && strcmp(module->op, "gamma") == 0
1007 && strcmp(message, "output") == 0;
1008}
1009
1010void *dt_dev_pixelpipe_cache_alloc_cl_device_buffer(int devid, const dt_iop_roi_t *roi, const size_t bpp,
1011 const dt_iop_module_t *module, const char *message,
1012 void *keep)
1013{
1014 const gboolean gamma_rgba8 = _is_gamma_rgba8_output(module, bpp, message);
1015 const int cl_bpp = gamma_rgba8 ? DT_OPENCL_BPP_ENCODE_RGBA8((int)bpp) : (int)bpp;
1016 return dt_opencl_alloc_device(devid, roi->width, roi->height, cl_bpp);
1017}
1018
1019void *dt_dev_pixelpipe_cache_get_cl_buffer(int devid, void *const host_ptr, const dt_iop_roi_t *roi,
1020 const size_t bpp, dt_iop_module_t *module,
1021 const char *message, dt_pixel_cache_entry_t *cache_entry,
1022 gboolean *out_reused, void *keep)
1023{
1024 // Need to use read-write mode because of in-place color space conversions.
1025 void *cl_mem_input = NULL;
1026 gboolean reused_from_cache = FALSE;
1027 const gboolean gamma_rgba8 = _is_gamma_rgba8_output(module, bpp, message);
1028 const int cl_bpp = gamma_rgba8 ? DT_OPENCL_BPP_ENCODE_RGBA8((int)bpp) : (int)bpp;
1029 static dt_atomic_int clmem_reuse_hits;
1030 static dt_atomic_int clmem_reuse_misses;
1031
1032 if(out_reused) *out_reused = FALSE;
1033
1034 if(host_ptr && dt_opencl_use_pinned_memory(devid))
1035 {
1036 const int flags = CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR;
1037
1038 // Try to reuse existing buffer
1039 if(cache_entry)
1040 {
1041 cl_mem_input = _pixel_cache_clmem_get(cache_entry, host_ptr, devid, roi->width, roi->height,
1042 (int)bpp, flags);
1043 reused_from_cache = (!IS_NULL_PTR(cl_mem_input));
1044 }
1045
1046 // This will internally try to free up cache space if first alloc fails
1047 if(IS_NULL_PTR(cl_mem_input))
1048 {
1049 cl_mem_input = dt_opencl_alloc_device_use_host_pointer(devid, roi->width, roi->height, cl_bpp,
1050 host_ptr, flags);
1052 dt_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] allocated a pinned GPU buffer for %s %s\n", module->name(), message);
1053 }
1054 }
1055 else
1056 {
1057 if(cache_entry)
1058 {
1059 /* Device-only allocations are tracked with a NULL host_ptr key and a normalized READ_WRITE
1060 * flag so scratch buffers can be reused deterministically across drivers. */
1061 cl_mem_input = _pixel_cache_clmem_get(cache_entry, NULL, devid, roi->width, roi->height,
1062 (int)bpp, CL_MEM_READ_WRITE);
1063 reused_from_cache = (!IS_NULL_PTR(cl_mem_input));
1064 }
1065
1066 // This will internally try to free up cache space if first alloc fails
1067 if(IS_NULL_PTR(cl_mem_input))
1068 {
1069 cl_mem_input = dt_dev_pixelpipe_cache_alloc_cl_device_buffer(devid, roi, bpp, module, message, keep);
1070
1072 dt_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] allocated a device-only GPU buffer for %s %s\n", module->name(), message);
1073 }
1074 }
1075
1076 if(IS_NULL_PTR(cl_mem_input))
1077 {
1078 dt_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] couldn't allocate GPU buffer for module %s %s\n", module->name(), message);
1079 }
1080 else if(reused_from_cache)
1081 {
1082 const int hits = dt_atomic_add_int(&clmem_reuse_hits, 1) + 1;
1083 const int misses = dt_atomic_get_int(&clmem_reuse_misses);
1086 "[dev_pixelpipe] reused GPU buffer from cache (hits=%d, misses=%d) for module %s %s\n",
1087 hits, misses, module->name(), message);
1088 }
1089 else
1090 {
1091 dt_atomic_add_int(&clmem_reuse_misses, 1);
1092 }
1093
1094 if(out_reused) *out_reused = reused_from_cache;
1095 return cl_mem_input;
1096}
1097
1121 void *host_ptr, const gboolean cache_device)
1122{
1123 if(!IS_NULL_PTR(cl_mem_buffer) && !IS_NULL_PTR(*cl_mem_buffer))
1124 {
1125 cl_mem mem = *cl_mem_buffer;
1126 if(cache_device && !IS_NULL_PTR(cache_entry))
1127 {
1128 _pixel_cache_clmem_put(cache_entry, host_ptr, mem);
1129 }
1130 else
1131 {
1132 if(!IS_NULL_PTR(cache_entry)) _pixel_cache_clmem_remove(cache_entry, mem);
1134 }
1135 *cl_mem_buffer = NULL;
1136 }
1137}
1138
1168int dt_dev_pixelpipe_cache_sync_cl_buffer(const int devid, void *host_ptr, void *cl_mem_buffer,
1169 const dt_iop_roi_t *roi, int cl_mode, size_t bpp,
1170 dt_iop_module_t *module, const char *message)
1171{
1172 if(IS_NULL_PTR(host_ptr) || IS_NULL_PTR(cl_mem_buffer)) return 1;
1173
1174 const cl_mem mem = (cl_mem)cl_mem_buffer;
1175
1176 // Fast path for true zero-copy pinned images: map/unmap is enough to synchronize host<->device.
1178 {
1179 void *mapped = dt_opencl_map_image(devid, mem, TRUE, cl_mode, roi->width, roi->height, (int)bpp);
1180 if(dt_opencl_unmap_mem_object(devid, mem, mapped) == CL_SUCCESS)
1181 {
1183 "[dev_pixelpipe] successfully synced image %s via map/unmap for module %s (%s)\n",
1184 (cl_mode == CL_MAP_WRITE) ? "host to device" : "device to host",
1185 (module) ? module->op : "base buffer", message);
1186 return 0;
1187 }
1188 }
1189
1190 // Fallback: explicit blocking transfers (safe on all drivers).
1191 cl_int err = CL_SUCCESS;
1192 if(cl_mode == CL_MAP_WRITE)
1193 err = dt_opencl_write_host_to_device(devid, host_ptr, mem, roi->width, roi->height, (int)bpp);
1194 else if(cl_mode == CL_MAP_READ)
1195 err = dt_opencl_read_host_from_device(devid, host_ptr, mem, roi->width, roi->height, (int)bpp);
1196 else
1197 return 1;
1198
1199 if(err != CL_SUCCESS)
1200 {
1201 dt_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] couldn't copy image %s for module %s (%s)\n",
1202 (cl_mode == CL_MAP_WRITE) ? "host to device" : "device to host",
1203 (module) ? module->op : "base buffer", message);
1204 return 1;
1205 }
1206
1207 dt_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] successfully copied image %s for module %s (%s)\n",
1208 (cl_mode == CL_MAP_WRITE) ? "host to device" : "device to host",
1209 (module) ? module->op : "base buffer", message);
1210 return 0;
1211}
1212
1227float *dt_dev_pixelpipe_cache_restore_cl_buffer(dt_dev_pixelpipe_t *pipe, float *input, void *cl_mem_input,
1228 const dt_iop_roi_t *roi_in, dt_iop_module_t *module,
1229 const size_t in_bpp, dt_pixel_cache_entry_t *input_entry,
1230 const char *message)
1231{
1232 if(IS_NULL_PTR(cl_mem_input)) return input;
1234
1235 const int fail = dt_dev_pixelpipe_cache_sync_cl_buffer(pipe->devid, input, cl_mem_input, roi_in,
1236 CL_MAP_READ, in_bpp, module, message);
1238 return fail ? NULL : input;
1239}
1240
1269 float *input, void **cl_mem_input,
1270 const dt_iop_roi_t *roi_in, const size_t in_bpp,
1271 dt_pixel_cache_entry_t *input_entry,
1272 dt_pixel_cache_entry_t **locked_input_entry, void *keep)
1273{
1274 if(IS_NULL_PTR(locked_input_entry)) return 1;
1275 *locked_input_entry = NULL;
1276
1277 if(!IS_NULL_PTR(*cl_mem_input))
1278 {
1279 // We passed the OpenCL memory buffer through directly on vRAM from previous module.
1280 // This is fast and efficient.
1281 // If it's a true zero-copy pinned image, keep the input cache entry read-locked until kernels complete,
1282 // otherwise another thread may overwrite host memory while the GPU is still reading it.
1283 dt_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] %s will use its input directly from vRAM\n", module->name());
1284 const cl_mem mem = (cl_mem)*cl_mem_input;
1286 {
1288 *locked_input_entry = input_entry;
1289 }
1290 return 0;
1291 }
1292
1293 if(IS_NULL_PTR(input))
1294 {
1295 dt_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] %s has no input (cache)\n", module->name());
1296 return 1;
1297 }
1298
1300
1301 // Try to reuse a cached pinned buffer; otherwise allocate a new pinned image backed by `input`.
1302 gboolean input_reused_from_cache = FALSE;
1303 *cl_mem_input = dt_dev_pixelpipe_cache_get_cl_buffer(pipe->devid, input, roi_in, in_bpp, module,
1304 "input", input_entry,
1305 &input_reused_from_cache, keep);
1306 int fail = (IS_NULL_PTR(*cl_mem_input));
1307
1308 // If the input is true zero-copy, the GPU will access host memory asynchronously: keep the cache
1309 // entry read-locked until all kernels have completed. If not, drivers may use a device-side copy
1310 // which must be synchronized from the host before running kernels.
1311 gboolean keep_lock = FALSE;
1312 cl_mem mem = NULL;
1313 if(!fail && *cl_mem_input)
1314 {
1315 mem = (cl_mem)*cl_mem_input;
1316 keep_lock = dt_opencl_is_pinned_memory(mem);
1317 }
1318
1319 /* A reused cached pinned image already carries the authoritative device payload from the
1320 * previous module output. Re-uploading host RAM here would overwrite that valid vRAM state
1321 * with whatever stale contents the host buffer still has when the previous stage stayed GPU-only.
1322 * Only freshly allocated pinned inputs need an explicit host->device copy. */
1323 if(!fail && mem && !keep_lock && !input_reused_from_cache)
1324 {
1325 const cl_int err = dt_opencl_write_host_to_device(pipe->devid, input, mem, roi_in->width, roi_in->height,
1326 (int)in_bpp);
1327 if(err != CL_SUCCESS)
1328 {
1329 dt_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] couldn't copy image host to device for module %s (%s)\n",
1330 (module) ? module->op : "base buffer", "cache to input");
1331 fail = TRUE;
1332 }
1333 else
1334 {
1335 dt_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] successfully copied image host to device for module %s (%s)\n",
1336 (module) ? module->op : "base buffer", "cache to input");
1337 }
1338 }
1339
1340 // Enforce sync with the CPU/RAM cache so lock validity is guaranteed.
1342
1343 if(keep_lock)
1344 *locked_input_entry = input_entry;
1345 else
1347
1348 return fail ? 1 : 0;
1349}
1350#else
1351void *dt_dev_pixelpipe_cache_get_cl_buffer(int devid, void *host_ptr, const dt_iop_roi_t *roi,
1352 size_t bpp, dt_iop_module_t *module, const char *message,
1354 gboolean *out_reused, void *keep)
1355{
1356 (void)devid;
1357 (void)host_ptr;
1358 (void)roi;
1359 (void)bpp;
1360 (void)module;
1361 (void)message;
1362 (void)entry;
1363 (void)keep;
1364 if(out_reused) *out_reused = FALSE;
1365 return NULL;
1366}
1367
1368void *dt_dev_pixelpipe_cache_alloc_cl_device_buffer(int devid, const dt_iop_roi_t *roi, size_t bpp,
1369 const dt_iop_module_t *module,
1370 const char *message, void *keep)
1371{
1372 (void)devid;
1373 (void)roi;
1374 (void)bpp;
1375 (void)module;
1376 (void)message;
1377 (void)keep;
1378 return NULL;
1379}
1380
1382 void *host_ptr, gboolean cache_device)
1383{
1384 (void)entry;
1385 (void)host_ptr;
1386 (void)cache_device;
1387 if(cl_mem_buffer) *cl_mem_buffer = NULL;
1388}
1389
1390int dt_dev_pixelpipe_cache_sync_cl_buffer(int devid, void *host_ptr, void *cl_mem_buffer,
1391 const dt_iop_roi_t *roi, int cl_mode, size_t bpp,
1392 dt_iop_module_t *module, const char *message)
1393{
1394 (void)devid;
1395 (void)host_ptr;
1396 (void)cl_mem_buffer;
1397 (void)roi;
1398 (void)cl_mode;
1399 (void)bpp;
1400 (void)module;
1401 (void)message;
1402 return 1;
1403}
1404
1406 void *cl_mem_input, const dt_iop_roi_t *roi_in,
1407 dt_iop_module_t *module, size_t in_bpp,
1408 dt_pixel_cache_entry_t *input_entry,
1409 const char *message)
1410{
1411 (void)pipe;
1412 (void)cl_mem_input;
1413 (void)roi_in;
1414 (void)module;
1415 (void)in_bpp;
1416 (void)input_entry;
1417 (void)message;
1418 return input;
1419}
1420
1422 float *input, void **cl_mem_input,
1423 const dt_iop_roi_t *roi_in, size_t in_bpp,
1424 dt_pixel_cache_entry_t *input_entry,
1425 dt_pixel_cache_entry_t **locked_input_entry,
1426 void *keep)
1427{
1428 (void)pipe;
1429 (void)module;
1430 (void)input;
1431 (void)cl_mem_input;
1432 (void)roi_in;
1433 (void)in_bpp;
1434 (void)input_entry;
1435 (void)locked_input_entry;
1436 (void)keep;
1437 return 1;
1438}
1439#endif
1440
1442 void *host_ptr)
1443{
1444 if(IS_NULL_PTR(cache) || IS_NULL_PTR(host_ptr)) return NULL;
1445
1446 dt_pthread_mutex_lock(&cache->lock);
1448 if(entry)
1451
1452 return entry;
1453}
1454
1455// Attempt to allocate from the arena; if fragmentation prevents it, evict LRU cache lines
1456// until a sufficiently large contiguous run is available (or nothing remains to evict).
1457static inline void *_arena_alloc_with_defrag(dt_dev_pixelpipe_cache_t *cache, size_t request_size,
1458 size_t *actual_size)
1459{
1460 void *buf = dt_cache_arena_alloc(&cache->arena, request_size, actual_size);
1461 if(!IS_NULL_PTR(buf)) return buf;
1462
1463 uint32_t pages_needed = 0;
1464 if(dt_cache_arena_calc(&cache->arena, request_size, &pages_needed, NULL))
1465 {
1466 dt_pthread_mutex_lock(&cache->lock);
1467 uint32_t total_free_pages = 0, largest_free_run_pages = 0;
1468 dt_cache_arena_stats(&cache->arena, &total_free_pages, &largest_free_run_pages);
1469
1470 while(largest_free_run_pages < pages_needed && g_hash_table_size(cache->entries) > 0)
1471 {
1473 dt_cache_arena_stats(&cache->arena, &total_free_pages, &largest_free_run_pages);
1474 }
1476 }
1477
1478 return dt_cache_arena_alloc(&cache->arena, request_size, actual_size);
1479}
1480
1481static inline void _arena_stats_bytes(dt_dev_pixelpipe_cache_t *cache, uint32_t *total_pages,
1482 uint32_t *largest_pages, size_t *total_bytes, size_t *largest_bytes)
1483{
1484 dt_cache_arena_stats(&cache->arena, total_pages, largest_pages);
1485 const size_t page_size = cache->arena.page_size ? cache->arena.page_size : 1;
1486 if(total_bytes) *total_bytes = (size_t)(*total_pages) * page_size;
1487 if(largest_bytes) *largest_bytes = (size_t)(*largest_pages) * page_size;
1488}
1489
1490static inline void _log_arena_allocation_failure(dt_dev_pixelpipe_cache_t *cache, size_t request_size,
1491 const char *entry_name, const char *module, uint64_t hash,
1492 gboolean name_is_file)
1493{
1494 uint32_t total_free_pages = 0, largest_free_run_pages = 0;
1495 size_t total_free_bytes = 0, largest_free_bytes = 0;
1496 _arena_stats_bytes(cache, &total_free_pages, &largest_free_run_pages, &total_free_bytes, &largest_free_bytes);
1497
1498 if(entry_name)
1499 fprintf(stdout,
1500 "[pixelpipe_cache] failed to allocate %" G_GSIZE_FORMAT " bytes for entry %" PRIu64 " (%s, module=%s) "
1501 "[arena largest=%" G_GSIZE_FORMAT " MiB, total=%" G_GSIZE_FORMAT " MiB, cache=%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT " MiB]\n",
1502 request_size, hash, entry_name, module ? module : "unknown",
1503 largest_free_bytes / (1024 * 1024), total_free_bytes / (1024 * 1024),
1504 cache->current_memory / (1024 * 1024), cache->max_memory / (1024 * 1024));
1505 else
1506 fprintf(stdout,
1507 "[pixelpipe_cache] failed to allocate %" G_GSIZE_FORMAT " bytes for entry %" PRIu64 " (module=%s) "
1508 "[arena largest=%" G_GSIZE_FORMAT " MiB, total=%" G_GSIZE_FORMAT " MiB, cache=%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT " MiB]\n",
1509 request_size, hash, module ? module : "unknown",
1510 largest_free_bytes / (1024 * 1024), total_free_bytes / (1024 * 1024),
1511 cache->current_memory / (1024 * 1024), cache->max_memory / (1024 * 1024));
1512
1513 if(!IS_NULL_PTR(entry_name) && !IS_NULL_PTR(module))
1514 dt_control_log(_("The pipeline cache is full while allocating `%s` (module `%s`). Either your RAM settings are too frugal or your RAM is too small."),
1515 entry_name, module);
1516 else if(!IS_NULL_PTR(entry_name))
1517 dt_control_log(_("The pipeline cache is full while allocating `%s`. Either your RAM settings are too frugal or your RAM is too small."),
1518 entry_name);
1519 else if(!IS_NULL_PTR(module))
1520 dt_control_log(_("The pipeline cache is full while processing module `%s`. Either your RAM settings are too frugal or your RAM is too small."),
1521 module);
1522 else
1523 dt_control_log(_("The pipeline cache is full. Either your RAM settings are too frugal or your RAM is too small."));
1524
1525 (void)name_is_file; // kept for signature symmetry if future callers need it.
1526}
1527
1528// keep: OpenCL buffer to NOT release
1529#ifdef HAVE_OPENCL
1530// Release this device's vRAM payloads for one entry. The caller has already established that
1531// the entry is idle (refcount == 0, not write-locked), so the device buffers are nobody's live
1532// input and reclaiming them honors the flush's purpose: free vRAM for later allocations.
1533// Returns TRUE if the entry holds no buffer at all afterwards (no host RAM, no vRAM on any
1534// device) and should therefore be evicted entirely instead of lingering as a husk.
1535static gboolean _cache_entry_clmem_flush_device(dt_pixel_cache_entry_t *entry, const int devid)
1536{
1537 // devid is always >= 0 here: dt_dev_pixelpipe_cache_flush_clmem() early-returns
1538 // otherwise. Only cachelines living on this specific device are candidates.
1540
1541 for(GList *l = g_list_first(entry->cl_mem_list); l;)
1542 {
1543 GList *next = g_list_next(l);
1544 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
1545 if(IS_NULL_PTR(c->mem))
1546 {
1547 // Don't keep cacheline with NULL buffer
1548 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
1549 dt_free(c);
1550 l = next;
1551 continue;
1552 }
1553
1554 gboolean referenced = c->refs > 0;
1555 gboolean not_ours = dt_opencl_get_mem_context_id(c->mem) != devid;
1556
1557 if(referenced || not_ours)
1558 {
1559 // Don't flush cachelines that don't belong to the current OpenCL device,
1560 // or are still borrowed by an in-flight GPU module (per-payload refs > 0).
1563 "[dt_dev_pixelpipe_cache_flush_clmem] for entry %" PRIu64 ": couldn't flush %p "
1564 "(referenced=%i not ours=%i)\n",
1565 entry->hash, c->mem, referenced, not_ours);
1566 l = next;
1567 continue;
1568 }
1569
1570 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
1572 dt_free(c);
1573 l = next;
1574 }
1575
1576 // A cacheline that now carries neither a host buffer nor any vRAM is a husk: the cache would
1577 // still hand it out as a hit, making a later consumer abort with "has no RAM nor vRAM input"
1578 // (issue #817 skull thumbnails). Signal the caller to delete it entirely.
1579 const gboolean empty = IS_NULL_PTR(entry->data) && IS_NULL_PTR(entry->cl_mem_list);
1581 return empty;
1582}
1583#else
1584static gboolean _cache_entry_clmem_flush_device(dt_pixel_cache_entry_t *entry, const int devid)
1585{
1586 return FALSE;
1587}
1588#endif
1589
1591{
1592 // allocate the data buffer
1593 if(IS_NULL_PTR(cache_entry->data))
1594 {
1595 cache_entry->data = _arena_alloc_with_defrag(cache, cache_entry->size, &cache_entry->size);
1596
1597 if(IS_NULL_PTR(cache_entry->data))
1598 {
1599 const char *module = dt_pixelpipe_cache_current_module;
1600 _log_arena_allocation_failure(cache, cache_entry->size, cache_entry->name, module,
1601 cache_entry->hash, FALSE);
1602 }
1603 }
1604
1605 return cache_entry->data;
1606}
1607
1609{
1610 return entry ? entry->data : NULL;
1611}
1612
1614{
1615 return entry ? entry->size : 0;
1616}
1617
1618// WARNING: non thread-safe
1619static int _free_space_to_alloc(dt_dev_pixelpipe_cache_t *cache, const size_t size, const uint64_t hash,
1620 const char *name)
1621{
1622 // Free up space if needed to match the max memory limit
1623 // If error, all entries are currently locked or in use, so we cannot free space to allocate a new entry.
1624 int error = 0;
1625 while(cache->current_memory + size > cache->max_memory && g_hash_table_size(cache->entries) > 0 && !error)
1627
1628 if(cache->current_memory + size > cache->max_memory)
1629 {
1630 const char *module = dt_pixelpipe_cache_current_module;
1631 const gboolean name_is_file = (!IS_NULL_PTR(name)) && (strchr(name, '/') != NULL) && (strchr(name, ':') != NULL);
1632 if(IS_NULL_PTR(name)) name = g_strdup("unknown");
1633
1634 if(hash)
1635 fprintf(stdout, "[pixelpipe] cache is full, cannot allocate new entry %" PRIu64 " (%s)\n", hash, name);
1636 else
1637 fprintf(stdout, "[pixelpipe] cache is full, cannot allocate new entry (%s)\n", name);
1638 if(!IS_NULL_PTR(name) && !IS_NULL_PTR(module) && name_is_file)
1639 dt_control_log(_("The pipeline cache is full while allocating `%s` (module `%s`). Either your RAM settings are too frugal or your RAM is too small."), name, module);
1640 else if(!IS_NULL_PTR(name))
1641 dt_control_log(_("The pipeline cache is full while allocating `%s`. Either your RAM settings are too frugal or your RAM is too small."), name);
1642 else if(!IS_NULL_PTR(module))
1643 dt_control_log(_("The pipeline cache is full while processing module `%s`. Either your RAM settings are too frugal or your RAM is too small."), module);
1644 else
1645 dt_control_log(_("The pipeline cache is full. Either your RAM settings are too frugal or your RAM is too small."));
1646 }
1647
1648 return error;
1649}
1650
1652 const char *name)
1653{
1654 // Free up space if needed to match the max memory limit
1655 // If error, all entries are currently locked or in use, so we cannot free space to allocate a new entry.
1656 dt_pthread_mutex_lock(&cache->lock);
1657 int error = _free_space_to_alloc(cache, size, 0, name);
1659
1660 if(error) return NULL;
1661
1662 // Page size is the desired size + AVX/SSE rounding
1663 size_t page_size = 0;
1664 void *buf = _arena_alloc_with_defrag(cache, size, &page_size);
1665
1666 if(IS_NULL_PTR(buf))
1667 {
1668 _log_arena_allocation_failure(cache, size, name, NULL, 0, FALSE);
1669 return NULL;
1670 }
1671
1672 void *aligned = __builtin_assume_aligned(buf, DT_CACHELINE_BYTES);
1673
1674 const uint64_t hash = (uint64_t)(uintptr_t)(aligned);
1675
1676 dt_pthread_mutex_lock(&cache->lock);
1677 dt_pixel_cache_entry_t *cache_entry
1678 = dt_pixel_cache_new_entry(hash, page_size, name, id, cache, FALSE, cache->external_entries);
1679
1680 if(IS_NULL_PTR(cache_entry))
1681 {
1683 dt_cache_arena_free(&cache->arena, buf, page_size);
1684 return NULL;
1685 }
1686
1687 // Keep this entry marked as "used" for diagnostics/bookkeeping.
1688 // Note that external_entries are not subject to LRU eviction, so we must not keep
1689 // a thread-owned rwlock held across the lifetime of the buffer (it may be freed
1690 // from a different thread during cleanup paths).
1691 _non_thread_safe_cache_ref_count_entry(cache, TRUE, cache_entry);
1692 cache_entry->data = aligned;
1693 cache_entry->age = g_get_monotonic_time();
1694 cache_entry->external_alloc = TRUE;
1696 return aligned;
1697}
1698
1699void dt_pixelpipe_cache_free_align_cache(dt_dev_pixelpipe_cache_t *cache, void **mem, const char *message)
1700{
1701 if(IS_NULL_PTR(mem) || !*mem) return;
1702
1703 dt_pthread_mutex_lock(&cache->lock);
1704 const uint64_t hash = (uint64_t)(uintptr_t)(*mem);
1706 if(IS_NULL_PTR(cache_entry) || !cache_entry->external_alloc)
1707 {
1709 fprintf(stdout, "error while freeing cache entry: no entry found but we have a buffer, %s.\n", message);
1710 raise(SIGSEGV); // triggers dt_set_signal_handlers() backtrace on Unix
1711 return;
1712 }
1713
1714 _non_thread_safe_cache_ref_count_entry(cache, FALSE, cache_entry);
1715 g_hash_table_remove(cache->external_entries, &cache_entry->hash);
1716 *mem = NULL;
1717
1719}
1720
1721
1722// WARNING: not thread-safe, protect its calls with mutex lock
1724 const char *name, const int id,
1725 dt_dev_pixelpipe_cache_t *cache, gboolean alloc,
1726 GHashTable *table)
1727{
1728 uint32_t pages_needed = 0;
1729 size_t rounded_size = 0;
1730 if(!dt_cache_arena_calc(&cache->arena, size, &pages_needed, &rounded_size))
1731 {
1732 fprintf(stderr, "[pixelpipe] invalid cache entry size %" G_GSIZE_FORMAT " for %s\n", size, name);
1733 return NULL;
1734 }
1735
1736 int error = _free_space_to_alloc(cache, rounded_size, hash, name);
1737 if(error) return NULL;
1738
1740 if(IS_NULL_PTR(cache_entry)) return NULL;
1741
1742 // Metadata, easy to free in batch if need be
1743 cache_entry->size = rounded_size;
1744 cache_entry->age = 0;
1745 cache_entry->hits = 0;
1746 cache_entry->hash = hash;
1747 cache_entry->serial = cache->next_serial++;
1748 cache_entry->id = id;
1750 cache_entry->refcount = 0;
1751 cache_entry->auto_destroy = FALSE;
1752 cache_entry->external_alloc = FALSE;
1753 cache_entry->data = NULL;
1754 cache_entry->cache = cache;
1755 cache_entry->cl_mem_list = NULL;
1756 dt_pthread_mutex_init(&cache_entry->cl_mem_lock, NULL);
1757
1758 // Optionally alloc the actual buffer, but still record its size in cache
1759 if(alloc) dt_pixel_cache_alloc(cache, cache_entry);
1760
1761 if(alloc && IS_NULL_PTR(cache_entry->data))
1762 {
1763 dt_free(cache_entry);
1764 return NULL;
1765 }
1766
1767 // Metadata that need alloc
1768 cache_entry->name = g_strdup(name);
1769 dt_pthread_rwlock_init(&cache_entry->lock, NULL);
1770
1771 uint64_t *key = g_malloc(sizeof(*key));
1772 if(IS_NULL_PTR(key))
1773 {
1774 dt_pthread_rwlock_destroy(&cache_entry->lock);
1775 dt_free(cache_entry->name);
1777 dt_free(cache_entry);
1778 return NULL;
1779 }
1780 *key = hash;
1781 g_hash_table_insert(table, key, cache_entry);
1782
1783 // Note : we grow the cache size even though the data buffer is not yet allocated
1784 // This is planning
1785 cache->current_memory += rounded_size;
1786
1787 return cache_entry;
1788}
1789
1790
1792{
1793 if(IS_NULL_PTR(cache_entry)) return;
1794
1795 _pixel_cache_message(cache_entry, "freed", FALSE);
1796
1798 dt_supervisor_cacheline_delete(cache_entry->hash, cache_entry->size, cache_entry->id,
1799 cache_entry->name);
1800
1801 /* Every live entry belongs to the one and only global pixelpipe cache, so its back-reference
1802 * must match it. If it doesn't, the entry struct has been corrupted (we have seen a single
1803 * flipped bit in the pointer from faulty RAM) or is stale: reaching cache->arena or
1804 * cache->current_memory through it would dereference a wild pointer and turn an innocuous
1805 * teardown into a SIGSEGV. Skip the arena free and the accounting in that case -- the arena is
1806 * unmapped wholesale right after, so nothing actually leaks. */
1807 dt_dev_pixelpipe_cache_t *cache = cache_entry->cache;
1808 if(cache != darktable.pixelpipe_cache)
1809 {
1810 fprintf(stderr, "[pixelpipe] cache entry %p has a corrupted back-reference (%p, expected %p); "
1811 "skipping arena free to avoid a crash\n",
1812 (void *)cache_entry, (void *)cache, (void *)darktable.pixelpipe_cache);
1813 cache = NULL;
1814 }
1815
1816 if(cache_entry->data && cache)
1817 {
1818#ifdef HAVE_OPENCL
1819 dt_pthread_mutex_lock(&cache_entry->cl_mem_lock);
1820 for(GList *l = cache_entry->cl_mem_list; l; l = g_list_next(l))
1821 {
1822 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
1823 if(IS_NULL_PTR(c) || c->host_ptr != cache_entry->data) continue;
1824
1825 /* Host-backed OpenCL images may still dereference `cache_entry->data` asynchronously until their
1826 * queued work completes. We therefore wait for the owning device before releasing the host arena slot,
1827 * otherwise an auto-destroyed intermediate can be recycled into another module output while the GPU
1828 * is still reading the previous pixels.
1829 *
1830 * dt_opencl_finish() flushes that device's event list, which is only thread-safe while we hold
1831 * darktable.opencl->dev[devid].lock. Touching it without that lock races the eventlist/cl_mem
1832 * bookkeeping of whichever pixelpipe is currently running on that device and crashes inside
1833 * clWaitForEvents (issues #859, #864, #131742439 -- the 3-min GUI garbage collection timeout
1834 * dt_dev_pixelpipe_cache_flush_old() owns no device lock). _free_cache_entry() also runs from LRU
1835 * eviction under a live pipe that already holds this very lock, so we can neither assume we hold it
1836 * nor block on it: trylock. If we acquire the device it is idle and we drain it safely; if we don't,
1837 * the owner is draining it itself at the end of its run and this refcount==0 entry is not one of its
1838 * live borrows, so skipping the finish is safe. */
1839 const int mem_devid = dt_opencl_get_mem_context_id((cl_mem)c->mem);
1840 if(mem_devid >= 0 && !IS_NULL_PTR(darktable.opencl) && darktable.opencl->inited
1842 {
1843 dt_opencl_finish(mem_devid);
1845 }
1846 }
1847 dt_pthread_mutex_unlock(&cache_entry->cl_mem_lock);
1848#endif
1849
1851 dt_cache_arena_free(&cache->arena, cache_entry->data, cache_entry->size);
1852 }
1853 else
1854 {
1856 }
1857
1858 cache_entry->data = NULL;
1859 if(cache) cache->current_memory -= cache_entry->size;
1860 dt_pthread_rwlock_destroy(&cache_entry->lock);
1862 dt_free(cache_entry->name);
1863 dt_free(cache_entry);
1864}
1865
1866static int garbage_collection = 0;
1867
1869{
1871 dt_pthread_mutex_init(&cache->lock, NULL);
1872 cache->entries = g_hash_table_new_full(g_int64_hash, g_int64_equal, dt_free_gpointer, (GDestroyNotify)_free_cache_entry);
1873 cache->external_entries = g_hash_table_new_full(g_int64_hash, g_int64_equal, dt_free_gpointer, (GDestroyNotify)_free_cache_entry);
1874 cache->max_memory = max_memory;
1875 cache->current_memory = 0;
1876 cache->next_serial = 1;
1877 cache->queries = cache->hits = 0;
1878
1879 if(IS_NULL_PTR(cache->entries) || IS_NULL_PTR(cache->external_entries))
1880 {
1881 if(cache->entries) g_hash_table_destroy(cache->entries);
1882 if(cache->external_entries) g_hash_table_destroy(cache->external_entries);
1884 dt_free(cache);
1885 return NULL;
1886 }
1887
1888 if(dt_cache_arena_init(&cache->arena, cache->max_memory))
1889 {
1891 g_hash_table_destroy(cache->external_entries);
1892 g_hash_table_destroy(cache->entries);
1893 dt_free(cache);
1894 return NULL;
1895 }
1896
1897 // Run every 3 minutes
1898 garbage_collection = g_timeout_add(3 * 60 * 1000, (GSourceFunc)dt_dev_pixelpipe_cache_flush_old, cache);
1899 return cache;
1900}
1901
1902
1904{
1905 g_hash_table_destroy(cache->external_entries);
1906 g_hash_table_destroy(cache->entries);
1907 cache->external_entries = NULL;
1908 cache->entries = NULL;
1911
1912 if(garbage_collection != 0)
1913 {
1914 g_source_remove(garbage_collection);
1916 }
1917}
1918
1920 const uint64_t hash, const size_t size,
1921 const char *name, const int id)
1922{
1923 dt_pixel_cache_entry_t *cache_entry = dt_pixel_cache_new_entry(hash, size, name, id, cache, FALSE, cache->entries);
1924 if(IS_NULL_PTR(cache_entry)) return NULL;
1925
1926 // Increase ref_count, consumer will have to decrease it
1927 _non_thread_safe_cache_ref_count_entry(cache, TRUE, cache_entry);
1928
1929 // Acquire write lock so caller can populate data safely
1930 dt_dev_pixelpipe_cache_wrlock_entry(cache, TRUE, cache_entry);
1931
1932 return cache_entry;
1933}
1934
1936 const uint64_t new_hash, const size_t size,
1937 const dt_pixel_cache_entry_t *reuse_hint)
1938{
1939 if(IS_NULL_PTR(cache) || IS_NULL_PTR(reuse_hint)) return NULL;
1940
1941 const uint64_t old_hash = reuse_hint->hash;
1942 if(old_hash == DT_PIXELPIPE_CACHE_HASH_INVALID || old_hash == new_hash) return NULL;
1943 if(reuse_hint->size < size) return NULL;
1944
1945 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, old_hash);
1946 if(IS_NULL_PTR(cache_entry)) return NULL;
1947 if(cache_entry->serial != reuse_hint->serial) return NULL;
1948 if(cache_entry->auto_destroy) return NULL;
1949 if(cache_entry->size < size) return NULL;
1950 if(_non_threadsafe_cache_get_entry(cache, cache->entries, new_hash)) return NULL;
1951
1952 _non_thread_safe_cache_ref_count_entry(cache, TRUE, cache_entry);
1953 dt_dev_pixelpipe_cache_wrlock_entry(cache, TRUE, cache_entry);
1954
1955 /* Rekey reuse transfers the RAM arena slot to a completely different hash. Any cached OpenCL payload
1956 * still attached to the previous owner would otherwise remain reachable through the new hash and could
1957 * later be materialized as if it belonged to the new module output. Bail out if some GPU path is still
1958 * borrowing one of those payloads, otherwise flush the stale bookkeeping before publishing the new hash. */
1959 dt_pthread_mutex_lock(&cache_entry->cl_mem_lock);
1960 for(GList *l = cache_entry->cl_mem_list; l; l = g_list_next(l))
1961 {
1962 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
1963 if(c && c->refs > 0)
1964 {
1965 dt_pthread_mutex_unlock(&cache_entry->cl_mem_lock);
1966 dt_dev_pixelpipe_cache_wrlock_entry(cache, FALSE, cache_entry);
1967 dt_dev_pixelpipe_cache_ref_count_entry(cache, FALSE, cache_entry);
1968 return NULL;
1969 }
1970 }
1971 dt_pthread_mutex_unlock(&cache_entry->cl_mem_lock);
1972
1973 gpointer stolen_key = NULL;
1974 gpointer stolen_value = NULL;
1975 if(!g_hash_table_steal_extended(cache->entries, &old_hash, &stolen_key, &stolen_value)
1976 || stolen_value != cache_entry)
1977 {
1978 if(stolen_key && stolen_value) g_hash_table_insert(cache->entries, stolen_key, stolen_value);
1979 dt_dev_pixelpipe_cache_wrlock_entry(cache, FALSE, cache_entry);
1980 dt_dev_pixelpipe_cache_ref_count_entry(cache, FALSE, cache_entry);
1981 return NULL;
1982 }
1983
1984 *(uint64_t *)stolen_key = new_hash;
1985 cache_entry->hash = new_hash;
1986 g_hash_table_insert(cache->entries, stolen_key, cache_entry);
1987
1988 if(dt_supervisor_active()) dt_supervisor_rekey(old_hash, new_hash);
1989
1991 "[pixelpipe_cache] writable rekey old=%" PRIu64 " new=%" PRIu64 " entry=%" PRIu64 "/%" PRIu64
1992 " refs=%i auto=%i data=%p module=%s\n",
1993 old_hash, new_hash, cache_entry->hash, cache_entry->serial,
1994 dt_atomic_get_int(&cache_entry->refcount), cache_entry->auto_destroy, cache_entry->data,
1996 return cache_entry;
1997}
1998
1999
2001 const size_t size, const char *name, const int id,
2002 const gboolean alloc, void **data,
2003 dt_pixel_cache_entry_t **entry)
2004{
2006 {
2007 dt_print(DT_DEBUG_PIPECACHE, "[pixelpipe_cache] refusing invalid hash allocation for %s\n",
2008 name ? name : "unknown");
2009 if(data) *data = NULL;
2010 if(entry) *entry = NULL;
2011 return 1;
2012 }
2013
2014 // Search or create cache entry (under cache lock)
2015 dt_pthread_mutex_lock(&cache->lock);
2016 cache->queries++;
2017
2018 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, hash);
2019 if(!IS_NULL_PTR(cache_entry) && cache_entry->auto_destroy)
2020 {
2021 _pixel_cache_message(cache_entry, "dropping auto-destroy entry before cache_get reuse", FALSE);
2022 if(_non_thread_safe_cache_remove(cache, FALSE, cache_entry, cache->entries) == 0)
2023 cache_entry = NULL;
2024 }
2025
2026 if(!IS_NULL_PTR(cache_entry))
2027 {
2028 cache->hits++;
2029 cache_entry->hits++;
2030 _non_thread_safe_cache_ref_count_entry(cache, TRUE, cache_entry);
2032
2033 // Allocate on demand if requested (e.g. when falling back from vRAM-only buffers).
2034 if(alloc && IS_NULL_PTR(cache_entry->data))
2035 {
2036 dt_dev_pixelpipe_cache_wrlock_entry(cache, TRUE, cache_entry);
2037 dt_pixel_cache_alloc(cache, cache_entry);
2038 dt_dev_pixelpipe_cache_wrlock_entry(cache, FALSE, cache_entry);
2039 }
2040
2041 _pixelpipe_cache_finalize_entry(cache_entry, data, "found");
2042 if(entry) *entry = cache_entry;
2043 // existing output reused: a cache hit (entry pinned by the ref above, safe to read)
2045 dt_supervisor_cacheline_read(hash, cache_entry->size);
2046
2047 return 0;
2048 }
2049
2050 cache_entry = _pixelpipe_cache_create_entry_locked(cache, hash, size, name, id);
2051 if(IS_NULL_PTR(cache_entry))
2052 {
2053 dt_print(DT_DEBUG_PIPECACHE, "couldn't allocate new cache entry %" PRIu64 "\n", hash);
2055 if(entry) *entry = NULL;
2056 return 1;
2057 }
2058
2059 // Release cache lock AFTER acquiring entry locks to prevent other threads to capture it in-between
2061
2062 // Alloc after releasing the lock for better runtimes
2063 if(alloc) dt_pixel_cache_alloc(cache, cache_entry);
2064
2065 dt_print(DT_DEBUG_PIPECACHE, "[pixelpipe_cache] Write-lock on entry (new cache entry %" PRIu64 " for %s pipeline)\n",
2066 hash, name);
2067 _pixelpipe_cache_finalize_entry(cache_entry, data, "created");
2068
2069 if(entry) *entry = cache_entry;
2070 return 1;
2071}
2072
2075 const size_t size, const char *name, const int id,
2076 const gboolean alloc, const gboolean allow_rekey_reuse,
2077 const dt_pixel_cache_entry_t *reuse_hint,
2078 void **data,
2079 dt_pixel_cache_entry_t **entry)
2080{
2082 {
2083 if(data) *data = NULL;
2084 if(entry) *entry = NULL;
2086 }
2087
2088 dt_pthread_mutex_lock(&cache->lock);
2089 cache->queries++;
2090
2091 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, hash);
2092 if(!IS_NULL_PTR(cache_entry) && cache_entry->auto_destroy)
2093 {
2094 _pixel_cache_message(cache_entry, "dropping auto-destroy entry before writable reuse", FALSE);
2095 if(_non_thread_safe_cache_remove(cache, FALSE, cache_entry, cache->entries) == 0)
2096 cache_entry = NULL;
2097 }
2098
2099 if(!IS_NULL_PTR(cache_entry))
2100 {
2102 if(data) *data = NULL;
2103 if(entry) *entry = NULL;
2105 }
2106
2107 if(allow_rekey_reuse)
2108 {
2109 cache_entry = _cache_try_rekey_reuse_locked(cache, hash, size, reuse_hint);
2110 if(!IS_NULL_PTR(cache_entry))
2111 {
2113 if(alloc && IS_NULL_PTR(cache_entry->data)) dt_pixel_cache_alloc(cache, cache_entry);
2114 _pixelpipe_cache_finalize_entry(cache_entry, data, "writable-rekeyed");
2115 if(entry) *entry = cache_entry;
2117 }
2118 }
2119
2120 cache_entry = _pixelpipe_cache_create_entry_locked(cache, hash, size, name, id);
2121 if(IS_NULL_PTR(cache_entry))
2122 {
2124 if(data) *data = NULL;
2125 if(entry) *entry = NULL;
2127 }
2128
2130
2131 if(alloc) dt_pixel_cache_alloc(cache, cache_entry);
2132 _pixelpipe_cache_finalize_entry(cache_entry, data, "writable-created");
2133 if(entry) *entry = cache_entry;
2135}
2136
2138 void **data)
2139{
2140 dt_pthread_mutex_lock(&cache->lock);
2141 cache->queries++;
2142 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, hash);
2143
2144 const gboolean hit = !IS_NULL_PTR(cache_entry);
2145 const size_t hit_size = hit ? cache_entry->size : 0;
2146 if(hit)
2147 {
2148 cache->hits++;
2149 cache_entry->hits++;
2150 _pixelpipe_cache_finalize_entry(cache_entry, data, "found");
2151 }
2152
2154
2155 if(hit && dt_supervisor_active())
2156 dt_supervisor_cacheline_read(hash, hit_size);
2157
2158 return cache_entry;
2159}
2160
2161#ifdef HAVE_OPENCL
2163 const int preferred_devid, void **cl_mem_output)
2164{
2165 if(IS_NULL_PTR(cache_entry) || IS_NULL_PTR(cl_mem_output) || !IS_NULL_PTR(*cl_mem_output) || preferred_devid < 0)
2166 return FALSE;
2167
2168 dt_pthread_mutex_lock(&cache_entry->cl_mem_lock);
2169 for(GList *l = cache_entry->cl_mem_list; l;)
2170 {
2171 GList *next = g_list_next(l);
2172 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
2173 if(!IS_NULL_PTR(c->mem) && c->refs == 0
2174 && dt_opencl_get_mem_context_id((cl_mem)c->mem) == preferred_devid)
2175 {
2176 cache_entry->cl_mem_list = g_list_delete_link(cache_entry->cl_mem_list, l);
2177 *cl_mem_output = c->mem;
2178 dt_free(c);
2179 break;
2180 }
2181 l = next;
2182 }
2183 dt_pthread_mutex_unlock(&cache_entry->cl_mem_lock);
2184
2185 return !IS_NULL_PTR(*cl_mem_output);
2186}
2187#else
2189 const int preferred_devid, void **cl_mem_output)
2190{
2191 return FALSE;
2192}
2193#endif
2194
2196 dt_pixel_cache_entry_t *cache_entry,
2197 const int preferred_devid, void **data)
2198{
2199 if(data) *data = NULL;
2200 if(IS_NULL_PTR(cache) || IS_NULL_PTR(cache_entry)) return FALSE;
2201
2202 if(dt_pixel_cache_entry_get_data(cache_entry) != NULL)
2203 {
2204 if(!IS_NULL_PTR(data)) *data = dt_pixel_cache_entry_get_data(cache_entry);
2205 return TRUE;
2206 }
2207
2208 if(!_cache_entry_materialize_host_data(cache, preferred_devid, cache_entry))
2209 return FALSE;
2210
2211 if(!IS_NULL_PTR(data)) *data = dt_pixel_cache_entry_get_data(cache_entry);
2212 return dt_pixel_cache_entry_get_data(cache_entry) != NULL;
2213}
2214
2215gboolean dt_dev_pixelpipe_cache_peek(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash, void **data,
2216 dt_pixel_cache_entry_t **entry, const int preferred_devid,
2217 void **cl_mem_output)
2218{
2219 if(data) *data = NULL;
2220 if(entry) *entry = NULL;
2221 if(cl_mem_output) *cl_mem_output = NULL;
2222
2224 return FALSE;
2225
2226 dt_pixel_cache_entry_t *cache_entry = _cache_lookup_existing(cache, hash, data);
2227 if(IS_NULL_PTR(cache_entry)) return FALSE;
2228
2229 if(data) *data = dt_pixel_cache_entry_get_data(cache_entry);
2230
2231 /* Exact-hit callers treat the returned payload as already published. Reject
2232 * cachelines that are still write-locked: reusable output cachelines are
2233 * rekeyed to their new hash before recompute starts, so exposing them here
2234 * would let concurrent pipes consume stale or half-written buffers. */
2235 if(dt_pthread_rwlock_tryrdlock(&cache_entry->lock) != 0)
2236 {
2237 _trace_exact_hit("locked", hash, cache_entry, data ? *data : NULL,
2238 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2239 if(data) *data = NULL;
2240 return FALSE;
2241 }
2242 dt_pthread_rwlock_unlock(&cache_entry->lock);
2243
2244 if(IS_NULL_PTR(data) && IS_NULL_PTR(cl_mem_output))
2245 {
2246 if(entry) *entry = cache_entry;
2247 return TRUE;
2248 }
2249
2250 /* Picker-triggered aborts can leave a cacheline temporarily present under its
2251 * hash while it is already marked auto-destroy. Those entries must never exact-hit:
2252 * they belong to the aborted lifecycle and must force a rebuild on the next run. */
2253 if(cache_entry->auto_destroy)
2254 {
2255 _trace_exact_hit("auto-destroy", hash, cache_entry, data ? *data : NULL,
2256 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2257 if(data) *data = NULL;
2258 return FALSE;
2259 }
2260
2261 if(dt_pixel_cache_entry_get_data(cache_entry) != NULL)
2262 {
2263 if(data) *data = dt_pixel_cache_entry_get_data(cache_entry);
2264 _cache_try_restore_device_payload(cache_entry, preferred_devid, cl_mem_output);
2265
2266 _trace_exact_hit("host", hash, cache_entry, data ? *data : NULL,
2267 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2268 if(entry) *entry = cache_entry;
2269 return TRUE;
2270 }
2271
2272 /* `preferred_devid < 0` means the caller is on a CPU path and does not own any
2273 * OpenCL device. In that case, hostless cachelines are not consumable here:
2274 * reopening device-only payloads would enqueue hidden GPU work without a locked
2275 * device, while reporting a device-only exact-hit would let CPU callers sample
2276 * an uninitialized host buffer. */
2277 if(preferred_devid < 0)
2278 {
2279 _trace_exact_hit("cpu-no-device", hash, cache_entry, NULL, NULL, preferred_devid, FALSE);
2280 return FALSE;
2281 }
2282
2283 if(_cache_try_restore_device_payload(cache_entry, preferred_devid, cl_mem_output))
2284 {
2285 _trace_exact_hit("device", hash, cache_entry, data ? *data : NULL,
2286 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2287 if(entry) *entry = cache_entry;
2288 return TRUE;
2289 }
2290
2291 if(!IS_NULL_PTR(data) && dt_dev_pixelpipe_cache_restore_host_payload(cache, cache_entry, preferred_devid, data))
2292 {
2293 _trace_exact_hit("restore-host", hash, cache_entry, data ? *data : NULL,
2294 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2295 if(entry) *entry = cache_entry;
2296 return TRUE;
2297 }
2298
2299 _trace_exact_hit("drop-invalid", hash, cache_entry, data ? *data : NULL,
2300 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2302 "[pixelpipe] cache entry %" PRIu64 " has no authoritative RAM nor vRAM payload and will be removed\n",
2303 hash);
2304 // If the entry removal fails, flag it for auto-destroy.
2305 if(dt_dev_pixelpipe_cache_remove(cache, TRUE, cache_entry))
2306 dt_dev_pixelpipe_cache_flag_auto_destroy(cache, cache_entry);
2307 if(data) *data = NULL;
2308 return FALSE;
2309}
2310
2311
2312static gboolean _for_each_remove(gpointer key, gpointer value, gpointer user_data)
2313{
2315 const int id = GPOINTER_TO_INT(user_data);
2316
2317 // Returns 1 if the lock is captured by another thread
2318 // 0 if WE capture the lock, and then need to release it
2319 gboolean locked = dt_pthread_rwlock_trywrlock(&cache_entry->lock);
2320 if(!locked) dt_pthread_rwlock_unlock(&cache_entry->lock);
2321
2322 return (cache_entry->id == id || id == -1) && !locked;
2323}
2324
2325
2327{
2328 dt_pthread_mutex_lock(&cache->lock);
2329 g_hash_table_foreach_remove(cache->entries, _for_each_remove, GINT_TO_POINTER(id));
2331}
2332
2334 const uint64_t *hashes,
2335 const size_t count)
2336{
2337 int retained = 0;
2338 dt_pthread_mutex_lock(&cache->lock);
2339
2340 // We are invalidating the cumulative outputs from one pipeline stage onward.
2341 // Look them up under the same cache lock used for removal so a shared preview
2342 // pipe cannot replace an entry between lookup and invalidation.
2343 for(size_t k = 0; k < count; k++)
2344 {
2345 if(hashes[k] == DT_PIXELPIPE_CACHE_HASH_INVALID) continue;
2346
2348 = _non_threadsafe_cache_get_entry(cache, cache->entries, hashes[k]);
2349 if(IS_NULL_PTR(entry)) continue;
2350
2351 // A displayed backbuffer or an in-flight consumer may still own this
2352 // shared state. Leave it valid; cache bypass on the retry still walks
2353 // through downstream stages after the provider has been regenerated.
2354 if(_non_thread_safe_cache_remove(cache, FALSE, entry, cache->entries))
2355 retained++;
2356 }
2357
2359 return retained;
2360}
2361
2362
2363static gboolean _for_each_remove_old(gpointer key, gpointer value, gpointer user_data)
2364{
2366
2367 // Returns 1 if the lock is captured by another thread
2368 // 0 if WE capture the lock, and then need to release it
2369 gboolean locked = dt_pthread_rwlock_trywrlock(&cache_entry->lock);
2370 if(!locked) dt_pthread_rwlock_unlock(&cache_entry->lock);
2371 gboolean used = dt_atomic_get_int(&cache_entry->refcount) > 0;
2372
2373 // in microseconds
2374 int64_t delta = g_get_monotonic_time() - cache_entry->age;
2375
2376 // 5 min in microseconds
2377 const int64_t three_min = 5 * 60 * 1000 * 1000;
2378
2379 gboolean too_old = (delta > three_min) && (cache_entry->hits < 4);
2380
2381 return too_old && !used && !locked;
2382}
2383
2385{
2386 // Don't hang the GUI thread if the cache is locked by a pipeline.
2387 // Better luck next time.
2388 if(dt_pthread_mutex_trylock(&cache->lock)) return G_SOURCE_CONTINUE;
2389 g_hash_table_foreach_remove(cache->entries, _for_each_remove_old, NULL);
2391 return G_SOURCE_CONTINUE;
2392}
2393
2399
2400
2402 dt_pixel_cache_entry_t *cache_entry)
2403{
2404 if(IS_NULL_PTR(cache_entry)) return;
2405
2406 if(lock)
2407 {
2408 dt_atomic_add_int(&cache_entry->refcount, 1);
2409 _pixel_cache_message(cache_entry, "ref count ++", TRUE);
2410 }
2411 else
2412 {
2413 dt_atomic_sub_int(&cache_entry->refcount, 1);
2414 _pixel_cache_message(cache_entry, "ref count --", TRUE);
2415 }
2416}
2417
2418
2426
2427
2429 dt_pixel_cache_entry_t *cache_entry)
2430{
2431 if(lock)
2432 {
2433 dt_pthread_rwlock_wrlock(&cache_entry->lock);
2434 _pixel_cache_message(cache_entry, "write lock", TRUE);
2435 }
2436 else
2437 {
2438 dt_pthread_rwlock_unlock(&cache_entry->lock);
2439 _pixel_cache_message(cache_entry, "write unlock", TRUE);
2440 // The producer node key travels alongside the hash so GUI waiters can match by
2441 // the node that produced this output even when the exact output hash the GUI
2442 // predicted has drifted from the one the worker actually published (the
2443 // never-served case, doc/pipeline-cache.md §8). INVALID for non-module outputs
2444 // (raster masks, republished inputs): waiters simply fall back to hash match.
2445 if(cache_entry && cache_entry->hash != DT_PIXELPIPE_CACHE_HASH_INVALID)
2447 cache_entry->producer_node_key);
2448 }
2449}
2450
2451
2453 dt_pixel_cache_entry_t *cache_entry)
2454{
2455 if(lock)
2456 {
2457 dt_pthread_rwlock_rdlock(&cache_entry->lock);
2458 _pixel_cache_message(cache_entry, "read lock", TRUE);
2459 }
2460 else
2461 {
2462 dt_pthread_rwlock_unlock(&cache_entry->lock);
2463 _pixel_cache_message(cache_entry, "read unlock", TRUE);
2464 }
2465}
2466
2467
2469 dt_pixel_cache_entry_t *cache_entry)
2470{
2471 dt_pthread_mutex_lock(&cache->lock);
2472 if(IS_NULL_PTR(cache_entry))
2473 {
2475 return;
2476 }
2477
2478 cache_entry->auto_destroy = TRUE;
2479 _pixel_cache_message(cache_entry, "auto destroy flagged", TRUE);
2481}
2482
2483
2485 dt_pixel_cache_entry_t *cache_entry)
2486{
2487 dt_pthread_mutex_lock(&cache->lock);
2488 if(IS_NULL_PTR(cache_entry))
2489 {
2491 return;
2492 }
2493
2494 if(cache_entry->auto_destroy)
2495 {
2496 /* `auto_destroy` is still a normal cache lifecycle: the creator flags a transient entry, then the final
2497 * consumer decrements its refcount and asks the cache to reap it. Only remove it once no consumer owns
2498 * it anymore and nobody still holds the entry lock, otherwise teardown paths can free cachelines that
2499 * still report `refs>0` and hide ownership bugs instead of exposing them. */
2500 const gboolean locked = dt_pthread_rwlock_trywrlock(&cache_entry->lock);
2501 if(!locked) dt_pthread_rwlock_unlock(&cache_entry->lock);
2502 const gboolean used = dt_atomic_get_int(&cache_entry->refcount) > 0;
2503
2504 if(!used && !locked)
2505 {
2506 _pixel_cache_message(cache_entry, "auto destroy removing", FALSE);
2507 g_hash_table_remove(cache->entries, &cache_entry->hash);
2508 }
2509 else if(used)
2510 {
2511 _pixel_cache_message(cache_entry, "auto destroy postponed: used", TRUE);
2512 }
2513 else
2514 {
2515 _pixel_cache_message(cache_entry, "auto destroy postponed: locked", TRUE);
2516 }
2517 }
2518 else
2519 {
2520 _pixel_cache_message(cache_entry, "auto destroy skipped", TRUE);
2521 }
2522
2524}
2525
2527{
2528 if(hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return;
2529
2530 dt_pthread_mutex_lock(&cache->lock);
2531 cache->queries++;
2532 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, hash);
2534
2535 if(cache_entry)
2536 dt_dev_pixelpipe_cache_ref_count_entry(cache, FALSE, cache_entry);
2537}
2538
2540 const uint64_t new_hash, dt_pixel_cache_entry_t *entry)
2541{
2542 if(IS_NULL_PTR(cache)) return 1;
2543 if(old_hash == new_hash) return 0;
2544
2545 dt_pthread_mutex_lock(&cache->lock);
2546
2547 if(IS_NULL_PTR(entry)) entry = _non_threadsafe_cache_get_entry(cache, cache->entries, old_hash);
2548 if(IS_NULL_PTR(entry))
2549 {
2551 "[pixelpipe_cache] rekey miss old=%" PRIu64 " new=%" PRIu64 " module=%s\n",
2552 old_hash, new_hash, _cache_debug_module_name());
2554 return 1;
2555 }
2556
2557 dt_pixel_cache_entry_t *conflict = _non_threadsafe_cache_get_entry(cache, cache->entries, new_hash);
2558 if(conflict && conflict != entry)
2559 {
2561 "[pixelpipe_cache] rekey conflict old=%" PRIu64 " new=%" PRIu64
2562 " entry=%" PRIu64 "/%" PRIu64 " conflict=%" PRIu64 "/%" PRIu64 " module=%s\n",
2563 old_hash, new_hash, entry->hash, entry->serial, conflict->hash, conflict->serial,
2566 return 1;
2567 }
2568
2569 gpointer stolen_key = NULL;
2570 gpointer stolen_value = NULL;
2571 if(!g_hash_table_steal_extended(cache->entries, &old_hash, &stolen_key, &stolen_value))
2572 {
2574 "[pixelpipe_cache] rekey steal-miss old=%" PRIu64 " new=%" PRIu64
2575 " entry=%" PRIu64 "/%" PRIu64 " module=%s\n",
2576 old_hash, new_hash, entry->hash, entry->serial, _cache_debug_module_name());
2578 return 1;
2579 }
2580
2581 if(stolen_value != entry)
2582 {
2584 "[pixelpipe_cache] rekey stolen-entry mismatch old=%" PRIu64 " new=%" PRIu64
2585 " expected=%" PRIu64 "/%" PRIu64 " got=%" PRIu64 "/%" PRIu64 " module=%s\n",
2586 old_hash, new_hash, entry->hash, entry->serial,
2587 ((dt_pixel_cache_entry_t *)stolen_value)->hash, ((dt_pixel_cache_entry_t *)stolen_value)->serial,
2589 g_hash_table_insert(cache->entries, stolen_key, stolen_value);
2591 return 1;
2592 }
2593
2594 /* Explicit rekeying also changes cacheline ownership. The OpenCL payload cache is only valid for the
2595 * previous hash, so do not let the new hash inherit stale device-side state. If some GPU code is still
2596 * borrowing one of these payloads, refuse the rekey instead of publishing an ambiguous cache entry. */
2598 for(GList *l = entry->cl_mem_list; l; l = g_list_next(l))
2599 {
2600 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
2601 if(c && c->refs > 0)
2602 {
2604 g_hash_table_insert(cache->entries, stolen_key, stolen_value);
2606 return 1;
2607 }
2608 }
2611
2612 *(uint64_t *)stolen_key = new_hash;
2613 entry->hash = new_hash;
2614 g_hash_table_insert(cache->entries, stolen_key, stolen_value);
2616 "[pixelpipe_cache] rekey old=%" PRIu64 " new=%" PRIu64 " entry=%" PRIu64 "/%" PRIu64
2617 " refs=%i auto=%i data=%p module=%s\n",
2618 old_hash, new_hash, entry->hash, entry->serial, dt_atomic_get_int(&entry->refcount),
2619 entry->auto_destroy, entry->data, _cache_debug_module_name());
2620
2622
2623 if(dt_supervisor_active()) dt_supervisor_rekey(old_hash, new_hash);
2624 return 0;
2625}
2626
2627
2629{
2630 if(!(darktable.unmuted & DT_DEBUG_PIPECACHE)) return;
2631
2632 dt_print(DT_DEBUG_PIPECACHE, "[pixelpipe] cache hit rate so far: %.3f%% - size: %" G_GSIZE_FORMAT " MiB over %" G_GSIZE_FORMAT " MiB - %i items\n",
2633 100. * (cache->hits) / (float)cache->queries, cache->current_memory / (1024 * 1024),
2634 cache->max_memory / (1024 * 1024),
2635 g_hash_table_size(cache->entries));
2636}
2637
2639{
2640 if(current) *current = 0;
2641 if(max) *max = 0;
2642 if(IS_NULL_PTR(cache)) return;
2643 dt_pthread_mutex_lock(&cache->lock);
2644 if(current) *current = cache->current_memory;
2645 if(max) *max = cache->max_memory;
2647}
2648
2650{
2651 GArray *out = g_array_new(FALSE, FALSE, sizeof(dt_pixel_cache_stats_entry_t));
2652 if(IS_NULL_PTR(cache)) return out;
2653
2654 dt_pthread_mutex_lock(&cache->lock);
2655 GHashTableIter it;
2656 gpointer key, value;
2657 g_hash_table_iter_init(&it, cache->entries);
2658 while(g_hash_table_iter_next(&it, &key, &value))
2659 {
2661 if(IS_NULL_PTR(e)) continue;
2663 s.hash = e->hash;
2664 s.size = e->size;
2666 s.hits = e->hits;
2667 if(e->name) g_strlcpy(s.name, e->name, sizeof(s.name));
2668
2669#ifdef HAVE_OPENCL
2670 // Account the OpenCL device buffers attached to this entry. trylock avoids
2671 // both deadlock and racing the list against concurrent clmem mutation.
2673 {
2674 for(GList *l = e->cl_mem_list; l; l = g_list_next(l))
2675 {
2676 const dt_cache_clmem_t *c = (const dt_cache_clmem_t *)l->data;
2677 if(IS_NULL_PTR(c) || IS_NULL_PTR(c->mem)) continue;
2678 s.cl_count++;
2679 s.cl_bytes += dt_opencl_get_mem_object_size((cl_mem)c->mem);
2680 }
2682 }
2683#endif
2684
2685 g_array_append_val(out, s);
2686 }
2688 return out;
2689}
2690
2692{
2693#ifdef HAVE_OPENCL
2695 size_t total = 0;
2696 for(int i = 0; i < darktable.opencl->num_devs; i++)
2697 total += (size_t)darktable.opencl->dev[i].max_global_mem;
2698 return total;
2699#else
2700 return 0;
2701#endif
2702}
2703
2704// clang-format off
2705// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2706// vim: shiftwidth=2 expandtab tabstop=2 cindent
2707// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2708// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int dt_atomic_get_int(dt_atomic_int *var)
int dt_atomic_sub_int(dt_atomic_int *var, int decr)
int dt_atomic_add_int(dt_atomic_int *var, int incr)
atomic_int dt_atomic_int
Definition atomic.h:66
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
const float delta
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
char * key
char * name
void dt_control_log(const char *msg,...)
Definition control.c:777
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
@ DT_DEBUG_OPENCL
Definition darktable.h:744
@ DT_DEBUG_PIPECACHE
Definition darktable.h:742
@ DT_DEBUG_VERBOSE
Definition darktable.h:765
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:485
#define dt_free(ptr)
Definition darktable.h:478
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
#define DT_CACHELINE_BYTES
Definition darktable.h:392
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:452
static int dt_pthread_rwlock_tryrdlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:573
static int dt_pthread_mutex_BAD_trylock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:607
static int dt_pthread_mutex_BAD_unlock(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:612
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:384
static int dt_pthread_rwlock_trywrlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:582
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:369
static int dt_pthread_rwlock_destroy(dt_pthread_rwlock_t *lock)
Definition dtpthread.h:447
static int dt_pthread_mutex_trylock(dt_pthread_mutex_t *mutex) TRY_ACQUIRE(0
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:389
static int dt_pthread_rwlock_init(dt_pthread_rwlock_t *lock, const pthread_rwlockattr_t *attr)
Definition dtpthread.h:427
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:502
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:534
int bpp
float *const restrict const size_t k
void dt_cache_arena_stats(dt_cache_arena_t *a, uint32_t *out_total_free_pages, uint32_t *out_largest_free_run_pages)
void dt_cache_arena_cleanup(dt_cache_arena_t *a)
gboolean dt_cache_arena_calc(const dt_cache_arena_t *a, size_t size, uint32_t *out_pages, size_t *out_size)
int dt_cache_arena_init(dt_cache_arena_t *a, size_t total_size)
void dt_cache_arena_free(dt_cache_arena_t *a, void *ptr, size_t size)
void * dt_cache_arena_alloc(dt_cache_arena_t *a, size_t size, size_t *out_size)
size_t size
Definition mipmap_cache.c:3
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
void * dt_opencl_alloc_device_use_host_pointer(const int devid, const int width, const int height, const int bpp, void *host, const int flags)
Definition opencl.c:2527
size_t dt_opencl_get_mem_object_size(cl_mem mem)
Definition opencl.c:2586
gboolean dt_opencl_is_pinned_memory(cl_mem mem)
Definition opencl.c:190
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2504
int dt_opencl_get_mem_context_id(cl_mem mem)
Definition opencl.c:2596
int dt_opencl_get_image_height(cl_mem mem)
Definition opencl.c:2634
int dt_opencl_unmap_mem_object(const int devid, cl_mem mem_object, void *mapped_ptr)
Definition opencl.c:2462
int dt_opencl_is_enabled(void)
Definition opencl.c:2835
gboolean dt_opencl_use_pinned_memory(const int devid)
Definition opencl.c:183
int dt_opencl_get_image_width(cl_mem mem)
Definition opencl.c:2623
void * dt_opencl_map_image(const int devid, cl_mem buffer, const int blocking, const int flags, size_t width, size_t height, int bpp)
Definition opencl.c:2442
gboolean dt_opencl_finish(const int devid)
Definition opencl.c:1375
void dt_opencl_events_wait_for(const int devid)
Definition opencl.c:3027
int dt_opencl_read_host_from_device(const int devid, void *host, void *device, const int width, const int height, const int bpp)
Definition opencl.c:2197
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2415
int dt_opencl_get_image_element_size(cl_mem mem)
Definition opencl.c:2645
int dt_opencl_write_host_to_device(const int devid, void *host, void *device, const int width, const int height, const int bpp)
Definition opencl.c:2244
#define DT_OPENCL_BPP_ENCODE_RGBA8(bpp)
Definition opencl.h:85
static __thread const char * dt_pixelpipe_cache_current_module
static const char * _cache_debug_module_name(void)
static void _trace_exact_hit(const char *phase, const uint64_t hash, dt_pixel_cache_entry_t *cache_entry, void *data, void *cl_mem_output, const int preferred_devid, const gboolean verbose)
void * dt_dev_pixelpipe_cache_borrow_cl_payload(dt_pixel_cache_entry_t *entry, int devid, int width, int height, int bpp)
Borrow a cached OpenCL payload attached to a cache entry.
static int _free_space_to_alloc(dt_dev_pixelpipe_cache_t *cache, const size_t size, const uint64_t hash, const char *name)
static gboolean _cache_entry_clmem_flush_device(dt_pixel_cache_entry_t *entry, const int devid)
size_t dt_dev_pixelpipe_cache_get_vram_total(void)
static void _arena_stats_bytes(dt_dev_pixelpipe_cache_t *cache, uint32_t *total_pages, uint32_t *largest_pages, size_t *total_bytes, size_t *largest_bytes)
void dt_pixelpipe_cache_free_align_cache(dt_dev_pixelpipe_cache_t *cache, void **mem, const char *message)
Free aligned memory allocated with dt_pixelpipe_cache_alloc_align_cache.
void dt_dev_pixelpipe_cache_cleanup(dt_dev_pixelpipe_cache_t *cache)
int dt_dev_pixelpipe_cache_invalidate_hashes(dt_dev_pixelpipe_cache_t *cache, const uint64_t *hashes, const size_t count)
Invalidate cache lines matching an explicit list of hashes.
GArray * dt_dev_pixelpipe_cache_get_entries_stats(dt_dev_pixelpipe_cache_t *cache)
static int garbage_collection
void * dt_pixel_cache_entry_get_data(dt_pixel_cache_entry_t *entry)
void * dt_dev_pixelpipe_cache_get_pinned_image(dt_dev_pixelpipe_cache_t *cache, void *host_ptr, dt_pixel_cache_entry_t *entry_hint, int devid, int width, int height, int bpp, int flags, gboolean *out_reused)
Acquire a pinned OpenCL image for a host buffer tracked by the pixelpipe cache.
static int dt_dev_pixelpipe_cache_flush_old(dt_dev_pixelpipe_cache_t *cache)
dt_dev_pixelpipe_cache_t * dt_dev_pixelpipe_cache_init(size_t max_memory)
static void _pixel_cache_clmem_remove(dt_pixel_cache_entry_t *entry, void *mem)
void dt_dev_pixelpipe_cache_ref_count_entry(dt_dev_pixelpipe_cache_t *cache, gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Increase/Decrease the reference count on the cache line as to prevent LRU item removal....
static dt_pixel_cache_entry_t * _cache_entry_for_host_ptr_locked(dt_dev_pixelpipe_cache_t *cache, void *host_ptr)
void dt_dev_pixelpipe_cache_print(dt_dev_pixelpipe_cache_t *cache)
void * dt_pixel_cache_alloc(dt_dev_pixelpipe_cache_t *cache, dt_pixel_cache_entry_t *cache_entry)
Actually allocate the memory buffer attached to the cache entry once you create it with dt_dev_pixelp...
void dt_dev_pixelpipe_cache_flush(dt_dev_pixelpipe_cache_t *cache, const int id)
Remove cache lines matching id. Entries locked in read/write or having reference count greater than 0...
static void _cache_get_oldest(gpointer key, gpointer value, gpointer user_data)
void dt_dev_pixelpipe_cache_unref_hash(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash)
Find the entry matching hash, and decrease its ref_count if found.
static dt_pixel_cache_entry_t * _pixelpipe_cache_create_entry_locked(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash, const size_t size, const char *name, const int id)
static void _print_cache_lines(gpointer key, gpointer value, gpointer user_data)
void dt_dev_pixelpipe_cache_flush_clmem_for_pipe(dt_dev_pixelpipe_cache_t *cache, const int devid)
Like dt_dev_pixelpipe_cache_flush_clmem(), for callers that do not hold darktable....
static int _non_thread_safe_pixel_pipe_cache_remove_lru(dt_dev_pixelpipe_cache_t *cache)
void dt_dev_pixelpipe_cache_flag_auto_destroy(dt_dev_pixelpipe_cache_t *cache, dt_pixel_cache_entry_t *cache_entry)
Flag the cache entry as "auto_destroy". This is useful for short-lived/disposable cache entries,...
void dt_dev_pixelpipe_cache_auto_destroy_apply(dt_dev_pixelpipe_cache_t *cache, dt_pixel_cache_entry_t *cache_entry)
Free the entry if it has the flag "auto_destroy". See dt_dev_pixelpipe_cache_flag_auto_destroy()....
static gboolean _cache_entry_clmem_has_host_pinned_locked(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid)
static gboolean _for_each_remove(gpointer key, gpointer value, gpointer user_data)
void dt_dev_pixelpipe_cache_put_pinned_image(dt_dev_pixelpipe_cache_t *cache, void *host_ptr, dt_pixel_cache_entry_t *entry_hint, void **mem)
Release or cache a pinned OpenCL image acquired with dt_dev_pixelpipe_cache_get_pinned_image().
void dt_dev_pixelpipe_cache_release_cl_buffer(void **cl_mem_buffer, dt_pixel_cache_entry_t *cache_entry, void *host_ptr, const gboolean cache_device)
Release or cache an OpenCL image associated with a host cache line.
gboolean dt_dev_pixelpipe_cache_peek(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry, const int preferred_devid, void **cl_mem_output)
Non-owning lookup of an existing cache line.
int dt_dev_pixel_pipe_cache_remove_lru(dt_dev_pixelpipe_cache_t *cache)
void dt_dev_pixelpipe_cache_flush_entry_clmem(dt_pixel_cache_entry_t *entry)
Flush all reusable OpenCL payloads cached on one cache entry.
static dt_pixel_cache_entry_t * _cache_try_rekey_reuse_locked(dt_dev_pixelpipe_cache_t *cache, const uint64_t new_hash, const size_t size, const dt_pixel_cache_entry_t *reuse_hint)
static dt_pixel_cache_entry_t * _cache_lookup_existing(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash, void **data)
void * dt_dev_pixelpipe_cache_get_cl_buffer(int devid, void *const host_ptr, const dt_iop_roi_t *roi, const size_t bpp, dt_iop_module_t *module, const char *message, dt_pixel_cache_entry_t *cache_entry, gboolean *out_reused, void *keep)
static int _pixel_cache_clmem_put(dt_pixel_cache_entry_t *entry, void *host_ptr, void *mem)
dt_pixel_cache_materialize_source_rank_t
@ DT_PIXEL_CACHE_MATERIALIZE_SOURCE_SECONDARY_PREFERRED
@ DT_PIXEL_CACHE_MATERIALIZE_SOURCE_PRIMARY_ANY
@ DT_PIXEL_CACHE_MATERIALIZE_SOURCE_SECONDARY_ANY
@ DT_PIXEL_CACHE_MATERIALIZE_SOURCE_NONE
@ DT_PIXEL_CACHE_MATERIALIZE_SOURCE_PRIMARY_PREFERRED
static size_t _pixel_cache_get_size(dt_pixel_cache_entry_t *cache_entry)
static gboolean _cache_entry_clmem_flush_host_pinned_locked(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid)
void dt_dev_pixelpipe_cache_get_usage(dt_dev_pixelpipe_cache_t *cache, size_t *current, size_t *max)
static void _free_cache_entry(dt_pixel_cache_entry_t *cache_entry)
dt_pixel_cache_entry_t * dt_dev_pixelpipe_cache_get_entry(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash)
Get an internal reference to the cache entry matching hash. If you are going to access this entry mor...
void * dt_dev_pixelpipe_cache_alloc_cl_device_buffer(int devid, const dt_iop_roi_t *roi, const size_t bpp, const dt_iop_module_t *module, const char *message, void *keep)
int dt_dev_pixelpipe_cache_get(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash, const size_t size, const char *name, const int id, const gboolean alloc, void **data, dt_pixel_cache_entry_t **entry)
Get a cache line from the cache.
int dt_dev_pixelpipe_cache_remove(dt_dev_pixelpipe_cache_t *cache, const gboolean force, dt_pixel_cache_entry_t *cache_entry)
Arbitrarily remove the cache entry matching hash. Entries having a reference count > 0 (inter-thread ...
gboolean dt_dev_pixelpipe_cache_ref_entry_by_hash(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry)
Resolve and retain an existing cache entry by hash.
int dt_dev_pixelpipe_cache_prepare_cl_input(dt_dev_pixelpipe_t *pipe, dt_iop_module_t *module, float *input, void **cl_mem_input, const dt_iop_roi_t *roi_in, const size_t in_bpp, dt_pixel_cache_entry_t *input_entry, dt_pixel_cache_entry_t **locked_input_entry, void *keep)
Prepare/obtain the OpenCL input image for a module.
static gboolean _for_each_remove_old(gpointer key, gpointer value, gpointer user_data)
dt_pixel_cache_entry_t * dt_dev_pixelpipe_cache_get_entry_by_data(dt_dev_pixelpipe_cache_t *cache, void *data)
static gboolean _is_gamma_rgba8_output(const dt_iop_module_t *module, const size_t bpp, const char *message)
dt_dev_pixelpipe_cache_writable_status_t dt_dev_pixelpipe_cache_get_writable(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash, const size_t size, const char *name, const int id, const gboolean alloc, const gboolean allow_rekey_reuse, const dt_pixel_cache_entry_t *reuse_hint, void **data, dt_pixel_cache_entry_t **entry)
gboolean dt_dev_pixelpipe_cache_restore_host_payload(dt_dev_pixelpipe_cache_t *cache, dt_pixel_cache_entry_t *cache_entry, const int preferred_devid, void **data)
Materialize a host payload for a live cache entry from its cached device payload.
void dt_dev_pixelpipe_cache_wrlock_entry(dt_dev_pixelpipe_cache_t *cache, gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the write lock on the entry.
static void _pixelpipe_cache_finalize_entry(dt_pixel_cache_entry_t *cache_entry, void **data, const char *message)
int dt_dev_pixelpipe_cache_sync_cl_buffer(const int devid, void *host_ptr, void *cl_mem_buffer, const dt_iop_roi_t *roi, int cl_mode, size_t bpp, dt_iop_module_t *module, const char *message)
Synchronize between host memory and a pinned OpenCL image.
void dt_dev_pixelpipe_cache_return_cl_payload(dt_pixel_cache_entry_t *entry, void *mem)
Return a borrowed cached OpenCL payload to its cache entry.
gboolean dt_dev_pixelpipe_cache_flush_host_pinned_image(dt_dev_pixelpipe_cache_t *cache, void *host_ptr, dt_pixel_cache_entry_t *entry_hint, int devid)
Drop cached pinned OpenCL images associated with a given host buffer.
size_t dt_pixel_cache_entry_get_size(dt_pixel_cache_entry_t *entry)
Peek the size (in bytes) reserved for the host buffer of a cache entry.
int dt_dev_pixelpipe_cache_rekey(dt_dev_pixelpipe_cache_t *cache, const uint64_t old_hash, const uint64_t new_hash, dt_pixel_cache_entry_t *entry)
Change the hash/key of an existing cache line in place, without freeing, reallocating or invalidating...
void dt_dev_pixelpipe_cache_rdlock_entry(dt_dev_pixelpipe_cache_t *cache, gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
static void _log_arena_allocation_failure(dt_dev_pixelpipe_cache_t *cache, size_t request_size, const char *entry_name, const char *module, uint64_t hash, gboolean name_is_file)
int _non_thread_safe_cache_remove(dt_dev_pixelpipe_cache_t *cache, const gboolean force, dt_pixel_cache_entry_t *cache_entry, GHashTable *table)
static gboolean _cache_try_restore_device_payload(dt_pixel_cache_entry_t *cache_entry, const int preferred_devid, void **cl_mem_output)
static void _pixel_cache_message(dt_pixel_cache_entry_t *cache_entry, const char *message, gboolean verbose)
static void * _arena_alloc_with_defrag(dt_dev_pixelpipe_cache_t *cache, size_t request_size, size_t *actual_size)
static void * _pixel_cache_clmem_get(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid, int width, int height, int bpp, int flags)
static dt_pixel_cache_entry_t * _non_threadsafe_cache_get_entry(dt_dev_pixelpipe_cache_t *cache, GHashTable *table, const uint64_t key)
void * dt_pixelpipe_cache_alloc_align_cache_impl(dt_dev_pixelpipe_cache_t *cache, size_t size, int id, const char *name)
Allocate aligned memory tracked by the pixelpipe cache. This allows LRU cache entries to be evicted i...
static gboolean _cache_entry_materialize_host_data(dt_dev_pixelpipe_cache_t *cache, int preferred_devid, dt_pixel_cache_entry_t *entry)
static gboolean _cache_entry_materialize_host_data_locked(dt_pixel_cache_entry_t *entry, int preferred_devid, gboolean prefer_device_payload)
dt_pixel_cache_entry_t * dt_dev_pixelpipe_cache_ref_entry_for_host_ptr(dt_dev_pixelpipe_cache_t *cache, void *host_ptr)
Resolve and retain the cache entry owning a host pointer.
static dt_pixel_cache_entry_t * dt_pixel_cache_new_entry(const uint64_t hash, const size_t size, const char *name, const int id, dt_dev_pixelpipe_cache_t *cache, gboolean alloc, GHashTable *table)
void _non_thread_safe_cache_ref_count_entry(dt_dev_pixelpipe_cache_t *cache, gboolean lock, dt_pixel_cache_entry_t *cache_entry)
void dt_dev_pixelpipe_cache_flush_clmem(dt_dev_pixelpipe_cache_t *cache, const int devid)
Release cached OpenCL buffers for a single device.
float * dt_dev_pixelpipe_cache_restore_cl_buffer(dt_dev_pixelpipe_t *pipe, float *input, void *cl_mem_input, const dt_iop_roi_t *roi_in, dt_iop_module_t *module, const size_t in_bpp, dt_pixel_cache_entry_t *input_entry, const char *message)
Force device → host resynchronization of the pixelpipe input cache line.
const char * dt_pixelpipe_cache_set_current_module(const char *module)
Set the current module name for cache diagnostics (thread-local).
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
dt_dev_pixelpipe_cache_writable_status_t
@ DT_DEV_PIXELPIPE_CACHE_WRITABLE_REKEYED
@ DT_DEV_PIXELPIPE_CACHE_WRITABLE_ERROR
@ DT_DEV_PIXELPIPE_CACHE_WRITABLE_CREATED
@ DT_DEV_PIXELPIPE_CACHE_WRITABLE_EXACT_HIT
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:366
@ DT_SIGNAL_CACHELINE_READY
This signal is raised when one cacheline write lock is released. 1 : uint64_t cacheline hash no retur...
Definition signal.h:185
const float uint32_t state[4]
unsigned __int64 uint64_t
Definition strptime.c:75
dt_pixel_cache_entry_t * cache_entry
struct dt_dev_pixelpipe_cache_t * pixelpipe_cache
Definition darktable.h:818
struct dt_control_signal_t * signals
Definition darktable.h:802
struct dt_opencl_t * opencl
Definition darktable.h:813
int32_t unmuted
Definition darktable.h:788
dt_pthread_mutex_t lock
GModule *dt_dev_operation_t op
Definition imageop.h:286
Region of interest passed through the pixelpipe.
Definition imageop.h:72
cl_ulong max_global_mem
Definition opencl.h:144
dt_pthread_mutex_t lock
Definition opencl.h:137
int num_devs
Definition opencl.h:263
dt_opencl_device_t * dev
Definition opencl.h:273
int inited
Definition opencl.h:259
uint64_t hash
gboolean auto_destroy
dt_atomic_int refcount
gboolean external_alloc
void * data
size_t size
int64_t age
uint64_t serial
dt_dev_pixelpipe_cache_t * cache
dt_pthread_rwlock_t lock
dt_pthread_mutex_t cl_mem_lock
GList * cl_mem_list
char * name
int hits
int id
uint64_t producer_node_key
int refcount
char name[64]
int hits
size_t size
size_t cl_bytes
uint64_t hash
int cl_count
void dt_supervisor_cacheline_delete(const uint64_t hash, const size_t size, const int owner_pipe_id, const char *name)
Definition supervisor.c:982
void dt_supervisor_rekey(const uint64_t old_hash, const uint64_t new_hash)
void dt_supervisor_cacheline_read(const uint64_t hash, const size_t size)
Definition supervisor.c:952
dt_pthread_mutex_t lock
Definition supervisor.c:119
static gboolean dt_supervisor_active(void)
Definition supervisor.h:94