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
21#include "common/conf.h"
22#include "system/macros.h"
24#include "system/mem_alloc.h"
25#include "common/hash.h"
26#include "common/logging.h"
27#include "common/times.h"
28#include "common/paths.h"
29#include "gui/application.h"
30#include "system/dtpthread.h"
31#include "system/atomic.h"
32#include "develop/dev_history.h"
34#include "develop/imageop.h"
36#include "develop/pixelpipe.h"
38#include "develop/supervisor.h"
39#include "develop/blend.h"
40#include "control/control.h"
41#include "control/signal.h"
42#include <stdint.h>
43#include <stdlib.h>
44
46 const uint32_t history_end, GList *start_node,
47 const char *debug_label);
48// dt_dev_pixelpipe_propagate_formats() is the authoritative forward buffer-format pass; it is
49// declared in dev_pixelpipe.h. It must run before any dt_pixelpipe_get_global_hash() because the
50// auto-disable it performs feeds the cumulative global hash (see its definition).
51static void _dt_dev_pixelpipe_cache_wait_ready_callback(gpointer instance, const guint64 hash,
52 const guint64 producer_node_key,
53 gpointer user_data);
54
55/* What stays here is the TRANSPORT and the PRESENTATION of the wait queue, not the queue:
56 * whether we are currently subscribed to the ready signal, and whether the busy cursor is up.
57 * The queue, its lock, its counters and its matching rule are cache state and live in
58 * caches/pixelpipe_cache_wait.{h,c}.
59 *
60 * Both flags are GUI-thread-only, like every accessor below: DT_SIGNAL_CACHELINE_READY is
61 * declared asynchronous (control/signal.c), so its handler is marshalled to the main loop, and
62 * every enqueue site is an expose, a widget callback or an idle. That asynchrony is load-bearing
63 * and must not be optimised away -- see the file comment in caches/pixelpipe_cache_wait.h. */
64static gboolean _cache_wait_connected = FALSE;
66
75
85
86static void _dump_one_pending(const dt_dev_pixelpipe_cache_wait_t *wait, const int64_t age_us,
87 gpointer user_data)
88{
89 (void)user_data;
91 "[cache-wait] pending id=%" PRIu64 " owner=%s hash=%" PRIu64
92 " target=%s connected=%d age_ms=%" PRId64 "\n",
93 wait->request_id,
94 !IS_NULL_PTR(wait->owner_tag) ? wait->owner_tag : "(unknown)",
95 wait->hash,
96 !IS_NULL_PTR(wait->module) ? wait->module_op : "backbuf",
97 wait->connected,
98 MAX(age_us / 1000, 0));
99}
100
102{
103 const char *context = !IS_NULL_PTR(reason) ? reason : "unspecified";
104
105 uint64_t queued = 0, served = 0, cancelled = 0, immediate = 0, misses = 0;
107
109 "[cache-wait] dump reason=%s pending=%u queued=%" PRIu64 " served=%" PRIu64
110 " cancelled=%" PRIu64 " immediate=%" PRIu64 " misses=%" PRIu64 "\n",
112 immediate, misses);
113
115}
116
118 const dt_iop_module_t *module)
119{
120 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(module)) return FALSE;
123 if(!pipe->gui_observable_source) return FALSE;
124
125 return !strcmp(module->op, "initialscale") || !strcmp(module->op, "colorout");
126}
127
129 const dt_iop_module_t *module)
130{
131 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(module)) return FALSE;
134 if(!pipe->gui_observable_source) return FALSE;
135
136 return !strcmp(module->op, "gamma");
137}
138
163
169
171{
172 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
173 {
175 if(!IS_NULL_PTR(piece) && !strcmp(piece->module->op, "detailmask")) return nodes;
176 }
177
178 return NULL;
179}
180
182{
185
186 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
187 {
189 if(IS_NULL_PTR(piece)) continue;
190
191 if(!strcmp(piece->module->op, "detailmask")) detailmask_piece = piece;
192 if(piece->detail_mask)
193 {
195 break;
196 }
197 }
198
200 {
202 && (detailmask_piece->dsc_in.channels == 4)
203 && (detailmask_piece->dsc_in.datatype == TYPE_FLOAT)
204 && (detailmask_piece->dsc_in.cst == IOP_CS_RGB);
205 detailmask_piece->enabled = enabled;
206 detailmask_piece->process_tiling_ready = !enabled;
207 if(!IS_NULL_PTR(detailmask_piece->data)) *((int *)detailmask_piece->data) = enabled ? 1 : 0;
208 }
209}
210
217
235/* Record the history item a resync last synchronized to, holding a reference on it.
236 *
237 * The resync runs against a snapshot outside history_mutex, so by the time it writes this the
238 * item may already have been cloned out of dev->history by the GUI thread's copy-on-write.
239 * Two things follow. The reference keeps the item alive for as long as the pipe names it, so an
240 * identity compare on the pointer can never hit a recycled address. The atomic exchange is
241 * what makes the write safe against dt_dev_history_cow_touch() re-pointing the same slot at the
242 * clone from the GUI thread: whichever side loses the race still ends up releasing exactly the
243 * reference it took, never the other side's. */
251
252/* `snap` is the caller's history snapshot (dt_dev_history_snapshot_take): this runs outside
253 * history_mutex and must not touch dev->history or dev->history_end directly. */
255{
257 dt_develop_t *dev = pipe->dev;
258 dt_iop_module_t *focus = dev->gui_module;
259
260 const gboolean transient = dt_dev_transient_params_active(dev, focus);
261 const gboolean realtime = dt_dev_pixelpipe_get_realtime(pipe);
262 if(!transient && !realtime) return FALSE;
263
264 const uint32_t history_end = (uint32_t)snap->history_end;
265 if(history_end == 0) return FALSE;
266
267 // Enable/blend state comes from the focused module's history item (transient edits are params-only).
268 dt_dev_history_item_t *hist = dt_dev_history_get_last_item_by_module(snap->items, focus, history_end);
269 if(IS_NULL_PTR(hist) || IS_NULL_PTR(hist->module) || IS_NULL_PTR(hist->params)) return FALSE;
270
272 if(IS_NULL_PTR(piece)) return FALSE;
273
274 // Resolve params: thread-safe transient snapshot when published, else the history snapshot. A copy is
275 // taken so the slot lock is not held across commit_params(); never the live GUI module->params.
277 void *tbuf = NULL;
279 gboolean used_transient = FALSE;
280 if(transient && focus->params_size > 0)
281 {
282 tbuf = g_malloc0(focus->params_size);
283 if(!IS_NULL_PTR(tbuf)
284 && dt_dev_transient_params_get(dev, focus, tbuf, (size_t)focus->params_size, NULL, 0, NULL,
286 {
289 }
290 else
291 dt_free(tbuf);
292 }
293
295 piece->enabled = hist->enabled;
296 piece->detail_mask = !IS_NULL_PTR(hist->blend_params) && hist->blend_params->details != 0.0f;
298 dt_free(tbuf);
299
300 /* `dt_iop_commit_params()` hands the params we pass to `module->commit_params()`, so the piece
301 * PROCESSES the transient blob -- but it derives the piece's identity from
302 * `dt_iop_compute_module_hash()`, which hashes `module->params`. That function says so itself:
303 * "WARNING: doesn't take into account parameters dynamically set at runtime". So a transient
304 * edit changed the pixels a recompute would produce without changing the hash that decides
305 * whether to recompute at all, and `dt_dev_pixelpipe_process()` exact-hit the cache and
306 * republished the previous frame. Drawlayer used to hide this by bumping `module->params`
307 * from its paint worker -- which is the very cross-thread write on GUI-owned state that the
308 * transient channel exists to avoid.
309 *
310 * The transient serial advances once per publish and is fetched under the same lock as the
311 * blob, so folding it in makes the piece's identity describe exactly the params it committed.
312 * Do this BEFORE `dt_pixelpipe_get_global_hash()` below, which folds the piece hashes into the
313 * cumulative pipe hash the cache is probed with. */
315 {
316 piece->hash = dt_hash(piece->hash, (const char *)&transient_serial, sizeof(transient_serial));
317 piece->global_hash = piece->hash;
318 }
321
322 // The global hash is cumulative and skips disabled nodes, so the format contract (which may
323 // disable a node on an incompatible input) must be settled first.
326
327 // Only advance the synch_top fence when the focused module is the newest history item (the realtime
328 // drawlayer case, where each heartbeat appended a top item). For a transient edit of a non-top module
329 // no history item changed, so the fence must stay where the last real history sync left it.
330 GList *last_item = g_list_nth(snap->items, history_end - 1);
331 if(last_item && (dt_dev_history_item_t *)last_item->data == hist)
332 {
333 pipe->last_history_hash = hist->hash;
335 }
336 return TRUE;
337}
338
371{
372 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(pipe->nodes)) return;
373
374 // The scopes/global histogram are not refreshed while a realtime stroke is in progress (the preview
375 // pipe is paused), so do not force any module to publish a host copy of its output just to feed them.
376 // This drops the per-frame RAM copy of `colorout` (and the histogram input/module caches) during
377 // realtime drawing, where it is pure overhead.
378 const gboolean realtime = dt_dev_pixelpipe_get_realtime(pipe);
379
380 // ONE read each, for the whole walk. Both belong to the GUI thread, and both were read LIVE at
381 // every node: a focus change or a picker toggle landing mid-walk gave the early nodes one
382 // answer and the later nodes another, so the sealed pipe described no single moment. Sampling
383 // them here does not make the pipeline the owner of these facts -- having the GUI publish them
384 // instead needs the layer flip, since gui/ may not reach up into develop/ today -- but it does
385 // make one resync describe one moment.
387 const dt_iop_module_t *const picker_module = pipe->dev->color_picker.module;
388
390
392 {
394 dt_iop_module_t *module = !IS_NULL_PTR(piece) ? piece->module : NULL;
395 if(IS_NULL_PTR(piece) || IS_NULL_PTR(module) || !piece->enabled) continue;
396
397 gboolean supports_opencl = FALSE;
398#ifdef HAVE_OPENCL
399 supports_opencl = dt_opencl_is_inited() && piece->process_cl_ready && module->process_cl;
400#endif
401
402 gchar *string = g_strdup_printf("/plugins/%s/cache", module->op);
403 if(!dt_conf_key_exists(string) || !dt_conf_key_not_empty(string))
404 dt_conf_set_bool(string, piece->cache_output_on_ram);
405
406 const gboolean authored_cache = piece->cache_output_on_ram;
407 const gboolean user_requested_cache = dt_conf_get_bool(string);
408 dt_free(string);
409
410 const gboolean color_picker_on = _module_feeds_color_picker(pipe, module, picker_module);
411 const gboolean global_hist_output_on
413 const gboolean global_hist_input_on
415 const gboolean module_hist_on
416 = !realtime
419 && (pipe->dev->gui_attached || !(piece->request_histogram & DT_REQUEST_ONLY_IN_GUI))
420 && (piece->request_histogram & DT_REQUEST_ON));
421 const gboolean active_in_gui
424
425 const gboolean has_autoset = pipe->autoset && !IS_NULL_PTR(module->autoset);
426
427 // The decision is a pure function of these nine facts (develop/pipe_cache_policy.h), pinned
428 // by tests/unittests/test_pipe_cache_policy.c. What stays here is GATHERING them, which
429 // is the part that needs a pipe and a dev to look at.
430 const dt_dev_pipe_cache_policy_inputs_t inputs = { .authored_cache = authored_cache,
431 .user_requested_cache = user_requested_cache,
432 .supports_opencl = supports_opencl,
433 .color_picker_on = color_picker_on,
434 .global_hist_output_on = global_hist_output_on,
435 .global_hist_input_on = global_hist_input_on,
436 .module_hist_on = module_hist_on,
437 .active_in_gui = active_in_gui,
438 .has_autoset = has_autoset };
439
440 /* One hop is enough: a node that did not publish host data leaves none for a later consumer
441 * to trust, since a rekeyed cacheline reports no host pixels until they are rewritten. */
442 piece->cache_output_on_ram
445 }
446}
447
449{
450 if(IS_NULL_PTR(dev) || !dev->gui_attached) return;
453 /* Rebuilt here, and deliberately not PUBLISHED here: these are the flag-raising paths, and
454 * nothing may observe geometry before the publication that belongs with it -- the ordering
455 * rule from the fix for #1157. */
457}
458
464
471
478
484
491
498
505
507{
508 if(IS_NULL_PTR(dev) || !dev->gui_attached) return;
509 /* Zoom/pan updates must run through the normal validity/ROI flow.
510 * If a module left the main pipe in realtime mode, force it back to
511 * standard mode here so zoom changes cannot get visually stuck on a
512 * best-effort backbuffer policy. */
516}
517
524
526{
527 if (IS_NULL_PTR(dev) || !dev->gui_attached) return;
528 /* Entering a zoom/pan change always exits realtime rendering policy.
529 * Realtime is stroke-scoped and should never control darkroom navigation. */
531 // Slightly different logic: killswitch ASAP,
532 // then redraw UI ASAP for feedback,
533 // finally flag the pipe as dirty for later recompute.
534 // Remember GUI responsiveness is paramount, since a laggy UI
535 // will make user repeat their order for lack of feedback,
536 // meaning relaunching a pipe recompute, meaning working more
537 // for the same contract.
543}
544
546{
547 return (dev // don't segfault
548 && dev->gui_attached // don't run on background/export pipes
549 && dev->gui_module // don't segfault
550 && dev->gui_module != current_module
551 // current_module is not the active one (capturing edit mode)
552 && dev->gui_module->operation_tags_filter() & current_module->operation_tags())
553 // current_module does operation(s) that active module doesn't want
555 // cache bypass is our hint that the active module is in "editing" mode
556}
557
558
560 const int width_in, const int height_in,
561 int *width, int *height)
562{
563 dt_iop_roi_t roi_in = (dt_iop_roi_t){ 0, 0, width_in, height_in, 1.0 };
564 dt_iop_roi_t roi_out = roi_in;
565 gchar *pipe_name = NULL;
568
569 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
570 {
572 dt_iop_module_t *module = piece->module;
573
574 piece->buf_in = roi_in;
575
576 // If in GUI and using a module that needs a full, undistorterted image,
577 // we need to shutdown temporarily any module distorting the image.
579 piece->enabled = FALSE;
580
581 // If module is disabled, modify_roi_out() is a no-op
582 if(piece->enabled)
583 module->modify_roi_out(module, pipe, piece, &roi_out, &roi_in);
584 else
585 roi_out = roi_in;
586
587 // Forward ROI planning answers "what output rectangle does this module
588 // produce from the previous one ?". Logging the tuple here makes each
589 // module-local geometry change visible on `-d pipe`.
590 if(piece->enabled && (dt_get_debug_flags() & DT_DEBUG_PIPE))
592 "[roi-out] pipe=%-15s module=%-18s enabled=%d in =(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)"
593 " out=(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)\n",
594 pipe_name, module->op, piece->enabled,
595 roi_in.x, roi_in.y, roi_in.width, roi_in.height, roi_in.scale,
596 roi_out.x, roi_out.y, roi_out.width, roi_out.height, roi_out.scale);
597
598 piece->buf_out = roi_out;
599 roi_in = roi_out;
600 }
601
603 *width = roi_out.width;
604 *height = roi_out.height;
605}
606
608{
609 // Start with a hash that is unique, image-wise.
610 return dt_hash(5381, (const char *)&pipe->dev->image_storage.filename, DT_MAX_FILENAME_LEN);
611}
612
613/* Publish, on every enabled node, the cumulative hash of the PARAMETERS of the enabled modules
614 * above it -- `dt_dev_pixelpipe_iop_t.upstream_hash`, which says what that field is for.
615 *
616 * It is its own pass, run at the top of ROI planning as well as from the global-hash pass,
617 * because ROI planning is its first consumer and runs BEFORE the global hash is built: a
618 * `modify_roi_in()` that memoises anything against the chain above it would otherwise find
619 * either the value a commit invalidated or one describing a chain that has since changed.
620 * Nothing here reads an ROI, so running it twice per plan costs a walk and answers the same. */
622{
624
625 for(GList *node = g_list_first(pipe->nodes); node; node = g_list_next(node))
626 {
628 if(IS_NULL_PTR(piece) || !piece->enabled) continue;
629
630 piece->upstream_hash = upstream;
631 upstream = dt_hash(upstream, (const char *)&piece->hash, sizeof(uint64_t));
632
633 /* Whether the module currently focused in the GUI suppresses this one's distortion. It is
634 * not in piece->hash -- it is GUI state, not history -- yet it decides whether this module
635 * takes part in dt_dev_distort_transform_plus(), which is precisely what a consumer of this
636 * hash back-transforms through. Focusing crop moves every drawn shape's box. */
638 upstream = dt_hash(upstream, (const char *)&bypass_distort, sizeof(int));
639 }
640}
641
643{
644 // while module->modify_roi_out describes how the current module will change the size of
645 // the output buffer depending on its parameters (pretty intuitive),
646 // module->modify_roi_in describes "how much material" the current module needs from the previous one,
647 // because some modules (lens correction) need a padding on their input.
648 // The tricky part is therefore that the effect of the current module->modify_roi_in() needs to be repercuted
649 // upstream in the pipeline for proper pipeline cache invalidation, so we need to browse the pipeline
650 // backwards.
651
652 // This function does not support NULL pipes or ad-hoc temp nodes.
653
654 // A modify_roi_in() that reads drawn shapes (retouch's source areas) resolves them in
655 // pipe->forms, the refcounted snapshot the run renders from, never in the GUI-owned dev->forms,
656 // which the GUI thread replaces and frees mid-edit. dt_dev_pixelpipe_process() takes its snapshot
657 // before planning, so both see the same shapes; any other caller plans against a snapshot held
658 // for the length of the walk, which it then owns and releases.
659 const gboolean owns_forms = IS_NULL_PTR(pipe->forms);
661
662 // a modify_roi_in() memoising against the chain above it needs this before the walk starts
664
665 dt_iop_roi_t roi_out_temp = roi_out;
666 dt_iop_roi_t roi_in;
667 gchar *pipe_name = NULL;
670 for(GList *nodes = g_list_last(pipe->nodes); nodes; nodes = g_list_previous(nodes))
671 {
673 dt_iop_module_t *module = piece->module;
674
675 piece->roi_out = roi_out_temp;
676
677 // If in GUI and using a module that needs a full, undistorterted image,
678 // we need to shutdown temporarily any module distorting the image.
680 piece->enabled = FALSE;
681
682 // If module is disabled, modify_roi_in() is a no-op
683 if(piece->enabled)
684 module->modify_roi_in(module, pipe, piece, &roi_out_temp, &roi_in);
685 else
686 roi_in = roi_out_temp;
687
688 // Backward ROI planning answers "how much input rectangle does this
689 // module need from upstream to deliver the requested downstream output ?".
690 // Logging that request before and after modify_roi_in() makes ROI growth
691 // and padding traceable module-by-module on `-d pipe`.
692 if(piece->enabled && (dt_get_debug_flags() & DT_DEBUG_PIPE))
694 "[roi-in ] pipe=%-15s module=%-18s enabled=%d out=(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)"
695 " in=(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)\n",
696 pipe_name, module->op, piece->enabled,
698 roi_in.x, roi_in.y, roi_in.width, roi_in.height, roi_in.scale);
699
700 piece->roi_in = roi_in;
701 roi_out_temp = roi_in;
702 }
703
705
707
708 /* ROI planning runs backwards, but rawprepare seals the effective Bayer/X-Trans phase only once
709 * the real input crop is known. Forward that authored RAW descriptor now so the downstream RAW
710 * modules process with the same CFA layout that rawprepare just computed for this run. */
712 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
713 {
715
716 if(piece->dsc_in.cst == IOP_CS_RAW && piece->dsc_in.channels == 1)
717 {
718 piece->dsc_in.filters = upstream_dsc.filters;
719 memcpy(piece->dsc_in.xtrans, upstream_dsc.xtrans, sizeof(piece->dsc_in.xtrans));
720 }
721
722 if(piece->dsc_out.cst == IOP_CS_RAW && piece->dsc_out.channels == 1
723 && (!piece->enabled || strcmp(piece->module->op, "rawprepare")))
724 {
725 piece->dsc_out.filters = piece->dsc_in.filters;
726 memcpy(piece->dsc_out.xtrans, piece->dsc_in.xtrans, sizeof(piece->dsc_out.xtrans));
727 }
728
729 upstream_dsc = piece->dsc_out;
730 }
731
732}
733
734
736 const dt_iop_roi_t roi_out, const int pos)
737{
738 // to be called at runtime, not at pipe init.
739
740 // Only at the first step of pipe, we don't have a module because we init the base buffer.
741 if(!IS_NULL_PTR(piece))
742 return piece->global_hash;
743 else
744 {
745 // This is used for the first step of the pipe, before modules, when initing base buffer
746 // We need to take care of the ROI manually
748 hash = dt_hash(hash, (const char *)&roi_out, sizeof(dt_iop_roi_t));
749 return dt_hash(hash, (const char *)&pos, sizeof(int));
750 }
751}
752
754 const dt_iop_module_t *module)
755{
756 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(module)) return NULL;
757
758 for(GList *node = g_list_first(pipe->nodes); node; node = g_list_next(node))
759 {
760 dt_dev_pixelpipe_iop_t *const piece = node->data;
761 if(piece && piece->enabled && piece->module == module)
762 return piece;
763 }
764
765 return NULL;
766}
767
770{
771 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(piece)) return NULL;
772
773 GList *node = g_list_find(pipe->nodes, (gpointer)piece);
774 if(IS_NULL_PTR(node)) return NULL;
775
776 for(node = g_list_previous(node); node; node = g_list_previous(node))
777 {
778 dt_dev_pixelpipe_iop_t *const previous = node->data;
779 if(previous && previous->enabled)
780 return previous;
781 }
782
783 return NULL;
784}
785
787 void **data, dt_pixel_cache_entry_t **cache_entry,
790 gpointer restart_data, const gboolean retain)
791{
792 if(!IS_NULL_PTR(data)) *data = NULL;
793 if(!IS_NULL_PTR(cache_entry)) *cache_entry = NULL;
794 if(IS_NULL_PTR(pipe)) return FALSE;
795
796 // Module-output cache requests are not satisfiable when the target piece
797 // itself bypasses cache retention. Re-queueing those from GUI redraws would
798 // keep transient edit modes in a self-feeding recompute loop.
799 // Upstream targets (for example color-picker input sampling) stay allowed even if a
800 // downstream piece enabled bypass mode.
801 if(!IS_NULL_PTR(piece)
803 return FALSE;
804
806
807 // For the final backbuffer (piece == NULL), GUI consumers display the last *published* frame,
808 // identified by pipe->backbuf.hash. The pipeline plans the next frame ahead of publishing it, so
809 // pipe->hash (the planned final hash) runs ahead of the backbuffer while a recompute is in flight.
810 // This is the normal steady state of realtime drawing: every heartbeat plans a new frame. Peeking
811 // the planned hash would then miss the perfectly valid published frame, the darkroom main-surface
812 // lock would fail, and the view would drop to the paused preview pipe (which lacks the in-progress
813 // stroke) — the flicker between "drawing applied" and "original". So look up the published backbuf
814 // hash for display, and keep the planned hash only to drive recompute on a genuine miss below.
816 if(IS_NULL_PTR(piece))
817 {
820 }
821
822 void *buffer = NULL;
824 // GUI consumers run on the CPU and never own the OpenCL device lock, so we pass preferred_devid = -1:
825 // dt_dev_pixelpipe_cache_peek() then returns host-resident cachelines directly but refuses to
826 // materialize device-only ones from the GPU (which would enqueue hidden GPU work from the GUI thread,
827 // racing whichever pipeline thread currently owns the device — the clReleaseEvent crash). A device-only
828 // entry is reported as a miss, so the request below waits for the pipeline to publish a host copy
829 // (modules whose output the GUI samples, e.g. initialscale, already cache to RAM).
830 //
831 // `retain` picks between the two ownership models a GUI consumer can have on a cacheline, and
832 // they are not interchangeable. A consumer that merely BORROWS the currently published
833 // backbuffer (the darkroom surface, the navigation thumbnail, the scopes) is covered by the
834 // keepalive reference its publisher holds, and must not take one of its own. A consumer that
835 // samples an INTERMEDIATE module cacheline (color picker, module histograms, autoset,
836 // colorequal) has no such cover: those entries sit at refcount 0 between renders and the
837 // pipeline may evict one at any moment. Looking one up and taking the reference afterwards
838 // leaves a window in which it is freed under the caller, so that acquisition happens under one
839 // hold of the cache lock -- the hazard `dt_dev_pixelpipe_process_rec()` already avoids for its
840 // own exact-hit lookups, for exactly the same reason.
843 : dt_dev_pixelpipe_cache_peek(display_hash, &buffer, &entry, -1, NULL));
844 if(got && !IS_NULL_PTR(buffer) && !IS_NULL_PTR(entry))
845 {
847 dt_dev_pixelpipe_cache_wait_cleanup(wait, "peek-gui-immediate-hit");
848 if(!IS_NULL_PTR(data)) *data = buffer;
849 if(!IS_NULL_PTR(cache_entry)) *cache_entry = entry;
850 return TRUE;
851 }
852
853 /* A retained entry the caller cannot be handed back (it asked for neither the data nor the
854 * entry) would leak its reference: nobody would ever release it. */
855 if(retain && got && !IS_NULL_PTR(entry))
856 {
858 entry = NULL;
859 buffer = NULL;
860 }
861
863
864 gboolean request_cacheline = TRUE;
865 if(!IS_NULL_PTR(wait) && !IS_NULL_PTR(restart) && hash != DT_PIXELPIPE_CACHE_HASH_INVALID)
866 {
867 const gboolean changed_target = !wait->connected
868 || wait->pipe != pipe
869 || wait->module != (!IS_NULL_PTR(piece) ? piece->module : NULL)
870 || wait->hash != hash
871 || wait->restart != restart;
873 {
874 gboolean activate_wait_cursor = FALSE;
875 dt_dev_pixelpipe_cache_wait_cleanup(wait, "peek-gui-target-changed");
876 wait->pipe = pipe;
877 wait->module = !IS_NULL_PTR(piece) ? piece->module : NULL;
878
879 // Copy the labels now, while both objects are certainly alive. The queue never reads
880 // through the two identity pointers above; everything it reports about a waiter comes
881 // from here.
884 {
885 g_strlcpy(wait->module_op, target_module->op, sizeof(wait->module_op));
886 wait->module_multi_priority = target_module->multi_priority;
887 }
888 else
889 {
890 wait->module_op[0] = '\0';
891 wait->module_multi_priority = 0;
892 }
893 wait->pipe_type = (int)pipe->type;
894 wait->pipe_imgid = pipe->imgid;
895
896 wait->hash = hash;
897 // Record the producing node identity so CACHELINE_READY can serve this waiter by
898 // its target module even if the exact awaited hash drifts before publish (§8). A
899 // backbuf target (piece == NULL) has no single producing node, so it keeps to the
900 // exact-hash match only.
901 wait->target_node_key = !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module)
902 ? dt_supervisor_node_key(pipe->type, piece->module->op,
903 piece->module->multi_priority)
905 wait->restart = restart;
906 wait->user_data = restart_data;
907 if(IS_NULL_PTR(wait->owner_tag))
908 wait->owner_tag = !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module) ? piece->module->op : "gui-backbuf";
909 if(IS_NULL_PTR(wait->owner_object))
911 wait->connected = TRUE;
912
913 // The queue assigns the request id and reports whether it just went from empty to
914 // non-empty; the subscription and the cursor are ours, and both hang off exactly that
915 // transition -- which is why they were one flag each in the old manager.
918 {
920 {
924 }
926 {
929 }
930 }
933
935 "[cache-wait] queued id=%" PRIu64 " owner=%s hash=%" PRIu64 " target=%s\n",
936 wait->request_id,
937 !IS_NULL_PTR(wait->owner_tag) ? wait->owner_tag : "(unknown)",
938 wait->hash,
939 !IS_NULL_PTR(wait->module) ? wait->module_op : "backbuf");
940
943 !IS_NULL_PTR(wait->module) ? wait->module_op : NULL,
945 (int)pipe->type, pipe->imgid, TRUE, NULL);
946 }
947 else
948 {
949 /* The GUI already waits for this exact cacheline. Re-emitting CACHE_REQUEST from
950 * each expose keeps retrying an unsatisfied target forever when the pipeline cannot
951 * publish it, for example after an OpenCL memory pre-check failure on very large images. */
953
954 /* A deduped re-poll: the consumer asked again for a wait already in flight, and no
955 * new CACHE_REQUEST was emitted. A run of these with no matching serve (and no
956 * cacheline ever published under wait->hash) is the "never processed, never
957 * finishes" signature. */
960 !IS_NULL_PTR(wait->module) ? wait->module_op : NULL,
962 (int)pipe->type, pipe->imgid, FALSE, "dedup-poll");
963 }
964 }
965
967 {
971 !IS_NULL_PTR(piece) ? piece->module : NULL);
973
974 dt_print(DT_DEBUG_DEV, "[pixelpipe/gui] request host cache pipe=%s target=%s hash=%" PRIu64 "\n",
976 !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module) ? piece->module->op : "backbuf", hash);
977 }
978
979 return FALSE;
980}
981
983 void **data, dt_pixel_cache_entry_t **cache_entry,
986 gpointer restart_data)
987{
988 return _peek_gui(pipe, piece, data, cache_entry, wait, restart, restart_data, FALSE);
989}
990
992 void **data, dt_pixel_cache_entry_t **cache_entry,
995 gpointer restart_data)
996{
997 return _peek_gui(pipe, piece, data, cache_entry, wait, restart, restart_data, TRUE);
998}
999
1000
1028static void _dt_dev_pixelpipe_cache_wait_ready_callback(gpointer instance, const guint64 hash,
1029 const guint64 producer_node_key,
1030 gpointer user_data)
1031{
1032 gboolean restore_wait_cursor = FALSE;
1033 gboolean drained = FALSE;
1034
1035 // The queue decides who this publication satisfies and hands them back; running their
1036 // callbacks is ours, and stays OUTSIDE any queue lock, because a restart handler queues
1037 // redraws and asks for new cachelines.
1039
1041 {
1045 }
1047 {
1050 }
1053
1055 {
1056 dt_dev_pixelpipe_cache_wait_t *wait = iter->data;
1058 gpointer restart_data = wait->user_data;
1059 // A drift serve: the awaited hash never published, but the target module did
1060 // (matched via producer_node_key). Record it as such so the supervisor timeline
1061 // shows the recovery instead of a phantom exact hit.
1062 const gboolean drift_serve = wait->hash != hash;
1063
1065 "[cache-wait] served id=%" PRIu64 " owner=%s awaited=%" PRIu64 " published=%" PRIu64
1066 " target=%s%s\n",
1067 wait->request_id,
1068 !IS_NULL_PTR(wait->owner_tag) ? wait->owner_tag : "(unknown)",
1069 wait->hash, hash,
1070 !IS_NULL_PTR(wait->module) ? wait->module_op : "backbuf",
1071 drift_serve ? " (drift: node-key)" : "");
1072
1075 !IS_NULL_PTR(wait->module) ? wait->module_op : NULL,
1077 !IS_NULL_PTR(wait->pipe) ? (int)wait->pipe_type : -1,
1078 !IS_NULL_PTR(wait->pipe) ? wait->pipe_imgid : -1, FALSE,
1079 drift_serve ? "served (drift: node-key)" : "served");
1080
1081 wait->pipe = NULL;
1082 wait->module = NULL;
1085 wait->restart = NULL;
1086 wait->user_data = NULL;
1087 wait->request_id = 0;
1088
1089 if(!IS_NULL_PTR(restart)) restart(restart_data);
1090 }
1092}
1093
1095{
1096 if(IS_NULL_PTR(wait) || !wait->connected) return;
1097 gboolean restore_wait_cursor = FALSE;
1098 const uint64_t request_id = wait->request_id;
1099 const uint64_t hash = wait->hash;
1100 const char *owner_tag = wait->owner_tag;
1101 // Snapshot the identity and its labels together, before the reset below clears them. Nothing
1102 // here dereferences the module or the pipe: the labels were copied when the wait was queued,
1103 // precisely because either object can be gone by the time a wait is cancelled.
1104 const gboolean had_module = !IS_NULL_PTR(wait->module);
1105 const gboolean had_pipe = !IS_NULL_PTR(wait->pipe);
1106 const char *module_op = had_module ? wait->module_op : NULL;
1107 const int module_multi_priority = wait->module_multi_priority;
1108 const int cancel_pipe_type = wait->pipe_type;
1109 const int32_t cancel_pipe_imgid = wait->pipe_imgid;
1110 const char *cancel_reason = !IS_NULL_PTR(reason) ? reason : "unspecified";
1111
1112 gboolean drained = FALSE;
1114
1116 {
1120 }
1122 {
1125 }
1128
1129 // -1: the queue owns the enqueue timestamp, and a cancellation is not worth an extra query
1130 // into it. The dump reports real ages for everything still pending.
1131 const int64_t age_ms = -1;
1133 "[cache-wait] cancelled id=%" PRIu64 " owner=%s hash=%" PRIu64
1134 " target=%s age_ms=%" PRId64 " reason=%s\n",
1135 request_id,
1136 !IS_NULL_PTR(owner_tag) ? owner_tag : "(unknown)",
1137 hash,
1138 had_module ? module_op : "backbuf",
1139 age_ms,
1141
1143 {
1144 char note[64];
1145 g_snprintf(note, sizeof(note), "cancelled: %s", cancel_reason);
1146 dt_supervisor_cache_wait(DT_SV_DELETE, request_id, hash, owner_tag,
1147 module_op, module_multi_priority,
1150 }
1151
1152 wait->pipe = NULL;
1153 wait->module = NULL;
1156 wait->restart = NULL;
1157 wait->user_data = NULL;
1158 wait->request_id = 0;
1159 wait->connected = FALSE;
1160}
1161
1163 const char *owner_tag,
1164 gpointer owner_object)
1165{
1166 if(IS_NULL_PTR(wait)) return;
1167 wait->owner_tag = owner_tag;
1168 wait->owner_object = owner_object;
1169}
1170
1171/* Establish the *input* side of a piece's buffer contract from the upstream descriptor by
1172 * asking the module's input_format(). This does NOT decide compatibility or finalize the
1173 * output: the single authoritative pass dt_dev_pixelpipe_propagate_formats() owns the mismatch/auto-disable
1174 * decision so it is taken once, on the fully-committed and consistently-threaded chain, never
1175 * on the stale partial state a synch_top / realtime edit can leave behind (issue #733).
1176 *
1177 * It is split out from that pass because commit_params() of a few modules (exposure, highlights,
1178 * invert, temperature) reads piece->dsc_in (filters, processed_maximum), so the commit loop must
1179 * set dsc_in before committing. */
1180// Short colorspace name for the [dsc] format-propagation debug log (-d pipe). Knowing the cst of
1181// each node's input/output is essential to spot when the pixelpipe's automatic RGB<->Lab conversion
1182// should fire: a Lab-domain module whose dsc_in.cst is not "lab" will silently consume RGB.
1183static const char *_dsc_cst_name(const int cst)
1184{
1185 switch(cst)
1186 {
1187 case IOP_CS_RAW: return "raw";
1188 case IOP_CS_LAB: return "lab";
1189 case IOP_CS_RGB: return "rgb";
1190 case IOP_CS_RGB_DISPLAY: return "display-rgb";
1191 case IOP_CS_LCH: return "lch";
1192 case IOP_CS_HSL: return "hsl";
1193 case IOP_CS_JZCZHZ: return "jzczhz";
1194 case IOP_CS_NONE: return "none";
1195 default: return "?";
1196 }
1197}
1198
1201{
1202 piece->dsc_in = *upstream_dsc;
1203 piece->dsc_out = *upstream_dsc;
1206
1207 /* Disabled modules are strict pass-through stages. Their advertised contracts must
1208 * not rewrite the upstream descriptor, otherwise a disabled RGB-only module can make
1209 * downstream RAW stages such as demosaic believe the stream was already converted. */
1210 if(!piece->enabled) return;
1211
1212 piece->module->input_format(piece->module, pipe, piece, &piece->dsc_in);
1214 piece->dsc_out = piece->dsc_in;
1216}
1217
1221{
1223
1224 // Set dsc_in before committing (commit_params() of some modules reads it). Compatibility and
1225 // the final output descriptor are settled later by dt_dev_pixelpipe_propagate_formats(), so this loop
1226 // never auto-disables a module on a possibly-stale upstream descriptor.
1228
1229 // This should run even if the module is disabled because
1230 // some modules with no history self-enable based on runtime context
1231 dt_iop_commit_params(piece->module, params, blend_params, pipe, piece);
1232
1233 if(piece->enabled)
1234 {
1235 piece->module->output_format(piece->module, pipe, piece, &piece->dsc_out);
1237 }
1238 else
1239 {
1240 piece->dsc_in = actual_input_dsc;
1241 piece->dsc_out = actual_input_dsc;
1244 }
1245
1246 *upstream_dsc = piece->dsc_out;
1247}
1248
1249/* Direction-independent forward format pass.
1250 *
1251 * The buffer-format contract (channels / datatype / colorspace / CFA presence flowing from one
1252 * module to the next) is conceptually independent of ROI planning, which runs in both
1253 * directions (end->start for a target size, start->end for the native size). Deriving the
1254 * contract inside the history-sync commit loop made it sensitive to partial syncs
1255 * (synch_top / realtime top edits): a downstream module could read a stale upstream descriptor
1256 * and get wrongly disabled for an "unexpected input buffer format" (issue #733). Worse, the
1257 * auto-disable is one-way, so a later pass could not undo a false positive.
1258 *
1259 * This single forward pass re-threads the whole chain from the input image descriptor after
1260 * params have been committed, and is the *only* place that disables a module for a genuinely
1261 * incompatible input. Disabling here is safe because the chain is consistent and fully threaded
1262 * from pipe->dev->image_storage.dsc. Only the first stage (basebuffer) reads the input image
1263 * type; every later node derives its contract solely from its upstream piece. The lone
1264 * ROI-dependent refinement, rawprepare's CFA phase shift, is finalized later in modify_roi_*().
1265 *
1266 * It is deliberately NON-destructive for compatible enabled modules: it verifies the input
1267 * contract and re-threads dsc_in, but keeps the dsc_out that commit_params() produced. dsc_out
1268 * carries runtime fields (processed_maximum, rawprepare black/white, temperature coeffs, CFA
1269 * phase) that input/output_format cannot reconstruct, so recomputing them here would wipe the
1270 * tonal scaling and render images black/white. */
1272{
1273 if(IS_NULL_PTR(pipe)) return;
1274
1277
1278 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1279 {
1281 if(IS_NULL_PTR(piece) || IS_NULL_PTR(piece->module)) continue;
1282
1284
1285 if(!piece->enabled)
1286 {
1287 // Disabled modules are strict pass-through stages.
1288 piece->dsc_in = actual_input_dsc;
1289 piece->dsc_out = actual_input_dsc;
1292 }
1293 else
1294 {
1295 // Ask the module what input it expects, into a scratch descriptor. We must NOT recompute
1296 // piece->dsc_out from input/output_format here: commit_params() already produced dsc_out
1297 // with runtime fields that those methods cannot reconstruct (processed_maximum, rawprepare
1298 // black/white, temperature coeffs, CFA phase). Recomputing would reset processed_maximum to
1299 // the input image's value and blow up downstream tone handling (black/white renders).
1302 piece->module->input_format(piece->module, pipe, piece, &declared_in);
1304
1305 // A module whose declared input does not match what the previous stage actually publishes
1306 // cannot run: disable it (in the pipe only; history is untouched) and turn it into a
1307 // pass-through so the contract keeps flowing to the next stage.
1308 const gboolean input_mismatch
1309 = (declared_in.bpp != actual_input_dsc.bpp
1310 || declared_in.channels != actual_input_dsc.channels
1311 || declared_in.filters != actual_input_dsc.filters);
1312
1313 if(input_mismatch)
1314 {
1315 dt_control_log(_("disabled module `%s`: unexpected input buffer format"),
1316 piece->module->name());
1318 "[pixelpipe] disabling module %s because input format expects %" G_GSIZE_FORMAT
1319 " B/px, %u channels, filters %u but upstream publishes %" G_GSIZE_FORMAT
1320 " B/px, %u channels, filters %u\n",
1321 piece->module->op, declared_in.bpp, declared_in.channels, declared_in.filters,
1322 actual_input_dsc.bpp, actual_input_dsc.channels, actual_input_dsc.filters);
1323
1324 piece->enabled = FALSE;
1326 piece->blendop_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1328 piece->global_mask_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1329 piece->upstream_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1330 piece->dsc_in = actual_input_dsc;
1331 piece->dsc_out = actual_input_dsc;
1334 }
1335 else
1336 {
1337 // Input is compatible: publish the module's DECLARED input descriptor. `declared_in` was
1338 // seeded from the upstream descriptor and then run through input_format(), so it already
1339 // carries the upstream runtime fields (processed_maximum, rawprepare, temperature, CFA
1340 // phase) AND the colorspace/channel layout the module actually consumes.
1341 //
1342 // Critically, we must NOT fall back to `actual_input_dsc` here: that would reset dsc_in.cst
1343 // to the upstream colorspace and make it equal to the buffer's cst, so the pixelpipe's
1344 // automatic colorspace conversion (pixelpipe_cpu.c / pixelpipe_gpu.c) would believe no
1345 // conversion is needed. Lab-domain modules (old color balance, color checker, atrous, ...)
1346 // inserted after an RGB stage would then receive RGB data interpreted as Lab and render
1347 // garbled/solid colors. The full-resync path keeps dsc_in from input_format() for the same
1348 // reason; this incremental (synch_top) path must match it.
1349 piece->dsc_in = declared_in;
1351 }
1352 }
1353
1354 if(pipe_name)
1356 "[dsc] pipe=%s module=%s enabled=%d in=(cst=%s ch=%i bpp=%" G_GSIZE_FORMAT
1357 " filters=%u) out=(cst=%s ch=%i bpp=%" G_GSIZE_FORMAT " filters=%u)\n",
1358 pipe_name, piece->module->op, piece->enabled,
1359 _dsc_cst_name(piece->dsc_in.cst), piece->dsc_in.channels, piece->dsc_in.bpp, piece->dsc_in.filters,
1360 _dsc_cst_name(piece->dsc_out.cst), piece->dsc_out.channels, piece->dsc_out.bpp, piece->dsc_out.filters);
1361
1362 upstream_dsc = piece->dsc_out;
1363 }
1364
1366}
1367
1368/* `history_list` is the snapshot's list (dt_dev_history_snapshot_t::items), never dev->history:
1369 * this runs OUTSIDE history_mutex, and the snapshot's references are what keep every item it
1370 * walks alive and unmodified (see dt_dev_history_snapshot_take). */
1372 const uint32_t history_end, const char *debug_label)
1373{
1376
1377 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1378 {
1380 if(IS_NULL_PTR(piece)) continue;
1381
1383 piece->blendop_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1385 piece->global_mask_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1386 piece->upstream_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1387 piece->enabled = piece->module->default_enabled;
1388 piece->detail_mask = FALSE;
1389
1390 dt_iop_params_t *params = piece->module->default_params;
1391 dt_develop_blend_params_t *blend_params = piece->module->default_blendop_params;
1393 gboolean found_history = FALSE;
1394
1395 for(GList *history = g_list_nth(history_list, history_end - 1);
1396 history;
1397 history = g_list_previous(history))
1398 {
1399 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1400 if(piece->module == hist->module)
1401 {
1402 piece->enabled = hist->enabled;
1403 params = hist->params;
1404 blend_params = hist->blend_params;
1405 history_param_hash = hist->hash; // the history entry key (module->hash)
1407 break;
1408 }
1409 }
1410
1411 piece->detail_mask = blend_params && blend_params->details != 0.0f;
1412 if(!strcmp(piece->module->op, "detailmask"))
1415
1416 // Tie this node to the history item it was just synchronized with, so the
1417 // cachelines it produces resolve to that history state through the node.
1418 // Use the history entry key (module->hash), NOT piece->hash: the latter folds
1419 // in runtime_data_hash() for some modules and then no longer matches history.
1422 dt_supervisor_node_key(pipe->type, piece->module->op, piece->module->multi_priority),
1424 piece->module->multi_priority, piece->module->iop_order, pipe->type, pipe->imgid);
1425
1426 if(!found_history)
1427 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] info: committed default params for %s (%s) in pipe %s\n",
1428 piece->module->op, piece->module->multi_name, debug_label);
1429 }
1430
1433 {
1435 if(detailmask_node)
1437 }
1438}
1439
1441 const uint32_t history_end, GList *start_node,
1442 const char *debug_label)
1443{
1444 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(start_node)) return;
1445
1448 for(GList *node = g_list_first(pipe->nodes); node && node != start_node; node = g_list_next(node))
1449 {
1451 if(IS_NULL_PTR(piece)) continue;
1452
1453 upstream_dsc = piece->dsc_out;
1454 }
1455
1456 for(GList *nodes = start_node; nodes; nodes = g_list_next(nodes))
1457 {
1459 if(IS_NULL_PTR(piece)) continue;
1460
1462 piece->blendop_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1464 piece->global_mask_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1465 piece->upstream_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1466
1467 piece->enabled = piece->module->default_enabled;
1468 piece->detail_mask = FALSE;
1469
1470 dt_iop_params_t *params = piece->module->default_params;
1471 dt_develop_blend_params_t *blend_params = piece->module->default_blendop_params;
1473 gboolean found_history = FALSE;
1474
1475 for(GList *history = g_list_nth(history_list, history_end - 1);
1476 history;
1477 history = g_list_previous(history))
1478 {
1479 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1480 if(piece->module == hist->module)
1481 {
1482 piece->enabled = hist->enabled;
1483 params = hist->params;
1484 blend_params = hist->blend_params;
1485 history_param_hash = hist->hash; // the history entry key (module->hash)
1487 break;
1488 }
1489 }
1490
1491 piece->detail_mask = blend_params && blend_params->details != 0.0f;
1492 if(!strcmp(piece->module->op, "detailmask"))
1495
1496 // Tie this node to the history item it was just synchronized with, so the
1497 // cachelines it produces resolve to that history state through the node.
1498 // Use the history entry key (module->hash), NOT piece->hash: the latter folds
1499 // in runtime_data_hash() for some modules and then no longer matches history.
1502 dt_supervisor_node_key(pipe->type, piece->module->op, piece->module->multi_priority),
1504 piece->module->multi_priority, piece->module->iop_order, pipe->type, pipe->imgid);
1505
1506 if(!found_history)
1507 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] info: committed default params for %s (%s) in pipe %s\n",
1508 piece->module->op, piece->module->multi_name, debug_label);
1509 }
1510
1513 {
1517 }
1518}
1519
1521{
1522 /* Traverse the pipeline node by node and compute the cumulative (global) hash of each module.
1523 * This hash takes into account the hashes of the previous modules and the size of the current ROI.
1524 * It is used to map pipeline cache states to current parameters.
1525 * It represents the state of internal modules params as well as their position in the pipe and their output size.
1526 * It is to be called at pipe init, not at runtime.
1527 */
1528
1529 // bernstein hash (djb2)
1531 gboolean passthrough_preview = FALSE;
1532
1533 // see dt_dev_pixelpipe_iop_t.upstream_hash: its own pass, and not derived from `hash` below,
1534 // which folds each piece's ROI
1536
1537 // Bypassing cache contaminates downstream modules, starting at the module requesting it.
1538 // Usecase : crop, clip, ashift, etc. that need the uncropped image ;
1539 // mask displays ; overexposed/clipping alerts and all other transient previews.
1540 gboolean bypass_cache = FALSE;
1541
1542 for(GList *node = g_list_first(pipe->nodes); node; node = g_list_next(node))
1543 {
1545 if(!piece->enabled) continue;
1546
1547 // Combine with the previous bypass states
1548 bypass_cache |= piece->module->bypass_cache;
1549 piece->bypass_cache = bypass_cache;
1550
1551 // Combine with the previous modules hashes
1553
1554 // Some GUI previews author their final display inside the active module,
1555 // then runtime forwards that exact buffer through later pass-through stages.
1556 // Keep the planned hash contract aligned with the published cacheline so the
1557 // GUI does not keep requesting a downstream output hash that will never exist.
1559 && !(piece->module->operation_tags() & IOP_TAG_DISTORT)
1560 && (piece->dsc_in.bpp == piece->dsc_out.bpp)
1561 && !memcmp(&piece->roi_in, &piece->roi_out, sizeof(dt_iop_roi_t)))
1562 {
1563 piece->global_mask_hash = hash;
1564 piece->global_hash = hash;
1565 continue;
1566 }
1567
1568 // Panning and zooming change the ROI. Some GUI modes (crop in editing mode) too.
1569 // dt_dev_get_roi_in() should have run before
1570 //
1571 // These two folds are ALSO how the darkroom viewport reaches this hash, which is why the
1572 // pipe's latched dt_dev_roi_request_t (pixelpipe_hb.h) deliberately does NOT contribute its
1573 // generation here. Every field of that record reaches the ROI by construction, in
1574 // _update_darkroom_roi(): zoom and the fit scale become roi.scale (their product), the
1575 // centre becomes roi.x/roi.y, the box and the processed size become roi.width/height. Even
1576 // finalscale's piece->enabled -- the one consumer that reads the record rather than the ROI
1577 // -- is a function of that same product, and a disabled piece is skipped above, so a toggle
1578 // changes the chain anyway.
1579 //
1580 // Folding the generation on top would therefore add no discriminating power and would only
1581 // over-invalidate: a republication that advances the generation while producing identical
1582 // ROIs (a sub-pixel pan that rounds to the same roi.x, a box change the fminf clamp absorbs)
1583 // would rekey the whole FULL-pipe cache chain for pixels that are bit-identical. Content,
1584 // not version, is the right key here -- unlike mask_preview_settings_revision below, which
1585 // hashes a revision precisely because its state reaches no ROI at all.
1586 local_hash = dt_hash(local_hash, (const char *)&piece->roi_in, sizeof(dt_iop_roi_t));
1587 local_hash = dt_hash(local_hash, (const char *)&piece->roi_out, sizeof(dt_iop_roi_t));
1588
1589 local_hash = dt_hash(local_hash, (const char *)&piece->dsc_in, sizeof(dt_iop_buffer_dsc_t));
1590 local_hash = dt_hash(local_hash, (const char *)&piece->dsc_out, sizeof(dt_iop_buffer_dsc_t));
1591
1592 /* The pipe's drawn-mask rasterisation step changes the mask a module blends with, so it
1593 * changes that module's pixels. It belongs HERE, with request_mask_display below, and for
1594 * the same reason: it is global GUI state that no module's history carries, so
1595 * dt_iop_commit_params() would only fold it when that module's own parameters changed --
1596 * flipping the preference from auto to never would leave every cached line valid-looking
1597 * and stale.
1598 *
1599 * Only for a module that actually draws a mask: the raw stages upstream cannot carry one,
1600 * and rehashing them would cost cache misses for pixels that cannot move. */
1601 // From piece->blendop_data, NOT piece->module->blend_params. This function runs on the
1602 // darkroom worker thread, and blend_params belongs to the GUI thread -- the rule this file
1603 // states twice above ("never the live GUI module->params"). Reading it here dereferences a
1604 // module the GUI can be tearing down or reallocating underneath us, which is Sentry
1605 // 142904894: EXCEPTION_ACCESS_VIOLATION at this line, from resync_pipe_with_history() on
1606 // the pipeline thread. blendop_data is the per-piece copy dt_iop_commit_params() writes
1607 // from the history snapshot, which is what blend.c itself reads.
1608 const dt_develop_blend_params_t *const blend_data
1609 = (const dt_develop_blend_params_t *)piece->blendop_data;
1610 if(!IS_NULL_PTR(blend_data)
1611 && (blend_data->mask_mode & DEVELOP_MASK_SHAPE)
1612 && blend_data->mask_id > 0)
1613 local_hash = dt_hash(local_hash, (const char *)&pipe->mask_rasterization_step, sizeof(int));
1614
1615/*
1616 fprintf(stdout, "start->end : %-17s | ROI in: %4ix%-4i @%2.4f | ROI out: %4ix%-4i @%2.4f\n", piece->module->op,
1617 piece->buf_in.width, piece->buf_in.height, piece->buf_in.scale, piece->buf_out.width,
1618 piece->buf_out.height, piece->buf_out.scale);
1619 fprintf(stdout, "end->start : %-17s | ROI in: %4ix%-4i @%2.4f | ROI out: %4ix%-4i @%2.4f\n", piece->module->op,
1620 piece->roi_in.width, piece->roi_in.height, piece->planned_roi_in.scale,
1621 piece->roi_out.width, piece->roi_out.height, piece->roi_out.scale);
1622*/
1623 // Mask preview display doesn't re-commit params, so we need to keep that of it here
1624 // Too much GUI stuff interleaved with pipeline stuff...
1625 // Mask display applies only to main preview in darkroom.
1627 {
1628 local_hash = dt_hash(local_hash, (const char *)&piece->module->request_mask_display, sizeof(int));
1629
1630 /* Mask-preview appearance is global GUI state, not module history. Hash
1631 * its revision at the module producing the active preview so upstream
1632 * cache entries remain reusable while this module and gamma are rerun. */
1633 if(pipe->dev->gui_attached
1634 && piece->module == pipe->dev->gui_module
1635 && piece->module->request_mask_display != DT_DEV_PIXELPIPE_DISPLAY_NONE)
1636 {
1638 local_hash = dt_hash(local_hash, (const char *)&revision, sizeof(revision));
1639 }
1640
1641 // bypass_cache_variant (see dt_iop_module_t.bypass_cache_variant) is, like
1642 // request_mask_display above, module-owned GUI state whose real effect a module such as
1643 // retouch applies only to this main/FULL pipe -- never to the preview pipe, which always
1644 // renders as if none of these toggles were active. But the field itself lives on
1645 // the shared dt_iop_module_t, so it reads the same non-zero value from every pipe type. Left
1646 // ungated, a preview-pipe run with the same ROI/params (e.g. at zoom == fit) can compute the
1647 // exact same hash chain as the FULL pipe despite publishing different pixels, and either
1648 // pipe's cache entry then gets silently reused by the other via the cross-pipe "another pipe
1649 // already owns this hash" exact-hit path -- exactly like request_mask_display's own hazard.
1650 local_hash = dt_hash(local_hash, (const char *)&piece->module->bypass_cache_variant, sizeof(int));
1651 }
1652 else
1653 {
1654 const int zero = 0;
1655 local_hash = dt_hash(local_hash, (const char *)&zero, sizeof(int));
1656 local_hash = dt_hash(local_hash, (const char *)&zero, sizeof(int));
1657 }
1658
1659 // Keep track of distortion bypass in GUI. That may affect upstream modules in the stack,
1660 // while bypass_cache only affects downstream ones.
1661 // In theory, distortion bypass should already affect planned ROI in/out, but it depends whether
1662 // internal params are committed. Anyway, make it more reliable.
1664 local_hash = dt_hash(local_hash, (const char *)&bypass_distort, sizeof(int));
1665
1666 // If the cache bypass is on, the corresponding cache lines will be freed immediately after use,
1667 // we need to track that. It somewhat overlaps module->request_mask_display, but...
1668 local_hash = dt_hash(local_hash, (const char *)&piece->bypass_cache, sizeof(gboolean));
1669
1670 // Update global hash for this stage
1671 hash = dt_hash(hash, (const char *)&local_hash, sizeof(uint64_t));
1672
1674 {
1677 "[pixelpipe] global hash for %20s (%s) in pipe %s enabled=%d bypass_cache=%d with hash %lu\n",
1678 piece->module->op, piece->module->multi_name, type, piece->enabled, piece->bypass_cache,
1679 (long unsigned int)hash);
1680 dt_free(type);
1681 }
1682 // In case of drawn masks, we would need to account only for the distortions of previous modules.
1683 // Aka conditional to: if((piece->module->operation_tags() & IOP_TAG_DISTORT) == IOP_TAG_DISTORT)
1684 // But in case of parametric masks, they depend on previous modules parameters.
1685 // So, all in all, (parametric | drawn | raster) masking depends on everything :
1686 // - if masking on output, internal params + blendop params + all previous modules internal params + ROI size,
1687 // - if masking on input, blendop params + all previous modules internal params + ROI size
1688 // So we use all that ot once :
1689 piece->global_mask_hash = dt_hash(hash, (const char *)&piece->blendop_hash, sizeof(uint64_t));
1690
1691 // Finally, the output of the module also depends on the mask:
1692 hash = dt_hash(hash, (const char *)&piece->global_mask_hash, sizeof(uint64_t));
1693 piece->global_hash = hash;
1694
1696 && pipe->dev->gui_attached
1697 && (piece->module == pipe->dev->gui_module)
1698 && (piece->module->request_mask_display != DT_DEV_PIXELPIPE_DISPLAY_NONE))
1699 {
1701 }
1702 }
1703
1704 // The pipe hash is the hash of its last module.
1706 pipe->bypass_cache = bypass_cache;
1707}
1708
1719/* Resync every node against a history snapshot. Runs outside history_mutex: everything it reads
1720 * is owned by `snap`, whose references keep the items alive and, through the writer's
1721 * copy-on-write gate, unmodified. */
1723 const char *caller_func)
1724{
1726 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch all modules with history for pipe %s called from %s\n", type, caller_func);
1727
1728 const uint32_t history_end = (uint32_t)snap->history_end;
1729 _sync_pipe_nodes_from_history(pipe, snap->items, history_end, type);
1730
1731 // Keep track of the last history item to have been synced
1732 GList *last_item = g_list_nth(snap->items, history_end - 1);
1733 if(last_item)
1734 {
1738 }
1739 else
1740 {
1743 }
1744
1745 dt_free(type);
1746}
1747
1748/* Public entry: export, snapshots and the focus overlay drive a throwaway pipe through this
1749 * without going through dt_dev_pixelpipe_change(), and none of them holds history_mutex. Take
1750 * the snapshot here, under a read lock held only for the copy. A caller that DOES already hold
1751 * the lock (as reader or writer) re-enters it on the same thread, which dt_pthread_rwlock_t
1752 * permits (see dtpthread.h). */
1764
1765/* Resync only the nodes touched since the last sync, against a history snapshot. Runs outside
1766 * history_mutex, same contract as _synch_all_snapshot(). */
1768{
1770
1771 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch top modules with history for pipe %s\n", type);
1772
1773 const uint32_t history_end = (uint32_t)snap->history_end;
1774 GList *last_item = g_list_nth(snap->items, history_end - 1);
1775 if(last_item)
1776 {
1777 // The identity marker is read once: the GUI thread may re-point it at a clone (see
1778 // dt_dev_history_cow_touch) while this walk runs, and the reference the pipe holds on it
1779 // is what makes comparing against a possibly-departed item safe.
1782
1784 for(GList *history = last_item; history; history = g_list_previous(history))
1785 {
1786 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1787 first_item = history;
1788
1789 if(hist->hash == pipe->last_history_hash || hist == last_synced)
1790 break;
1791 }
1792
1793 GList *fence_item = g_list_nth(snap->items, history_end);
1794 for(GList *history = first_item; history && history != fence_item; history = g_list_next(history))
1795 {
1796 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1797 if(!hist || !hist->module) continue;
1798 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] synch top history module `%s` (%s) for pipe %s\n",
1799 hist->module->op, hist->module->multi_name, type);
1800 for(GList *nodes = g_list_last(pipe->nodes); nodes; nodes = g_list_previous(nodes))
1801 {
1803 if(IS_NULL_PTR(piece) || piece->module != hist->module) continue;
1804
1805 _sync_pipe_nodes_from_history_from_node(pipe, snap->items, history_end, nodes, type);
1806 break;
1807 }
1808 }
1809
1813 }
1814 else
1815 {
1816 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch top history module missing error for pipe %s\n", type);
1819 }
1820
1821 dt_free(type);
1822}
1823
1835
1836// Modules without history need to be resynced unconditionnally with their internal params
1837// because some of them are self-enabled/disabled from commit_params() methods
1839{
1840 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1841 {
1843 if(IS_NULL_PTR(piece) || IS_NULL_PTR(piece->module)) continue;
1844 dt_iop_module_t *module = piece->module;
1845 if(module->flags() & IOP_FLAGS_NO_HISTORY_STACK)
1846 dt_iop_commit_params(module, module->default_params, module->default_blendop_params, pipe, piece);
1847 }
1848}
1849
1851{
1852 dt_times_t start;
1853 dt_get_times(&start);
1854
1865
1867 char *status_str = g_strdup_printf("%s%s%s%s%s%s%s%s",
1868 (status & DT_DEV_PIPE_UNCHANGED) ? "UNCHANGED " : "",
1869 (status & DT_DEV_PIPE_REMOVE) ? "REMOVE " : "",
1870 (status & DT_DEV_PIPE_TOP_CHANGED) ? "TOP_CHANGED " : "",
1871 (status & DT_DEV_PIPE_SYNCH) ? "SYNCH " : "",
1872 (status & DT_DEV_PIPE_ZOOMED) ? "ZOOMED " : "",
1873 (status & DT_DEV_PIPE_CACHE_REQUEST) ? "CACHE_REQUEST " : "",
1874 (status & DT_DEV_PIPE_REENTRY) ? "REENTRY " : "",
1875 (status & DT_DEV_PIPE_SWITCHED) ? "SWITCHED " : "");
1876
1877 dt_print(DT_DEBUG_DEV, "[dt_dev_pixelpipe_change] pipeline state changing for pipe %s, flag %s\n",
1878 type, status_str);
1879
1881
1882 // mask display off as a starting point
1884 pipe->bypass_blendif = 0;
1885
1886 // Only raised here: whatever changes next, the run that renders the switched-to state must
1887 // leave the previous state's outputs alone. dt_dev_pixelpipe_process() lowers it once it did.
1889
1890 /* Zoom/pan only replans ROI and hashes, it does not replay history sync.
1891 * Rebuild the aggregate detail-mask demand from the already synchronized
1892 * pieces in that case, otherwise the pipe forgets that the hidden
1893 * `detailmask` stage is needed before the next processing run starts. */
1896 else
1898
1899 // Snapshot history under the read lock, then resync against the snapshot with the lock
1900 // RELEASED. The resync is O(nodes x history) and runs every module's commit_params() --
1901 // tens of ms routinely, over 200 ms under mask-heavy history -- and it used to hold this
1902 // lock for all of it. The GUI thread needs the writer side of the same lock on every
1903 // commit (each slider tick, each throttled mask-drag commit), and glibc's writer-preferring
1904 // policy then also blocks every NEW reader behind the queued writer: one slow resync stalled
1905 // the whole application until it finished. The snapshot holds a reference on each item, and
1906 // the writer's copy-on-write gate (dt_dev_history_cow_touch) clones a shared item before
1907 // touching it, so what this resync reads can neither move nor change under it.
1908 //
1909 // A call chain that already holds history_mutex as writer on this same thread can re-enter
1910 // here (e.g. history-commit paths that resync a pipe while still holding the write lock);
1911 // dt_pthread_rwlock_rdlock is same-thread-recursive for exactly that (see dtpthread.h).
1912 const double _history_mutex_hold_start = dt_get_wtime();
1918 if(_history_mutex_hold_ms > 1.0)
1920 "[dt_dev_pixelpipe_change] tid %lu pipe %s held history_mutex for %.2f ms (status 0x%x)\n",
1921 (unsigned long)pthread_self(), type, _history_mutex_hold_ms, (unsigned)status);
1922
1923 // The realtime in-place path settles the format contract and the global hash itself (it must,
1924 // since that hash is cumulative over enabled nodes); the other paths defer it to the
1925 // unconditional pass below.
1926 gboolean formats_propagated = FALSE;
1927
1928 // case DT_DEV_PIPE_UNCHANGED: case DT_DEV_PIPE_ZOOMED:
1930 {
1931 // modules have been added in between or removed. need to rebuild the whole pipeline.
1936 }
1937 else if(status & DT_DEV_PIPE_SYNCH)
1938 {
1939 // pipeline topology remains intact, only change all params.
1942 }
1944 {
1945 // only top history item(s) changed, or a focused module published transient/realtime params
1946 if(_sync_focused_in_place(pipe, &snap))
1947 {
1949 }
1950 else
1951 {
1953 _synch_top_snapshot(pipe, &snap);
1954 }
1955 }
1956 else // DT_DEV_PIPE_ZOOMED DT_DEV_PIPE_CACHE_REQUEST DT_DEV_PIPE_REENTRY
1957 {
1958 // Finalscale will need to self-enable/disable depending on zoom level
1960 }
1961 // The hash the SNAPSHOT carried, not the live one: a commit may have landed since the lock
1962 // was released, and this pipe is synced to what it just resynced against, nothing newer. The
1963 // worker loop compares this against the live hash and re-flags the pipe if they differ.
1966 const double _resync_ms = (dt_get_wtime() - _history_mutex_hold_start) * 1000.0;
1967 if(_resync_ms > 1.0)
1969 "[dt_dev_pixelpipe_change] tid %lu pipe %s resynced from snapshot in %.2f ms, lock-free (status 0x%x)\n",
1970 (unsigned long)pthread_self(), type, _resync_ms, (unsigned)status);
1971
1972 // Re-establish the buffer-format contract of the whole chain once, after whichever sync path
1973 // (full, top-only or realtime-in-place) committed params. This is the single authoritative,
1974 // direction-independent place where per-node dsc_in/dsc_out are settled and incompatible
1975 // modules are disabled (issue #733), so the OpenCL cache policy and ROI planning below see a
1976 // consistent contract. The realtime path already did this before computing its global hash.
1979
1981
1982 // Update theoritical final scale based on distorting modules
1983 // This also writes piece->buf_in/out for each pipe->nodes piece,
1984 // so it's not nearly a matter of getting processed_width/height
1987
1988 dt_show_times_f(&start, "[dev_pixelpipe] pipeline resync with history", "for pipe %s", type);
1989 dt_free(type);
1990}
1991
1999
struct dt_ui_t * dt_gui_get_ui(void)
GtkWidget * dt_gui_center_widget(void)
#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)
static void * dt_atomic_exch_ptr(dt_atomic_ptr *var, void *value)
Definition atomic.h:80
static void * dt_atomic_get_ptr(const dt_atomic_ptr *var)
Definition atomic.h:76
atomic_int dt_atomic_int
Definition atomic.h:68
@ DEVELOP_MASK_SHAPE
Definition blend.h:114
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_RAW
@ IOP_CS_LCH
@ IOP_CS_JZCZHZ
@ IOP_CS_RGB
@ IOP_CS_HSL
@ IOP_CS_LAB
@ IOP_CS_NONE
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)
Does key have a value?
gboolean dt_conf_key_not_empty(const char *name)
void dt_control_log(const char *msg,...)
Definition control.c:839
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:939
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:335
void dt_control_commit_cursor()
Definition control.c:343
void dt_control_navigation_redraw()
Request a redraw of the navigation module's widget.
Definition control.c:944
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
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, uint64_t *out_serial)
void dt_dev_history_snapshot_release(dt_dev_history_snapshot_t *snapshot)
Release every reference a snapshot holds and reset it. Needs no lock.
void dt_dev_free_history_item(gpointer data)
Release a reference to a history item (used as GList free callback).
dt_dev_history_item_t * dt_dev_history_item_ref(dt_dev_history_item_t *item)
Take a reference on a history item. Pair with dt_dev_free_history_item().
void dt_dev_history_snapshot_take(dt_develop_t *dev, dt_dev_history_snapshot_t *snapshot) REQUIRES_SHARED(dev -> history_mutex)
Take a snapshot of dev->history. Caller holds history_mutex (reader suffices).
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)
void dt_iop_params_t
Definition dev_history.h:43
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)
static void _sync_pipe_nodes_from_history_from_node(dt_dev_pixelpipe_t *pipe, GList *history_list, const uint32_t history_end, GList *start_node, const char *debug_label)
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)
static gboolean _module_feeds_color_picker(const dt_dev_pixelpipe_t *pipe, const dt_iop_module_t *module, const dt_iop_module_t *picker_module)
Is module the one a picker samples, or the one feeding it?
static void _dump_one_pending(const dt_dev_pixelpipe_cache_wait_t *wait, const int64_t age_us, gpointer user_data)
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 gboolean _sync_focused_in_place(dt_dev_pixelpipe_t *pipe, const dt_dev_history_snapshot_t *snap)
static void _pipe_set_last_history_item(dt_dev_pixelpipe_t *pipe, dt_dev_history_item_t *hist)
Re-commit the focused module's piece in place from transient (or realtime) params.
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)
static gboolean _cache_wait_connected
void dt_dev_pixelpipe_get_roi_out(dt_dev_pixelpipe_t *pipe, const int width_in, const int height_in, int *width, int *height)
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 gboolean _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, const gboolean retain)
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)
static void _synch_top_snapshot(dt_dev_pixelpipe_t *pipe, const dt_dev_history_snapshot_t *snap)
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)
void dt_dev_pixelpipe_sync_no_history(dt_dev_pixelpipe_t *pipe)
static GList * _find_detailmask_node(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_rebuild_all_real(dt_develop_t *dev)
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)
static void _sync_pipe_nodes_from_history(dt_dev_pixelpipe_t *pipe, GList *history_list, const uint32_t history_end, const char *debug_label)
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)
gboolean dt_dev_pixelpipe_cache_peek_gui_retained(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)
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 void _publish_upstream_hashes(dt_dev_pixelpipe_t *pipe)
static gboolean _module_requires_global_histogram_input_cache(const dt_dev_pixelpipe_t *pipe, const dt_iop_module_t *module)
static void _synch_all_snapshot(dt_dev_pixelpipe_t *pipe, const dt_dev_history_snapshot_t *snap, const char *caller_func)
Find the last history item matching each pipeline node (module), in the order of pipeline execution....
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)
static gboolean _cache_wait_cursor_active
#define dt_dev_pixelpipe_rebuild_all(dev)
#define dt_dev_pixelpipe_resync_history_preview(dev)
#define dt_dev_pixelpipe_resync_history_main(dev)
dt_pixelpipe_cache_ready_callback_t dt_dev_pixelpipe_cache_ready_callback_t
#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:2137
static uint64_t dt_dev_get_history_hash(const dt_develop_t *dev)
Definition develop.h:494
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:122
@ DT_DEV_DETAIL_MASK_ENABLED
Definition develop.h:150
@ DT_DEV_DETAIL_MASK_NONE
Definition develop.h:149
gboolean enabled
–doc was passed on the command line
GtkWidget * status
result of the last capture
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
@ IOP_CS_RGB_DISPLAY
Definition format.h:71
@ TYPE_FLOAT
Definition format.h:56
void dt_geometry_chain_rebuild(dt_develop_t *dev)
Rebuild the chain from the dev's current modules and history. GUI thread only.
Definition geometry.c:247
Where things are on the image, answered without a pipeline.
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
const char flag
Definition image.h:311
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)
Copy params into the pipeline piece, so the next render uses them.
Definition imageop.c:1575
gboolean dt_iop_get_cache_bypass(dt_iop_module_t *module)
Definition imageop.c:1680
@ IOP_FLAGS_NO_HISTORY_STACK
Definition imageop.h:193
@ IOP_TAG_DISTORT
Definition imageop.h:170
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
hash
Definition layers.h:90
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_PIPE
Definition logging.h:76
@ DT_DEBUG_PIPECACHE
Definition logging.h:56
@ DT_DEBUG_HISTORY
Definition logging.h:75
@ DT_DEBUG_VERBOSE
Definition logging.h:78
@ DT_DEBUG_PARAMS
Definition logging.h:71
@ DT_DEBUG_DEV
Definition logging.h:53
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
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.
#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
GList * dt_masks_snapshot_current_forms(dt_develop_t *dev, gboolean reset_changed)
void dt_masks_forms_snapshot_release(GList **snapshot)
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
int dt_opencl_is_inited(void)
Definition opencl.c:3296
#define DT_MAX_FILENAME_LEN
Definition paths.h:35
Whether one pipeline node must keep a host-RAM copy of its output.
static gboolean dt_dev_pipe_cache_policy_decide(const dt_dev_pipe_cache_policy_inputs_t *in, const gboolean inherited_requirement, gboolean *upstream_requirement)
Decide one node's host-cache requirement, walking the pipe from the end backwards.
void dt_iop_buffer_dsc_update_bpp(dt_iop_buffer_dsc_t *dsc)
@ DT_REQUEST_ON
Definition pixelpipe.h:52
@ DT_REQUEST_ONLY_IN_GUI
Definition pixelpipe.h:53
@ DT_DEV_PIXELPIPE_NONE
Definition pixelpipe.h:41
@ DT_DEV_PIXELPIPE_PREVIEW
Definition pixelpipe.h:44
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:43
void dt_dev_pixelpipe_cache_ref_count_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Take or release one reference on a cache line. A referenced line is never evicted,...
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.
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...
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.
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
guint dt_pixelpipe_cache_wait_pending_count(void)
How many requests are outstanding. Diagnostics only.
void dt_pixelpipe_cache_wait_foreach_pending(dt_pixelpipe_cache_wait_visitor_t callback, gpointer user_data)
gboolean dt_pixelpipe_cache_wait_cancel(dt_pixelpipe_cache_wait_t *wait, gboolean *drained)
Remove wait from the queue if it is there, and reset it to an inert state.
GList * dt_pixelpipe_cache_wait_take_matching(const uint64_t hash, const uint64_t producer_node_key, gboolean *drained)
Take every waiter satisfied by a publication of hash from node producer_node_key.
gboolean dt_pixelpipe_cache_wait_enqueue(dt_pixelpipe_cache_wait_t *wait)
Queue wait, or refresh it in place if it is already queued for the same target.
void dt_pixelpipe_cache_wait_count_immediate_hit(void)
Count one cache hit that never needed to queue.
void dt_pixelpipe_cache_wait_get_stats(uint64_t *queued, uint64_t *served, uint64_t *cancelled, uint64_t *immediate_hits, uint64_t *misses)
Snapshot of the lifetime counters, for the dump. Any pointer may be NULL.
void dt_pixelpipe_cache_wait_count_miss(void)
Count one miss that is about to queue.
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_SWITCHED
@ 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)
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:411
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ 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:189
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:400
unsigned __int64 uint64_t
Definition strptime.c:75
dt_iop_params_t * params
Definition dev_history.h:54
struct dt_develop_blend_params_t * blend_params
Definition dev_history.h:57
struct dt_iop_module_t *gboolean enabled
Definition dev_history.h:52
A reference-sharing snapshot of dev->history, for readers that outlive a lock.
Everything the decision depends on, named.
struct dt_iop_module_t *void * data
dt_atomic_ptr last_history_item
gboolean gui_observable_source
uint64_t last_history_hash
dt_backbuf_t backbuf
dt_atomic_uint64 hash
dt_atomic_int shutdown
int mask_rasterization_step
Rasterisation step for drawn masks, in image pixels. Always >= 1.
dt_dev_pixelpipe_type_t type
dt_atomic_int changed
struct dt_develop_t * dev
int32_t gui_attached
Definition develop.h:167
dt_image_t image_storage
Definition develop.h:225
struct dt_develop_t::@13 color_picker
Authoritative darkroom color-picker state.
struct dt_iop_module_t * gui_module
Definition develop.h:170
dt_pthread_rwlock_t history_mutex
Definition develop.h:247
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
dt_atomic_int mask_preview_settings_revision
Revision of the global mask-preview appearance.
Definition develop.h:181
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
dt_iop_buffer_dsc_t dsc
Definition image.h:419
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:386
int32_t params_size
Definition imageop.h:335
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
One consumer's outstanding request, owned by that consumer, not by the queue.
dt_pixelpipe_cache_ready_callback_t restart
const void *char module_op[20]
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:165
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:80
@ DT_SV_UPDATE
Definition supervisor.h:79
@ DT_SV_CREATE
Definition supervisor.h:78
@ DT_SV_DELETE
Definition supervisor.h:81
static gboolean dt_supervisor_active(void)
Definition supervisor.h:95
#define MAX(a, b)
Definition thinplate.c:29
static void dt_get_times(dt_times_t *t)
Definition times.h:50
static double dt_get_wtime(void)
Definition times.h:43
void dt_show_times_f(const dt_times_t *start, const char *prefix, const char *suffix,...) __attribute__((format(printf
gboolean dt_gui_widgets_suppressed(void)