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 <stdarg.h>
39#include <glib.h>
40#include <stdlib.h>
41#include <signal.h>
42#include <string.h>
43
48#include "common/opencl.h"
49#include "pixel/format.h"
50/* For dt_iop_module_t: the cache reads `module->op` to special-case the gamma module
51 * and calls `module->name()` for its diagnostics. That is the last edge keeping this
52 * file above develop/; taking a name string instead of a module would cut it. */
53#include "develop/imageop.h"
54
55/* Set once by dt_dev_pixelpipe_cache_init(); see the header for why they are not read live. */
56static gboolean _verbose = FALSE;
57static gboolean _verbose_detail = FALSE;
58
59/* Statement-safe: a bare `if(_verbose) dt_print(...)` swallows a following `else`. */
60#define _cache_print(channel, ...) \
61 do { \
62 if(_verbose) dt_print((channel), __VA_ARGS__); \
63 } while(0)
64
65
66
67/* PRIVATE. Nothing outside this file reads a field of it -- the header exposes the
68 * type as an opaque handle so that it cannot. */
70{
71 GHashTable *entries;
72 // External (temporary) buffers keyed by address hash, separate from pipeline cache entries.
73 GHashTable *external_entries;
77 size_t max_memory;
79 // System memory-pressure probe cache, guarded by `lock` (see the pressure valve
80 // in pixelpipe_cache.c): last probed system-wide available RAM, decremented by
81 // our own allocations between two rate-limited probes. The estimate legitimately
82 // reaches 0 under pressure, so whether the platform answers at all is a separate
83 // flag rather than an `est == 0` sentinel.
87 /* Kernel memory pressure: how much the cache may hold while the machine stalls, and what
88 * decides it -- caches/pixelpipe_cache_pressure.c, which this file feeds through _pressure_sink()
89 * and which never touches an entry itself. Guarded by `lock`, like the fields above. */
91 dt_pthread_mutex_t lock; // mutex to protect the cache entries
94
95
96/* The cache instance, owned HERE. darktable.c used to hold it on the application struct and
97 * implement the accessor; it still decides HOW BIG the cache is and still retries smaller on
98 * failure -- that is resource policy, not a cache decision -- but the pointer lives here. */
100
102{
104}
105
106/* Installed by the orchestrator; see dt_dev_pixelpipe_cache_set_handlers(). NULL means
107 * nobody is listening, which is a working configuration, not an error. */
111
120
121/* printf-style, so the call sites keep reading as they did; the formatting happens here and
122 * the handler receives a finished string. */
123static void _warn_user(const char *format, ...) __attribute__((format(printf, 1, 2)));
124static void _warn_user(const char *format, ...)
125{
126 if(!_warn_handler) return;
127 va_list ap;
128 va_start(ap, format);
129 char *message = g_strdup_vprintf(format, ap);
130 va_end(ap);
131 if(message) _warn_handler(message);
132 g_free(message);
133}
134
135static inline gboolean _observed(void)
136{
137 return _observer && _observer->active && _observer->active();
138}
139
140/* Each member is checked at its own call: `active` returning TRUE says the supervisor is
141 * watching, not that it implements every hook. */
142static inline void _observe_read(uint64_t hash, size_t size)
143{
145}
146static inline void _observe_delete(uint64_t hash, size_t size, int owner_pipe_id, const char *name)
147{
148 if(_observed() && _observer->cacheline_delete) _observer->cacheline_delete(hash, size, owner_pipe_id, name);
149}
150static inline void _observe_rekey(uint64_t old_hash, uint64_t new_hash)
151{
152 if(_observed() && _observer->rekey) _observer->rekey(old_hash, new_hash);
153}
154
155
156static __thread const char *dt_pixelpipe_cache_current_module = NULL;
157
159 const uint64_t key);
160
161static inline const char *_cache_debug_module_name(void)
162{
164}
165
166static void _trace_exact_hit(const char *phase, const uint64_t hash, dt_pixel_cache_entry_t *cache_entry,
167 void *data, void *cl_mem_output, const int preferred_devid, const gboolean verbose)
168{
169 if(!_verbose) return;
170 if(verbose && !_verbose_detail) return;
171
173 "[pixelpipe_cache] exact-hit %s req=%" PRIu64 " entry=%" PRIu64 "/%" PRIu64
174 " data=%p cl=%p refs=%i auto=%i dev=%i module=%s name=%s\n",
175 phase, hash, cache_entry ? cache_entry->hash : DT_PIXELPIPE_CACHE_HASH_INVALID,
176 cache_entry ? cache_entry->serial : 0, data, cl_mem_output,
177 cache_entry ? dt_atomic_get_int(&cache_entry->refcount) : -1,
178 cache_entry ? cache_entry->auto_destroy : -1, preferred_devid, _cache_debug_module_name(),
179 (cache_entry && cache_entry->name) ? cache_entry->name : "-");
180}
181
182const char *dt_pixelpipe_cache_set_current_module(const char *module)
183{
184 const char *previous = dt_pixelpipe_cache_current_module;
186 return previous;
187}
188
189typedef struct dt_cache_clmem_t
190{
191 void *host_ptr;
192 void *mem;
193 int refs;
195
204
205
207 dt_pixel_cache_entry_t *cache_entry);
208static void _free_cache_entry(dt_pixel_cache_entry_t *cache_entry);
209static void _pixelpipe_cache_finalize_entry(dt_pixel_cache_entry_t *cache_entry, void **data,
210 const char *message);
211int _non_thread_safe_cache_remove(dt_dev_pixelpipe_cache_t *cache, const gboolean force,
212 dt_pixel_cache_entry_t *cache_entry, GHashTable *table);
213
215 const uint64_t hash, const size_t size,
216 const char *name, const int id);
217static dt_pixel_cache_entry_t *dt_pixel_cache_new_entry(const uint64_t hash, const size_t size,
218 const char *name, const int id,
219 dt_dev_pixelpipe_cache_t *cache, gboolean alloc,
220 GHashTable *table);
221static gboolean _cache_entry_clmem_flush_device(dt_pixel_cache_entry_t *entry, const int devid);
222static gboolean _cache_entry_materialize_host_data_locked(dt_pixel_cache_entry_t *entry, int preferred_devid,
223 gboolean prefer_device_payload);
226
227#ifdef HAVE_OPENCL
228static gboolean _cache_entry_clmem_flush_host_pinned_locked(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid);
229#endif
230
231/* Single writer of `cache_entry->age`, and the only way an entry's MRU timestamp moves. EVERY
232 * path that hands an entry over to a consumer must call this -- the intra-run producer->consumer
233 * handoff included, since that is still a use as far as eviction is concerned. An entry whose age
234 * is only ever set at creation is aged by the eviction sweeps as if it had never been read, so the
235 * cacheline the pipeline consumes on every frame becomes the first LRU victim.
236 *
237 * This deliberately does NOT touch `hits`. `age` answers "when was this last needed" and every
238 * use, reuse or passthrough, answers it. `hits` answers "has this ever been reused ASYNCHRONOUSLY"
239 * and passthrough does not: a module publishing its output with one reference reserved for the
240 * next module, which reopens it as its input and releases it, is the normal pipeline flow, not a
241 * cache win. Only the lookups that also count a `cache->queries`/`cache->hits` pair may bump it. */
242static inline void _pixel_cache_touch(dt_pixel_cache_entry_t *cache_entry)
243{
244 if(IS_NULL_PTR(cache_entry)) return;
245 dt_atomic_set_uint64(&cache_entry->age, (uint64_t)g_get_monotonic_time());
246}
247
248static inline int64_t _pixel_cache_get_age(dt_pixel_cache_entry_t *cache_entry)
249{
250 return (int64_t)dt_atomic_get_uint64(&cache_entry->age);
251}
252
253
255{
256 if(IS_NULL_PTR(cache) || IS_NULL_PTR(host_ptr)) return NULL;
257
258 const uint64_t hash = (uint64_t)(uintptr_t)host_ptr;
260 if(entry && entry->external_alloc && entry->data == host_ptr) return entry;
261 return NULL;
262}
263
265 const uint64_t key)
266{
267 dt_pixel_cache_entry_t *entry = (dt_pixel_cache_entry_t *)g_hash_table_lookup(table, &key);
268 return entry;
269}
270
271
273{
275 if(hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return NULL;
278 /* Refresh recency only. `develop/pixelpipe_hb.c` reopens each module's INPUT cacheline through
279 * here on every recursion step: that is the producer->consumer handoff of one pipeline run, a
280 * use for eviction purposes but not an asynchronous reuse, so neither `hits` counter moves. */
281 _pixel_cache_touch(entry);
283 return entry;
284}
285
286
288{
290 if(!cache || !data) return NULL;
291
293
294 GHashTableIter iter;
295 gpointer key, value;
296
297 /* Search regular entries table */
298 g_hash_table_iter_init(&iter, cache->entries);
299 while(g_hash_table_iter_next(&iter, &key, &value))
300 {
302 if(entry && entry->data == data)
303 {
304 _pixel_cache_touch(entry);
306 return entry;
307 }
308 }
309
310 /* Search external entries table */
311 g_hash_table_iter_init(&iter, cache->external_entries);
312 while(g_hash_table_iter_next(&iter, &key, &value))
313 {
315 if(entry && entry->data == data)
316 {
317 _pixel_cache_touch(entry);
319 return entry;
320 }
321 }
322
324 return NULL;
325}
326
327
329{
330 return cache_entry->size / (1024 * 1024);
331}
332
333
334static void _pixel_cache_message(dt_pixel_cache_entry_t *cache_entry, const char *message, gboolean verbose)
335{
336 if(!_verbose) return;
337 if(verbose && !_verbose_detail) return;
339 "[pixelpipe] cache entry %" PRIu64 "/%" PRIu64 ": %s (data=%p - %" G_GSIZE_FORMAT " MiB - age %" PRId64
340 " - hits %i - refs %i - auto %i - ext %i - id %i - module %s) %s\n",
341 cache_entry->hash, cache_entry->serial,
342 cache_entry->name ? cache_entry->name : "-", cache_entry->data,
343 _pixel_cache_get_size(cache_entry), _pixel_cache_get_age(cache_entry), cache_entry->hits,
344 dt_atomic_get_int(&cache_entry->refcount), cache_entry->auto_destroy,
345 cache_entry->external_alloc, cache_entry->id, _cache_debug_module_name(), message);
346}
347
348static void _pixelpipe_cache_finalize_entry(dt_pixel_cache_entry_t *cache_entry, void **data,
349 const char *message)
350{
351 _pixel_cache_touch(cache_entry);
352 if(data)
353 *data = cache_entry->data ? __builtin_assume_aligned(cache_entry->data, DT_CACHELINE_BYTES) : NULL;
354 _pixel_cache_message(cache_entry, message, FALSE);
355}
356
358 void **data,
360{
362 if(!IS_NULL_PTR(data)) *data = NULL;
363 if(!IS_NULL_PTR(entry)) *entry = NULL;
364 if(IS_NULL_PTR(cache) || hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
365
367 cache->queries++;
368
369 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, hash);
370 if(!IS_NULL_PTR(cache_entry) && !cache_entry->auto_destroy)
371 {
372 cache->hits++;
373 cache_entry->hits++;
374 _non_thread_safe_cache_ref_count_entry(cache, TRUE, cache_entry);
375 _pixelpipe_cache_finalize_entry(cache_entry, data, "ref-by-hash");
376 if(!IS_NULL_PTR(entry)) *entry = cache_entry;
377 }
378
379 const gboolean found = !IS_NULL_PTR(cache_entry) && !cache_entry->auto_destroy;
380 const size_t found_size = found ? cache_entry->size : 0;
382
383 if(found ) _observe_read(hash, found_size);
384
385 return found;
386}
387
388
390 void **data,
392{
394 if(!IS_NULL_PTR(data)) *data = NULL;
395 if(!IS_NULL_PTR(entry)) *entry = NULL;
396 if(IS_NULL_PTR(cache) || hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return FALSE;
397
398 gboolean found = FALSE;
399 size_t found_size = 0;
400
402 cache->queries++;
403
404 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, hash);
405 if(!IS_NULL_PTR(cache_entry) && !cache_entry->auto_destroy && !IS_NULL_PTR(cache_entry->data))
406 {
407 /* The same rejection `dt_dev_pixelpipe_cache_peek()` applies, for the same reason: a reusable
408 * output cacheline is rekeyed to its new hash BEFORE the recompute that fills it starts, so an
409 * entry somebody currently holds the write lock on carries stale or half-written pixels under a
410 * key that already claims to describe the new ones. Never WAIT on that lock -- report a miss and
411 * let the caller queue for the publication instead. */
412 if(dt_pthread_rwlock_tryrdlock(&cache_entry->lock) == 0)
413 {
414 dt_pthread_rwlock_unlock(&cache_entry->lock);
415 cache->hits++;
416 cache_entry->hits++;
417 _non_thread_safe_cache_ref_count_entry(cache, TRUE, cache_entry);
418 _pixelpipe_cache_finalize_entry(cache_entry, data, "ref-host-by-hash");
419 if(!IS_NULL_PTR(entry)) *entry = cache_entry;
420 found = TRUE;
421 found_size = cache_entry->size;
422 }
423 }
424
426
427 if(found) _observe_read(hash, found_size);
428
429 return found;
430}
431
432// remove the cache entry with the given hash and update the cache memory usage
433// WARNING: not internally thread-safe, protect its calls with mutex lock
434// return 0 on success, 1 on error
436 dt_pixel_cache_entry_t *cache_entry, GHashTable *table)
437{
438 if(!IS_NULL_PTR(cache_entry))
439 {
440 // Returns 1 if the lock is captured by another thread
441 // 0 if WE capture the lock, and then need to release it
442 gboolean locked = dt_pthread_rwlock_trywrlock(&cache_entry->lock);
443 if(!locked) dt_pthread_rwlock_unlock(&cache_entry->lock);
444 gboolean used = dt_atomic_get_int(&cache_entry->refcount) > 0;
445
446 /* Force-removal may bypass caller lifecycle checks but must never destroy
447 * an entry that still has active readers/writers. Active users can still
448 * access cl_mem_list after this call (for example borrowed GPU payloads),
449 * so removing a referenced entry here would create dangling pointers. */
450 if(!used && (!locked || force))
451 {
452 // Note: the free callback takes care of flushing OpenCL buffers too
453 g_hash_table_remove(table, &cache_entry->hash);
454 return 0;
455 }
456 else if(used)
457 _pixel_cache_message(cache_entry, "cannot remove: used", TRUE);
458 else if(locked)
459 _pixel_cache_message(cache_entry, "cannot remove: locked", TRUE);
460 }
461 else
462 {
463 _cache_print(DT_DEBUG_PIPECACHE, "[pixelpipe] cache entry not found, will not be removed\n");
464 }
465 return 1;
466}
467
468
469int dt_dev_pixelpipe_cache_remove(const gboolean force,
470 dt_pixel_cache_entry_t *cache_entry)
471{
474 int error = _non_thread_safe_cache_remove(cache, force, cache_entry, cache->entries);
476 return error;
477}
478
479#ifdef HAVE_OPENCL
480static gboolean _cache_entry_materialize_host_data_locked(dt_pixel_cache_entry_t *entry, int preferred_devid,
481 gboolean prefer_device_payload)
482{
483 dt_cache_clmem_t *source = NULL;
484 gboolean ok = FALSE;
486
487 /* We materialize RAM from the most authoritative cached payload in one pass instead of
488 * walking the list multiple times with slightly different predicates:
489 * - when RAM existed before, prefer pinned host-backed payloads first because they should
490 * already alias the cacheline or be the cheapest path back to host,
491 * - when RAM has just been allocated for a GPU-only cacheline, prefer device payloads first,
492 * - if a preferred OpenCL device is known, rank payloads from that device ahead of the rest.
493 * This keeps the fallback order explicit without scattering it over six loops. */
495 for(GList *l = g_list_first(entry->cl_mem_list); l; l = g_list_next(l))
496 {
497 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
498 if(IS_NULL_PTR(c) || IS_NULL_PTR(c->mem)) continue;
499
500 /* We are looking for one authoritative cached payload to materialize back to RAM.
501 * Only consider records whose live OpenCL context still matches the recorded device.
502 * Other cached payloads may belong to a different pipeline/device and must stay untouched. */
503 const int mem_devid = dt_opencl_get_mem_context_id((cl_mem)c->mem);
504 if(mem_devid != preferred_devid) continue;
505
506 const gboolean host_backed = (c->host_ptr == entry->data);
507 const gboolean device_only = (IS_NULL_PTR(c->host_ptr));
508 if(!host_backed && !device_only) continue;
509
511 if(!prefer_device_payload)
512 {
513 if(host_backed && (preferred_devid < 0 || mem_devid == preferred_devid))
515 else if(device_only && preferred_devid >= 0 && mem_devid == preferred_devid)
517 else if(device_only)
519 else if(host_backed)
521 }
522 else
523 {
524 if(device_only && preferred_devid >= 0 && mem_devid == preferred_devid)
526 else if(device_only)
528 else if(host_backed && (preferred_devid < 0 || mem_devid == preferred_devid))
530 else if(host_backed)
532 }
533
534 if(rank > best_rank)
535 {
536 best_rank = rank;
537 source = c;
539 }
540 }
541
542 if(source)
543 {
544
545 const int devid = dt_opencl_get_mem_context_id(source->mem);
546 const int width = dt_opencl_get_image_width(source->mem);
547 const int height = dt_opencl_get_image_height(source->mem);
548 const int bpp = dt_opencl_get_image_element_size(source->mem);
549
550 if(dt_opencl_is_pinned_memory((cl_mem)source->mem) && source->host_ptr == entry->data)
551 {
552 void *mapped = dt_opencl_map_image(devid, (cl_mem)source->mem, TRUE, CL_MAP_READ,
553 width, height, bpp);
554 ok = (dt_opencl_unmap_mem_object(devid, (cl_mem)source->mem, mapped) == CL_SUCCESS);
555 }
556 if(!ok)
557 {
558 ok = (dt_opencl_read_host_from_device(devid, entry->data, source->mem,
559 width, height, bpp) == CL_SUCCESS);
560 }
561 }
562
564 return ok;
565}
566#else
567static gboolean _cache_entry_materialize_host_data_locked(dt_pixel_cache_entry_t *entry, int preferred_devid,
568 gboolean prefer_device_payload)
569{
570 (void)preferred_devid;
571 (void)prefer_device_payload;
572 return entry && !IS_NULL_PTR(entry->data);
573}
574#endif
575
576static gboolean _cache_entry_materialize_host_data(dt_dev_pixelpipe_cache_t *cache, int preferred_devid,
578{
579 if(IS_NULL_PTR(cache) || IS_NULL_PTR(entry)) return FALSE;
580 if(preferred_devid < 0 && dt_pixel_cache_entry_get_data(entry) == NULL) return FALSE;
581
583 gboolean use_host_ptr = TRUE;
585 {
587 use_host_ptr = FALSE;
588 }
589 const gboolean ok = _cache_entry_materialize_host_data_locked(entry, preferred_devid, use_host_ptr);
591
592 return ok;
593}
594
595#ifdef HAVE_OPENCL
596static gboolean _cache_entry_clmem_has_host_pinned_locked(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid)
597{
598 if(IS_NULL_PTR(entry) || IS_NULL_PTR(host_ptr)) return FALSE;
599
600 gboolean found = FALSE;
602 for(GList *l = g_list_first(entry->cl_mem_list); l; l = g_list_next(l))
603 {
604 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
605 if(IS_NULL_PTR(c) || IS_NULL_PTR(c->mem)) continue;
606
607 if(c->refs == 0 && devid == dt_opencl_get_mem_context_id((cl_mem)c->mem))
608 {
609 found = TRUE;
610 break;
611 }
612 }
614
615 return found;
616}
617
618static gboolean _cache_entry_clmem_flush_host_pinned_locked(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid)
619{
620 // If host_ptr is NULL, we don't have RAM cache for this buffer,
621 // so we can't flush the vRAM cache or we would loose it forever.
622 if(IS_NULL_PTR(entry) || IS_NULL_PTR(host_ptr)) return FALSE;
623
624 gboolean flushed = FALSE;
625
627 for(GList *l = g_list_first(entry->cl_mem_list); l;)
628 {
629 GList *next = g_list_next(l);
630 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
631 if(IS_NULL_PTR(c->mem))
632 {
633 // Current cacheline holds an empty buffer, no point keeping it
634 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
635 dt_free(c);
636 l = next;
637 continue;
638 }
639 if(dt_opencl_get_mem_context_id(c->mem) != devid)
640 {
641 // Current cacheline doesn't belong to current OpenCL devide: don't touch it
642 l = next;
643 continue;
644 }
645
646 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
648 dt_free(c);
649 flushed = TRUE;
650 l = next;
651 }
652
654
655 return flushed;
656}
657#endif
658
660{
662 // devid < 0 means the calling pipe never used OpenCL: it owns no device-side
663 // payload in the cache, so there is nothing of its own to release here.
664 //
665 // This used to also support devid == -1 as "drop cl_mem from every device,
666 // regardless of who else is using it", called from per-pipe cleanup. That ran
667 // without holding any dev[].lock, so it could race the eventlist/cl_mem
668 // bookkeeping of whichever OTHER pixelpipe was concurrently running on that
669 // device, corrupting it and crashing inside clGetEventInfo/clWaitForEvents
670 // (see #859 and #864). A global, all-devices teardown is never needed during
671 // normal operation: dt_cleanup() already finishes every device and
672 // dt_dev_pixelpipe_cache_cleanup() unconditionally releases all remaining
673 // cl_mem objects at application exit, when nothing else is running.
674 if(devid < 0) return;
675
676 // NOTE: the caller must hold the device lock (dt_opencl_reserve_device_by_id()) -- either
677 // because it IS the pixelpipe currently running on that device (the lock
678 // dt_opencl_reserve_device_for_pipe() handed it for the duration of its run), or because
679 // it explicitly took that lock to safely flush this device's cache entries
680 // after its own run finished (see dt_dev_pixelpipe_cache_flush_clmem_for_pipe()).
682
684 GHashTableIter iter;
685 gpointer key, value;
686 g_hash_table_iter_init(&iter, cache->entries);
687 while(g_hash_table_iter_next(&iter, &key, &value))
688 {
690
691 /* Only idle cachelines may have their vRAM reclaimed. An entry that is referenced or
692 * write-locked is somebody's live (or about-to-be-consumed) buffer: the recursion reserves
693 * an entry-level ref for the next consumer before that consumer borrows the cl_mem payload,
694 * so a payload can be unborrowed (per-payload refs == 0) yet still belong to an in-flight
695 * pipe. Honoring the same protection the LRU/removal paths use (refcount + non-blocking
696 * write-lock probe) keeps us from yanking the sole vRAM copy of another pipe's input out
697 * from under it -- which left a husk and produced skull thumbnails (issue #817). The
698 * trywrlock never waits, so this stays lightweight and cannot deadlock against renders that
699 * already hold entry locks. */
700 const gboolean used = dt_atomic_get_int(&entry->refcount) > 0;
701 gboolean locked = dt_pthread_rwlock_trywrlock(&entry->lock);
702 if(!locked) dt_pthread_rwlock_unlock(&entry->lock);
703 if(used || locked)
704 {
707 "[dt_dev_pixelpipe_cache_flush_clmem] entry %" PRIu64 " is in use (refcount=%i locked=%i), "
708 "keeping its vRAM\n", entry->hash, dt_atomic_get_int(&entry->refcount), locked);
709 continue;
710 }
711
714 "[dt_dev_pixelpipe_cache_flush_clmem] trying to flush vRAM for entry %" PRIu64 " on device %d...\n",
715 entry->hash, devid);
716
717 /* If reclaiming this device's vRAM leaves the entry with no buffer at all, delete it now
718 * instead of letting a payload-less husk persist as a cache hit. We hold cache->lock for the
719 * whole iteration, and lookups bump the consumer ref under that same lock, so no consumer can
720 * be mid-acquisition of this (refcount == 0) entry. iter_remove runs _free_cache_entry, which
721 * releases any remaining resources. */
722 if(_cache_entry_clmem_flush_device(entry, devid))
723 g_hash_table_iter_remove(&iter);
724 }
726}
727
728#ifdef HAVE_OPENCL
730{
731 // Like dt_dev_pixelpipe_cache_flush_clmem(), but for callers that do NOT
732 // currently hold the device lock (dt_opencl_reserve_device_by_id()) -- typically a pipe's own
733 // cleanup, running after dt_dev_pixelpipe_process() already released that
734 // lock. Taking it here ensures we can't race the eventlist/cl_mem bookkeeping
735 // of whichever OTHER pixelpipe is now running on that device.
736 if(devid < 0 || !dt_opencl_is_inited()) return;
737
741}
742#else
744{
745 (void)devid;
746}
747#endif
748
755
756
757// find the cache entry hash with the oldest use
758static void _cache_get_oldest(gpointer key, gpointer value, gpointer user_data)
759{
761 _cache_lru_t *lru = (_cache_lru_t *)user_data;
762
763 // Don't remove LRU entries that are still in use
764 // NOTE: with all the killswitches mechanisms and safety measures,
765 // we might have more things decreasing refcount than increasing it.
766 // It's no big deal though, as long as the (final output) backbuf
767 // is checked for NULL and not reused if pipeline is DIRTY.
768 const int64_t age = _pixel_cache_get_age(cache_entry);
769 if(age < lru->max_age)
770 {
771 // Returns 1 if the lock is captured by another thread
772 // 0 if WE capture the lock, and then need to release it
773 gboolean locked = dt_pthread_rwlock_trywrlock(&cache_entry->lock);
774 if(!locked) dt_pthread_rwlock_unlock(&cache_entry->lock);
775 gboolean used = dt_atomic_get_int(&cache_entry->refcount) > 0;
776
777 if(!locked && !used)
778 {
779 lru->max_age = age;
780 lru->hash = cache_entry->hash;
781 lru->cache_entry = cache_entry;
782 _pixel_cache_message(cache_entry, "candidate for deletion", TRUE);
783 }
784 else if(used)
785 _pixel_cache_message(cache_entry, "cannot be deleted: used", TRUE);
786 else if(locked)
787 _pixel_cache_message(cache_entry, "cannot be deleted: locked", TRUE);
788 }
789}
790
791static void _print_cache_lines(gpointer key, gpointer value, gpointer user_data)
792{
794 _pixel_cache_message(cache_entry, "", FALSE);
795}
796
797
798// remove the least used cache entry
799// return 0 on success, 1 on error
800// error is : we couldn't find a candidate for deletion because all entries are either locked or in use
801// or we found one but failed to remove it.
803{
804 _cache_lru_t *lru = (_cache_lru_t *)malloc(sizeof(_cache_lru_t));
805 lru->max_age = g_get_monotonic_time();
806 lru->hash = 0;
807 lru->cache_entry = NULL;
808 int error = 1;
809 g_hash_table_foreach(cache->entries, _cache_get_oldest, lru);
810
811 if(lru->hash > 0)
812 {
814 if(error)
815 _cache_print(DT_DEBUG_PIPECACHE, "[pixelpipe] couldn't remove LRU %" PRIu64 "\n", lru->hash);
816 else
817 _cache_print(DT_DEBUG_PIPECACHE, "[pixelpipe] LRU %" PRIu64 " removed. Total cache size: %" G_GSIZE_FORMAT " MiB\n",
818 lru->hash, cache->current_memory / (1024 * 1024));
819 }
820 else
821 {
822 _cache_print(DT_DEBUG_PIPECACHE, "[pixelpipe] couldn't remove LRU, %i items and all are used\n", g_hash_table_size(cache->entries));
823 g_hash_table_foreach(cache->entries, _print_cache_lines, NULL);
824 }
825
826 dt_free(lru);
827 return error;
828}
829
830// return 0 on success 1 on error
839
840#ifdef HAVE_OPENCL
841static void *_pixel_cache_clmem_get(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid,
842 int width, int height, int bpp, int flags)
843{
845
848 "[_pixel_cache_clmem_get] %u output entries in %" PRIu64 "\n",
849 g_list_length(entry->cl_mem_list), entry->hash);
850
851 for(GList *l = g_list_first(entry->cl_mem_list); l;)
852 {
853 GList *next = g_list_next(l);
854 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
855 if(IS_NULL_PTR(c->mem))
856 {
857 // No point in keeping buffer-less cachelines
858 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
859 dt_free(c);
860 l = next;
861 continue;
862 }
863
864 // Buffer reuse must stay on the same OpenCL device and ensure proper size
865 if(dt_opencl_get_mem_context_id(c->mem) == devid
866 && dt_opencl_get_image_width(c->mem) == width
869 && c->refs == 0)
870 {
871 // Destroy the current OpenCL cacheline and return the buffer, the cacheline will be recreated
872 // when we are done consuming the buffer
873 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
874 void *mem = c->mem;
875 dt_free(c);
876 // Reusing the entry's vRAM payload is a use of the entry, host side read or not.
877 _pixel_cache_touch(entry);
879 return mem;
880 }
881
882 l = next;
883 }
885
886 return NULL;
887}
888#endif
889
891 int width, int height, int bpp)
892{
893#ifdef HAVE_OPENCL
894
896
897 for(GList *l = g_list_first(entry->cl_mem_list); l; l = g_list_next(l))
898 {
899 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
900 if(dt_opencl_get_mem_context_id(c->mem) == devid
901 && dt_opencl_get_image_width(c->mem) == width
904 {
905 c->refs++;
906 void *mem = c->mem;
907 _pixel_cache_touch(entry);
909 return mem;
910 }
911 }
913
914#endif
915
916 return NULL;
917}
918
920{
921#ifdef HAVE_OPENCL
922
923 if(IS_NULL_PTR(entry) || IS_NULL_PTR(mem)) return;
924
926 for(GList *l = entry->cl_mem_list; l; l = g_list_next(l))
927 {
928 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
929 if(c && c->mem == mem)
930 {
931 if(c->refs > 0) c->refs--;
932 break;
933 }
934 }
936
937#else
938 (void)entry;
939 (void)mem;
940#endif
941}
942
943#ifdef HAVE_OPENCL
953static int _pixel_cache_clmem_put(dt_pixel_cache_entry_t *entry, void *host_ptr, void *mem)
954{
955 cl_mem clmem = (cl_mem)mem;
956 const int devid = dt_opencl_get_mem_context_id(clmem);
957
959 for(GList *l = g_list_first(entry->cl_mem_list); l; l = g_list_next(l))
960 {
961 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
962 if(c->mem == mem)
963 {
965 return 3;
966 }
967 if(dt_opencl_get_mem_context_id(c->mem) == devid)
968 {
969 // We keep one GPU cacheline per GPU device per pipeline cache entry
970 // If refs > 0 here, we have a problem earlier.
971 if(c->refs > 0) continue;
972
973 void *old = c->mem;
974 c->mem = mem;
975 c->host_ptr = host_ptr;
978 return 2;
979 }
980 }
981
982 dt_cache_clmem_t *c = (dt_cache_clmem_t *)g_malloc0(sizeof(*c));
983 if(IS_NULL_PTR(c))
984 {
987 return 0;
988 }
989
990 c->host_ptr = host_ptr;
991 c->mem = mem;
992 entry->cl_mem_list = g_list_prepend(entry->cl_mem_list, c);
994 return 1;
995}
996
998{
999 if(IS_NULL_PTR(entry) || IS_NULL_PTR(mem)) return;
1000
1002 for(GList *l = entry->cl_mem_list; l;)
1003 {
1004 GList *next = g_list_next(l);
1005 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
1006 if(c && c->mem == mem)
1007 {
1008 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
1009 dt_free(c);
1010 }
1011 l = next;
1012 }
1014}
1015#endif
1016
1018{
1020 for(GList *l = entry->cl_mem_list; l;)
1021 {
1022 GList *next = g_list_next(l);
1023 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
1024 if(c->refs > 0)
1025 {
1026 l = next;
1027 continue;
1028 }
1029
1030 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
1032 dt_free(c);
1033 l = next;
1034 }
1036}
1037
1038#ifdef HAVE_OPENCL
1040 dt_pixel_cache_entry_t *entry_hint, int devid,
1041 int width, int height, int bpp, int flags,
1042 gboolean *out_reused)
1043{
1045 if(!IS_NULL_PTR(out_reused)) *out_reused = FALSE;
1046 if(devid < 0 || width <= 0 || height <= 0 || bpp <= 0) return NULL;
1047
1048 // Pinning is enabled if the calling function requests it and if it is allowed by user for this device
1049 gboolean use_pinned = dt_opencl_use_pinned_memory(devid) && (flags & CL_MEM_USE_HOST_PTR);
1050
1051 // If no pinning, remove the allocation flag now because pinning happens at vRAM alloc time
1052 if(!use_pinned) flags &= ~CL_MEM_USE_HOST_PTR;
1053
1054 // Reuse the entry hint if available, else find the cache entry attached to the host_ptr
1055 dt_pixel_cache_entry_t *entry = entry_hint;
1056 if(IS_NULL_PTR(entry))
1057 {
1058 dt_pthread_mutex_lock(&cache->lock);
1059 entry = _cache_entry_for_host_ptr_locked(cache, host_ptr);
1061 }
1062
1063 // Reuse the vRAM buffer attached to the cache entry if any
1064 void *mem = NULL;
1065 if(entry)
1066 {
1067 mem = _pixel_cache_clmem_get(entry, host_ptr, devid, width, height, bpp, flags);
1068 if(!IS_NULL_PTR(mem) && !IS_NULL_PTR(out_reused)) *out_reused = TRUE;
1069 }
1070
1071 // If no vRAM buffer was found, allocate a new one, pinning the host_ptr memory if the option is enabled
1072 if(IS_NULL_PTR(mem))
1073 {
1074 mem = dt_opencl_alloc_device_use_host_pointer(devid, width, height, bpp, use_pinned ? host_ptr : NULL, flags);
1075 if(IS_NULL_PTR(mem)) return NULL;
1076 }
1077
1078 gboolean synced = FALSE;
1079
1080 // Synchronize host_ptr with mem
1082 {
1083 // Zero-copy for pinned buffers : note that some drivers may still use non-zero-copy,
1084 // in which case that degrades to basic memory copy.
1085 void *mapped = dt_opencl_map_image(devid, mem, TRUE, CL_MAP_WRITE, width, height, bpp);
1086 synced = (dt_opencl_unmap_mem_object(devid, mem, mapped) == CL_SUCCESS);
1087 }
1088
1089 if(!synced)
1090 {
1091 // Zero-copy failed or pinned memory is disabled for this device : use plain memory transfer
1092 if(dt_opencl_write_host_to_device(devid, host_ptr, mem, width, height, bpp) != CL_SUCCESS)
1093 {
1094 // Clean everything up on error and abort
1095 if(entry) _pixel_cache_clmem_remove(entry, mem);
1097 _cache_print(DT_DEBUG_OPENCL, "[dt_dev_pixelpipe_cache_get_pinned_image] failed to synchronize\n");
1098 return NULL;
1099 }
1100 else
1101 {
1102 _cache_print(DT_DEBUG_OPENCL, "[dt_dev_pixelpipe_cache_get_pinned_image] synchronized with write_host_to_device\n");
1103 }
1104 }
1105 else
1106 {
1107 _cache_print(DT_DEBUG_OPENCL, "[dt_dev_pixelpipe_cache_get_pinned_image] synchronized with mapping/unmapping\n");
1108 }
1109
1110 return mem;
1111}
1112
1114 dt_pixel_cache_entry_t *entry_hint, void **mem)
1115{
1116 if(IS_NULL_PTR(mem) || IS_NULL_PTR(*mem) || IS_NULL_PTR(host_ptr)) return;
1117 dt_pixel_cache_entry_t *entry = entry_hint;
1118 if(IS_NULL_PTR(entry))
1119 {
1121 _cache_print(DT_DEBUG_OPENCL, "[dt_dev_pixelpipe_cache_put_pinned_image] no cache entry to put the vRAM buffer\n");
1122 return;
1123 }
1124
1125 // FIXME: is it safe to cache non-pinned vRAM buffers (aka no CL_MEM_USE_HOST_PTR in flags) ?
1126 const int state = _pixel_cache_clmem_put(entry, host_ptr, (cl_mem)*mem);
1127 *mem = NULL;
1129 _cache_print(DT_DEBUG_OPENCL, "[dt_dev_pixelpipe_cache_put_pinned_image] cache entry put the vRAM buffer (state=%i) in %p\n", state, entry);
1130}
1131
1133 dt_pixel_cache_entry_t *entry_hint, int devid)
1134{
1136 if(IS_NULL_PTR(cache) || IS_NULL_PTR(host_ptr)) return FALSE;
1137
1138 dt_pixel_cache_entry_t *entry = entry_hint;
1139 if(IS_NULL_PTR(entry))
1140 {
1141 dt_pthread_mutex_lock(&cache->lock);
1142 entry = _cache_entry_for_host_ptr_locked(cache, host_ptr);
1144 }
1145
1146 if(IS_NULL_PTR(entry)) return FALSE;
1147 if(!_cache_entry_clmem_has_host_pinned_locked(entry, host_ptr, devid)) return FALSE;
1148
1149 if(devid >= 0) dt_opencl_events_wait_for(devid);
1151 const gboolean flushed = _cache_entry_clmem_flush_host_pinned_locked(entry, host_ptr, devid);
1153 return flushed;
1154}
1155
1156#else
1157
1158void dt_dev_pixelpipe_cache_put_pinned_image(void *host_ptr,
1159 dt_pixel_cache_entry_t *entry_hint, void **mem)
1160{
1161 (void)host_ptr;
1162 (void)entry_hint;
1163 if(mem) *mem = NULL;
1164}
1165
1167 dt_pixel_cache_entry_t *entry_hint, int devid)
1168{
1169 (void)host_ptr;
1170 (void)entry_hint;
1171 (void)devid;
1172 return FALSE;
1173}
1174
1175void dt_dev_pixelpipe_cache_resync_host_pinned_image(dt_dev_pixelpipe_cache_t *cache, void *host_ptr,
1176 dt_pixel_cache_entry_t *entry_hint, int devid)
1177{
1178 (void)host_ptr;
1179 (void)entry_hint;
1180 (void)devid;
1181}
1182#endif
1183
1184#ifdef HAVE_OPENCL
1185static inline gboolean _is_gamma_rgba8_output(const dt_iop_module_t *module, const size_t bpp,
1186 const char *message)
1187{
1188 return module && message && bpp == 4 * sizeof(uint8_t) && strcmp(module->op, "gamma") == 0
1189 && strcmp(message, "output") == 0;
1190}
1191
1192void *dt_dev_pixelpipe_cache_alloc_cl_device_buffer(int devid, const dt_iop_roi_t *roi, const size_t bpp,
1193 const dt_iop_module_t *module, const char *message,
1194 void *keep)
1195{
1196 const gboolean gamma_rgba8 = _is_gamma_rgba8_output(module, bpp, message);
1197 const int cl_bpp = gamma_rgba8 ? DT_OPENCL_BPP_ENCODE_RGBA8((int)bpp) : (int)bpp;
1198 return dt_opencl_alloc_device(devid, roi->width, roi->height, cl_bpp);
1199}
1200
1201void *dt_dev_pixelpipe_cache_get_cl_buffer(int devid, void *const host_ptr, const dt_iop_roi_t *roi,
1202 const size_t bpp, dt_iop_module_t *module,
1203 const char *message, dt_pixel_cache_entry_t *cache_entry,
1204 gboolean *out_reused, void *keep)
1205{
1206 // Need to use read-write mode because of in-place color space conversions.
1207 void *cl_mem_input = NULL;
1208 gboolean reused_from_cache = FALSE;
1209 const gboolean gamma_rgba8 = _is_gamma_rgba8_output(module, bpp, message);
1210 const int cl_bpp = gamma_rgba8 ? DT_OPENCL_BPP_ENCODE_RGBA8((int)bpp) : (int)bpp;
1211 static dt_atomic_int clmem_reuse_hits;
1212 static dt_atomic_int clmem_reuse_misses;
1213
1214 if(out_reused) *out_reused = FALSE;
1215
1216 if(host_ptr && dt_opencl_use_pinned_memory(devid))
1217 {
1218 const int flags = CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR;
1219
1220 // Try to reuse existing buffer
1221 if(cache_entry)
1222 {
1223 cl_mem_input = _pixel_cache_clmem_get(cache_entry, host_ptr, devid, roi->width, roi->height,
1224 (int)bpp, flags);
1225 reused_from_cache = (!IS_NULL_PTR(cl_mem_input));
1226 }
1227
1228 // This will internally try to free up cache space if first alloc fails
1229 if(IS_NULL_PTR(cl_mem_input))
1230 {
1231 cl_mem_input = dt_opencl_alloc_device_use_host_pointer(devid, roi->width, roi->height, cl_bpp,
1232 host_ptr, flags);
1234 _cache_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] allocated a pinned GPU buffer for %s %s\n", module->name(), message);
1235 }
1236 }
1237 else
1238 {
1239 if(cache_entry)
1240 {
1241 /* Device-only allocations are tracked with a NULL host_ptr key and a normalized READ_WRITE
1242 * flag so scratch buffers can be reused deterministically across drivers. */
1243 cl_mem_input = _pixel_cache_clmem_get(cache_entry, NULL, devid, roi->width, roi->height,
1244 (int)bpp, CL_MEM_READ_WRITE);
1245 reused_from_cache = (!IS_NULL_PTR(cl_mem_input));
1246 }
1247
1248 // This will internally try to free up cache space if first alloc fails
1249 if(IS_NULL_PTR(cl_mem_input))
1250 {
1251 cl_mem_input = dt_dev_pixelpipe_cache_alloc_cl_device_buffer(devid, roi, bpp, module, message, keep);
1252
1254 _cache_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] allocated a device-only GPU buffer for %s %s\n", module->name(), message);
1255 }
1256 }
1257
1258 if(IS_NULL_PTR(cl_mem_input))
1259 {
1260 _cache_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] couldn't allocate GPU buffer for module %s %s\n", module->name(), message);
1261 }
1262 else if(reused_from_cache)
1263 {
1264 const int hits = dt_atomic_add_int(&clmem_reuse_hits, 1) + 1;
1265 const int misses = dt_atomic_get_int(&clmem_reuse_misses);
1268 "[dev_pixelpipe] reused GPU buffer from cache (hits=%d, misses=%d) for module %s %s\n",
1269 hits, misses, module->name(), message);
1270 }
1271 else
1272 {
1273 dt_atomic_add_int(&clmem_reuse_misses, 1);
1274 }
1275
1276 if(out_reused) *out_reused = reused_from_cache;
1277 return cl_mem_input;
1278}
1279
1303 void *host_ptr, const gboolean cache_device)
1304{
1305 if(!IS_NULL_PTR(cl_mem_buffer) && !IS_NULL_PTR(*cl_mem_buffer))
1306 {
1307 cl_mem mem = *cl_mem_buffer;
1308 if(cache_device && !IS_NULL_PTR(cache_entry))
1309 {
1310 _pixel_cache_clmem_put(cache_entry, host_ptr, mem);
1311 }
1312 else
1313 {
1314 if(!IS_NULL_PTR(cache_entry)) _pixel_cache_clmem_remove(cache_entry, mem);
1316 }
1317 *cl_mem_buffer = NULL;
1318 }
1319}
1320
1350int dt_dev_pixelpipe_cache_sync_cl_buffer(const int devid, void *host_ptr, void *cl_mem_buffer,
1351 const dt_iop_roi_t *roi, int cl_mode, size_t bpp,
1352 dt_iop_module_t *module, const char *message)
1353{
1354 if(IS_NULL_PTR(host_ptr) || IS_NULL_PTR(cl_mem_buffer)) return 1;
1355
1356 const cl_mem mem = (cl_mem)cl_mem_buffer;
1357
1358 // Fast path for true zero-copy pinned images: map/unmap is enough to synchronize host<->device.
1360 {
1361 void *mapped = dt_opencl_map_image(devid, mem, TRUE, cl_mode, roi->width, roi->height, (int)bpp);
1362 if(dt_opencl_unmap_mem_object(devid, mem, mapped) == CL_SUCCESS)
1363 {
1365 "[dev_pixelpipe] successfully synced image %s via map/unmap for module %s (%s)\n",
1366 (cl_mode == CL_MAP_WRITE) ? "host to device" : "device to host",
1367 (module) ? module->op : "base buffer", message);
1368 return 0;
1369 }
1370 }
1371
1372 // Fallback: explicit blocking transfers (safe on all drivers).
1373 cl_int err = CL_SUCCESS;
1374 if(cl_mode == CL_MAP_WRITE)
1375 err = dt_opencl_write_host_to_device(devid, host_ptr, mem, roi->width, roi->height, (int)bpp);
1376 else if(cl_mode == CL_MAP_READ)
1377 err = dt_opencl_read_host_from_device(devid, host_ptr, mem, roi->width, roi->height, (int)bpp);
1378 else
1379 return 1;
1380
1381 if(err != CL_SUCCESS)
1382 {
1383 _cache_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] couldn't copy image %s for module %s (%s)\n",
1384 (cl_mode == CL_MAP_WRITE) ? "host to device" : "device to host",
1385 (module) ? module->op : "base buffer", message);
1386 return 1;
1387 }
1388
1389 _cache_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] successfully copied image %s for module %s (%s)\n",
1390 (cl_mode == CL_MAP_WRITE) ? "host to device" : "device to host",
1391 (module) ? module->op : "base buffer", message);
1392 return 0;
1393}
1394
1409float *dt_dev_pixelpipe_cache_restore_cl_buffer(dt_dev_pixelpipe_t *pipe, float *input, void *cl_mem_input,
1410 const dt_iop_roi_t *roi_in, dt_iop_module_t *module,
1411 const size_t in_bpp, dt_pixel_cache_entry_t *input_entry,
1412 const char *message)
1413{
1414 if(IS_NULL_PTR(cl_mem_input)) return input;
1416
1417 const int fail = dt_dev_pixelpipe_cache_sync_cl_buffer(pipe->devid, input, cl_mem_input, roi_in,
1418 CL_MAP_READ, in_bpp, module, message);
1420 return fail ? NULL : input;
1421}
1422
1451 float *input, void **cl_mem_input,
1452 const dt_iop_roi_t *roi_in, const size_t in_bpp,
1453 dt_pixel_cache_entry_t *input_entry,
1454 dt_pixel_cache_entry_t **locked_input_entry, void *keep)
1455{
1456 if(IS_NULL_PTR(locked_input_entry)) return 1;
1457 *locked_input_entry = NULL;
1458
1459 if(!IS_NULL_PTR(*cl_mem_input))
1460 {
1461 // We passed the OpenCL memory buffer through directly on vRAM from previous module.
1462 // This is fast and efficient.
1463 // If it's a true zero-copy pinned image, keep the input cache entry read-locked until kernels complete,
1464 // otherwise another thread may overwrite host memory while the GPU is still reading it.
1465 _cache_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] %s will use its input directly from vRAM\n", module->name());
1466 const cl_mem mem = (cl_mem)*cl_mem_input;
1468 {
1470 *locked_input_entry = input_entry;
1471 }
1472 return 0;
1473 }
1474
1475 if(IS_NULL_PTR(input))
1476 {
1477 _cache_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] %s has no input (cache)\n", module->name());
1478 return 1;
1479 }
1480
1482
1483 // Try to reuse a cached pinned buffer; otherwise allocate a new pinned image backed by `input`.
1484 gboolean input_reused_from_cache = FALSE;
1485 *cl_mem_input = dt_dev_pixelpipe_cache_get_cl_buffer(pipe->devid, input, roi_in, in_bpp, module,
1486 "input", input_entry,
1487 &input_reused_from_cache, keep);
1488 int fail = (IS_NULL_PTR(*cl_mem_input));
1489
1490 // If the input is true zero-copy, the GPU will access host memory asynchronously: keep the cache
1491 // entry read-locked until all kernels have completed. If not, drivers may use a device-side copy
1492 // which must be synchronized from the host before running kernels.
1493 gboolean keep_lock = FALSE;
1494 cl_mem mem = NULL;
1495 if(!fail && *cl_mem_input)
1496 {
1497 mem = (cl_mem)*cl_mem_input;
1498 keep_lock = dt_opencl_is_pinned_memory(mem);
1499 }
1500
1501 /* A reused cached pinned image already carries the authoritative device payload from the
1502 * previous module output. Re-uploading host RAM here would overwrite that valid vRAM state
1503 * with whatever stale contents the host buffer still has when the previous stage stayed GPU-only.
1504 * Only freshly allocated pinned inputs need an explicit host->device copy. */
1505 if(!fail && mem && !keep_lock && !input_reused_from_cache)
1506 {
1507 const cl_int err = dt_opencl_write_host_to_device(pipe->devid, input, mem, roi_in->width, roi_in->height,
1508 (int)in_bpp);
1509 if(err != CL_SUCCESS)
1510 {
1511 _cache_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] couldn't copy image host to device for module %s (%s)\n",
1512 (module) ? module->op : "base buffer", "cache to input");
1513 fail = TRUE;
1514 }
1515 else
1516 {
1517 _cache_print(DT_DEBUG_OPENCL, "[dev_pixelpipe] successfully copied image host to device for module %s (%s)\n",
1518 (module) ? module->op : "base buffer", "cache to input");
1519 }
1520 }
1521
1522 // Enforce sync with the CPU/RAM cache so lock validity is guaranteed.
1524
1525 if(keep_lock)
1526 *locked_input_entry = input_entry;
1527 else
1529
1530 return fail ? 1 : 0;
1531}
1532#else
1533void *dt_dev_pixelpipe_cache_get_cl_buffer(int devid, void *host_ptr, const dt_iop_roi_t *roi,
1534 size_t bpp, dt_iop_module_t *module, const char *message,
1536 gboolean *out_reused, void *keep)
1537{
1538 (void)devid;
1539 (void)host_ptr;
1540 (void)roi;
1541 (void)bpp;
1542 (void)module;
1543 (void)message;
1544 (void)entry;
1545 (void)keep;
1546 if(out_reused) *out_reused = FALSE;
1547 return NULL;
1548}
1549
1550void *dt_dev_pixelpipe_cache_alloc_cl_device_buffer(int devid, const dt_iop_roi_t *roi, size_t bpp,
1551 const dt_iop_module_t *module,
1552 const char *message, void *keep)
1553{
1554 (void)devid;
1555 (void)roi;
1556 (void)bpp;
1557 (void)module;
1558 (void)message;
1559 (void)keep;
1560 return NULL;
1561}
1562
1564 void *host_ptr, gboolean cache_device)
1565{
1566 (void)entry;
1567 (void)host_ptr;
1568 (void)cache_device;
1569 if(cl_mem_buffer) *cl_mem_buffer = NULL;
1570}
1571
1572int dt_dev_pixelpipe_cache_sync_cl_buffer(int devid, void *host_ptr, void *cl_mem_buffer,
1573 const dt_iop_roi_t *roi, int cl_mode, size_t bpp,
1574 dt_iop_module_t *module, const char *message)
1575{
1576 (void)devid;
1577 (void)host_ptr;
1578 (void)cl_mem_buffer;
1579 (void)roi;
1580 (void)cl_mode;
1581 (void)bpp;
1582 (void)module;
1583 (void)message;
1584 return 1;
1585}
1586
1588 void *cl_mem_input, const dt_iop_roi_t *roi_in,
1589 dt_iop_module_t *module, size_t in_bpp,
1590 dt_pixel_cache_entry_t *input_entry,
1591 const char *message)
1592{
1593 (void)pipe;
1594 (void)cl_mem_input;
1595 (void)roi_in;
1596 (void)module;
1597 (void)in_bpp;
1598 (void)input_entry;
1599 (void)message;
1600 return input;
1601}
1602
1604 float *input, void **cl_mem_input,
1605 const dt_iop_roi_t *roi_in, size_t in_bpp,
1606 dt_pixel_cache_entry_t *input_entry,
1607 dt_pixel_cache_entry_t **locked_input_entry,
1608 void *keep)
1609{
1610 (void)pipe;
1611 (void)module;
1612 (void)input;
1613 (void)cl_mem_input;
1614 (void)roi_in;
1615 (void)in_bpp;
1616 (void)input_entry;
1617 (void)locked_input_entry;
1618 (void)keep;
1619 return 1;
1620}
1621#endif
1622
1624{
1626 if(IS_NULL_PTR(cache) || IS_NULL_PTR(host_ptr)) return NULL;
1627
1628 dt_pthread_mutex_lock(&cache->lock);
1630 if(entry)
1633
1634 return entry;
1635}
1636
1637/* Allocations at or below this size bypass the system-pressure valve: they cannot
1638 * change the pressure meaningfully, and refusing them breaks functionality for no
1639 * benefit. Deliberately small -- this is an escape hatch for scratch and bookkeeping
1640 * buffers, not a hole large enough for pipeline tiles. */
1641#define DT_PIXELPIPE_CACHE_PRESSURE_EXEMPT_SIZE ((size_t)1024 * 1024)
1642
1643/* System memory-pressure valve (issue #1083).
1644 *
1645 * The internal budget (max_memory) is only a plan made at startup: it says nothing
1646 * about what the system can actually back RIGHT NOW, with other applications competing
1647 * for the same physical RAM. Before growing our committed footprint by `request_size`,
1648 * make sure the system-wide available memory keeps the configured floor
1649 * (dt_get_memory_pressure_floor()); when it doesn't, evict LRU entries to cover the
1650 * deficit, hand their pages back to the OS for real, and re-read what that actually
1651 * bought — so the system OOM-killer never has a reason to look at us.
1652 *
1653 * The probe is rate-limited and the cached value is decremented by our own
1654 * allocations in between, so the hot path pays one probe per PROBE_PERIOD at most.
1655 * That running estimate legitimately reaches 0 under sustained pressure, which is why
1656 * "the platform answers at all" is tracked separately (`sys_probe_valid`) instead of
1657 * being read off a 0 estimate: conflating the two would disable the valve at exactly
1658 * the moment it matters.
1659 *
1660 * Returns TRUE when the allocation may proceed. Returns FALSE when, even after
1661 * shedding everything evictable, the system could not take `request_size` more bytes
1662 * without dropping under HALF the floor: the caller must fail the allocation cleanly —
1663 * a failed pipeline with a message beats a silent SIGKILL from the OOM-killer.
1664 */
1665static gboolean _system_memory_pressure_valve(dt_dev_pixelpipe_cache_t *cache, size_t request_size)
1666{
1667 const size_t pressure_floor = dt_get_memory_pressure_floor();
1668 if(pressure_floor == 0) return TRUE;
1669
1670 dt_pthread_mutex_lock(&cache->lock);
1671
1672 const gint64 now = g_get_monotonic_time();
1673 const gint64 PROBE_PERIOD_US = 100000; // 100 ms
1674 if(cache->sys_probe_time_us == 0 || now - cache->sys_probe_time_us > PROBE_PERIOD_US)
1675 {
1676 const size_t probed = dt_get_system_available_mem();
1677 cache->sys_probe_valid = (probed > 0);
1678 cache->sys_available_est = probed;
1679 cache->sys_probe_time_us = now;
1680 }
1681
1682 // The platform gives us no way to know: pressure handling disabled, never refuse.
1683 if(!cache->sys_probe_valid)
1684 {
1686 return TRUE;
1687 }
1688
1689 if(cache->sys_available_est < request_size + pressure_floor)
1690 {
1691 const size_t deficit = request_size + pressure_floor - cache->sys_available_est;
1692 size_t freed = 0;
1693 while(freed < deficit && g_hash_table_size(cache->entries) > 0)
1694 {
1695 const size_t before = cache->current_memory;
1697 freed += before - cache->current_memory;
1698 }
1699
1700 if(freed)
1701 {
1702 /* Evicting only returns the pages to the ARENA. How much of that reaches the OS
1703 * is not ours to guess: the per-free lazy release is a no-op on Windows until a
1704 * decommit runs, and even where MADV_FREE applies, the kernel decides when those
1705 * pages stop counting against us. So hand them over for real, then take the new
1706 * number from the OS instead of crediting what we think we released — an
1707 * over-credit here would let the allocation through on a system that is still
1708 * just as full, which is precisely the OOM this valve exists to prevent.
1709 * Ordering note: cache->lock is held and dt_cache_arena_trim() takes arena.lock,
1710 * matching the cache->lock → arena.lock order the rest of this file already uses
1711 * (see the defrag path below). */
1712 dt_cache_arena_trim(&cache->arena);
1714 const size_t probed = dt_get_system_available_mem();
1715 cache->sys_probe_valid = (probed > 0);
1716 cache->sys_available_est = probed;
1717 cache->sys_probe_time_us = g_get_monotonic_time();
1718
1720 "[pixelpipe_cache] system memory pressure: shed %" G_GSIZE_FORMAT " MiB of cache "
1721 "(%" G_GSIZE_FORMAT " MiB available after trim, floor %" G_GSIZE_FORMAT " MiB)\n",
1722 freed / (1024 * 1024), cache->sys_available_est / (1024 * 1024),
1723 pressure_floor / (1024 * 1024));
1724 }
1725 }
1726
1727 // The post-trim re-probe can itself come back unanswered; absence of information is
1728 // never a reason to refuse (see the dt_get_system_available_mem() header contract).
1729 /* A request too small to move the needle must never be refused. The valve exists to
1730 * stop the cache COMMITTING LARGE BUFFERS into the memory the OS and other
1731 * applications need -- not to make Ansel fail. Because the floor term dominates the
1732 * comparison below, a system already under the floor refused EVERY allocation
1733 * regardless of size, printing the giveaway line
1734 *
1735 * [pixelpipe_cache] refusing to allocate 0 MiB: the system has only 1638 MiB ...
1736 *
1737 * and turning a transient shortage into a hard failure (it segfaulted startup through
1738 * an unchecked allocation in common/points.h). Anything at or below this size cannot
1739 * meaningfully change system pressure, and refusing it buys nothing. Larger requests
1740 * remain fully gated.
1741 *
1742 * The comparison below uses the WHOLE floor, not half of it. The floor used to be a
1743 * large derived number where a half-way hard limit made sense as hysteresis; it is now
1744 * an absolute reserve (DT_MEMORY_PRESSURE_FLOOR_DEFAULT, 200 MiB), and "always leave
1745 * 200 MiB to the OS" has to mean 200, not 100. The shed/trim step above already ran at
1746 * this same threshold, so reaching here means trimming did not recover enough. */
1747 const gboolean negligible = request_size <= DT_PIXELPIPE_CACHE_PRESSURE_EXEMPT_SIZE;
1748
1749 const gboolean allowed = negligible
1750 || !cache->sys_probe_valid
1751 || (cache->sys_available_est >= request_size + pressure_floor);
1752
1753 if(allowed)
1754 {
1755 // Optimistically debit the estimate now that the caller will commit these pages;
1756 // self-corrects at the next probe if the arena allocation fails afterwards.
1757 cache->sys_available_est
1758 = (cache->sys_available_est > request_size) ? cache->sys_available_est - request_size : 0;
1759 }
1760 else
1761 {
1762 // Warn the user at most every 10 s: this fires per failed allocation, on a
1763 // system that is already drowning.
1764 static gint64 last_warning_us = 0;
1765 if(now - last_warning_us > 10000000)
1766 {
1767 last_warning_us = now;
1768 _warn_user(_("Your system is running out of memory. "
1769 "Close other applications or add more RAM to your system."));
1770 }
1771 fprintf(stdout,
1772 "[pixelpipe_cache] refusing to allocate %" G_GSIZE_FORMAT " MiB: the system has only "
1773 "%" G_GSIZE_FORMAT " MiB of available RAM left (pressure floor: %" G_GSIZE_FORMAT " MiB)\n",
1774 request_size / (1024 * 1024), cache->sys_available_est / (1024 * 1024),
1775 pressure_floor / (1024 * 1024));
1776 }
1777
1779 return allowed;
1780}
1781
1782// Attempt to allocate from the arena; if fragmentation prevents it, evict LRU cache lines
1783// until a sufficiently large contiguous run is available (or nothing remains to evict).
1784static inline void *_arena_alloc_with_defrag(dt_dev_pixelpipe_cache_t *cache, size_t request_size,
1785 size_t *actual_size)
1786{
1787 // Never grow the committed footprint past what the system can actually take,
1788 // whatever our internal budget still allows.
1789 if(!_system_memory_pressure_valve(cache, request_size)) return NULL;
1790
1791 void *buf = dt_cache_arena_alloc(&cache->arena, request_size, actual_size);
1792 if(!IS_NULL_PTR(buf)) return buf;
1793
1794 uint32_t pages_needed = 0;
1795 if(dt_cache_arena_calc(&cache->arena, request_size, &pages_needed, NULL))
1796 {
1797 dt_pthread_mutex_lock(&cache->lock);
1798 uint32_t total_free_pages = 0, largest_free_run_pages = 0;
1799 dt_cache_arena_stats(&cache->arena, &total_free_pages, &largest_free_run_pages);
1800
1801 while(largest_free_run_pages < pages_needed && g_hash_table_size(cache->entries) > 0)
1802 {
1804 dt_cache_arena_stats(&cache->arena, &total_free_pages, &largest_free_run_pages);
1805 }
1807 }
1808
1809 return dt_cache_arena_alloc(&cache->arena, request_size, actual_size);
1810}
1811
1812static inline void _arena_stats_bytes(dt_dev_pixelpipe_cache_t *cache, uint32_t *total_pages,
1813 uint32_t *largest_pages, size_t *total_bytes, size_t *largest_bytes)
1814{
1815 dt_cache_arena_stats(&cache->arena, total_pages, largest_pages);
1816 const size_t page_size = cache->arena.page_size ? cache->arena.page_size : 1;
1817 if(total_bytes) *total_bytes = (size_t)(*total_pages) * page_size;
1818 if(largest_bytes) *largest_bytes = (size_t)(*largest_pages) * page_size;
1819}
1820
1821/* Largest contiguous free run in the arena, in bytes. The arena serves every
1822 * allocation from ONE contiguous run, and pinned entries partition its
1823 * address space — so at tiling-planning time this, not the byte headroom, is
1824 * the number a module's working set must actually fit (see the available-
1825 * memory cap in develop/tiling.c). Locked because the arena bitmap mutates
1826 * under concurrent pipes. */
1828{
1830 uint32_t total_pages = 0, largest_pages = 0;
1831 size_t total_bytes = 0, largest_bytes = 0;
1832 dt_pthread_mutex_lock(&cache->lock);
1833 _arena_stats_bytes(cache, &total_pages, &largest_pages, &total_bytes, &largest_bytes);
1835 return largest_bytes;
1836}
1837
1838static inline void _log_arena_allocation_failure(dt_dev_pixelpipe_cache_t *cache, size_t request_size,
1839 const char *entry_name, const char *module, uint64_t hash,
1840 gboolean name_is_file)
1841{
1842 uint32_t total_free_pages = 0, largest_free_run_pages = 0;
1843 size_t total_free_bytes = 0, largest_free_bytes = 0;
1844 _arena_stats_bytes(cache, &total_free_pages, &largest_free_run_pages, &total_free_bytes, &largest_free_bytes);
1845
1846 if(entry_name)
1847 fprintf(stdout,
1848 "[pixelpipe_cache] failed to allocate %" G_GSIZE_FORMAT " bytes for entry %" PRIu64 " (%s, module=%s) "
1849 "[arena largest=%" G_GSIZE_FORMAT " MiB, total=%" G_GSIZE_FORMAT " MiB, cache=%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT " MiB]\n",
1850 request_size, hash, entry_name, module ? module : "unknown",
1851 largest_free_bytes / (1024 * 1024), total_free_bytes / (1024 * 1024),
1852 cache->current_memory / (1024 * 1024), cache->max_memory / (1024 * 1024));
1853 else
1854 fprintf(stdout,
1855 "[pixelpipe_cache] failed to allocate %" G_GSIZE_FORMAT " bytes for entry %" PRIu64 " (module=%s) "
1856 "[arena largest=%" G_GSIZE_FORMAT " MiB, total=%" G_GSIZE_FORMAT " MiB, cache=%" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT " MiB]\n",
1857 request_size, hash, module ? module : "unknown",
1858 largest_free_bytes / (1024 * 1024), total_free_bytes / (1024 * 1024),
1859 cache->current_memory / (1024 * 1024), cache->max_memory / (1024 * 1024));
1860
1861 if(!IS_NULL_PTR(entry_name) && !IS_NULL_PTR(module))
1862 _warn_user(_("The pipeline cache is full while allocating `%s` (module `%s`). Either your RAM settings are too frugal or your RAM is too small."),
1863 entry_name, module);
1864 else if(!IS_NULL_PTR(entry_name))
1865 _warn_user(_("The pipeline cache is full while allocating `%s`. Either your RAM settings are too frugal or your RAM is too small."),
1866 entry_name);
1867 else if(!IS_NULL_PTR(module))
1868 _warn_user(_("The pipeline cache is full while processing module `%s`. Either your RAM settings are too frugal or your RAM is too small."),
1869 module);
1870 else
1871 _warn_user(_("The pipeline cache is full. Either your RAM settings are too frugal or your RAM is too small."));
1872
1873 (void)name_is_file; // kept for signature symmetry if future callers need it.
1874}
1875
1876// keep: OpenCL buffer to NOT release
1877#ifdef HAVE_OPENCL
1878// Release this device's vRAM payloads for one entry. The caller has already established that
1879// the entry is idle (refcount == 0, not write-locked), so the device buffers are nobody's live
1880// input and reclaiming them honors the flush's purpose: free vRAM for later allocations.
1881// Returns TRUE if the entry holds no buffer at all afterwards (no host RAM, no vRAM on any
1882// device) and should therefore be evicted entirely instead of lingering as a husk.
1883static gboolean _cache_entry_clmem_flush_device(dt_pixel_cache_entry_t *entry, const int devid)
1884{
1885 // devid is always >= 0 here: dt_dev_pixelpipe_cache_flush_clmem() early-returns
1886 // otherwise. Only cachelines living on this specific device are candidates.
1888
1889 for(GList *l = g_list_first(entry->cl_mem_list); l;)
1890 {
1891 GList *next = g_list_next(l);
1892 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
1893 if(IS_NULL_PTR(c->mem))
1894 {
1895 // Don't keep cacheline with NULL buffer
1896 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
1897 dt_free(c);
1898 l = next;
1899 continue;
1900 }
1901
1902 gboolean referenced = c->refs > 0;
1903 gboolean not_ours = dt_opencl_get_mem_context_id(c->mem) != devid;
1904
1905 if(referenced || not_ours)
1906 {
1907 // Don't flush cachelines that don't belong to the current OpenCL device,
1908 // or are still borrowed by an in-flight GPU module (per-payload refs > 0).
1911 "[dt_dev_pixelpipe_cache_flush_clmem] for entry %" PRIu64 ": couldn't flush %p "
1912 "(referenced=%i not ours=%i)\n",
1913 entry->hash, c->mem, referenced, not_ours);
1914 l = next;
1915 continue;
1916 }
1917
1918 entry->cl_mem_list = g_list_delete_link(entry->cl_mem_list, l);
1920 dt_free(c);
1921 l = next;
1922 }
1923
1924 // A cacheline that now carries neither a host buffer nor any vRAM is a husk: the cache would
1925 // still hand it out as a hit, making a later consumer abort with "has no RAM nor vRAM input"
1926 // (issue #817 skull thumbnails). Signal the caller to delete it entirely.
1927 const gboolean empty = IS_NULL_PTR(entry->data) && IS_NULL_PTR(entry->cl_mem_list);
1929 return empty;
1930}
1931#else
1932static gboolean _cache_entry_clmem_flush_device(dt_pixel_cache_entry_t *entry, const int devid)
1933{
1934 return FALSE;
1935}
1936#endif
1937
1939{
1941 // allocate the data buffer
1942 if(IS_NULL_PTR(cache_entry->data))
1943 {
1944 cache_entry->data = _arena_alloc_with_defrag(cache, cache_entry->size, &cache_entry->size);
1945
1946 if(IS_NULL_PTR(cache_entry->data))
1947 {
1948 const char *module = dt_pixelpipe_cache_current_module;
1949 _log_arena_allocation_failure(cache, cache_entry->size, cache_entry->name, module,
1950 cache_entry->hash, FALSE);
1951 }
1952 }
1953
1954 return cache_entry->data;
1955}
1956
1958{
1959 return entry ? entry->data : NULL;
1960}
1961
1963{
1964 return entry ? entry->size : 0;
1965}
1966
1967/* Kernel memory pressure: the cache's half of it.
1968 *
1969 * caches/pixelpipe_cache_pressure.c decides WHEN memory must go back to the system and HOW MUCH,
1970 * from the kernel counters system/memory_pressure.c reads; this is the part only the cache can
1971 * answer -- what it holds, and what giving some of it back costs. Both run under `lock`, taken by
1972 * whoever called into the monitor, the watcher's own thread included. */
1973static size_t _pressure_held(void *user)
1974{
1975 return ((dt_dev_pixelpipe_cache_t *)user)->current_memory;
1976}
1977
1978static size_t _pressure_shed(void *user, const size_t target, size_t *given_back)
1979{
1981
1982 while(cache->current_memory > target && g_hash_table_size(cache->entries) > 0)
1984
1985 // Hand the pages over now rather than when the kernel gets to MADV_FREE, and make the
1986 // available-RAM valve re-read the system instead of trusting its pre-shed estimate.
1987 *given_back = dt_cache_arena_trim(&cache->arena);
1989 cache->sys_probe_time_us = 0;
1990 return cache->current_memory;
1991}
1992
1999
2000// The kernel woke the watcher, on a thread of its own: take the lock the monitor is guarded by.
2001static void _pressure_wake(void *user)
2002{
2005 dt_pthread_mutex_lock(&cache->lock);
2008}
2009
2010// What allocations evict down to: the plan, or less under kernel memory pressure.
2011// WARNING: non thread-safe
2012static inline size_t _cache_budget_locked(const dt_dev_pixelpipe_cache_t *cache)
2013{
2015}
2016
2017// Close the kernel-pressure window and act on it. WARNING: non thread-safe
2023
2024// WARNING: non thread-safe
2025static int _free_space_to_alloc(dt_dev_pixelpipe_cache_t *cache, const size_t size, const uint64_t hash,
2026 const char *name)
2027{
2028 /* Under kernel memory pressure, keep to the lowered budget as far as eviction allows. It is a
2029 * target, never a reason to fail: when everything left is in use the allocation still goes
2030 * ahead, and only the plan below is a hard limit. */
2032 const size_t budget = _cache_budget_locked(cache);
2033 while(cache->current_memory + size > budget && g_hash_table_size(cache->entries) > 0)
2035
2036 // Free up space if needed to match the max memory limit
2037 // If error, all entries are currently locked or in use, so we cannot free space to allocate a new entry.
2038 int error = 0;
2039 while(cache->current_memory + size > cache->max_memory && g_hash_table_size(cache->entries) > 0 && !error)
2041
2042 if(cache->current_memory + size > cache->max_memory)
2043 {
2044 const char *module = dt_pixelpipe_cache_current_module;
2045 const gboolean name_is_file = (!IS_NULL_PTR(name)) && (strchr(name, '/') != NULL) && (strchr(name, ':') != NULL);
2046 if(IS_NULL_PTR(name)) name = g_strdup("unknown");
2047
2048 if(hash)
2049 fprintf(stdout, "[pixelpipe] cache is full, cannot allocate new entry %" PRIu64 " (%s)\n", hash, name);
2050 else
2051 fprintf(stdout, "[pixelpipe] cache is full, cannot allocate new entry (%s)\n", name);
2052 if(!IS_NULL_PTR(name) && !IS_NULL_PTR(module) && name_is_file)
2053 _warn_user(_("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);
2054 else if(!IS_NULL_PTR(name))
2055 _warn_user(_("The pipeline cache is full while allocating `%s`. Either your RAM settings are too frugal or your RAM is too small."), name);
2056 else if(!IS_NULL_PTR(module))
2057 _warn_user(_("The pipeline cache is full while processing module `%s`. Either your RAM settings are too frugal or your RAM is too small."), module);
2058 else
2059 _warn_user(_("The pipeline cache is full. Either your RAM settings are too frugal or your RAM is too small."));
2060 }
2061
2062 return error;
2063}
2064
2066 const char *name)
2067{
2069 // Free up space if needed to match the max memory limit
2070 // If error, all entries are currently locked or in use, so we cannot free space to allocate a new entry.
2071 dt_pthread_mutex_lock(&cache->lock);
2072 int error = _free_space_to_alloc(cache, size, 0, name);
2074
2075 if(error) return NULL;
2076
2077 // Page size is the desired size + AVX/SSE rounding
2078 size_t page_size = 0;
2079 void *buf = _arena_alloc_with_defrag(cache, size, &page_size);
2080
2081 if(IS_NULL_PTR(buf))
2082 {
2083 _log_arena_allocation_failure(cache, size, name, NULL, 0, FALSE);
2084 return NULL;
2085 }
2086
2087 void *aligned = __builtin_assume_aligned(buf, DT_CACHELINE_BYTES);
2088
2089 const uint64_t hash = (uint64_t)(uintptr_t)(aligned);
2090
2091 dt_pthread_mutex_lock(&cache->lock);
2092 dt_pixel_cache_entry_t *cache_entry
2093 = dt_pixel_cache_new_entry(hash, page_size, name, id, cache, FALSE, cache->external_entries);
2094
2095 if(IS_NULL_PTR(cache_entry))
2096 {
2098 dt_cache_arena_free(&cache->arena, buf, page_size);
2099 return NULL;
2100 }
2101
2102 // Keep this entry marked as "used" for diagnostics/bookkeeping.
2103 // Note that external_entries are not subject to LRU eviction, so we must not keep
2104 // a thread-owned rwlock held across the lifetime of the buffer (it may be freed
2105 // from a different thread during cleanup paths).
2106 _non_thread_safe_cache_ref_count_entry(cache, TRUE, cache_entry);
2107 cache_entry->data = aligned;
2108 _pixel_cache_touch(cache_entry);
2109 cache_entry->external_alloc = TRUE;
2111 return aligned;
2112}
2113
2114void dt_pixelpipe_cache_free_align_cache(void **mem, const char *message)
2115{
2117 if(IS_NULL_PTR(mem) || !*mem) return;
2118
2119 dt_pthread_mutex_lock(&cache->lock);
2120 const uint64_t hash = (uint64_t)(uintptr_t)(*mem);
2122 if(IS_NULL_PTR(cache_entry) || !cache_entry->external_alloc)
2123 {
2125 fprintf(stdout, "error while freeing cache entry: no entry found but we have a buffer, %s.\n", message);
2126 raise(SIGSEGV); // triggers dt_set_signal_handlers() backtrace on Unix
2127 return;
2128 }
2129
2130 _non_thread_safe_cache_ref_count_entry(cache, FALSE, cache_entry);
2131 g_hash_table_remove(cache->external_entries, &cache_entry->hash);
2132 *mem = NULL;
2133
2135}
2136
2137
2138// WARNING: not thread-safe, protect its calls with mutex lock
2140 const char *name, const int id,
2141 dt_dev_pixelpipe_cache_t *cache, gboolean alloc,
2142 GHashTable *table)
2143{
2144 uint32_t pages_needed = 0;
2145 size_t rounded_size = 0;
2146 if(!dt_cache_arena_calc(&cache->arena, size, &pages_needed, &rounded_size))
2147 {
2148 fprintf(stderr, "[pixelpipe] invalid cache entry size %" G_GSIZE_FORMAT " for %s\n", size, name);
2149 return NULL;
2150 }
2151
2152 int error = _free_space_to_alloc(cache, rounded_size, hash, name);
2153 if(error) return NULL;
2154
2156 if(IS_NULL_PTR(cache_entry)) return NULL;
2157
2158 // Metadata, easy to free in batch if need be
2159 cache_entry->size = rounded_size;
2160 /* A fresh entry is the MOST recently used one, not the oldest: leaving `age` at 0 until the
2161 * caller reaches _pixelpipe_cache_finalize_entry() makes it the unconditional LRU victim and
2162 * gives _for_each_remove_old() a `delta` of the whole process uptime in between. */
2163 dt_atomic_set_uint64(&cache_entry->age, (uint64_t)g_get_monotonic_time());
2164 cache_entry->hits = 0;
2165 cache_entry->hash = hash;
2166 cache_entry->serial = cache->next_serial++;
2167 cache_entry->id = id;
2169 cache_entry->refcount = 0;
2170 cache_entry->auto_destroy = FALSE;
2171 cache_entry->external_alloc = FALSE;
2172 cache_entry->data = NULL;
2173 cache_entry->cache = cache;
2174 cache_entry->cl_mem_list = NULL;
2175 dt_pthread_mutex_init(&cache_entry->cl_mem_lock, NULL);
2176
2177 // Optionally alloc the actual buffer, but still record its size in cache
2178 if(alloc) dt_pixel_cache_alloc(cache_entry);
2179
2180 if(alloc && IS_NULL_PTR(cache_entry->data))
2181 {
2182 dt_free(cache_entry);
2183 return NULL;
2184 }
2185
2186 // Metadata that need alloc
2187 cache_entry->name = g_strdup(name);
2188 dt_pthread_rwlock_init(&cache_entry->lock, NULL);
2189
2190 uint64_t *key = g_malloc(sizeof(*key));
2191 if(IS_NULL_PTR(key))
2192 {
2193 dt_pthread_rwlock_destroy(&cache_entry->lock);
2194 dt_free(cache_entry->name);
2196 dt_free(cache_entry);
2197 return NULL;
2198 }
2199 *key = hash;
2200 g_hash_table_insert(table, key, cache_entry);
2201
2202 // Note : we grow the cache size even though the data buffer is not yet allocated
2203 // This is planning
2204 cache->current_memory += rounded_size;
2205
2206 return cache_entry;
2207}
2208
2209
2211{
2212 if(IS_NULL_PTR(cache_entry)) return;
2213
2214 _pixel_cache_message(cache_entry, "freed", FALSE);
2215
2216 _observe_delete(cache_entry->hash, cache_entry->size, cache_entry->id,
2217 cache_entry->name);
2218
2219 /* Every live entry belongs to the one and only global pixelpipe cache, so its back-reference
2220 * must match it. If it doesn't, the entry struct has been corrupted (we have seen a single
2221 * flipped bit in the pointer from faulty RAM) or is stale: reaching cache->arena or
2222 * cache->current_memory through it would dereference a wild pointer and turn an innocuous
2223 * teardown into a SIGSEGV. Skip the arena free and the accounting in that case -- the arena is
2224 * unmapped wholesale right after, so nothing actually leaks. */
2225 dt_dev_pixelpipe_cache_t *cache = cache_entry->cache;
2226 if(cache != _pixelpipe_cache)
2227 {
2228 fprintf(stderr, "[pixelpipe] cache entry %p has a corrupted back-reference (%p, expected %p); "
2229 "skipping arena free to avoid a crash\n",
2230 (void *)cache_entry, (void *)cache, (void *)_pixelpipe_cache);
2231 cache = NULL;
2232 }
2233
2234 if(cache_entry->data && cache)
2235 {
2236#ifdef HAVE_OPENCL
2237 dt_pthread_mutex_lock(&cache_entry->cl_mem_lock);
2238 for(GList *l = cache_entry->cl_mem_list; l; l = g_list_next(l))
2239 {
2240 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
2241 if(IS_NULL_PTR(c) || c->host_ptr != cache_entry->data) continue;
2242
2243 /* Host-backed OpenCL images may still dereference `cache_entry->data` asynchronously until their
2244 * queued work completes. We therefore wait for the owning device before releasing the host arena slot,
2245 * otherwise an auto-destroyed intermediate can be recycled into another module output while the GPU
2246 * is still reading the previous pixels.
2247 *
2248 * dt_opencl_finish() flushes that device's event list, which is only thread-safe while we hold
2249 * the device lock (dt_opencl_reserve_device_by_id()). Touching it without that lock races the eventlist/cl_mem
2250 * bookkeeping of whichever pixelpipe is currently running on that device and crashes inside
2251 * clWaitForEvents (issues #859, #864, #131742439 -- the 3-min GUI garbage collection timeout
2252 * dt_dev_pixelpipe_cache_flush_old() owns no device lock). _free_cache_entry() also runs from LRU
2253 * eviction under a live pipe that already holds this very lock, so we can neither assume we hold it
2254 * nor block on it: trylock. If we acquire the device it is idle and we drain it safely; if we don't,
2255 * the owner is draining it itself at the end of its run and this refcount==0 entry is not one of its
2256 * live borrows, so skipping the finish is safe. */
2257 const int mem_devid = dt_opencl_get_mem_context_id((cl_mem)c->mem);
2258 if(mem_devid >= 0 && dt_opencl_is_inited()
2260 {
2261 dt_opencl_finish(mem_devid);
2262 dt_opencl_release_device(mem_devid);
2263 }
2264 }
2265 dt_pthread_mutex_unlock(&cache_entry->cl_mem_lock);
2266#endif
2267
2269 dt_cache_arena_free(&cache->arena, cache_entry->data, cache_entry->size);
2270 }
2271 else
2272 {
2274 }
2275
2276 cache_entry->data = NULL;
2277 if(cache) cache->current_memory -= cache_entry->size;
2278 dt_pthread_rwlock_destroy(&cache_entry->lock);
2280 dt_free(cache_entry->name);
2281 dt_free(cache_entry);
2282}
2283
2284static int garbage_collection = 0;
2285static int pressure_shedding = 0;
2286
2287gboolean dt_dev_pixelpipe_cache_init(size_t max_memory, const gboolean verbose,
2288 const gboolean verbose_detail)
2289{
2290 _verbose = verbose;
2291 _verbose_detail = verbose_detail;
2293 dt_pthread_mutex_init(&cache->lock, NULL);
2294 cache->entries = g_hash_table_new_full(g_int64_hash, g_int64_equal, dt_free_gpointer, (GDestroyNotify)_free_cache_entry);
2295 cache->external_entries = g_hash_table_new_full(g_int64_hash, g_int64_equal, dt_free_gpointer, (GDestroyNotify)_free_cache_entry);
2296 cache->max_memory = max_memory;
2297 cache->current_memory = 0;
2298 cache->next_serial = 1;
2299 cache->queries = cache->hits = 0;
2300 cache->sys_probe_time_us = 0;
2301 cache->sys_available_est = 0;
2302 cache->sys_probe_valid = FALSE;
2303 dt_pixelpipe_cache_pressure_monitor_init(&cache->psi, max_memory);
2304
2305 if(IS_NULL_PTR(cache->entries) || IS_NULL_PTR(cache->external_entries))
2306 {
2307 if(cache->entries) g_hash_table_destroy(cache->entries);
2308 if(cache->external_entries) g_hash_table_destroy(cache->external_entries);
2310 dt_free(cache);
2311 return FALSE;
2312 }
2313
2314 if(dt_cache_arena_init(&cache->arena, cache->max_memory))
2315 {
2317 g_hash_table_destroy(cache->external_entries);
2318 g_hash_table_destroy(cache->entries);
2319 dt_free(cache);
2320 return FALSE;
2321 }
2322
2323 // Run every 3 minutes
2324 garbage_collection = g_timeout_add(3 * 60 * 1000, (GSourceFunc)dt_dev_pixelpipe_cache_flush_old, cache);
2325
2326 // React within seconds when ANOTHER application's allocations push the system
2327 // toward memory starvation while we sit idle (the alloc-time pressure valve only
2328 // runs when we allocate). No-ops when the system has RAM to spare. Every 2 s, one
2329 // kernel-pressure window (DT_PIXELPIPE_CACHE_PSI_WINDOW_US).
2330 pressure_shedding = g_timeout_add_seconds(2, (GSourceFunc)_memory_pressure_shedder, cache);
2331
2332 // And react without waiting for either, since a thrashing machine runs neither.
2334
2335 _pixelpipe_cache = cache;
2336 return TRUE;
2337}
2338
2339
2341{
2343
2344 // Before anything it holds goes away: the watcher takes `lock` and walks the entries.
2346
2347 g_hash_table_destroy(cache->external_entries);
2348 g_hash_table_destroy(cache->entries);
2349 cache->external_entries = NULL;
2350 cache->entries = NULL;
2353
2354 if(garbage_collection != 0)
2355 {
2356 g_source_remove(garbage_collection);
2358 }
2359
2360 if(pressure_shedding != 0)
2361 {
2362 g_source_remove(pressure_shedding);
2364 }
2365
2366 if(_pixelpipe_cache == cache) _pixelpipe_cache = NULL;
2367}
2368
2370 const uint64_t hash, const size_t size,
2371 const char *name, const int id)
2372{
2373 dt_pixel_cache_entry_t *cache_entry = dt_pixel_cache_new_entry(hash, size, name, id, cache, FALSE, cache->entries);
2374 if(IS_NULL_PTR(cache_entry)) return NULL;
2375
2376 // Increase ref_count, consumer will have to decrease it
2377 _non_thread_safe_cache_ref_count_entry(cache, TRUE, cache_entry);
2378
2379 // Acquire write lock so caller can populate data safely
2381
2382 return cache_entry;
2383}
2384
2386 const uint64_t new_hash, const size_t size,
2387 const dt_pixel_cache_entry_t *reuse_hint)
2388{
2389 if(IS_NULL_PTR(cache) || IS_NULL_PTR(reuse_hint)) return NULL;
2390
2391 const uint64_t old_hash = reuse_hint->hash;
2392 if(old_hash == DT_PIXELPIPE_CACHE_HASH_INVALID || old_hash == new_hash) return NULL;
2393 if(reuse_hint->size < size) return NULL;
2394
2395 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, old_hash);
2396 if(IS_NULL_PTR(cache_entry)) return NULL;
2397 if(cache_entry->serial != reuse_hint->serial) return NULL;
2398 if(cache_entry->auto_destroy) return NULL;
2399 if(cache_entry->size < size) return NULL;
2400 if(_non_threadsafe_cache_get_entry(cache, cache->entries, new_hash)) return NULL;
2401
2402 _non_thread_safe_cache_ref_count_entry(cache, TRUE, cache_entry);
2404
2405 /* Rekey reuse transfers the RAM arena slot to a completely different hash. Any cached OpenCL payload
2406 * still attached to the previous owner would otherwise remain reachable through the new hash and could
2407 * later be materialized as if it belonged to the new module output. Bail out if some GPU path is still
2408 * borrowing one of those payloads, otherwise flush the stale bookkeeping before publishing the new hash. */
2409 dt_pthread_mutex_lock(&cache_entry->cl_mem_lock);
2410 for(GList *l = cache_entry->cl_mem_list; l; l = g_list_next(l))
2411 {
2412 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
2413 if(c && c->refs > 0)
2414 {
2415 dt_pthread_mutex_unlock(&cache_entry->cl_mem_lock);
2417 // _non_thread_safe_: our caller holds cache->lock (see the function name), and the
2418 // locking variant would take it again on a non-recursive mutex.
2419 _non_thread_safe_cache_ref_count_entry(cache, FALSE, cache_entry);
2420 return NULL;
2421 }
2422 }
2423 dt_pthread_mutex_unlock(&cache_entry->cl_mem_lock);
2424
2425 gpointer stolen_key = NULL;
2426 gpointer stolen_value = NULL;
2427 if(!g_hash_table_steal_extended(cache->entries, &old_hash, &stolen_key, &stolen_value)
2428 || stolen_value != cache_entry)
2429 {
2430 if(stolen_key && stolen_value) g_hash_table_insert(cache->entries, stolen_key, stolen_value);
2432 // _non_thread_safe_: same reason as the bail path above -- cache->lock is already ours.
2433 _non_thread_safe_cache_ref_count_entry(cache, FALSE, cache_entry);
2434 return NULL;
2435 }
2436
2437 *(uint64_t *)stolen_key = new_hash;
2438 cache_entry->hash = new_hash;
2439 g_hash_table_insert(cache->entries, stolen_key, cache_entry);
2440
2441 _observe_rekey(old_hash, new_hash);
2442
2444 "[pixelpipe_cache] writable rekey old=%" PRIu64 " new=%" PRIu64 " entry=%" PRIu64 "/%" PRIu64
2445 " refs=%i auto=%i data=%p module=%s\n",
2446 old_hash, new_hash, cache_entry->hash, cache_entry->serial,
2447 dt_atomic_get_int(&cache_entry->refcount), cache_entry->auto_destroy, cache_entry->data,
2449 return cache_entry;
2450}
2451
2452
2454 const size_t size, const char *name, const int id,
2455 const gboolean alloc, void **data,
2456 dt_pixel_cache_entry_t **entry)
2457{
2460 {
2461 _cache_print(DT_DEBUG_PIPECACHE, "[pixelpipe_cache] refusing invalid hash allocation for %s\n",
2462 name ? name : "unknown");
2463 if(data) *data = NULL;
2464 if(entry) *entry = NULL;
2465 return 1;
2466 }
2467
2468 // Search or create cache entry (under cache lock)
2469 dt_pthread_mutex_lock(&cache->lock);
2470 cache->queries++;
2471
2472 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, hash);
2473 if(!IS_NULL_PTR(cache_entry) && cache_entry->auto_destroy)
2474 {
2475 _pixel_cache_message(cache_entry, "dropping auto-destroy entry before cache_get reuse", FALSE);
2476 if(_non_thread_safe_cache_remove(cache, FALSE, cache_entry, cache->entries) == 0)
2477 cache_entry = NULL;
2478 }
2479
2480 if(!IS_NULL_PTR(cache_entry))
2481 {
2482 cache->hits++;
2483 cache_entry->hits++;
2484 _non_thread_safe_cache_ref_count_entry(cache, TRUE, cache_entry);
2486
2487 // Allocate on demand if requested (e.g. when falling back from vRAM-only buffers).
2488 if(alloc && IS_NULL_PTR(cache_entry->data))
2489 {
2491 dt_pixel_cache_alloc(cache_entry);
2493 }
2494
2495 _pixelpipe_cache_finalize_entry(cache_entry, data, "found");
2496 if(entry) *entry = cache_entry;
2497 // existing output reused: a cache hit (entry pinned by the ref above, safe to read)
2498 _observe_read(hash, cache_entry->size);
2499
2500 return 0;
2501 }
2502
2503 cache_entry = _pixelpipe_cache_create_entry_locked(cache, hash, size, name, id);
2504 if(IS_NULL_PTR(cache_entry))
2505 {
2506 _cache_print(DT_DEBUG_PIPECACHE, "couldn't allocate new cache entry %" PRIu64 "\n", hash);
2508 if(entry) *entry = NULL;
2509 return 1;
2510 }
2511
2512 // Release cache lock AFTER acquiring entry locks to prevent other threads to capture it in-between
2514
2515 // Alloc after releasing the lock for better runtimes
2516 if(alloc) dt_pixel_cache_alloc(cache_entry);
2517
2518 _cache_print(DT_DEBUG_PIPECACHE, "[pixelpipe_cache] Write-lock on entry (new cache entry %" PRIu64 " for %s pipeline)\n",
2519 hash, name);
2520 _pixelpipe_cache_finalize_entry(cache_entry, data, "created");
2521
2522 if(entry) *entry = cache_entry;
2523 return 1;
2524}
2525
2528 const size_t size, const char *name, const int id,
2529 const gboolean alloc, const gboolean allow_rekey_reuse,
2530 const dt_pixel_cache_entry_t *reuse_hint,
2531 void **data,
2532 dt_pixel_cache_entry_t **entry)
2533{
2536 {
2537 if(data) *data = NULL;
2538 if(entry) *entry = NULL;
2540 }
2541
2542 dt_pthread_mutex_lock(&cache->lock);
2543 cache->queries++;
2544
2545 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, hash);
2546 if(!IS_NULL_PTR(cache_entry) && cache_entry->auto_destroy)
2547 {
2548 _pixel_cache_message(cache_entry, "dropping auto-destroy entry before writable reuse", FALSE);
2549 if(_non_thread_safe_cache_remove(cache, FALSE, cache_entry, cache->entries) == 0)
2550 cache_entry = NULL;
2551 }
2552
2553 if(!IS_NULL_PTR(cache_entry))
2554 {
2555 /* Another pipe already owns this exact hash: the caller consumes that cacheline instead of
2556 * writing its own, so this is a genuine hit. `queries` was already counted above; count the
2557 * hit and refresh the MRU too, or a cacheline shared between the preview and the full pipe
2558 * ages as if only its original producer ever touched it. */
2559 cache->hits++;
2560 cache_entry->hits++;
2561 _pixel_cache_touch(cache_entry);
2563 if(data) *data = NULL;
2564 if(entry) *entry = NULL;
2566 }
2567
2568 if(allow_rekey_reuse)
2569 {
2570 cache_entry = _cache_try_rekey_reuse_locked(cache, hash, size, reuse_hint);
2571 if(!IS_NULL_PTR(cache_entry))
2572 {
2574 if(alloc && IS_NULL_PTR(cache_entry->data)) dt_pixel_cache_alloc(cache_entry);
2575 _pixelpipe_cache_finalize_entry(cache_entry, data, "writable-rekeyed");
2576 if(entry) *entry = cache_entry;
2578 }
2579 }
2580
2581 cache_entry = _pixelpipe_cache_create_entry_locked(cache, hash, size, name, id);
2582 if(IS_NULL_PTR(cache_entry))
2583 {
2585 if(data) *data = NULL;
2586 if(entry) *entry = NULL;
2588 }
2589
2591
2592 if(alloc) dt_pixel_cache_alloc(cache_entry);
2593 _pixelpipe_cache_finalize_entry(cache_entry, data, "writable-created");
2594 if(entry) *entry = cache_entry;
2596}
2597
2599 void **data)
2600{
2601 dt_pthread_mutex_lock(&cache->lock);
2602 cache->queries++;
2603 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, hash);
2604
2605 const gboolean hit = !IS_NULL_PTR(cache_entry);
2606 const size_t hit_size = hit ? cache_entry->size : 0;
2607 if(hit)
2608 {
2609 cache->hits++;
2610 cache_entry->hits++;
2611 _pixelpipe_cache_finalize_entry(cache_entry, data, "found");
2612 }
2613
2615
2616 if(hit ) _observe_read(hash, hit_size);
2617
2618 return cache_entry;
2619}
2620
2621#ifdef HAVE_OPENCL
2623 const int preferred_devid, void **cl_mem_output)
2624{
2625 if(IS_NULL_PTR(cache_entry) || IS_NULL_PTR(cl_mem_output) || !IS_NULL_PTR(*cl_mem_output) || preferred_devid < 0)
2626 return FALSE;
2627
2628 dt_pthread_mutex_lock(&cache_entry->cl_mem_lock);
2629 for(GList *l = cache_entry->cl_mem_list; l;)
2630 {
2631 GList *next = g_list_next(l);
2632 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
2633 if(!IS_NULL_PTR(c->mem) && c->refs == 0
2634 && dt_opencl_get_mem_context_id((cl_mem)c->mem) == preferred_devid)
2635 {
2636 cache_entry->cl_mem_list = g_list_delete_link(cache_entry->cl_mem_list, l);
2637 *cl_mem_output = c->mem;
2638 dt_free(c);
2639 _pixel_cache_touch(cache_entry);
2640 break;
2641 }
2642 l = next;
2643 }
2644 dt_pthread_mutex_unlock(&cache_entry->cl_mem_lock);
2645
2646 return !IS_NULL_PTR(*cl_mem_output);
2647}
2648#else
2650 const int preferred_devid, void **cl_mem_output)
2651{
2652 return FALSE;
2653}
2654#endif
2655
2657 const int preferred_devid, void **data)
2658{
2660 if(data) *data = NULL;
2661 if(IS_NULL_PTR(cache) || IS_NULL_PTR(cache_entry)) return FALSE;
2662
2663 if(dt_pixel_cache_entry_get_data(cache_entry) != NULL)
2664 {
2665 _pixel_cache_touch(cache_entry);
2666 if(!IS_NULL_PTR(data)) *data = dt_pixel_cache_entry_get_data(cache_entry);
2667 return TRUE;
2668 }
2669
2670 if(!_cache_entry_materialize_host_data(cache, preferred_devid, cache_entry))
2671 return FALSE;
2672
2673 _pixel_cache_touch(cache_entry);
2674 if(!IS_NULL_PTR(data)) *data = dt_pixel_cache_entry_get_data(cache_entry);
2675 return dt_pixel_cache_entry_get_data(cache_entry) != NULL;
2676}
2677
2678gboolean dt_dev_pixelpipe_cache_peek(const uint64_t hash, void **data,
2679 dt_pixel_cache_entry_t **entry, const int preferred_devid,
2680 void **cl_mem_output)
2681{
2683 if(data) *data = NULL;
2684 if(entry) *entry = NULL;
2685 if(cl_mem_output) *cl_mem_output = NULL;
2686
2688 return FALSE;
2689
2690 dt_pixel_cache_entry_t *cache_entry = _cache_lookup_existing(cache, hash, data);
2691 if(IS_NULL_PTR(cache_entry)) return FALSE;
2692
2693 if(data) *data = dt_pixel_cache_entry_get_data(cache_entry);
2694
2695 /* Exact-hit callers treat the returned payload as already published. Reject
2696 * cachelines that are still write-locked: reusable output cachelines are
2697 * rekeyed to their new hash before recompute starts, so exposing them here
2698 * would let concurrent pipes consume stale or half-written buffers. */
2699 if(dt_pthread_rwlock_tryrdlock(&cache_entry->lock) != 0)
2700 {
2701 _trace_exact_hit("locked", hash, cache_entry, data ? *data : NULL,
2702 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2703 if(data) *data = NULL;
2704 return FALSE;
2705 }
2706 dt_pthread_rwlock_unlock(&cache_entry->lock);
2707
2708 if(IS_NULL_PTR(data) && IS_NULL_PTR(cl_mem_output))
2709 {
2710 if(entry) *entry = cache_entry;
2711 return TRUE;
2712 }
2713
2714 /* Picker-triggered aborts can leave a cacheline temporarily present under its
2715 * hash while it is already marked auto-destroy. Those entries must never exact-hit:
2716 * they belong to the aborted lifecycle and must force a rebuild on the next run. */
2717 if(cache_entry->auto_destroy)
2718 {
2719 _trace_exact_hit("auto-destroy", hash, cache_entry, data ? *data : NULL,
2720 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2721 if(data) *data = NULL;
2722 return FALSE;
2723 }
2724
2725 if(dt_pixel_cache_entry_get_data(cache_entry) != NULL)
2726 {
2727 if(data) *data = dt_pixel_cache_entry_get_data(cache_entry);
2728 _cache_try_restore_device_payload(cache_entry, preferred_devid, cl_mem_output);
2729
2730 _trace_exact_hit("host", hash, cache_entry, data ? *data : NULL,
2731 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2732 if(entry) *entry = cache_entry;
2733 return TRUE;
2734 }
2735
2736 /* `preferred_devid < 0` means the caller is on a CPU path and does not own any
2737 * OpenCL device. In that case, hostless cachelines are not consumable here:
2738 * reopening device-only payloads would enqueue hidden GPU work without a locked
2739 * device, while reporting a device-only exact-hit would let CPU callers sample
2740 * an uninitialized host buffer. */
2741 if(preferred_devid < 0)
2742 {
2743 _trace_exact_hit("cpu-no-device", hash, cache_entry, NULL, NULL, preferred_devid, FALSE);
2744 return FALSE;
2745 }
2746
2747 if(_cache_try_restore_device_payload(cache_entry, preferred_devid, cl_mem_output))
2748 {
2749 _trace_exact_hit("device", hash, cache_entry, data ? *data : NULL,
2750 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2751 if(entry) *entry = cache_entry;
2752 return TRUE;
2753 }
2754
2755 if(!IS_NULL_PTR(data) && dt_dev_pixelpipe_cache_restore_host_payload(cache_entry, preferred_devid, data))
2756 {
2757 _trace_exact_hit("restore-host", hash, cache_entry, data ? *data : NULL,
2758 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2759 if(entry) *entry = cache_entry;
2760 return TRUE;
2761 }
2762
2763 _trace_exact_hit("drop-invalid", hash, cache_entry, data ? *data : NULL,
2764 cl_mem_output ? *cl_mem_output : NULL, preferred_devid, FALSE);
2766 "[pixelpipe] cache entry %" PRIu64 " has no authoritative RAM nor vRAM payload and will be removed\n",
2767 hash);
2768 // If the entry removal fails, flag it for auto-destroy.
2769 if(dt_dev_pixelpipe_cache_remove(TRUE, cache_entry))
2771 if(data) *data = NULL;
2772 return FALSE;
2773}
2774
2775
2776static gboolean _for_each_remove(gpointer key, gpointer value, gpointer user_data)
2777{
2779 const int id = GPOINTER_TO_INT(user_data);
2780
2781 // Returns 1 if the lock is captured by another thread
2782 // 0 if WE capture the lock, and then need to release it
2783 gboolean locked = dt_pthread_rwlock_trywrlock(&cache_entry->lock);
2784 if(!locked) dt_pthread_rwlock_unlock(&cache_entry->lock);
2785
2786 return (cache_entry->id == id || id == -1) && !locked;
2787}
2788
2789
2791{
2793 dt_pthread_mutex_lock(&cache->lock);
2794 g_hash_table_foreach_remove(cache->entries, _for_each_remove, GINT_TO_POINTER(id));
2796}
2797
2799 const size_t count)
2800{
2802 int retained = 0;
2803 dt_pthread_mutex_lock(&cache->lock);
2804
2805 // We are invalidating the cumulative outputs from one pipeline stage onward.
2806 // Look them up under the same cache lock used for removal so a shared preview
2807 // pipe cannot replace an entry between lookup and invalidation.
2808 for(size_t k = 0; k < count; k++)
2809 {
2810 if(hashes[k] == DT_PIXELPIPE_CACHE_HASH_INVALID) continue;
2811
2813 = _non_threadsafe_cache_get_entry(cache, cache->entries, hashes[k]);
2814 if(IS_NULL_PTR(entry)) continue;
2815
2816 // A displayed backbuffer or an in-flight consumer may still own this
2817 // shared state. Leave it valid; cache bypass on the retry still walks
2818 // through downstream stages after the provider has been regenerated.
2819 if(_non_thread_safe_cache_remove(cache, FALSE, entry, cache->entries))
2820 retained++;
2821 }
2822
2824 return retained;
2825}
2826
2827
2828// Find the age of the most recently used entry in the table.
2829static void _cache_get_newest(gpointer key, gpointer value, gpointer user_data)
2830{
2832 int64_t *newest = (int64_t *)user_data;
2833 const int64_t age = _pixel_cache_get_age(cache_entry);
2834 if(age > *newest) *newest = age;
2835}
2836
2837/* `user_data` is the age cutoff computed by dt_dev_pixelpipe_cache_flush_old(): entries last used
2838 * before it are candidates for collection. */
2839static gboolean _for_each_remove_old(gpointer key, gpointer value, gpointer user_data)
2840{
2842 const int64_t cutoff = *(const int64_t *)user_data;
2843
2844 // Returns 1 if the lock is captured by another thread
2845 // 0 if WE capture the lock, and then need to release it
2846 gboolean locked = dt_pthread_rwlock_trywrlock(&cache_entry->lock);
2847 if(!locked) dt_pthread_rwlock_unlock(&cache_entry->lock);
2848 gboolean used = dt_atomic_get_int(&cache_entry->refcount) > 0;
2849
2850 /* Two independent signals, and both must agree before an entry is dropped: it was last used
2851 * before the cutoff, i.e. the cache has moved on from it, and `hits` says it was never
2852 * asynchronously reused either, so a cacheline that has repeatedly proven reusable across runs
2853 * is kept for the next one. */
2854 const gboolean too_old = (_pixel_cache_get_age(cache_entry) < cutoff) && (cache_entry->hits < 4);
2855
2856 return too_old && !used && !locked;
2857}
2858
2860{
2861 // Don't hang the GUI thread if the cache is locked by a pipeline.
2862 // Better luck next time.
2863 if(dt_pthread_mutex_trylock(&cache->lock)) return G_SOURCE_CONTINUE;
2864
2865 /* Age entries against the cache's OWN most recent activity, not against wall-clock now.
2866 *
2867 * "Older than now - 5 min" measures how long the user has been away from the application, which
2868 * is not what this sweep is about: leave the darkroom open over a coffee break and it wipes a
2869 * perfectly warm working set the next interaction would have reused, paying a full recompute for
2870 * a delay the cache had no say in. Anchoring on the newest cacheline instead makes the window
2871 * relative to the PIPELINE's activity: "the cache has moved on by more than 10 minutes of work
2872 * since this entry was last needed". An idle cache's newest entry ages alongside everything
2873 * else, so the whole set stays inside the window and nothing is dropped -- the sweep only bites
2874 * while something is actively producing newer cachelines, which is exactly when the memory is
2875 * worth reclaiming.
2876 *
2877 * Both passes run under the same lock hold, so the cutoff describes the table it is applied to. */
2878 int64_t newest = INT64_MIN;
2879 g_hash_table_foreach(cache->entries, _cache_get_newest, &newest);
2880 if(newest != INT64_MIN)
2881 {
2882 // 10 min in microseconds
2883 const int64_t ten_min = 10 * 60 * 1000 * 1000;
2884 int64_t cutoff = newest - ten_min;
2885 g_hash_table_foreach_remove(cache->entries, _for_each_remove_old, &cutoff);
2886 }
2887
2889
2890 // Hand free pages back to the OS while we're at it, but only when the system
2891 // actually runs lowish: the per-free MADV_FREE already makes them reclaimable
2892 // (they count as available), and a hard trim of a large resident free set is
2893 // O(resident pages) under the arena lock — a needless stall of this (GUI)
2894 // thread when RAM is plentiful.
2895 const size_t available = dt_get_system_available_mem();
2896 const size_t pressure_floor = dt_get_memory_pressure_floor();
2897 if(available > 0 && pressure_floor > 0 && available < 2 * pressure_floor)
2898 dt_cache_arena_trim(&cache->arena);
2899 return G_SOURCE_CONTINUE;
2900}
2901
2902/* Periodic system memory-pressure shedder (issue #1083), the idle-time counterpart of
2903 * the alloc-time _system_memory_pressure_valve(): when OTHER applications push the
2904 * system toward starvation while we are not allocating anything, nobody runs the valve,
2905 * so watch the system-wide available RAM here and shed cache below the floor. The
2906 * hard trim matters: the per-free lazy release (MADV_FREE) technically keeps the pages
2907 * ours until the kernel scavenges them, while decommitting hands them over NOW. */
2909{
2910 // Kernel memory pressure first. A pipeline holding the lock is allocating, and allocations
2911 // react to it themselves (_free_space_to_alloc()).
2912 if(!dt_pthread_mutex_trylock(&cache->lock))
2913 {
2916 }
2917
2918 const size_t pressure_floor = dt_get_memory_pressure_floor();
2919 if(pressure_floor == 0) return G_SOURCE_CONTINUE;
2920
2921 const size_t available = dt_get_system_available_mem();
2922 if(available == 0 || available >= pressure_floor) return G_SOURCE_CONTINUE;
2923
2924 // Don't hang the GUI thread if the cache is locked by a pipeline.
2925 // The valve covers pressure handling while pipelines are allocating anyway.
2926 if(dt_pthread_mutex_trylock(&cache->lock)) return G_SOURCE_CONTINUE;
2927
2928 const size_t deficit = pressure_floor - available;
2929 size_t freed = 0;
2930 while(freed < deficit && g_hash_table_size(cache->entries) > 0)
2931 {
2932 const size_t before = cache->current_memory;
2934 freed += before - cache->current_memory;
2935 }
2936
2937 // Same reasoning as in the valve: hand the freed pages over for real, then let the
2938 // OS — not our own bookkeeping — say what that bought, and seed the valve's probe
2939 // cache with that ground truth.
2940 const size_t trimmed = dt_cache_arena_trim(&cache->arena);
2942 const size_t available_after = dt_get_system_available_mem();
2943 cache->sys_probe_valid = (available_after > 0);
2944 cache->sys_available_est = available_after;
2945 cache->sys_probe_time_us = g_get_monotonic_time();
2947
2949 "[pixelpipe_cache] system memory pressure while idle: %" G_GSIZE_FORMAT " MiB available "
2950 "under the %" G_GSIZE_FORMAT " MiB floor — shed %" G_GSIZE_FORMAT " MiB of cache, "
2951 "returned %" G_GSIZE_FORMAT " MiB to the OS, now %" G_GSIZE_FORMAT " MiB available\n",
2952 available / (1024 * 1024), pressure_floor / (1024 * 1024), freed / (1024 * 1024),
2953 trimmed / (1024 * 1024), available_after / (1024 * 1024));
2954 return G_SOURCE_CONTINUE;
2955}
2956
2962
2963
2965 dt_pixel_cache_entry_t *cache_entry)
2966{
2967 if(IS_NULL_PTR(cache_entry)) return;
2968
2969 if(lock)
2970 {
2971 dt_atomic_add_int(&cache_entry->refcount, 1);
2972 // Pinning an entry is a use: refresh the MRU timestamp so it does not age while held.
2973 _pixel_cache_touch(cache_entry);
2974 _pixel_cache_message(cache_entry, "ref count ++", TRUE);
2975 }
2976 else
2977 {
2978 dt_atomic_sub_int(&cache_entry->refcount, 1);
2979 _pixel_cache_message(cache_entry, "ref count --", TRUE);
2980 }
2981}
2982
2983
2992
2993
2995 dt_pixel_cache_entry_t *cache_entry)
2996{
2997 if(lock)
2998 {
2999 dt_pthread_rwlock_wrlock(&cache_entry->lock);
3000 _pixel_cache_message(cache_entry, "write lock", TRUE);
3001 }
3002 else
3003 {
3004 dt_pthread_rwlock_unlock(&cache_entry->lock);
3005 _pixel_cache_message(cache_entry, "write unlock", TRUE);
3006 // The producer node key travels alongside the hash so GUI waiters can match by
3007 // the node that produced this output even when the exact output hash the GUI
3008 // predicted has drifted from the one the worker actually published (the
3009 // never-served case, doc/pipeline-cache.md §8). INVALID for non-module outputs
3010 // (raster masks, republished inputs): waiters simply fall back to hash match.
3011 if(cache_entry && cache_entry->hash != DT_PIXELPIPE_CACHE_HASH_INVALID && _ready_handler)
3012 _ready_handler(cache_entry->hash, cache_entry->producer_node_key);
3013 }
3014}
3015
3016
3018 dt_pixel_cache_entry_t *cache_entry)
3019{
3020 if(lock)
3021 {
3022 dt_pthread_rwlock_rdlock(&cache_entry->lock);
3023 _pixel_cache_message(cache_entry, "read lock", TRUE);
3024 }
3025 else
3026 {
3027 dt_pthread_rwlock_unlock(&cache_entry->lock);
3028 _pixel_cache_message(cache_entry, "read unlock", TRUE);
3029 }
3030}
3031
3032
3034{
3036 dt_pthread_mutex_lock(&cache->lock);
3037 if(IS_NULL_PTR(cache_entry))
3038 {
3040 return;
3041 }
3042
3043 cache_entry->auto_destroy = TRUE;
3044 _pixel_cache_message(cache_entry, "auto destroy flagged", TRUE);
3046}
3047
3048
3050{
3052 dt_pthread_mutex_lock(&cache->lock);
3053 if(IS_NULL_PTR(cache_entry))
3054 {
3056 return;
3057 }
3058
3059 if(cache_entry->auto_destroy)
3060 {
3061 /* `auto_destroy` is still a normal cache lifecycle: the creator flags a transient entry, then the final
3062 * consumer decrements its refcount and asks the cache to reap it. Only remove it once no consumer owns
3063 * it anymore and nobody still holds the entry lock, otherwise teardown paths can free cachelines that
3064 * still report `refs>0` and hide ownership bugs instead of exposing them. */
3065 const gboolean locked = dt_pthread_rwlock_trywrlock(&cache_entry->lock);
3066 if(!locked) dt_pthread_rwlock_unlock(&cache_entry->lock);
3067 const gboolean used = dt_atomic_get_int(&cache_entry->refcount) > 0;
3068
3069 if(!used && !locked)
3070 {
3071 _pixel_cache_message(cache_entry, "auto destroy removing", FALSE);
3072 g_hash_table_remove(cache->entries, &cache_entry->hash);
3073 }
3074 else if(used)
3075 {
3076 _pixel_cache_message(cache_entry, "auto destroy postponed: used", TRUE);
3077 }
3078 else
3079 {
3080 _pixel_cache_message(cache_entry, "auto destroy postponed: locked", TRUE);
3081 }
3082 }
3083 else
3084 {
3085 _pixel_cache_message(cache_entry, "auto destroy skipped", TRUE);
3086 }
3087
3089}
3090
3092{
3094 if(hash == DT_PIXELPIPE_CACHE_HASH_INVALID) return;
3095
3096 dt_pthread_mutex_lock(&cache->lock);
3097 cache->queries++;
3098 dt_pixel_cache_entry_t *cache_entry = _non_threadsafe_cache_get_entry(cache, cache->entries, hash);
3100
3101 if(cache_entry)
3103}
3104
3106 const uint64_t new_hash, dt_pixel_cache_entry_t *entry)
3107{
3109 if(IS_NULL_PTR(cache)) return 1;
3110 if(old_hash == new_hash) return 0;
3111
3112 dt_pthread_mutex_lock(&cache->lock);
3113
3114 if(IS_NULL_PTR(entry)) entry = _non_threadsafe_cache_get_entry(cache, cache->entries, old_hash);
3115 if(IS_NULL_PTR(entry))
3116 {
3118 "[pixelpipe_cache] rekey miss old=%" PRIu64 " new=%" PRIu64 " module=%s\n",
3119 old_hash, new_hash, _cache_debug_module_name());
3121 return 1;
3122 }
3123
3124 dt_pixel_cache_entry_t *conflict = _non_threadsafe_cache_get_entry(cache, cache->entries, new_hash);
3125 if(conflict && conflict != entry)
3126 {
3128 "[pixelpipe_cache] rekey conflict old=%" PRIu64 " new=%" PRIu64
3129 " entry=%" PRIu64 "/%" PRIu64 " conflict=%" PRIu64 "/%" PRIu64 " module=%s\n",
3130 old_hash, new_hash, entry->hash, entry->serial, conflict->hash, conflict->serial,
3133 return 1;
3134 }
3135
3136 gpointer stolen_key = NULL;
3137 gpointer stolen_value = NULL;
3138 if(!g_hash_table_steal_extended(cache->entries, &old_hash, &stolen_key, &stolen_value))
3139 {
3141 "[pixelpipe_cache] rekey steal-miss old=%" PRIu64 " new=%" PRIu64
3142 " entry=%" PRIu64 "/%" PRIu64 " module=%s\n",
3143 old_hash, new_hash, entry->hash, entry->serial, _cache_debug_module_name());
3145 return 1;
3146 }
3147
3148 if(stolen_value != entry)
3149 {
3151 "[pixelpipe_cache] rekey stolen-entry mismatch old=%" PRIu64 " new=%" PRIu64
3152 " expected=%" PRIu64 "/%" PRIu64 " got=%" PRIu64 "/%" PRIu64 " module=%s\n",
3153 old_hash, new_hash, entry->hash, entry->serial,
3154 ((dt_pixel_cache_entry_t *)stolen_value)->hash, ((dt_pixel_cache_entry_t *)stolen_value)->serial,
3156 g_hash_table_insert(cache->entries, stolen_key, stolen_value);
3158 return 1;
3159 }
3160
3161 /* Explicit rekeying also changes cacheline ownership. The OpenCL payload cache is only valid for the
3162 * previous hash, so do not let the new hash inherit stale device-side state. If some GPU code is still
3163 * borrowing one of these payloads, refuse the rekey instead of publishing an ambiguous cache entry. */
3165 for(GList *l = entry->cl_mem_list; l; l = g_list_next(l))
3166 {
3167 dt_cache_clmem_t *c = (dt_cache_clmem_t *)l->data;
3168 if(c && c->refs > 0)
3169 {
3171 g_hash_table_insert(cache->entries, stolen_key, stolen_value);
3173 return 1;
3174 }
3175 }
3178
3179 *(uint64_t *)stolen_key = new_hash;
3180 entry->hash = new_hash;
3181 g_hash_table_insert(cache->entries, stolen_key, stolen_value);
3183 "[pixelpipe_cache] rekey old=%" PRIu64 " new=%" PRIu64 " entry=%" PRIu64 "/%" PRIu64
3184 " refs=%i auto=%i data=%p module=%s\n",
3185 old_hash, new_hash, entry->hash, entry->serial, dt_atomic_get_int(&entry->refcount),
3186 entry->auto_destroy, entry->data, _cache_debug_module_name());
3187
3189
3190 _observe_rekey(old_hash, new_hash);
3191 return 0;
3192}
3193
3194
3196{
3198 if(!_verbose) return;
3199
3200 _cache_print(DT_DEBUG_PIPECACHE, "[pixelpipe] cache hit rate so far: %.3f%% - size: %" G_GSIZE_FORMAT " MiB over %" G_GSIZE_FORMAT " MiB - %i items\n",
3201 100. * (cache->hits) / (float)cache->queries, cache->current_memory / (1024 * 1024),
3202 cache->max_memory / (1024 * 1024),
3203 g_hash_table_size(cache->entries));
3204}
3205
3206void dt_dev_pixelpipe_cache_get_usage(size_t *current, size_t *max)
3207{
3209 if(current) *current = 0;
3210 if(max) *max = 0;
3211 if(IS_NULL_PTR(cache)) return;
3212 dt_pthread_mutex_lock(&cache->lock);
3213 if(current) *current = cache->current_memory;
3214 // The budget allocations currently evict down to, so tiling plans against it -- and never
3215 // under what is held, since the callers compute `max - current` unsigned.
3218}
3219
3221{
3223 GArray *out = g_array_new(FALSE, FALSE, sizeof(dt_pixel_cache_stats_entry_t));
3224 if(IS_NULL_PTR(cache)) return out;
3225
3226 dt_pthread_mutex_lock(&cache->lock);
3227 GHashTableIter it;
3228 gpointer key, value;
3229 g_hash_table_iter_init(&it, cache->entries);
3230 while(g_hash_table_iter_next(&it, &key, &value))
3231 {
3233 if(IS_NULL_PTR(e)) continue;
3235 s.hash = e->hash;
3236 s.size = e->size;
3238 s.hits = e->hits;
3239 if(e->name) g_strlcpy(s.name, e->name, sizeof(s.name));
3240
3241#ifdef HAVE_OPENCL
3242 // Account the OpenCL device buffers attached to this entry. trylock avoids
3243 // both deadlock and racing the list against concurrent clmem mutation.
3245 {
3246 for(GList *l = e->cl_mem_list; l; l = g_list_next(l))
3247 {
3248 const dt_cache_clmem_t *c = (const dt_cache_clmem_t *)l->data;
3249 if(IS_NULL_PTR(c) || IS_NULL_PTR(c->mem)) continue;
3250 s.cl_count++;
3251 s.cl_bytes += dt_opencl_get_mem_object_size((cl_mem)c->mem);
3252 }
3254 }
3255#endif
3256
3257 g_array_append_val(out, s);
3258 }
3260 return out;
3261}
3262
3264{
3265#ifdef HAVE_OPENCL
3266 if(!dt_opencl_is_enabled()) return 0;
3267 size_t total = 0;
3268 for(int i = 0; i < dt_opencl_get_num_devices(); i++)
3270 return total;
3271#else
3272 return 0;
3273#endif
3274}
3275
3276// clang-format off
3277// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
3278// vim: shiftwidth=2 expandtab tabstop=2 cindent
3279// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
3280// 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)
uint64_t dt_atomic_get_uint64(const dt_atomic_uint64 *var)
int dt_atomic_sub_int(dt_atomic_int *var, int decr)
int dt_atomic_add_int(dt_atomic_int *var, int incr)
void dt_atomic_set_uint64(dt_atomic_uint64 *var, uint64_t value)
atomic_int dt_atomic_int
Definition atomic.h:68
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
void dt_invalidate_system_available_mem(void)
Definition darktable.c:2358
size_t dt_get_system_available_mem(void)
Definition darktable.c:2339
size_t dt_get_memory_pressure_floor(void)
Definition darktable.c:2418
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock) ACQUIRE(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:299
static int dt_pthread_rwlock_trywrlock(dt_pthread_rwlock_t *rwlock) TRY_ACQUIRE(0
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:267
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
Definition dtpthread.h:104
static int dt_pthread_rwlock_destroy(dt_pthread_rwlock_t *lock)
Definition dtpthread.h:212
static int dt_pthread_rwlock_tryrdlock(dt_pthread_rwlock_t *rwlock) TRY_ACQUIRE_SHARED(0
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:132
static int dt_pthread_rwlock_init(dt_pthread_rwlock_t *lock, const pthread_rwlockattr_t *attr)
Definition dtpthread.h:192
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
int bpp
@ DT_DEBUG_OPENCL
Definition logging.h:57
@ DT_DEBUG_PIPECACHE
Definition logging.h:56
@ DT_DEBUG_MEMORY
Definition logging.h:59
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#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 macros.h:96
static void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
#define DT_CACHELINE_BYTES
Definition mem_alloc.h:66
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 dt_cache_arena_trim(dt_cache_arena_t *a)
char * key
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
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:2917
size_t dt_opencl_get_mem_object_size(cl_mem mem)
Definition opencl.c:2976
gboolean dt_opencl_is_pinned_memory(cl_mem mem)
Definition opencl.c:238
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2894
int dt_opencl_is_inited(void)
Definition opencl.c:3255
int dt_opencl_get_mem_context_id(cl_mem mem)
Definition opencl.c:2986
int dt_opencl_get_image_height(cl_mem mem)
Definition opencl.c:3024
void dt_opencl_reserve_device_by_id(const int devid)
Reserve a device the caller has already identified, blocking until it is free.
Definition opencl.c:1983
int dt_opencl_try_reserve_device_by_id(const int devid)
Reserve a device only if it is free right now.
Definition opencl.c:1994
int dt_opencl_unmap_mem_object(const int devid, cl_mem mem_object, void *mapped_ptr)
Definition opencl.c:2852
int dt_opencl_is_enabled(void)
Definition opencl.c:3262
gboolean dt_opencl_use_pinned_memory(const int devid)
Definition opencl.c:231
int dt_opencl_get_image_width(cl_mem mem)
Definition opencl.c:3013
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:2832
gboolean dt_opencl_finish(const int devid)
Definition opencl.c:1671
size_t dt_opencl_get_device_max_global_mem(const int devid)
Total device memory in bytes, or 0 for an out-of-range id.
Definition opencl.c:2027
void dt_opencl_events_wait_for(const int devid)
Definition opencl.c:3454
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:2587
void dt_opencl_release_device(const int devid)
Release a device reserved by either reserve function.
Definition opencl.c:1972
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
int dt_opencl_get_image_element_size(cl_mem mem)
Definition opencl.c:3035
int dt_opencl_get_num_devices(void)
Number of usable OpenCL devices; 0 when OpenCL is unavailable.
Definition opencl.c:2002
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:2634
#define DT_OPENCL_BPP_ENCODE_RGBA8(bpp)
Definition opencl.h:89
const char * name
Definition pdf.h:90
static __thread const char * dt_pixelpipe_cache_current_module
void dt_dev_pixelpipe_cache_wrlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the write lock on the entry.
static const char * _cache_debug_module_name(void)
static size_t _pressure_held(void *user)
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 void _pressure_wake(void *user)
GArray * dt_dev_pixelpipe_cache_get_entries_stats(void)
static void _observe_rekey(uint64_t old_hash, uint64_t new_hash)
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)
int dt_dev_pixelpipe_cache_invalidate_hashes(const uint64_t *hashes, const size_t count)
Invalidate cache lines matching an explicit list of hashes.
void * dt_dev_pixelpipe_cache_get_pinned_image(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 void _cache_get_newest(gpointer key, gpointer value, gpointer user_data)
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)
static int pressure_shedding
void dt_dev_pixelpipe_cache_cleanup(void)
void dt_dev_pixelpipe_cache_auto_destroy_apply(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 int garbage_collection
void * dt_pixelpipe_cache_alloc_align_cache_impl(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...
void * dt_pixel_cache_entry_get_data(dt_pixel_cache_entry_t *entry)
static int dt_dev_pixelpipe_cache_flush_old(dt_dev_pixelpipe_cache_t *cache)
static gboolean _verbose
static void _pixel_cache_clmem_remove(dt_pixel_cache_entry_t *entry, void *mem)
void dt_dev_pixelpipe_cache_ref_count_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Increase/Decrease the reference count on the cache line as to prevent LRU item removal....
gboolean dt_dev_pixelpipe_cache_peek(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.
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_flush_clmem(const int devid)
Release cached OpenCL buffers for a single device.
static void _cache_get_oldest(gpointer key, gpointer value, gpointer user_data)
int dt_dev_pixel_pipe_cache_remove_lru(void)
#define _cache_print(channel,...)
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)
static int _non_thread_safe_pixel_pipe_cache_remove_lru(dt_dev_pixelpipe_cache_t *cache)
static int64_t _pixel_cache_get_age(dt_pixel_cache_entry_t *cache_entry)
static void _observe_delete(uint64_t hash, size_t size, int owner_pipe_id, const char *name)
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_rdlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
void dt_dev_pixelpipe_cache_flush_clmem_for_pipe(const int devid)
Like dt_dev_pixelpipe_cache_flush_clmem(), for callers that do not hold the device lock (dt_opencl_re...
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.
void dt_dev_pixelpipe_cache_flush(const int id)
Remove cache lines matching id. Entries locked in read/write or having reference count greater than 0...
void dt_dev_pixelpipe_cache_print(void)
static void _pressure_react_locked(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)
size_t dt_pixelpipe_cache_get_largest_free_run(void)
static const dt_pixelpipe_cache_observer_t * _observer
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_put_pinned_image(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().
gboolean dt_dev_pixelpipe_cache_restore_host_payload(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_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)
gboolean dt_dev_pixelpipe_cache_flush_host_pinned_image(void *host_ptr, dt_pixel_cache_entry_t *entry_hint, int devid)
Drop cached pinned OpenCL images associated with a given host buffer.
int dt_dev_pixelpipe_cache_rekey(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...
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
dt_pixel_cache_entry_t * dt_dev_pixelpipe_cache_get_entry_by_data(void *data)
static size_t _pixel_cache_get_size(dt_pixel_cache_entry_t *cache_entry)
static dt_pixelpipe_cache_pressure_sink_t _pressure_sink(dt_dev_pixelpipe_cache_t *cache)
void * dt_pixel_cache_alloc(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...
static dt_pixelpipe_cache_warn_handler_t _warn_handler
static gboolean _cache_entry_clmem_flush_host_pinned_locked(dt_pixel_cache_entry_t *entry, void *host_ptr, int devid)
static void _free_cache_entry(dt_pixel_cache_entry_t *cache_entry)
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)
static void _warn_user(const char *format,...) __attribute__((format(printf
static void _observe_read(uint64_t hash, size_t size)
static gboolean _system_memory_pressure_valve(dt_dev_pixelpipe_cache_t *cache, size_t request_size)
void dt_dev_pixelpipe_cache_get_usage(size_t *current, size_t *max)
int dt_dev_pixelpipe_cache_get(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.
dt_pixel_cache_entry_t * dt_dev_pixelpipe_cache_get_entry(const uint64_t hash)
Get an internal reference to the cache entry matching hash. If you are going to access this entry mor...
static gboolean _observed(void)
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_dev_pixelpipe_cache_writable_status_t dt_dev_pixelpipe_cache_get_writable(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)
static int _memory_pressure_shedder(dt_dev_pixelpipe_cache_t *cache)
#define DT_PIXELPIPE_CACHE_PRESSURE_EXEMPT_SIZE
int dt_dev_pixelpipe_cache_remove(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 ...
static gboolean _is_gamma_rgba8_output(const dt_iop_module_t *module, const size_t bpp, const char *message)
gboolean dt_dev_pixelpipe_cache_init(size_t max_memory, const gboolean verbose, const gboolean verbose_detail)
Reserve the cache's arena and start it.
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.
static gboolean _verbose_detail
static size_t _cache_budget_locked(const dt_dev_pixelpipe_cache_t *cache)
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.
static size_t _pressure_shed(void *user, const size_t target, size_t *given_back)
void dt_dev_pixelpipe_cache_unref_hash(const uint64_t hash)
Find the entry matching hash, and decrease its ref_count if found.
void dt_pixelpipe_cache_free_align_cache(void **mem, const char *message)
Free aligned memory allocated with dt_pixelpipe_cache_alloc_align_cache.
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)
static dt_pixelpipe_cache_ready_handler_t _ready_handler
static gboolean _cache_entry_materialize_host_data(dt_dev_pixelpipe_cache_t *cache, int preferred_devid, dt_pixel_cache_entry_t *entry)
dt_pixel_cache_entry_t * dt_dev_pixelpipe_cache_ref_entry_for_host_ptr(void *host_ptr)
Resolve and retain the cache entry owning a host pointer.
static dt_dev_pixelpipe_cache_t * _pixelpipe_cache
static gboolean _cache_entry_materialize_host_data_locked(dt_pixel_cache_entry_t *entry, int preferred_devid, gboolean prefer_device_payload)
gboolean dt_dev_pixelpipe_cache_ref_entry_by_hash(const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry)
Resolve and retain an existing cache entry by hash.
static void _pixel_cache_touch(dt_pixel_cache_entry_t *cache_entry)
void dt_dev_pixelpipe_cache_flag_auto_destroy(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_set_handlers(dt_pixelpipe_cache_warn_handler_t warn, dt_pixelpipe_cache_ready_handler_t ready, const dt_pixelpipe_cache_observer_t *observer)
Install the handlers. Call once, from the orchestrator, before any pipe runs.
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)
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.
gboolean dt_dev_pixelpipe_cache_ref_host_entry_by_hash(const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry)
Resolve and retain an existing cacheline that already holds HOST pixels.
const char * dt_pixelpipe_cache_set_current_module(const char *module)
Set the current module name for cache diagnostics (thread-local).
gboolean dt_dev_pixelpipe_cache_is_ready(void)
Has the pixelpipe cache been initialised? Callers that run before dt_dev_pixelpipe_cache_init() succe...
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
void(* dt_pixelpipe_cache_warn_handler_t)(const char *message)
Tell the user something went wrong. Called with an already-translated, already-formatted string; the ...
void(* dt_pixelpipe_cache_ready_handler_t)(uint64_t hash, uint64_t producer_node_key)
A cacheline finished and is readable.
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
void dt_pixelpipe_cache_pressure_monitor_init(dt_pixelpipe_cache_pressure_monitor_t *m, const size_t plan)
void dt_pixelpipe_cache_pressure_triggered(dt_pixelpipe_cache_pressure_monitor_t *m, const dt_pixelpipe_cache_pressure_sink_t *sink)
void dt_pixelpipe_cache_pressure_watch_start(dt_pixelpipe_cache_pressure_monitor_t *m, void(*wake)(void *user), void *user)
size_t dt_pixelpipe_cache_pressure_react(dt_pixelpipe_cache_pressure_monitor_t *m, const dt_pixelpipe_cache_pressure_sink_t *sink)
void dt_pixelpipe_cache_pressure_watch_stop(dt_pixelpipe_cache_pressure_monitor_t *m)
static size_t dt_pixelpipe_cache_pressure_reported(const dt_pixelpipe_cache_pressure_t *p, const size_t current)
static size_t dt_pixelpipe_cache_pressure_budget(const dt_pixelpipe_cache_pressure_t *p)
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
unsigned __int64 uint64_t
Definition strptime.c:75
dt_pixel_cache_entry_t * cache_entry
dt_pthread_mutex_t lock
dt_pixelpipe_cache_pressure_monitor_t psi
dt_dev_operation_t op
Definition imageop.h:259
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
uint64_t hash
gboolean auto_destroy
dt_atomic_int refcount
gboolean external_alloc
void * data
dt_atomic_uint64 age
size_t size
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
The supervisor's view of the cache. All four may be NULL; active gates the other three so the cache p...
void(* cacheline_delete)(uint64_t hash, size_t size, int owner_pipe_id, const char *name)
void(* cacheline_read)(uint64_t hash, size_t size)
void(* rekey)(uint64_t old_hash, uint64_t new_hash)
dt_pthread_mutex_t lock
Definition supervisor.c:123