Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
dev_pixelpipe.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2026 Aurélien PIERRE.
4 Copyright (C) 2026 Guillaume Stutin.
5
6 Ansel is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Ansel is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#include "common/debug.h"
21#include "common/darktable.h"
22#include "common/dtpthread.h"
23#include "develop/imageop.h"
24#include "develop/pixelpipe.h"
26#include "develop/supervisor.h"
27#include "develop/blend.h"
29#include "control/control.h"
30#include "control/signal.h"
31#include <stdint.h>
32#include <stdlib.h>
33
34// Keep a preview-like virtual pipe in sync with history without running pixels.
37 const uint32_t history_end, GList *start_node,
38 const char *debug_label);
39// dt_dev_pixelpipe_propagate_formats() is the authoritative forward buffer-format pass; it is
40// declared in dev_pixelpipe.h. It must run before any dt_pixelpipe_get_global_hash() because the
41// auto-disable it performs feeds the cumulative global hash (see its definition).
42static void _dt_dev_pixelpipe_cache_wait_ready_callback(gpointer instance, const guint64 hash,
43 const guint64 producer_node_key,
44 gpointer user_data);
45
52
66
68 = { .lock = { PTHREAD_MUTEX_INITIALIZER }, .pending = NULL, .connected = FALSE,
69 .wait_cursor_active = FALSE,
70 .next_request_id = 1, .queued_requests = 0, .served_requests = 0,
71 .cancelled_requests = 0, .immediate_hits = 0, .misses = 0 };
72
73static gboolean _cache_wait_cursor_progress(gpointer user_data)
74{
76 return G_SOURCE_REMOVE;
77
79 return G_SOURCE_REMOVE;
80}
81
82static gboolean _cache_wait_cursor_restore(gpointer user_data)
83{
85 return G_SOURCE_REMOVE;
86
89 return G_SOURCE_REMOVE;
90}
91
93{
94 for(GList *iter = _cache_wait_manager.pending; iter; iter = g_list_next(iter))
95 {
96 dt_dev_pixelpipe_cache_wait_record_t *record = iter->data;
97 if(!IS_NULL_PTR(record) && record->wait == wait)
98 {
99 _cache_wait_manager.pending = g_list_delete_link(_cache_wait_manager.pending, iter);
100 return record;
101 }
102 }
103
104 return NULL;
105}
106
108{
109 const char *context = !IS_NULL_PTR(reason) ? reason : "unspecified";
110 const int64_t now_us = g_get_monotonic_time();
111
112 // This is a diagnostic snapshot only. Skip it rather than stalling GUI teardown
113 // behind a cache-ready callback that is already updating the same wait list.
115 {
116 dt_print(DT_DEBUG_PIPECACHE, "[cache-wait] dump reason=%s skipped=lock-busy\n", context);
117 return;
118 }
119
120 const guint pending_count = g_list_length(_cache_wait_manager.pending);
122 "[cache-wait] dump reason=%s pending=%u queued=%" PRIu64 " served=%" PRIu64
123 " cancelled=%" PRIu64 " immediate=%" PRIu64 " misses=%" PRIu64 "\n",
124 context, pending_count, _cache_wait_manager.queued_requests,
127
128 for(GList *iter = _cache_wait_manager.pending; iter; iter = g_list_next(iter))
129 {
130 dt_dev_pixelpipe_cache_wait_record_t *record = iter->data;
131 dt_dev_pixelpipe_cache_wait_t *wait = !IS_NULL_PTR(record) ? record->wait : NULL;
132 if(IS_NULL_PTR(wait) || IS_NULL_PTR(record)) continue;
133
134 const int64_t age_ms = MAX((now_us - record->queued_at_us) / 1000, 0);
136 "[cache-wait] pending id=%" PRIu64 " owner=%s hash=%" PRIu64
137 " target=%s connected=%d age_ms=%" PRId64 "\n",
138 wait->request_id,
139 !IS_NULL_PTR(wait->owner_tag) ? wait->owner_tag : "(unknown)",
140 wait->hash,
141 !IS_NULL_PTR(wait->module) ? wait->module->op : "backbuf",
142 wait->connected,
143 age_ms);
144 }
146}
147
149 const dt_iop_module_t *module)
150{
151 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(module)) return FALSE;
152 if(pipe->type != DT_DEV_PIXELPIPE_PREVIEW) return FALSE;
153 if(dt_dev_pixelpipe_get_realtime(pipe)) return FALSE;
154 if(!pipe->gui_observable_source) return FALSE;
155
156 return !strcmp(module->op, "initialscale") || !strcmp(module->op, "colorout");
157}
158
160 const dt_iop_module_t *module)
161{
162 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(module)) return FALSE;
163 if(pipe->type != DT_DEV_PIXELPIPE_PREVIEW) return FALSE;
164 if(dt_dev_pixelpipe_get_realtime(pipe)) return FALSE;
165 if(!pipe->gui_observable_source) return FALSE;
166
167 return !strcmp(module->op, "gamma");
168}
169
170static gchar *_get_debug_pipe_name(const dt_dev_pixelpipe_t *pipe, const dt_develop_t *dev)
171{
172 if(!IS_NULL_PTR(dev) && !IS_NULL_PTR(pipe) && dev->virtual_pipe == pipe)
173 return g_strdup("virtual-preview");
174
175 return g_strdup(dt_pixelpipe_get_pipe_name(!IS_NULL_PTR(pipe) ? pipe->type : DT_DEV_PIXELPIPE_NONE));
176}
177
179{
180 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
181 {
183 if(!IS_NULL_PTR(piece) && !strcmp(piece->module->op, "detailmask")) return nodes;
184 }
185
186 return NULL;
187}
188
190{
191 dt_dev_pixelpipe_iop_t *detailmask_piece = NULL;
193
194 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
195 {
197 if(IS_NULL_PTR(piece)) continue;
198
199 if(!strcmp(piece->module->op, "detailmask")) detailmask_piece = piece;
200 if(piece->detail_mask)
201 {
203 break;
204 }
205 }
206
207 if(!IS_NULL_PTR(detailmask_piece))
208 {
209 const gboolean enabled = (pipe->want_detail_mask == DT_DEV_DETAIL_MASK_ENABLED)
210 && (detailmask_piece->dsc_in.channels == 4)
211 && (detailmask_piece->dsc_in.datatype == TYPE_FLOAT)
212 && (detailmask_piece->dsc_in.cst == IOP_CS_RGB);
213 detailmask_piece->enabled = enabled;
214 detailmask_piece->process_tiling_ready = !enabled;
215 if(!IS_NULL_PTR(detailmask_piece->data)) *((int *)detailmask_piece->data) = enabled ? 1 : 0;
216 }
217}
218
225
244{
245 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(pipe->dev) || IS_NULL_PTR(pipe->dev->gui_module)) return FALSE;
246 dt_develop_t *dev = pipe->dev;
247 dt_iop_module_t *focus = dev->gui_module;
248
249 const gboolean transient = dt_dev_transient_params_active(dev, focus);
250 const gboolean realtime = dt_dev_pixelpipe_get_realtime(pipe);
251 if(!transient && !realtime) return FALSE;
252
253 const uint32_t history_end = dt_dev_get_history_end_ext(dev);
254 if(history_end == 0) return FALSE;
255
256 // Enable/blend state comes from the focused module's history item (transient edits are params-only).
258 if(IS_NULL_PTR(hist) || IS_NULL_PTR(hist->module) || IS_NULL_PTR(hist->params)) return FALSE;
259
261 if(IS_NULL_PTR(piece)) return FALSE;
262
263 // Resolve params: thread-safe transient snapshot when published, else the history snapshot. A copy is
264 // taken so the slot lock is not held across commit_params(); never the live GUI module->params.
265 dt_iop_params_t *params = hist->params;
266 void *tbuf = NULL;
267 if(transient && focus->params_size > 0)
268 {
269 tbuf = g_malloc0(focus->params_size);
270 if(!IS_NULL_PTR(tbuf)
271 && dt_dev_transient_params_get(dev, focus, tbuf, (size_t)focus->params_size, NULL, 0, NULL))
272 params = (dt_iop_params_t *)tbuf;
273 else
274 dt_free(tbuf);
275 }
276
277 const gboolean previous_want_detail_mask = (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE);
278 piece->enabled = hist->enabled;
279 piece->detail_mask = !IS_NULL_PTR(hist->blend_params) && hist->blend_params->details != 0.0f;
280 dt_iop_commit_params(focus, params, hist->blend_params, pipe, piece);
281 dt_free(tbuf);
283 if(previous_want_detail_mask != (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE)) return FALSE;
284
285 // The global hash is cumulative and skips disabled nodes, so the format contract (which may
286 // disable a node on an incompatible input) must be settled first.
289
290 // Only advance the synch_top fence when the focused module is the newest history item (the realtime
291 // drawlayer case, where each heartbeat appended a top item). For a transient edit of a non-top module
292 // no history item changed, so the fence must stay where the last real history sync left it.
293 GList *last_item = g_list_nth(dev->history, history_end - 1);
294 if(last_item && (dt_dev_history_item_t *)last_item->data == hist)
295 {
296 pipe->last_history_hash = hist->hash;
297 pipe->last_history_item = hist;
298 }
299 return TRUE;
300}
301
334{
335 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(pipe->nodes)) return;
336
337 // The scopes/global histogram are not refreshed while a realtime stroke is in progress (the preview
338 // pipe is paused), so do not force any module to publish a host copy of its output just to feed them.
339 // This drops the per-frame RAM copy of `colorout` (and the histogram input/module caches) during
340 // realtime drawing, where it is pure overhead.
341 const gboolean realtime = dt_dev_pixelpipe_get_realtime(pipe);
342
343 gboolean current_output_must_cache_host = TRUE;
344
345 for(GList *pieces = g_list_last(pipe->nodes); pieces; pieces = g_list_previous(pieces))
346 {
347 dt_dev_pixelpipe_iop_t *piece = pieces->data;
348 dt_iop_module_t *module = !IS_NULL_PTR(piece) ? piece->module : NULL;
349 if(IS_NULL_PTR(piece) || IS_NULL_PTR(module) || !piece->enabled) continue;
350
351 gboolean supports_opencl = FALSE;
352#ifdef HAVE_OPENCL
353 supports_opencl = dt_opencl_is_inited() && piece->process_cl_ready && module->process_cl;
354#endif
355
356 gchar *string = g_strdup_printf("/plugins/%s/cache", module->op);
357 if(!dt_conf_key_exists(string) || !dt_conf_key_not_empty(string))
359
360 const gboolean authored_cache = piece->cache_output_on_ram;
361 const gboolean user_requested_cache = dt_conf_get_bool(string);
362 dt_free(string);
363
364 const gboolean color_picker_on = dt_iop_color_picker_force_cache(pipe, module);
365 const gboolean global_hist_output_on
366 = !realtime && _module_requires_global_histogram_output_cache(pipe, module);
367 const gboolean global_hist_input_on
368 = !realtime && _module_requires_global_histogram_input_cache(pipe, module);
369 const gboolean module_hist_on
370 = !realtime
371 && (pipe->type == DT_DEV_PIXELPIPE_PREVIEW
372 && pipe->gui_observable_source
374 && (piece->request_histogram & DT_REQUEST_ON));
375 const gboolean active_in_gui
377 && pipe->dev->gui_module == module;
378
379 const gboolean has_autoset = pipe->autoset && !IS_NULL_PTR(module->autoset);
380
381 const gboolean previous_output_must_cache_host
382 = !supports_opencl || active_in_gui || module_hist_on || global_hist_input_on || has_autoset;
383
385 = authored_cache || user_requested_cache || color_picker_on
386 || global_hist_output_on
387 || current_output_must_cache_host;
388
389 // A GPU-capable module that itself needs no host copy of its input must not erase a host
390 // requirement inherited from further downstream (e.g. a CPU-only module reached through it,
391 // or through a module that is disabled right now but was enabled a moment ago and left a
392 // stale host-less cacheline behind). Once established, the requirement has to keep
393 // propagating upstream, module after module, or an intermediate GPU module silently resets
394 // it and the module before it skips the readback that a later, non-adjacent consumer needs.
395 current_output_must_cache_host = previous_output_must_cache_host || current_output_must_cache_host;
396 }
397}
398
406
412
414{
415 if(IS_NULL_PTR(dev) || !dev->gui_attached) return;
417 // Virtual pipe mirrors preview history for GUI coordinate transforms.
419}
420
427
433
435{
436 if(IS_NULL_PTR(dev) || !dev->gui_attached) return;
438 // Virtual pipe mirrors preview history for GUI coordinate transforms.
440}
441
448
450{
451 if(IS_NULL_PTR(dev) || !dev->gui_attached) return;
453 // Keep the virtual pipe aligned with preview ROI/zoom changes for GUI transforms.
455}
456
458{
459 if(IS_NULL_PTR(dev) || !dev->gui_attached) return;
460 /* Zoom/pan updates must run through the normal validity/ROI flow.
461 * If a module left the main pipe in realtime mode, force it back to
462 * standard mode here so zoom changes cannot get visually stuck on a
463 * best-effort backbuffer policy. */
467}
468
475
477{
478 if (IS_NULL_PTR(dev) || !dev->gui_attached) return;
479 /* Entering a zoom/pan change always exits realtime rendering policy.
480 * Realtime is stroke-scoped and should never control darkroom navigation. */
482 // Slightly different logic: killswitch ASAP,
483 // then redraw UI ASAP for feedback,
484 // finally flag the pipe as dirty for later recompute.
485 // Remember GUI responsiveness is paramount, since a laggy UI
486 // will make user repeat their order for lack of feedback,
487 // meaning relaunching a pipe recompute, meaning working more
488 // for the same contract.
491 gtk_widget_queue_draw(dt_ui_center(darktable.gui->ui));
494}
495
497{
498 return (dev // don't segfault
499 && dev->gui_attached // don't run on background/export pipes
500 && dev->gui_module // don't segfault
501 && dev->gui_module != current_module
502 // current_module is not the active one (capturing edit mode)
503 && dev->gui_module->operation_tags_filter() & current_module->operation_tags())
504 // current_module does operation(s) that active module doesn't want
506 // cache bypass is our hint that the active module is in "editing" mode
507}
508
509
511 const int width_in, const int height_in,
512 int *width, int *height)
513{
514 dt_iop_roi_t roi_in = (dt_iop_roi_t){ 0, 0, width_in, height_in, 1.0 };
515 dt_iop_roi_t roi_out = roi_in;
516 gchar *pipe_name = NULL;
518 pipe_name = _get_debug_pipe_name(pipe, pipe->dev);
519
520 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
521 {
523 dt_iop_module_t *module = piece->module;
524
525 piece->buf_in = roi_in;
526
527 // If in GUI and using a module that needs a full, undistorterted image,
528 // we need to shutdown temporarily any module distorting the image.
530 piece->enabled = FALSE;
531
532 // If module is disabled, modify_roi_out() is a no-op
533 if(piece->enabled)
534 module->modify_roi_out(module, pipe, piece, &roi_out, &roi_in);
535 else
536 roi_out = roi_in;
537
538 // Forward ROI planning answers "what output rectangle does this module
539 // produce from the previous one ?". Logging the tuple here makes each
540 // module-local geometry change visible on `-d pipe`.
541 if(piece->enabled && (darktable.unmuted & DT_DEBUG_PIPE))
543 "[roi-out] pipe=%-15s module=%-18s enabled=%d in =(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)"
544 " out=(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)\n",
545 pipe_name, module->op, piece->enabled,
546 roi_in.x, roi_in.y, roi_in.width, roi_in.height, roi_in.scale,
547 roi_out.x, roi_out.y, roi_out.width, roi_out.height, roi_out.scale);
548
549 piece->buf_out = roi_out;
550 roi_in = roi_out;
551 }
552
553 if(pipe_name) dt_free(pipe_name);
554 *width = roi_out.width;
555 *height = roi_out.height;
556}
557
559{
560 // while module->modify_roi_out describes how the current module will change the size of
561 // the output buffer depending on its parameters (pretty intuitive),
562 // module->modify_roi_in describes "how much material" the current module needs from the previous one,
563 // because some modules (lens correction) need a padding on their input.
564 // The tricky part is therefore that the effect of the current module->modify_roi_in() needs to be repercuted
565 // upstream in the pipeline for proper pipeline cache invalidation, so we need to browse the pipeline
566 // backwards.
567
568 // The virtual pipe is expected to be ready before calling this.
569 // This function no longer supports NULL pipes or ad-hoc temp nodes.
570
571 dt_iop_roi_t roi_out_temp = roi_out;
572 dt_iop_roi_t roi_in;
573 gchar *pipe_name = NULL;
575 pipe_name = _get_debug_pipe_name(pipe, pipe->dev);
576 for(GList *nodes = g_list_last(pipe->nodes); nodes; nodes = g_list_previous(nodes))
577 {
579 dt_iop_module_t *module = piece->module;
580
581 piece->roi_out = roi_out_temp;
582
583 // If in GUI and using a module that needs a full, undistorterted image,
584 // we need to shutdown temporarily any module distorting the image.
586 piece->enabled = FALSE;
587
588 // If module is disabled, modify_roi_in() is a no-op
589 if(piece->enabled)
590 module->modify_roi_in(module, pipe, piece, &roi_out_temp, &roi_in);
591 else
592 roi_in = roi_out_temp;
593
594 // Backward ROI planning answers "how much input rectangle does this
595 // module need from upstream to deliver the requested downstream output ?".
596 // Logging that request before and after modify_roi_in() makes ROI growth
597 // and padding traceable module-by-module on `-d pipe`.
598 if(piece->enabled && (darktable.unmuted & DT_DEBUG_PIPE))
600 "[roi-in ] pipe=%-15s module=%-18s enabled=%d out=(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)"
601 " in=(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)\n",
602 pipe_name, module->op, piece->enabled,
603 roi_out_temp.x, roi_out_temp.y, roi_out_temp.width, roi_out_temp.height, roi_out_temp.scale,
604 roi_in.x, roi_in.y, roi_in.width, roi_in.height, roi_in.scale);
605
606 piece->roi_in = roi_in;
607 roi_out_temp = roi_in;
608 }
609
610 if(pipe_name) dt_free(pipe_name);
611
612 /* ROI planning runs backwards, but rawprepare seals the effective Bayer/X-Trans phase only once
613 * the real input crop is known. Forward that authored RAW descriptor now so the downstream RAW
614 * modules process with the same CFA layout that rawprepare just computed for this run. */
615 dt_iop_buffer_dsc_t upstream_dsc = pipe->dev->image_storage.dsc;
616 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
617 {
619
620 if(piece->dsc_in.cst == IOP_CS_RAW && piece->dsc_in.channels == 1)
621 {
622 piece->dsc_in.filters = upstream_dsc.filters;
623 memcpy(piece->dsc_in.xtrans, upstream_dsc.xtrans, sizeof(piece->dsc_in.xtrans));
624 }
625
626 if(piece->dsc_out.cst == IOP_CS_RAW && piece->dsc_out.channels == 1
627 && (!piece->enabled || strcmp(piece->module->op, "rawprepare")))
628 {
629 piece->dsc_out.filters = piece->dsc_in.filters;
630 memcpy(piece->dsc_out.xtrans, piece->dsc_in.xtrans, sizeof(piece->dsc_out.xtrans));
631 }
632
633 upstream_dsc = piece->dsc_out;
634 }
635
636}
637
639{
640 // Start with a hash that is unique, image-wise.
641 return dt_hash(5381, (const char *)&pipe->dev->image_storage.filename, DT_MAX_FILENAME_LEN);
642}
643
645 const dt_iop_roi_t roi_out, const int pos)
646{
647 // to be called at runtime, not at pipe init.
648
649 // Only at the first step of pipe, we don't have a module because we init the base buffer.
650 if(!IS_NULL_PTR(piece))
651 return piece->global_hash;
652 else
653 {
654 // This is used for the first step of the pipe, before modules, when initing base buffer
655 // We need to take care of the ROI manually
656 uint64_t hash = _default_pipe_hash(pipe);
657 hash = dt_hash(hash, (const char *)&roi_out, sizeof(dt_iop_roi_t));
658 return dt_hash(hash, (const char *)&pos, sizeof(int));
659 }
660}
661
663 const dt_iop_module_t *module)
664{
665 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(module)) return NULL;
666
667 for(GList *node = g_list_first(pipe->nodes); node; node = g_list_next(node))
668 {
669 dt_dev_pixelpipe_iop_t *const piece = node->data;
670 if(piece && piece->enabled && piece->module == module)
671 return piece;
672 }
673
674 return NULL;
675}
676
678 const dt_dev_pixelpipe_iop_t *piece)
679{
680 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(piece)) return NULL;
681
682 GList *node = g_list_find(pipe->nodes, (gpointer)piece);
683 if(IS_NULL_PTR(node)) return NULL;
684
685 for(node = g_list_previous(node); node; node = g_list_previous(node))
686 {
687 dt_dev_pixelpipe_iop_t *const previous = node->data;
688 if(previous && previous->enabled)
689 return previous;
690 }
691
692 return NULL;
693}
694
696 void **data, dt_pixel_cache_entry_t **cache_entry,
699 gpointer restart_data)
700{
701 if(!IS_NULL_PTR(data)) *data = NULL;
702 if(!IS_NULL_PTR(cache_entry)) *cache_entry = NULL;
703 if(IS_NULL_PTR(pipe)) return FALSE;
704
705 // Module-output cache requests are not satisfiable when the target piece
706 // itself bypasses cache retention. Re-queueing those from GUI redraws would
707 // keep transient edit modes in a self-feeding recompute loop.
708 // Upstream targets (for example color-picker input sampling) stay allowed even if a
709 // downstream piece enabled bypass mode.
710 if(!IS_NULL_PTR(piece)
711 && (dt_dev_pixelpipe_get_realtime(pipe) || pipe->no_cache || piece->bypass_cache))
712 return FALSE;
713
714 const uint64_t hash = !IS_NULL_PTR(piece) ? piece->global_hash : dt_dev_pixelpipe_get_hash(pipe);
715
716 // For the final backbuffer (piece == NULL), GUI consumers display the last *published* frame,
717 // identified by pipe->backbuf.hash. The pipeline plans the next frame ahead of publishing it, so
718 // pipe->hash (the planned final hash) runs ahead of the backbuffer while a recompute is in flight.
719 // This is the normal steady state of realtime drawing: every heartbeat plans a new frame. Peeking
720 // the planned hash would then miss the perfectly valid published frame, the darkroom main-surface
721 // lock would fail, and the view would drop to the paused preview pipe (which lacks the in-progress
722 // stroke) — the flicker between "drawing applied" and "original". So look up the published backbuf
723 // hash for display, and keep the planned hash only to drive recompute on a genuine miss below.
724 uint64_t display_hash = hash;
725 if(IS_NULL_PTR(piece))
726 {
727 const uint64_t backbuf_hash = dt_dev_backbuf_get_hash(&pipe->backbuf);
728 if(backbuf_hash != DT_PIXELPIPE_CACHE_HASH_INVALID) display_hash = backbuf_hash;
729 }
730
731 void *buffer = NULL;
732 dt_pixel_cache_entry_t *entry = NULL;
733 // GUI consumers run on the CPU and never own the OpenCL device lock, so we pass preferred_devid = -1:
734 // dt_dev_pixelpipe_cache_peek() then returns host-resident cachelines directly but refuses to
735 // materialize device-only ones from the GPU (which would enqueue hidden GPU work from the GUI thread,
736 // racing whichever pipeline thread currently owns the device — the clReleaseEvent crash). A device-only
737 // entry is reported as a miss, so the request below waits for the pipeline to publish a host copy
738 // (modules whose output the GUI samples, e.g. initialscale, already cache to RAM).
739 if(display_hash != DT_PIXELPIPE_CACHE_HASH_INVALID
740 && dt_dev_pixelpipe_cache_peek(darktable.pixelpipe_cache, display_hash, &buffer, &entry, -1, NULL)
741 && !IS_NULL_PTR(buffer) && !IS_NULL_PTR(entry))
742 {
743 // These counters are only diagnostic; cache consumers should not wait for
744 // queue lifecycle updates before reopening an already available cacheline.
746 {
749 }
750 dt_dev_pixelpipe_cache_wait_cleanup(wait, "peek-gui-immediate-hit");
751 if(!IS_NULL_PTR(data)) *data = buffer;
752 if(!IS_NULL_PTR(cache_entry)) *cache_entry = entry;
753 return TRUE;
754 }
755
756 // A missed GUI peek already requests cache publication below. The miss counter
757 // is diagnostic, so it must not block redraw/picker paths under contention.
759 {
762 }
763
764 gboolean request_cacheline = TRUE;
765 if(!IS_NULL_PTR(wait) && !IS_NULL_PTR(restart) && hash != DT_PIXELPIPE_CACHE_HASH_INVALID)
766 {
767 const gboolean changed_target = !wait->connected
768 || wait->pipe != pipe
769 || wait->module != (!IS_NULL_PTR(piece) ? piece->module : NULL)
770 || wait->hash != hash
771 || wait->restart != restart;
772 if(changed_target)
773 {
774 gboolean activate_wait_cursor = FALSE;
775 dt_dev_pixelpipe_cache_wait_cleanup(wait, "peek-gui-target-changed");
776 wait->pipe = pipe;
777 wait->module = !IS_NULL_PTR(piece) ? piece->module : NULL;
778 wait->hash = hash;
779 // Record the producing node identity so CACHELINE_READY can serve this waiter by
780 // its target module even if the exact awaited hash drifts before publish (§8). A
781 // backbuf target (piece == NULL) has no single producing node, so it keeps to the
782 // exact-hash match only.
783 wait->target_node_key = !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module)
784 ? dt_supervisor_node_key(pipe->type, piece->module->op,
785 piece->module->multi_priority)
787 wait->restart = restart;
788 wait->user_data = restart_data;
789 if(IS_NULL_PTR(wait->owner_tag))
790 wait->owner_tag = !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module) ? piece->module->op : "gui-backbuf";
791 if(IS_NULL_PTR(wait->owner_object))
792 wait->owner_object = restart_data;
793 wait->connected = TRUE;
794
798 dt_dev_pixelpipe_cache_wait_record_t *record = calloc(1, sizeof(*record));
799 record->wait = wait;
800 record->request_id = wait->request_id;
801 record->queued_at_us = g_get_monotonic_time();
802 _cache_wait_manager.pending = g_list_prepend(_cache_wait_manager.pending, record);
804 {
808 }
810 {
812 activate_wait_cursor = TRUE;
813 }
815 if(activate_wait_cursor)
816 g_main_context_invoke(NULL, _cache_wait_cursor_progress, NULL);
817
819 "[cache-wait] queued id=%" PRIu64 " owner=%s hash=%" PRIu64 " target=%s\n",
820 wait->request_id,
821 !IS_NULL_PTR(wait->owner_tag) ? wait->owner_tag : "(unknown)",
822 wait->hash,
823 !IS_NULL_PTR(wait->module) ? wait->module->op : "backbuf");
824
827 !IS_NULL_PTR(wait->module) ? wait->module->op : NULL,
828 !IS_NULL_PTR(wait->module) ? wait->module->multi_priority : 0,
829 (int)pipe->type, pipe->imgid, TRUE, NULL);
830 }
831 else
832 {
833 /* The GUI already waits for this exact cacheline. Re-emitting CACHE_REQUEST from
834 * each expose keeps retrying an unsatisfied target forever when the pipeline cannot
835 * publish it, for example after an OpenCL memory pre-check failure on very large images. */
836 request_cacheline = FALSE;
837
838 /* A deduped re-poll: the consumer asked again for a wait already in flight, and no
839 * new CACHE_REQUEST was emitted. A run of these with no matching serve (and no
840 * cacheline ever published under wait->hash) is the "never processed, never
841 * finishes" signature. */
844 !IS_NULL_PTR(wait->module) ? wait->module->op : NULL,
845 !IS_NULL_PTR(wait->module) ? wait->module->multi_priority : 0,
846 (int)pipe->type, pipe->imgid, FALSE, "dedup-poll");
847 }
848 }
849
850 if(request_cacheline)
851 {
855 !IS_NULL_PTR(piece) ? piece->module : NULL);
857
858 dt_print(DT_DEBUG_DEV, "[pixelpipe/gui] request host cache pipe=%s target=%s hash=%" PRIu64 "\n",
860 !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module) ? piece->module->op : "backbuf", hash);
861 }
862
863 return FALSE;
864}
865
893static void _dt_dev_pixelpipe_cache_wait_ready_callback(gpointer instance, const guint64 hash,
894 const guint64 producer_node_key,
895 gpointer user_data)
896{
897 GList *to_restart = NULL;
898 gboolean restore_wait_cursor = FALSE;
899 const gboolean node_key_valid
900 = producer_node_key != 0 && producer_node_key != DT_PIXELPIPE_CACHE_HASH_INVALID;
901
903 for(GList *iter = _cache_wait_manager.pending; iter; )
904 {
905 GList *next = g_list_next(iter);
906 dt_dev_pixelpipe_cache_wait_record_t *record = iter->data;
907 dt_dev_pixelpipe_cache_wait_t *wait = !IS_NULL_PTR(record) ? record->wait : NULL;
908 const gboolean node_match = node_key_valid && !IS_NULL_PTR(wait)
909 && wait->target_node_key == producer_node_key;
910 if(!IS_NULL_PTR(wait) && wait->connected && (wait->hash == hash || node_match))
911 {
912 _cache_wait_manager.pending = g_list_delete_link(_cache_wait_manager.pending, iter);
914 wait->connected = FALSE;
915 to_restart = g_list_prepend(to_restart, wait);
916 dt_free(record);
917 }
918 iter = next;
919 }
920
922 {
926 }
928 {
930 restore_wait_cursor = TRUE;
931 }
933 if(restore_wait_cursor)
934 g_main_context_invoke(NULL, _cache_wait_cursor_restore, NULL);
935
936 for(GList *iter = to_restart; iter; iter = g_list_next(iter))
937 {
938 dt_dev_pixelpipe_cache_wait_t *wait = iter->data;
940 gpointer restart_data = wait->user_data;
941 // A drift serve: the awaited hash never published, but the target module did
942 // (matched via producer_node_key). Record it as such so the supervisor timeline
943 // shows the recovery instead of a phantom exact hit.
944 const gboolean drift_serve = wait->hash != hash;
945
947 "[cache-wait] served id=%" PRIu64 " owner=%s awaited=%" PRIu64 " published=%" PRIu64
948 " target=%s%s\n",
949 wait->request_id,
950 !IS_NULL_PTR(wait->owner_tag) ? wait->owner_tag : "(unknown)",
951 wait->hash, hash,
952 !IS_NULL_PTR(wait->module) ? wait->module->op : "backbuf",
953 drift_serve ? " (drift: node-key)" : "");
954
957 !IS_NULL_PTR(wait->module) ? wait->module->op : NULL,
958 !IS_NULL_PTR(wait->module) ? wait->module->multi_priority : 0,
959 !IS_NULL_PTR(wait->pipe) ? (int)wait->pipe->type : -1,
960 !IS_NULL_PTR(wait->pipe) ? wait->pipe->imgid : -1, FALSE,
961 drift_serve ? "served (drift: node-key)" : "served");
962
963 wait->pipe = NULL;
964 wait->module = NULL;
967 wait->restart = NULL;
968 wait->user_data = NULL;
969 wait->request_id = 0;
970
971 if(!IS_NULL_PTR(restart)) restart(restart_data);
972 }
973 g_list_free(to_restart);
974}
975
977{
978 if(IS_NULL_PTR(wait) || !wait->connected) return;
979 gboolean restore_wait_cursor = FALSE;
980 int64_t queued_at_us = 0;
981 const uint64_t request_id = wait->request_id;
982 const uint64_t hash = wait->hash;
983 const char *owner_tag = wait->owner_tag;
984 const dt_iop_module_t *module = wait->module;
985 const dt_dev_pixelpipe_t *cancel_pipe = wait->pipe;
986 const char *cancel_reason = !IS_NULL_PTR(reason) ? reason : "unspecified";
987
990 if(!IS_NULL_PTR(record))
991 {
992 queued_at_us = record->queued_at_us;
993 dt_free(record);
994 }
997 {
1001 }
1003 {
1005 restore_wait_cursor = TRUE;
1006 }
1008 if(restore_wait_cursor)
1009 g_main_context_invoke(NULL, _cache_wait_cursor_restore, NULL);
1010
1011 const int64_t age_ms = queued_at_us > 0 ? MAX((g_get_monotonic_time() - queued_at_us) / 1000, 0) : -1;
1013 "[cache-wait] cancelled id=%" PRIu64 " owner=%s hash=%" PRIu64
1014 " target=%s age_ms=%" PRId64 " reason=%s\n",
1015 request_id,
1016 !IS_NULL_PTR(owner_tag) ? owner_tag : "(unknown)",
1017 hash,
1018 !IS_NULL_PTR(module) ? module->op : "backbuf",
1019 age_ms,
1020 cancel_reason);
1021
1023 {
1024 char note[64];
1025 g_snprintf(note, sizeof(note), "cancelled: %s", cancel_reason);
1026 dt_supervisor_cache_wait(DT_SV_DELETE, request_id, hash, owner_tag,
1027 !IS_NULL_PTR(module) ? module->op : NULL,
1028 !IS_NULL_PTR(module) ? module->multi_priority : 0,
1029 !IS_NULL_PTR(cancel_pipe) ? (int)cancel_pipe->type : -1,
1030 !IS_NULL_PTR(cancel_pipe) ? cancel_pipe->imgid : -1, FALSE, note);
1031 }
1032
1033 wait->pipe = NULL;
1034 wait->module = NULL;
1037 wait->restart = NULL;
1038 wait->user_data = NULL;
1039 wait->request_id = 0;
1040 wait->connected = FALSE;
1041}
1042
1044 const char *owner_tag,
1045 gpointer owner_object)
1046{
1047 if(IS_NULL_PTR(wait)) return;
1048 wait->owner_tag = owner_tag;
1049 wait->owner_object = owner_object;
1050}
1051
1052/* Establish the *input* side of a piece's buffer contract from the upstream descriptor by
1053 * asking the module's input_format(). This does NOT decide compatibility or finalize the
1054 * output: the single authoritative pass dt_dev_pixelpipe_propagate_formats() owns the mismatch/auto-disable
1055 * decision so it is taken once, on the fully-committed and consistently-threaded chain, never
1056 * on the stale partial state a synch_top / realtime edit can leave behind (issue #733).
1057 *
1058 * It is split out from that pass because commit_params() of a few modules (exposure, highlights,
1059 * invert, temperature) reads piece->dsc_in (filters, processed_maximum), so the commit loop must
1060 * set dsc_in before committing. */
1061// Short colorspace name for the [dsc] format-propagation debug log (-d pipe). Knowing the cst of
1062// each node's input/output is essential to spot when the pixelpipe's automatic RGB<->Lab conversion
1063// should fire: a Lab-domain module whose dsc_in.cst is not "lab" will silently consume RGB.
1064static const char *_dsc_cst_name(const int cst)
1065{
1066 switch(cst)
1067 {
1068 case IOP_CS_RAW: return "raw";
1069 case IOP_CS_LAB: return "lab";
1070 case IOP_CS_RGB: return "rgb";
1071 case IOP_CS_RGB_DISPLAY: return "display-rgb";
1072 case IOP_CS_LCH: return "lch";
1073 case IOP_CS_HSL: return "hsl";
1074 case IOP_CS_JZCZHZ: return "jzczhz";
1075 case IOP_CS_NONE: return "none";
1076 default: return "?";
1077 }
1078}
1079
1081 const dt_iop_buffer_dsc_t *upstream_dsc)
1082{
1083 piece->dsc_in = *upstream_dsc;
1084 piece->dsc_out = *upstream_dsc;
1087
1088 /* Disabled modules are strict pass-through stages. Their advertised contracts must
1089 * not rewrite the upstream descriptor, otherwise a disabled RGB-only module can make
1090 * downstream RAW stages such as demosaic believe the stream was already converted. */
1091 if(!piece->enabled) return;
1092
1093 piece->module->input_format(piece->module, pipe, piece, &piece->dsc_in);
1095 piece->dsc_out = piece->dsc_in;
1097}
1098
1100 dt_iop_params_t *params, dt_develop_blend_params_t *blend_params,
1101 dt_iop_buffer_dsc_t *upstream_dsc)
1102{
1103 const dt_iop_buffer_dsc_t actual_input_dsc = *upstream_dsc;
1104
1105 // Set dsc_in before committing (commit_params() of some modules reads it). Compatibility and
1106 // the final output descriptor are settled later by dt_dev_pixelpipe_propagate_formats(), so this loop
1107 // never auto-disables a module on a possibly-stale upstream descriptor.
1108 _prepare_piece_input_contract(pipe, piece, upstream_dsc);
1109
1110 // This should run even if the module is disabled because
1111 // some modules with no history self-enable based on runtime context
1112 dt_iop_commit_params(piece->module, params, blend_params, pipe, piece);
1113
1114 if(piece->enabled)
1115 {
1116 piece->module->output_format(piece->module, pipe, piece, &piece->dsc_out);
1118 }
1119 else
1120 {
1121 piece->dsc_in = actual_input_dsc;
1122 piece->dsc_out = actual_input_dsc;
1125 }
1126
1127 *upstream_dsc = piece->dsc_out;
1128}
1129
1130/* Direction-independent forward format pass.
1131 *
1132 * The buffer-format contract (channels / datatype / colorspace / CFA presence flowing from one
1133 * module to the next) is conceptually independent of ROI planning, which runs in both
1134 * directions (end->start for a target size, start->end for the native size). Deriving the
1135 * contract inside the history-sync commit loop made it sensitive to partial syncs
1136 * (synch_top / realtime top edits): a downstream module could read a stale upstream descriptor
1137 * and get wrongly disabled for an "unexpected input buffer format" (issue #733). Worse, the
1138 * auto-disable is one-way, so a later pass could not undo a false positive.
1139 *
1140 * This single forward pass re-threads the whole chain from the input image descriptor after
1141 * params have been committed, and is the *only* place that disables a module for a genuinely
1142 * incompatible input. Disabling here is safe because the chain is consistent and fully threaded
1143 * from pipe->dev->image_storage.dsc. Only the first stage (basebuffer) reads the input image
1144 * type; every later node derives its contract solely from its upstream piece. The lone
1145 * ROI-dependent refinement, rawprepare's CFA phase shift, is finalized later in modify_roi_*().
1146 *
1147 * It is deliberately NON-destructive for compatible enabled modules: it verifies the input
1148 * contract and re-threads dsc_in, but keeps the dsc_out that commit_params() produced. dsc_out
1149 * carries runtime fields (processed_maximum, rawprepare black/white, temperature coeffs, CFA
1150 * phase) that input/output_format cannot reconstruct, so recomputing them here would wipe the
1151 * tonal scaling and render images black/white. */
1153{
1154 if(IS_NULL_PTR(pipe)) return;
1155
1156 dt_iop_buffer_dsc_t upstream_dsc = pipe->dev->image_storage.dsc;
1157 gchar *pipe_name = (darktable.unmuted & DT_DEBUG_PIPE) ? _get_debug_pipe_name(pipe, NULL) : NULL;
1158
1159 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1160 {
1162 if(IS_NULL_PTR(piece) || IS_NULL_PTR(piece->module)) continue;
1163
1164 const dt_iop_buffer_dsc_t actual_input_dsc = upstream_dsc;
1165
1166 if(!piece->enabled)
1167 {
1168 // Disabled modules are strict pass-through stages.
1169 piece->dsc_in = actual_input_dsc;
1170 piece->dsc_out = actual_input_dsc;
1173 }
1174 else
1175 {
1176 // Ask the module what input it expects, into a scratch descriptor. We must NOT recompute
1177 // piece->dsc_out from input/output_format here: commit_params() already produced dsc_out
1178 // with runtime fields that those methods cannot reconstruct (processed_maximum, rawprepare
1179 // black/white, temperature coeffs, CFA phase). Recomputing would reset processed_maximum to
1180 // the input image's value and blow up downstream tone handling (black/white renders).
1181 dt_iop_buffer_dsc_t declared_in = actual_input_dsc;
1182 dt_iop_buffer_dsc_update_bpp(&declared_in);
1183 piece->module->input_format(piece->module, pipe, piece, &declared_in);
1184 dt_iop_buffer_dsc_update_bpp(&declared_in);
1185
1186 // A module whose declared input does not match what the previous stage actually publishes
1187 // cannot run: disable it (in the pipe only; history is untouched) and turn it into a
1188 // pass-through so the contract keeps flowing to the next stage.
1189 const gboolean input_mismatch
1190 = (declared_in.bpp != actual_input_dsc.bpp
1191 || declared_in.channels != actual_input_dsc.channels
1192 || declared_in.filters != actual_input_dsc.filters);
1193
1194 if(input_mismatch)
1195 {
1196 dt_control_log(_("disabled module `%s`: unexpected input buffer format"),
1197 piece->module->name());
1199 "[pixelpipe] disabling module %s because input format expects %" G_GSIZE_FORMAT
1200 " B/px, %u channels, filters %u but upstream publishes %" G_GSIZE_FORMAT
1201 " B/px, %u channels, filters %u\n",
1202 piece->module->op, declared_in.bpp, declared_in.channels, declared_in.filters,
1203 actual_input_dsc.bpp, actual_input_dsc.channels, actual_input_dsc.filters);
1204
1205 piece->enabled = FALSE;
1210 piece->dsc_in = actual_input_dsc;
1211 piece->dsc_out = actual_input_dsc;
1214 }
1215 else
1216 {
1217 // Input is compatible: publish the module's DECLARED input descriptor. `declared_in` was
1218 // seeded from the upstream descriptor and then run through input_format(), so it already
1219 // carries the upstream runtime fields (processed_maximum, rawprepare, temperature, CFA
1220 // phase) AND the colorspace/channel layout the module actually consumes.
1221 //
1222 // Critically, we must NOT fall back to `actual_input_dsc` here: that would reset dsc_in.cst
1223 // to the upstream colorspace and make it equal to the buffer's cst, so the pixelpipe's
1224 // automatic colorspace conversion (pixelpipe_cpu.c / pixelpipe_gpu.c) would believe no
1225 // conversion is needed. Lab-domain modules (old color balance, color checker, atrous, ...)
1226 // inserted after an RGB stage would then receive RGB data interpreted as Lab and render
1227 // garbled/solid colors. The full-resync path keeps dsc_in from input_format() for the same
1228 // reason; this incremental (synch_top) path must match it.
1229 piece->dsc_in = declared_in;
1231 }
1232 }
1233
1234 if(pipe_name)
1236 "[dsc] pipe=%s module=%s enabled=%d in=(cst=%s ch=%i bpp=%" G_GSIZE_FORMAT
1237 " filters=%u) out=(cst=%s ch=%i bpp=%" G_GSIZE_FORMAT " filters=%u)\n",
1238 pipe_name, piece->module->op, piece->enabled,
1239 _dsc_cst_name(piece->dsc_in.cst), piece->dsc_in.channels, piece->dsc_in.bpp, piece->dsc_in.filters,
1240 _dsc_cst_name(piece->dsc_out.cst), piece->dsc_out.channels, piece->dsc_out.bpp, piece->dsc_out.filters);
1241
1242 upstream_dsc = piece->dsc_out;
1243 }
1244
1245 dt_free(pipe_name);
1246}
1247
1248static void _sync_pipe_nodes_from_history(dt_dev_pixelpipe_t *pipe, dt_develop_t *dev, const uint32_t history_end,
1249 const char *debug_label)
1250{
1251 dt_iop_buffer_dsc_t upstream_dsc = pipe->dev->image_storage.dsc;
1252 const gboolean previous_want_detail_mask = (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE);
1253
1254 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1255 {
1257 if(IS_NULL_PTR(piece)) continue;
1258
1263 piece->enabled = piece->module->default_enabled;
1264 piece->detail_mask = FALSE;
1265
1266 dt_iop_params_t *params = piece->module->default_params;
1267 dt_develop_blend_params_t *blend_params = piece->module->default_blendop_params;
1268 uint64_t history_param_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1269 gboolean found_history = FALSE;
1270
1271 for(GList *history = g_list_nth(dev->history, history_end - 1);
1272 history;
1273 history = g_list_previous(history))
1274 {
1275 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1276 if(piece->module == hist->module)
1277 {
1278 piece->enabled = hist->enabled;
1279 params = hist->params;
1280 blend_params = hist->blend_params;
1281 history_param_hash = hist->hash; // the history entry key (module->hash)
1282 found_history = TRUE;
1283 break;
1284 }
1285 }
1286
1287 piece->detail_mask = blend_params && blend_params->details != 0.0f;
1288 if(!strcmp(piece->module->op, "detailmask"))
1290 _commit_piece_contract(pipe, piece, params, blend_params, &upstream_dsc);
1291
1292 // Tie this node to the history item it was just synchronized with, so the
1293 // cachelines it produces resolve to that history state through the node.
1294 // Use the history entry key (module->hash), NOT piece->hash: the latter folds
1295 // in runtime_data_hash() for some modules and then no longer matches history.
1296 if(dt_supervisor_active() && found_history)
1298 dt_supervisor_node_key(pipe->type, piece->module->op, piece->module->multi_priority),
1299 history_param_hash, DT_PIXELPIPE_CACHE_HASH_INVALID, piece->module->op,
1300 piece->module->multi_priority, piece->module->iop_order, pipe->type, pipe->imgid);
1301
1302 if(!found_history)
1303 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] info: committed default params for %s (%s) in pipe %s\n",
1304 piece->module->op, piece->module->multi_name, debug_label);
1305 }
1306
1308 if(previous_want_detail_mask != (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE))
1309 {
1310 GList *detailmask_node = _find_detailmask_node(pipe);
1311 if(detailmask_node)
1312 _sync_pipe_nodes_from_history_from_node(pipe, history_end, detailmask_node, debug_label);
1313 }
1314}
1315
1317 const uint32_t history_end, GList *start_node,
1318 const char *debug_label)
1319{
1320 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(start_node)) return;
1321
1322 dt_iop_buffer_dsc_t upstream_dsc = pipe->dev->image_storage.dsc;
1323 const gboolean previous_want_detail_mask = (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE);
1324 for(GList *node = g_list_first(pipe->nodes); node && node != start_node; node = g_list_next(node))
1325 {
1327 if(IS_NULL_PTR(piece)) continue;
1328
1329 upstream_dsc = piece->dsc_out;
1330 }
1331
1332 for(GList *nodes = start_node; nodes; nodes = g_list_next(nodes))
1333 {
1335 if(IS_NULL_PTR(piece)) continue;
1336
1341
1342 piece->enabled = piece->module->default_enabled;
1343 piece->detail_mask = FALSE;
1344
1345 dt_iop_params_t *params = piece->module->default_params;
1346 dt_develop_blend_params_t *blend_params = piece->module->default_blendop_params;
1347 uint64_t history_param_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1348 gboolean found_history = FALSE;
1349
1350 for(GList *history = g_list_nth(pipe->dev->history, history_end - 1);
1351 history;
1352 history = g_list_previous(history))
1353 {
1354 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1355 if(piece->module == hist->module)
1356 {
1357 piece->enabled = hist->enabled;
1358 params = hist->params;
1359 blend_params = hist->blend_params;
1360 history_param_hash = hist->hash; // the history entry key (module->hash)
1361 found_history = TRUE;
1362 break;
1363 }
1364 }
1365
1366 piece->detail_mask = blend_params && blend_params->details != 0.0f;
1367 if(!strcmp(piece->module->op, "detailmask"))
1369 _commit_piece_contract(pipe, piece, params, blend_params, &upstream_dsc);
1370
1371 // Tie this node to the history item it was just synchronized with, so the
1372 // cachelines it produces resolve to that history state through the node.
1373 // Use the history entry key (module->hash), NOT piece->hash: the latter folds
1374 // in runtime_data_hash() for some modules and then no longer matches history.
1375 if(dt_supervisor_active() && found_history)
1377 dt_supervisor_node_key(pipe->type, piece->module->op, piece->module->multi_priority),
1378 history_param_hash, DT_PIXELPIPE_CACHE_HASH_INVALID, piece->module->op,
1379 piece->module->multi_priority, piece->module->iop_order, pipe->type, pipe->imgid);
1380
1381 if(!found_history)
1382 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] info: committed default params for %s (%s) in pipe %s\n",
1383 piece->module->op, piece->module->multi_name, debug_label);
1384 }
1385
1387 if(previous_want_detail_mask != (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE))
1388 {
1389 GList *detailmask_node = _find_detailmask_node(pipe);
1390 if(detailmask_node && detailmask_node != start_node)
1391 _sync_pipe_nodes_from_history_from_node(pipe, history_end, detailmask_node, debug_label);
1392 }
1393}
1394
1396{
1397 /* Traverse the pipeline node by node and compute the cumulative (global) hash of each module.
1398 * This hash takes into account the hashes of the previous modules and the size of the current ROI.
1399 * It is used to map pipeline cache states to current parameters.
1400 * It represents the state of internal modules params as well as their position in the pipe and their output size.
1401 * It is to be called at pipe init, not at runtime.
1402 */
1403
1404 // bernstein hash (djb2)
1405 uint64_t hash = _default_pipe_hash(pipe);
1406 gboolean passthrough_preview = FALSE;
1407
1408 // Bypassing cache contaminates downstream modules, starting at the module requesting it.
1409 // Usecase : crop, clip, ashift, etc. that need the uncropped image ;
1410 // mask displays ; overexposed/clipping alerts and all other transient previews.
1411 gboolean bypass_cache = FALSE;
1412
1413 for(GList *node = g_list_first(pipe->nodes); node; node = g_list_next(node))
1414 {
1416 if(!piece->enabled) continue;
1417
1418 // Combine with the previous bypass states
1419 bypass_cache |= piece->module->bypass_cache;
1420 piece->bypass_cache = bypass_cache;
1421
1422 // Combine with the previous modules hashes
1423 uint64_t local_hash = piece->hash;
1424
1425 // Some GUI previews author their final display inside the active module,
1426 // then runtime forwards that exact buffer through later pass-through stages.
1427 // Keep the planned hash contract aligned with the published cacheline so the
1428 // GUI does not keep requesting a downstream output hash that will never exist.
1429 if(passthrough_preview
1430 && !(piece->module->operation_tags() & IOP_TAG_DISTORT)
1431 && (piece->dsc_in.bpp == piece->dsc_out.bpp)
1432 && !memcmp(&piece->roi_in, &piece->roi_out, sizeof(dt_iop_roi_t)))
1433 {
1434 piece->global_mask_hash = hash;
1435 piece->global_hash = hash;
1436 continue;
1437 }
1438
1439 // Panning and zooming change the ROI. Some GUI modes (crop in editing mode) too.
1440 // dt_dev_get_roi_in() should have run before
1441 local_hash = dt_hash(local_hash, (const char *)&piece->roi_in, sizeof(dt_iop_roi_t));
1442 local_hash = dt_hash(local_hash, (const char *)&piece->roi_out, sizeof(dt_iop_roi_t));
1443
1444 local_hash = dt_hash(local_hash, (const char *)&piece->dsc_in, sizeof(dt_iop_buffer_dsc_t));
1445 local_hash = dt_hash(local_hash, (const char *)&piece->dsc_out, sizeof(dt_iop_buffer_dsc_t));
1446
1447/*
1448 fprintf(stdout, "start->end : %-17s | ROI in: %4ix%-4i @%2.4f | ROI out: %4ix%-4i @%2.4f\n", piece->module->op,
1449 piece->buf_in.width, piece->buf_in.height, piece->buf_in.scale, piece->buf_out.width,
1450 piece->buf_out.height, piece->buf_out.scale);
1451 fprintf(stdout, "end->start : %-17s | ROI in: %4ix%-4i @%2.4f | ROI out: %4ix%-4i @%2.4f\n", piece->module->op,
1452 piece->roi_in.width, piece->roi_in.height, piece->planned_roi_in.scale,
1453 piece->roi_out.width, piece->roi_out.height, piece->roi_out.scale);
1454*/
1455 // Mask preview display doesn't re-commit params, so we need to keep that of it here
1456 // Too much GUI stuff interleaved with pipeline stuff...
1457 // Mask display applies only to main preview in darkroom.
1458 if(pipe->type == DT_DEV_PIXELPIPE_FULL)
1459 {
1460 local_hash = dt_hash(local_hash, (const char *)&piece->module->request_mask_display, sizeof(int));
1461
1462 /* Mask-preview appearance is global GUI state, not module history. Hash
1463 * its revision at the module producing the active preview so upstream
1464 * cache entries remain reusable while this module and gamma are rerun. */
1465 if(pipe->dev->gui_attached
1466 && piece->module == pipe->dev->gui_module
1467 && piece->module->request_mask_display != DT_DEV_PIXELPIPE_DISPLAY_NONE)
1468 {
1469 const int revision = dt_atomic_get_int(&pipe->dev->mask_preview_settings_revision);
1470 local_hash = dt_hash(local_hash, (const char *)&revision, sizeof(revision));
1471 }
1472
1473 // bypass_cache_variant (see dt_iop_module_t.bypass_cache_variant) is, like
1474 // request_mask_display above, module-owned GUI state whose real effect a module such as
1475 // retouch applies only to this main/FULL pipe -- never to preview/virtual-preview, which
1476 // always render as if none of these toggles were active. But the field itself lives on
1477 // the shared dt_iop_module_t, so it reads the same non-zero value from every pipe type. Left
1478 // ungated, a preview-pipe run with the same ROI/params (e.g. at zoom == fit) can compute the
1479 // exact same hash chain as the FULL pipe despite publishing different pixels, and either
1480 // pipe's cache entry then gets silently reused by the other via the cross-pipe "another pipe
1481 // already owns this hash" exact-hit path -- exactly like request_mask_display's own hazard.
1482 local_hash = dt_hash(local_hash, (const char *)&piece->module->bypass_cache_variant, sizeof(int));
1483 }
1484 else
1485 {
1486 const int zero = 0;
1487 local_hash = dt_hash(local_hash, (const char *)&zero, sizeof(int));
1488 local_hash = dt_hash(local_hash, (const char *)&zero, sizeof(int));
1489 }
1490
1491 // Keep track of distortion bypass in GUI. That may affect upstream modules in the stack,
1492 // while bypass_cache only affects downstream ones.
1493 // In theory, distortion bypass should already affect planned ROI in/out, but it depends whether
1494 // internal params are committed. Anyway, make it more reliable.
1495 int bypass_distort = dt_dev_pixelpipe_activemodule_disables_currentmodule(pipe->dev, piece->module);
1496 local_hash = dt_hash(local_hash, (const char *)&bypass_distort, sizeof(int));
1497
1498 // If the cache bypass is on, the corresponding cache lines will be freed immediately after use,
1499 // we need to track that. It somewhat overlaps module->request_mask_display, but...
1500 local_hash = dt_hash(local_hash, (const char *)&piece->bypass_cache, sizeof(gboolean));
1501
1502 // Update global hash for this stage
1503 hash = dt_hash(hash, (const char *)&local_hash, sizeof(uint64_t));
1504
1506 {
1507 gchar *type = _get_debug_pipe_name(pipe, pipe->dev);
1509 "[pixelpipe] global hash for %20s (%s) in pipe %s enabled=%d bypass_cache=%d with hash %lu\n",
1510 piece->module->op, piece->module->multi_name, type, piece->enabled, piece->bypass_cache,
1511 (long unsigned int)hash);
1512 dt_free(type);
1513 }
1514 // In case of drawn masks, we would need to account only for the distortions of previous modules.
1515 // Aka conditional to: if((piece->module->operation_tags() & IOP_TAG_DISTORT) == IOP_TAG_DISTORT)
1516 // But in case of parametric masks, they depend on previous modules parameters.
1517 // So, all in all, (parametric | drawn | raster) masking depends on everything :
1518 // - if masking on output, internal params + blendop params + all previous modules internal params + ROI size,
1519 // - if masking on input, blendop params + all previous modules internal params + ROI size
1520 // So we use all that ot once :
1521 piece->global_mask_hash = dt_hash(hash, (const char *)&piece->blendop_hash, sizeof(uint64_t));
1522
1523 // Finally, the output of the module also depends on the mask:
1524 hash = dt_hash(hash, (const char *)&piece->global_mask_hash, sizeof(uint64_t));
1525 piece->global_hash = hash;
1526
1527 if(pipe->type == DT_DEV_PIXELPIPE_FULL
1528 && pipe->dev->gui_attached
1529 && (piece->module == pipe->dev->gui_module)
1530 && (piece->module->request_mask_display != DT_DEV_PIXELPIPE_DISPLAY_NONE))
1531 {
1532 passthrough_preview = TRUE;
1533 }
1534 }
1535
1536 // The pipe hash is the hash of its last module.
1537 dt_dev_pixelpipe_set_hash(pipe, hash);
1538 pipe->bypass_cache = bypass_cache;
1539}
1540
1551void dt_dev_pixelpipe_synch_all_real(dt_dev_pixelpipe_t *pipe, const char *caller_func)
1552{
1553 gchar *type = _get_debug_pipe_name(pipe, pipe->dev);
1554 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch all modules with history for pipe %s called from %s\n", type, caller_func);
1555
1556 const uint32_t history_end = dt_dev_get_history_end_ext(pipe->dev);
1557 _sync_pipe_nodes_from_history(pipe, pipe->dev, history_end, type);
1558
1559 // Keep track of the last history item to have been synced
1560 GList *last_item = g_list_nth(pipe->dev->history, history_end - 1);
1561 if(last_item)
1562 {
1563 dt_dev_history_item_t *last_hist = (dt_dev_history_item_t *)last_item->data;
1564 pipe->last_history_hash = last_hist->hash;
1565 pipe->last_history_item = last_hist;
1566 }
1567 else
1568 {
1570 pipe->last_history_item = NULL;
1571 }
1572
1573 dt_free(type);
1574}
1575
1577{
1578 gchar *type = _get_debug_pipe_name(pipe, pipe->dev);
1579
1580 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch top modules with history for pipe %s\n", type);
1581
1582 const uint32_t history_end = dt_dev_get_history_end_ext(pipe->dev);
1583 GList *last_item = g_list_nth(pipe->dev->history, history_end - 1);
1584 if(last_item)
1585 {
1586 GList *first_item = NULL;
1587 for(GList *history = last_item; history; history = g_list_previous(history))
1588 {
1589 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1590 first_item = history;
1591
1592 if(hist->hash == pipe->last_history_hash || hist == pipe->last_history_item)
1593 break;
1594 }
1595
1596 GList *fence_item = g_list_nth(pipe->dev->history, history_end);
1597 for(GList *history = first_item; history && history != fence_item; history = g_list_next(history))
1598 {
1599 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1600 if(!hist || !hist->module) continue;
1601 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] synch top history module `%s` (%s) for pipe %s\n",
1602 hist->module->op, hist->module->multi_name, type);
1603 for(GList *nodes = g_list_last(pipe->nodes); nodes; nodes = g_list_previous(nodes))
1604 {
1606 if(IS_NULL_PTR(piece) || piece->module != hist->module) continue;
1607
1608 _sync_pipe_nodes_from_history_from_node(pipe, history_end, nodes, type);
1609 break;
1610 }
1611 }
1612
1613 dt_dev_history_item_t *last_hist = (dt_dev_history_item_t *)last_item->data;
1614 pipe->last_history_hash = last_hist->hash;
1615 pipe->last_history_item = last_hist;
1616 }
1617 else
1618 {
1619 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch top history module missing error for pipe %s\n", type);
1621 pipe->last_history_item = NULL;
1622 }
1623
1624 dt_free(type);
1625}
1626
1627// Modules without history need to be resynced unconditionnally with their internal params
1628// because some of them are self-enabled/disabled from commit_params() methods
1630{
1631 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1632 {
1634 if(IS_NULL_PTR(piece) || IS_NULL_PTR(piece->module)) continue;
1635 dt_iop_module_t *module = piece->module;
1636 if(module->flags() & IOP_FLAGS_NO_HISTORY_STACK)
1637 dt_iop_commit_params(module, module->default_params, module->default_blendop_params, pipe, piece);
1638 }
1639}
1640
1642{
1643 dt_times_t start;
1644 dt_get_times(&start);
1645
1656
1657 gchar *type = _get_debug_pipe_name(pipe, pipe->dev);
1658 char *status_str = g_strdup_printf("%s%s%s%s%s%s%s",
1659 (status & DT_DEV_PIPE_UNCHANGED) ? "UNCHANGED " : "",
1660 (status & DT_DEV_PIPE_REMOVE) ? "REMOVE " : "",
1661 (status & DT_DEV_PIPE_TOP_CHANGED) ? "TOP_CHANGED " : "",
1662 (status & DT_DEV_PIPE_SYNCH) ? "SYNCH " : "",
1663 (status & DT_DEV_PIPE_ZOOMED) ? "ZOOMED " : "",
1664 (status & DT_DEV_PIPE_CACHE_REQUEST) ? "CACHE_REQUEST " : "",
1665 (status & DT_DEV_PIPE_REENTRY) ? "REENTRY " : "");
1666
1667 dt_print(DT_DEBUG_DEV, "[dt_dev_pixelpipe_change] pipeline state changing for pipe %s, flag %s\n",
1668 type, status_str);
1669
1670 dt_free(status_str);
1671
1672 // mask display off as a starting point
1674 pipe->bypass_blendif = 0;
1675
1676 /* Zoom/pan only replans ROI and hashes, it does not replay history sync.
1677 * Rebuild the aggregate detail-mask demand from the already synchronized
1678 * pieces in that case, otherwise the pipe forgets that the hidden
1679 * `detailmask` stage is needed before the next processing run starts. */
1682 else
1684
1685 // A call chain that already holds history_mutex as writer on this same thread can re-enter
1686 // here (e.g. history-commit paths that resync the virtual pipe while still holding the write
1687 // lock). dt_pthread_rwlock_rdlock is same-thread-recursive for this exact case (see
1688 // dtpthread.h), so this proceeds immediately instead of deadlocking or deferring the sync.
1689 const double _history_mutex_hold_start = dt_get_wtime();
1691
1692 // The realtime in-place path settles the format contract and the global hash itself (it must,
1693 // since that hash is cumulative over enabled nodes); the other paths defer it to the
1694 // unconditional pass below.
1695 gboolean formats_propagated = FALSE;
1696
1697 // case DT_DEV_PIPE_UNCHANGED: case DT_DEV_PIPE_ZOOMED:
1698 if(status & DT_DEV_PIPE_REMOVE)
1699 {
1700 // modules have been added in between or removed. need to rebuild the whole pipeline.
1701 if(pipe->nodes) dt_dev_pixelpipe_cleanup_nodes(pipe);
1705 }
1706 else if(status & DT_DEV_PIPE_SYNCH)
1707 {
1708 // pipeline topology remains intact, only change all params.
1711 }
1712 else if(status & DT_DEV_PIPE_TOP_CHANGED)
1713 {
1714 // only top history item(s) changed, or a focused module published transient/realtime params
1715 if(_sync_focused_in_place(pipe))
1716 {
1717 formats_propagated = TRUE;
1718 }
1719 else
1720 {
1723 }
1724 }
1725 else // DT_DEV_PIPE_ZOOMED DT_DEV_PIPE_CACHE_REQUEST DT_DEV_PIPE_REENTRY
1726 {
1727 // Finalscale will need to self-enable/disable depending on zoom level
1729 }
1731 const double _history_mutex_hold_ms = (dt_get_wtime() - _history_mutex_hold_start) * 1000.0;
1732 if(_history_mutex_hold_ms > 1.0)
1734 "[dt_dev_pixelpipe_change] tid %lu pipe %s held history_mutex for %.2f ms (status 0x%x)\n",
1735 (unsigned long)pthread_self(), type, _history_mutex_hold_ms, (unsigned)status);
1737
1738 // Re-establish the buffer-format contract of the whole chain once, after whichever sync path
1739 // (full, top-only or realtime-in-place) committed params. This is the single authoritative,
1740 // direction-independent place where per-node dsc_in/dsc_out are settled and incompatible
1741 // modules are disabled (issue #733), so the OpenCL cache policy and ROI planning below see a
1742 // consistent contract. The realtime path already did this before computing its global hash.
1743 if(!formats_propagated)
1745
1747
1748 // Update theoritical final scale based on distorting modules
1749 // This also writes piece->buf_in/out for each pipe->nodes piece,
1750 // so it's not nearly a matter of getting processed_width/height
1752 &pipe->processed_height);
1753
1754 dt_show_times_f(&start, "[dev_pixelpipe] pipeline resync with history", "for pipe %s", type);
1755 dt_free(type);
1756}
1757
1759{
1760 // Virtual pipe exists only for GUI geometry (ROI/mask transforms) and never processes pixels.
1761 if(IS_NULL_PTR(dev) || !dev->gui_attached || IS_NULL_PTR(dev->virtual_pipe)) return;
1762 if(!dev->roi.raw_inited || dev->image_storage.id <= 0) return;
1763
1764 // Ensure its input image metadata matches the current dev state.
1765 if(dev->virtual_pipe->imgid != dev->image_storage.id
1766 || dev->virtual_pipe->iwidth != dev->roi.raw_width
1767 || dev->virtual_pipe->iheight != dev->roi.raw_height
1768 || dev->virtual_pipe->dev->image_storage.id != dev->image_storage.id)
1769 {
1771 dev->roi.raw_width, dev->roi.raw_height, 1.0f, DT_MIPMAP_FULL);
1772 }
1773
1774 // Mirror the preview-pipe change flags and commit immediately.
1777}
1778
1783
1791
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_atomic_set_int(dt_atomic_int *var, int value)
int dt_atomic_get_int(dt_atomic_int *var)
int dt_atomic_exch_int(dt_atomic_int *var, int value)
atomic_int dt_atomic_int
Definition atomic.h:66
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
@ IOP_CS_RAW
@ IOP_CS_LCH
@ IOP_CS_JZCZHZ
@ IOP_CS_RGB
@ IOP_CS_HSL
@ IOP_CS_LAB
@ IOP_CS_NONE
gboolean dt_iop_color_picker_force_cache(const dt_dev_pixelpipe_t *pipe, const dt_iop_module_t *module)
int type
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
int dt_conf_key_exists(const char *key)
gboolean dt_conf_key_not_empty(const char *name)
void dt_control_log(const char *msg,...)
Definition control.c:777
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:877
void dt_control_change_cursor_by_name_and_flush(const char *curs_str)
Apply a named cursor immediatelly and flush display updates for immediate feedback.
Definition control.c:326
void dt_control_commit_cursor()
Definition control.c:334
void dt_control_navigation_redraw()
request redraw of the navigation widget. This redraws the wiget of the navigation module.
Definition control.c:882
darktable_t darktable
Definition darktable.c:183
void dt_show_times_f(const dt_times_t *start, const char *prefix, const char *suffix,...)
Definition darktable.c:1652
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
@ DT_DEBUG_PIPE
Definition darktable.h:763
@ DT_DEBUG_PIPECACHE
Definition darktable.h:742
@ DT_DEBUG_HISTORY
Definition darktable.h:762
@ DT_DEBUG_VERBOSE
Definition darktable.h:765
@ DT_DEBUG_PARAMS
Definition darktable.h:758
@ DT_DEBUG_DEV
Definition darktable.h:739
#define dt_free(ptr)
Definition darktable.h:478
void void void gboolean dt_gui_widgets_suppressed(void)
Definition gtk.c:194
static void dt_get_times(dt_times_t *t)
Definition darktable.h:983
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition darktable.h:1123
static double dt_get_wtime(void)
Definition darktable.h:976
#define DT_MAX_FILENAME_LEN
Definition darktable.h:1179
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
gboolean dt_dev_transient_params_get(dt_develop_t *dev, const dt_iop_module_t *module, void *out_params, const size_t out_params_size, void *out_blend, const size_t out_blend_size, gboolean *out_has_blend)
dt_dev_history_item_t * dt_dev_history_get_last_item_by_module(GList *history_list, dt_iop_module_t *module, int history_end)
Find the last history item referencing a module up to history_end.
gboolean dt_dev_transient_params_active(dt_develop_t *dev, const dt_iop_module_t *module)
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1723
void dt_iop_params_t
Definition dev_history.h:42
static void _sync_pipe_nodes_from_history_from_node(dt_dev_pixelpipe_t *pipe, const uint32_t history_end, GList *start_node, const char *debug_label)
void dt_dev_pixelpipe_propagate_formats(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_update_zoom_main_real(dt_develop_t *dev)
static gboolean _cache_wait_cursor_restore(gpointer user_data)
static void _prepare_piece_input_contract(dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, const dt_iop_buffer_dsc_t *upstream_dsc)
void dt_dev_pixelpipe_update_history_all_real(dt_develop_t *dev)
const dt_dev_pixelpipe_iop_t * dt_dev_pixelpipe_get_module_piece(const dt_dev_pixelpipe_t *pipe, const dt_iop_module_t *module)
void dt_dev_pixelpipe_update_history_main_real(dt_develop_t *dev)
void dt_dev_pixelpipe_synch_top(dt_dev_pixelpipe_t *pipe)
static void _refresh_pipe_detail_mask_state(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_cache_wait_cleanup(dt_dev_pixelpipe_cache_wait_t *wait, const char *reason)
Cancel one pending GUI cache wait request and clear its runtime state.
uint64_t dt_dev_pixelpipe_node_hash(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t roi_out, const int pos)
const dt_dev_pixelpipe_iop_t * dt_dev_pixelpipe_get_prev_enabled_piece(const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
static dt_dev_pixelpipe_cache_wait_record_t * _cache_wait_manager_remove_wait_locked(dt_dev_pixelpipe_cache_wait_t *wait)
void dt_pixelpipe_get_global_hash(dt_dev_pixelpipe_t *pipe)
gboolean dt_dev_pixelpipe_is_backbufer_valid(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_reset_all(dt_develop_t *dev)
static void _commit_piece_contract(dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_params_t *params, dt_develop_blend_params_t *blend_params, dt_iop_buffer_dsc_t *upstream_dsc)
void dt_dev_pixelpipe_get_roi_out(dt_dev_pixelpipe_t *pipe, const int width_in, const int height_in, int *width, int *height)
static void _sync_pipe_nodes_from_history(dt_dev_pixelpipe_t *pipe, dt_develop_t *dev, const uint32_t history_end, const char *debug_label)
void dt_dev_pixelpipe_change(dt_dev_pixelpipe_t *pipe)
static const char * _dsc_cst_name(const int cst)
void dt_dev_pixelpipe_cache_wait_dump_pending(const char *reason)
Dump pending GUI cache wait requests for lifecycle debugging.
static gchar * _get_debug_pipe_name(const dt_dev_pixelpipe_t *pipe, const dt_develop_t *dev)
static void _change_pipe(dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_change_t flag)
void dt_dev_pixelpipe_change_zoom_main(dt_develop_t *dev)
void dt_dev_pixelpipe_synch_all_real(dt_dev_pixelpipe_t *pipe, const char *caller_func)
Find the last history item matching each pipeline node (module), in the order of pipeline execution....
void dt_dev_pixelpipe_sync_no_history(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_sync_virtual(dt_develop_t *dev, dt_dev_pixelpipe_change_t flag)
static GList * _find_detailmask_node(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_rebuild_all_real(dt_develop_t *dev)
static void _sync_virtual_pipe(dt_develop_t *dev, dt_dev_pixelpipe_change_t flag)
static gboolean _sync_focused_in_place(dt_dev_pixelpipe_t *pipe)
Re-commit the focused module's piece in place from transient (or realtime) params.
static void _dt_dev_pixelpipe_cache_wait_ready_callback(gpointer instance, const guint64 hash, const guint64 producer_node_key, gpointer user_data)
Serve queued GUI cache waiters matching one published cacheline hash.
void dt_dev_pixelpipe_cache_wait_set_owner(dt_dev_pixelpipe_cache_wait_t *wait, const char *owner_tag, gpointer owner_object)
Attach debug ownership metadata to one cache wait request.
void dt_dev_pixelpipe_get_roi_in(dt_dev_pixelpipe_t *pipe, const struct dt_iop_roi_t roi_out)
static void _seal_opencl_cache_policy(dt_dev_pixelpipe_t *pipe)
Seal host-cache retention policy on synchronized pieces before processing starts.
void dt_dev_pixelpipe_update_history_preview_real(dt_develop_t *dev)
void dt_dev_pixelpipe_update_zoom_preview_real(dt_develop_t *dev)
gboolean dt_dev_pixelpipe_is_pipeline_valid(dt_dev_pixelpipe_t *pipe)
static uint64_t _default_pipe_hash(dt_dev_pixelpipe_t *pipe)
static gboolean _cache_wait_cursor_progress(gpointer user_data)
void dt_dev_pixelpipe_resync_history_all_real(dt_develop_t *dev)
static dt_dev_pixelpipe_cache_wait_manager_t _cache_wait_manager
gboolean dt_dev_pixelpipe_activemodule_disables_currentmodule(struct dt_develop_t *dev, struct dt_iop_module_t *current_module)
static gboolean _module_requires_global_histogram_output_cache(const dt_dev_pixelpipe_t *pipe, const dt_iop_module_t *module)
void dt_dev_pixelpipe_resync_history_main_real(dt_develop_t *dev)
void dt_dev_pixelpipe_resync_history_preview_real(dt_develop_t *dev)
static gboolean _module_requires_global_histogram_input_cache(const dt_dev_pixelpipe_t *pipe, const dt_iop_module_t *module)
gboolean dt_dev_pixelpipe_cache_peek_gui(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, void **data, dt_pixel_cache_entry_t **cache_entry, dt_dev_pixelpipe_cache_wait_t *wait, dt_dev_pixelpipe_cache_ready_callback_t restart, gpointer restart_data)
#define dt_dev_pixelpipe_rebuild_all(dev)
void(* dt_dev_pixelpipe_cache_ready_callback_t)(gpointer user_data)
#define dt_dev_pixelpipe_resync_history_preview(dev)
#define dt_dev_pixelpipe_resync_history_main(dev)
#define dt_dev_pixelpipe_update_history_main(dev)
#define dt_dev_pixelpipe_update_zoom_main(dev)
#define dt_dev_pixelpipe_update_history_preview(dev)
void dt_dev_update_mouse_effect_radius(dt_develop_t *dev)
Convert absolute output-image coordinates to input image space by calling dt_dev_coordinates_image_ab...
Definition develop.c:1940
static uint64_t dt_dev_get_history_hash(const dt_develop_t *dev)
Definition develop.h:510
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:117
@ DT_DEV_DETAIL_MASK_ENABLED
Definition develop.h:145
@ DT_DEV_DETAIL_MASK_NONE
Definition develop.h:144
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:452
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:384
static int dt_pthread_mutex_trylock(dt_pthread_mutex_t *mutex) TRY_ACQUIRE(0
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:502
void dt_iop_buffer_dsc_update_bpp(dt_iop_buffer_dsc_t *dsc)
Definition format.c:28
@ TYPE_FLOAT
Definition format.h:46
GtkWidget * dt_ui_center(dt_ui_t *ui)
get the center drawable widget
const char flag
Definition image.h:252
void dt_iop_commit_params(dt_iop_module_t *module, dt_iop_params_t *params, dt_develop_blend_params_t *blendop_params, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition imageop.c:1951
gboolean dt_iop_get_cache_bypass(dt_iop_module_t *module)
Definition imageop.c:2984
@ IOP_FLAGS_NO_HISTORY_STACK
Definition imageop.h:204
@ IOP_CS_RGB_DISPLAY
Definition imageop.h:240
@ IOP_TAG_DISTORT
Definition imageop.h:181
@ DT_MIPMAP_FULL
int dt_opencl_is_inited(void)
Definition opencl.c:2828
@ DT_REQUEST_ON
Definition pixelpipe.h:48
@ DT_REQUEST_ONLY_IN_GUI
Definition pixelpipe.h:49
@ DT_DEV_PIXELPIPE_NONE
Definition pixelpipe.h:37
@ DT_DEV_PIXELPIPE_PREVIEW
Definition pixelpipe.h:40
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:39
void dt_dev_pixelpipe_cache_flush(dt_dev_pixelpipe_cache_t *cache, const int id)
Remove cache lines matching id. Entries locked in read/write or having reference count greater than 0...
gboolean dt_dev_pixelpipe_cache_peek(dt_dev_pixelpipe_cache_t *cache, const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry, const int preferred_devid, void **cl_mem_output)
Non-owning lookup of an existing cache line.
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
void dt_dev_pixelpipe_set_input(dt_dev_pixelpipe_t *pipe, int32_t imgid, int width, int height, float iscale, dt_mipmap_size_t size)
gboolean dt_dev_pixelpipe_get_realtime(const dt_dev_pixelpipe_t *pipe)
char * dt_pixelpipe_get_pipe_name(dt_dev_pixelpipe_type_t pipe_type)
void dt_dev_pixelpipe_create_nodes(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_set_realtime(dt_dev_pixelpipe_t *pipe, gboolean state)
void dt_dev_pixelpipe_cleanup_nodes(dt_dev_pixelpipe_t *pipe)
static void dt_dev_pixelpipe_set_hash(dt_dev_pixelpipe_t *pipe, const uint64_t hash)
static uint64_t dt_dev_backbuf_get_history_hash(const dt_backbuf_t *backbuf)
static uint64_t dt_dev_pixelpipe_get_hash(const dt_dev_pixelpipe_t *pipe)
static void dt_dev_pixelpipe_or_changed(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_change_t flags)
static void dt_dev_pixelpipe_set_cache_request(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_cache_request_t request, const struct dt_iop_module_t *module)
dt_dev_pixelpipe_change_t
@ DT_DEV_PIPE_REENTRY
@ DT_DEV_PIPE_ZOOMED
@ DT_DEV_PIPE_CACHE_REQUEST
@ DT_DEV_PIPE_SYNCH
@ DT_DEV_PIPE_TOP_CHANGED
@ DT_DEV_PIPE_REMOVE
@ DT_DEV_PIPE_UNCHANGED
static uint64_t dt_dev_pixelpipe_get_history_hash(const dt_dev_pixelpipe_t *pipe)
static void dt_dev_pixelpipe_set_history_hash(dt_dev_pixelpipe_t *pipe, const uint64_t history_hash)
#define dt_dev_pixelpipe_synch_all(pipe)
static uint64_t dt_dev_backbuf_get_hash(const dt_backbuf_t *backbuf)
@ DT_DEV_PIXELPIPE_CACHE_REQUEST_BACKBUF
@ DT_DEV_PIXELPIPE_CACHE_REQUEST_MODULE
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:387
@ DT_SIGNAL_CACHELINE_READY
This signal is raised when one cacheline write lock is released. 1 : uint64_t cacheline hash no retur...
Definition signal.h:185
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:376
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_dev_pixelpipe_cache_t * pixelpipe_cache
Definition darktable.h:818
struct dt_gui_gtk_t * gui
Definition darktable.h:803
struct dt_control_signal_t * signals
Definition darktable.h:802
int32_t unmuted
Definition darktable.h:788
struct dt_control_t * control
Definition darktable.h:801
dt_iop_params_t * params
Definition dev_history.h:53
struct dt_develop_blend_params_t * blend_params
Definition dev_history.h:56
struct dt_iop_module_t *gboolean enabled
Definition dev_history.h:51
dt_dev_pixelpipe_cache_wait_t * wait
struct dt_dev_pixelpipe_t * pipe
const struct dt_iop_module_t *uint64_t hash
dt_dev_pixelpipe_cache_ready_callback_t restart
dt_iop_buffer_dsc_t dsc_out
dt_dev_request_flags_t request_histogram
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
gpointer last_history_item
gboolean gui_observable_source
uint64_t last_history_hash
dt_backbuf_t backbuf
dt_atomic_int shutdown
dt_dev_pixelpipe_type_t type
dt_atomic_int changed
struct dt_develop_t * dev
int32_t gui_attached
Definition develop.h:162
dt_image_t image_storage
Definition develop.h:259
int32_t raw_height
Definition develop.h:228
struct dt_iop_module_t * gui_module
Definition develop.h:165
dt_pthread_rwlock_t history_mutex
Definition develop.h:263
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:247
GList * history
Definition develop.h:275
int32_t raw_width
Definition develop.h:228
struct dt_dev_pixelpipe_t * virtual_pipe
Definition develop.h:251
dt_atomic_int mask_preview_settings_revision
Revision of the global mask-preview appearance.
Definition develop.h:176
struct dt_develop_t::@17 roi
gboolean raw_inited
Definition develop.h:239
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:247
dt_ui_t * ui
Definition gtk.h:165
dt_iop_buffer_dsc_t dsc
Definition image.h:337
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:304
int32_t id
Definition image.h:319
uint32_t filters
Definition format.h:60
unsigned int channels
Definition format.h:54
uint8_t xtrans[6][6]
Definition format.h:70
dt_iop_buffer_type_t datatype
Definition format.h:56
GModule *dt_dev_operation_t op
Definition imageop.h:286
int32_t params_size
Definition imageop.h:346
Region of interest passed through the pixelpipe.
Definition imageop.h:72
double scale
Definition imageop.h:74
void dt_supervisor_cache_wait(const dt_sv_op_t op, const uint64_t request_id, const uint64_t awaited_hash, const char *owner_tag, const char *op_name, const int multi_priority, const int pipe_type, const int32_t imgid, const gboolean request_emitted, const char *note)
uint64_t dt_supervisor_node_key(const int pipe_type, const char *op_name, const int multi_priority)
Definition supervisor.c:161
void dt_supervisor_node(const dt_sv_op_t op, const uint64_t node_hash, const uint64_t param_hash, const uint64_t predecessor_hash, const char *op_name, const int multi_priority, const int iop_order, const int pipe_type, const int32_t imgid)
Definition supervisor.c:856
@ DT_SV_READ
Definition supervisor.h:79
@ DT_SV_UPDATE
Definition supervisor.h:78
@ DT_SV_CREATE
Definition supervisor.h:77
@ DT_SV_DELETE
Definition supervisor.h:80
static gboolean dt_supervisor_active(void)
Definition supervisor.h:94
#define MAX(a, b)
Definition thinplate.c:29