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
67static gboolean _cache_wait_cursor_progress(gpointer user_data)
68{
70 return G_SOURCE_REMOVE;
71
73 return G_SOURCE_REMOVE;
74}
75
76static gboolean _cache_wait_cursor_restore(gpointer user_data)
77{
79 return G_SOURCE_REMOVE;
80
83 return G_SOURCE_REMOVE;
84}
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;
106 dt_pixelpipe_cache_wait_get_stats(&queued, &served, &cancelled, &immediate, &misses);
107
109 "[cache-wait] dump reason=%s pending=%u queued=%" PRIu64 " served=%" PRIu64
110 " cancelled=%" PRIu64 " immediate=%" PRIu64 " misses=%" PRIu64 "\n",
111 context, dt_pixelpipe_cache_wait_pending_count(), queued, served, cancelled,
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
152 const dt_iop_module_t *module,
153 const dt_iop_module_t *picker_module)
154{
155 if(IS_NULL_PTR(picker_module) || IS_NULL_PTR(module)) return FALSE;
156 if(module == picker_module) return TRUE;
157
160
161 return !IS_NULL_PTR(previous_piece) && previous_piece->module == module;
162}
163
165{
166 (void)dev;
168}
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{
183 dt_dev_pixelpipe_iop_t *detailmask_piece = NULL;
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
199 if(!IS_NULL_PTR(detailmask_piece))
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;
278 uint64_t transient_serial = 0;
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,
285 &transient_serial))
286 {
287 params = (dt_iop_params_t *)tbuf;
288 used_transient = TRUE;
289 }
290 else
291 dt_free(tbuf);
292 }
293
294 const gboolean previous_want_detail_mask = (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE);
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. */
314 if(used_transient)
315 {
316 piece->hash = dt_hash(piece->hash, (const char *)&transient_serial, sizeof(transient_serial));
317 piece->global_hash = piece->hash;
318 }
320 if(previous_want_detail_mask != (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE)) return FALSE;
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.
386 const dt_iop_module_t *const focused_module = pipe->dev->gui_module;
387 const dt_iop_module_t *const picker_module = pipe->dev->color_picker.module;
388
389 gboolean current_output_must_cache_host = TRUE;
390
391 for(GList *pieces = g_list_last(pipe->nodes); pieces; pieces = g_list_previous(pieces))
392 {
393 dt_dev_pixelpipe_iop_t *piece = pieces->data;
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
423 && focused_module == module;
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
443 = dt_dev_pipe_cache_policy_decide(&inputs, current_output_must_cache_host,
444 &current_output_must_cache_host);
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.
540 gtk_widget_queue_draw(dt_gui_center_widget());
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;
567 pipe_name = _get_debug_pipe_name(pipe, pipe->dev);
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
602 if(pipe_name) dt_free(pipe_name);
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{
623 uint64_t upstream = _default_pipe_hash(pipe);
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. */
637 const int bypass_distort = dt_dev_pixelpipe_activemodule_disables_currentmodule(pipe->dev, piece->module);
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;
669 pipe_name = _get_debug_pipe_name(pipe, pipe->dev);
670 // A modify_roi_in() can allocate from the pixelpipe cache -- lens does --, and the cache names the
671 // module and its image when it refuses, as it does while the module processes (pixelpipe_hb.c).
672 gchar *image = g_strdup_printf(_("`%s` (image id %d)"), pipe->dev->image_storage.filename,
674 const char *prev_image = dt_pixelpipe_cache_set_current_image(image);
675 for(GList *nodes = g_list_last(pipe->nodes); nodes; nodes = g_list_previous(nodes))
676 {
678 dt_iop_module_t *module = piece->module;
679
680 piece->roi_out = roi_out_temp;
681
682 // If in GUI and using a module that needs a full, undistorterted image,
683 // we need to shutdown temporarily any module distorting the image.
685 piece->enabled = FALSE;
686
687 // If module is disabled, modify_roi_in() is a no-op
688 if(piece->enabled)
689 {
690 const char *prev_module = dt_pixelpipe_cache_set_current_module(module->op);
691 module->modify_roi_in(module, pipe, piece, &roi_out_temp, &roi_in);
693 }
694 else
695 roi_in = roi_out_temp;
696
697 // Backward ROI planning answers "how much input rectangle does this
698 // module need from upstream to deliver the requested downstream output ?".
699 // Logging that request before and after modify_roi_in() makes ROI growth
700 // and padding traceable module-by-module on `-d pipe`.
701 if(piece->enabled && (dt_get_debug_flags() & DT_DEBUG_PIPE))
703 "[roi-in ] pipe=%-15s module=%-18s enabled=%d out=(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)"
704 " in=(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)\n",
705 pipe_name, module->op, piece->enabled,
706 roi_out_temp.x, roi_out_temp.y, roi_out_temp.width, roi_out_temp.height, roi_out_temp.scale,
707 roi_in.x, roi_in.y, roi_in.width, roi_in.height, roi_in.scale);
708
709 piece->roi_in = roi_in;
710 roi_out_temp = roi_in;
711 }
712
714 dt_free(image);
715 if(pipe_name) dt_free(pipe_name);
716
718
719 /* ROI planning runs backwards, but rawprepare seals the effective Bayer/X-Trans phase only once
720 * the real input crop is known. Forward that authored RAW descriptor now so the downstream RAW
721 * modules process with the same CFA layout that rawprepare just computed for this run. */
723 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
724 {
726
727 if(piece->dsc_in.cst == IOP_CS_RAW && piece->dsc_in.channels == 1)
728 {
729 piece->dsc_in.filters = upstream_dsc.filters;
730 memcpy(piece->dsc_in.xtrans, upstream_dsc.xtrans, sizeof(piece->dsc_in.xtrans));
731 }
732
733 if(piece->dsc_out.cst == IOP_CS_RAW && piece->dsc_out.channels == 1
734 && (!piece->enabled || strcmp(piece->module->op, "rawprepare")))
735 {
736 piece->dsc_out.filters = piece->dsc_in.filters;
737 memcpy(piece->dsc_out.xtrans, piece->dsc_in.xtrans, sizeof(piece->dsc_out.xtrans));
738 }
739
740 upstream_dsc = piece->dsc_out;
741 }
742
743}
744
745
747 const dt_iop_roi_t roi_out, const int pos)
748{
749 // to be called at runtime, not at pipe init.
750
751 // Only at the first step of pipe, we don't have a module because we init the base buffer.
752 if(!IS_NULL_PTR(piece))
753 return piece->global_hash;
754 else
755 {
756 // This is used for the first step of the pipe, before modules, when initing base buffer
757 // We need to take care of the ROI manually
759 hash = dt_hash(hash, (const char *)&roi_out, sizeof(dt_iop_roi_t));
760 return dt_hash(hash, (const char *)&pos, sizeof(int));
761 }
762}
763
765 const dt_iop_module_t *module)
766{
767 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(module)) return NULL;
768
769 for(GList *node = g_list_first(pipe->nodes); node; node = g_list_next(node))
770 {
771 dt_dev_pixelpipe_iop_t *const piece = node->data;
772 if(piece && piece->enabled && piece->module == module)
773 return piece;
774 }
775
776 return NULL;
777}
778
781{
782 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(piece)) return NULL;
783
784 GList *node = g_list_find(pipe->nodes, (gpointer)piece);
785 if(IS_NULL_PTR(node)) return NULL;
786
787 for(node = g_list_previous(node); node; node = g_list_previous(node))
788 {
789 dt_dev_pixelpipe_iop_t *const previous = node->data;
790 if(previous && previous->enabled)
791 return previous;
792 }
793
794 return NULL;
795}
796
798 void **data, dt_pixel_cache_entry_t **cache_entry,
801 gpointer restart_data, const gboolean retain)
802{
803 if(!IS_NULL_PTR(data)) *data = NULL;
804 if(!IS_NULL_PTR(cache_entry)) *cache_entry = NULL;
805 if(IS_NULL_PTR(pipe)) return FALSE;
806
807 // Module-output cache requests are not satisfiable when the target piece
808 // itself bypasses cache retention. Re-queueing those from GUI redraws would
809 // keep transient edit modes in a self-feeding recompute loop.
810 // Upstream targets (for example color-picker input sampling) stay allowed even if a
811 // downstream piece enabled bypass mode.
812 if(!IS_NULL_PTR(piece)
814 return FALSE;
815
817
818 // For the final backbuffer (piece == NULL), GUI consumers display the last *published* frame,
819 // identified by pipe->backbuf.hash. The pipeline plans the next frame ahead of publishing it, so
820 // pipe->hash (the planned final hash) runs ahead of the backbuffer while a recompute is in flight.
821 // This is the normal steady state of realtime drawing: every heartbeat plans a new frame. Peeking
822 // the planned hash would then miss the perfectly valid published frame, the darkroom main-surface
823 // lock would fail, and the view would drop to the paused preview pipe (which lacks the in-progress
824 // stroke) — the flicker between "drawing applied" and "original". So look up the published backbuf
825 // hash for display, and keep the planned hash only to drive recompute on a genuine miss below.
826 uint64_t display_hash = hash;
827 if(IS_NULL_PTR(piece))
828 {
829 const uint64_t backbuf_hash = dt_dev_backbuf_get_hash(&pipe->backbuf);
830 if(backbuf_hash != DT_PIXELPIPE_CACHE_HASH_INVALID) display_hash = backbuf_hash;
831 }
832
833 void *buffer = NULL;
834 dt_pixel_cache_entry_t *entry = NULL;
835 // GUI consumers run on the CPU and never own the OpenCL device lock, so we pass preferred_devid = -1:
836 // dt_dev_pixelpipe_cache_peek() then returns host-resident cachelines directly but refuses to
837 // materialize device-only ones from the GPU (which would enqueue hidden GPU work from the GUI thread,
838 // racing whichever pipeline thread currently owns the device — the clReleaseEvent crash). A device-only
839 // entry is reported as a miss, so the request below waits for the pipeline to publish a host copy
840 // (modules whose output the GUI samples, e.g. initialscale, already cache to RAM).
841 //
842 // `retain` picks between the two ownership models a GUI consumer can have on a cacheline, and
843 // they are not interchangeable. A consumer that merely BORROWS the currently published
844 // backbuffer (the darkroom surface, the navigation thumbnail, the scopes) is covered by the
845 // keepalive reference its publisher holds, and must not take one of its own. A consumer that
846 // samples an INTERMEDIATE module cacheline (color picker, module histograms, autoset,
847 // colorequal) has no such cover: those entries sit at refcount 0 between renders and the
848 // pipeline may evict one at any moment. Looking one up and taking the reference afterwards
849 // leaves a window in which it is freed under the caller, so that acquisition happens under one
850 // hold of the cache lock -- the hazard `dt_dev_pixelpipe_process_rec()` already avoids for its
851 // own exact-hit lookups, for exactly the same reason.
852 const gboolean got = display_hash != DT_PIXELPIPE_CACHE_HASH_INVALID
853 && (retain ? dt_dev_pixelpipe_cache_ref_host_entry_by_hash(display_hash, &buffer, &entry)
854 : dt_dev_pixelpipe_cache_peek(display_hash, &buffer, &entry, -1, NULL));
855 if(got && !IS_NULL_PTR(buffer) && !IS_NULL_PTR(entry))
856 {
858 dt_dev_pixelpipe_cache_wait_cleanup(wait, "peek-gui-immediate-hit");
859 if(!IS_NULL_PTR(data)) *data = buffer;
860 if(!IS_NULL_PTR(cache_entry)) *cache_entry = entry;
861 return TRUE;
862 }
863
864 /* A retained entry the caller cannot be handed back (it asked for neither the data nor the
865 * entry) would leak its reference: nobody would ever release it. */
866 if(retain && got && !IS_NULL_PTR(entry))
867 {
869 entry = NULL;
870 buffer = NULL;
871 }
872
874
875 gboolean request_cacheline = TRUE;
876 if(!IS_NULL_PTR(wait) && !IS_NULL_PTR(restart) && hash != DT_PIXELPIPE_CACHE_HASH_INVALID)
877 {
878 const gboolean changed_target = !wait->connected
879 || wait->pipe != pipe
880 || wait->module != (!IS_NULL_PTR(piece) ? piece->module : NULL)
881 || wait->hash != hash
882 || wait->restart != restart;
883 if(changed_target)
884 {
885 gboolean activate_wait_cursor = FALSE;
886 dt_dev_pixelpipe_cache_wait_cleanup(wait, "peek-gui-target-changed");
887 wait->pipe = pipe;
888 wait->module = !IS_NULL_PTR(piece) ? piece->module : NULL;
889
890 // Copy the labels now, while both objects are certainly alive. The queue never reads
891 // through the two identity pointers above; everything it reports about a waiter comes
892 // from here.
893 const dt_iop_module_t *target_module = !IS_NULL_PTR(piece) ? piece->module : NULL;
894 if(!IS_NULL_PTR(target_module))
895 {
896 g_strlcpy(wait->module_op, target_module->op, sizeof(wait->module_op));
897 wait->module_multi_priority = target_module->multi_priority;
898 }
899 else
900 {
901 wait->module_op[0] = '\0';
902 wait->module_multi_priority = 0;
903 }
904 wait->pipe_type = (int)pipe->type;
905 wait->pipe_imgid = pipe->imgid;
906
907 wait->hash = hash;
908 // Record the producing node identity so CACHELINE_READY can serve this waiter by
909 // its target module even if the exact awaited hash drifts before publish (§8). A
910 // backbuf target (piece == NULL) has no single producing node, so it keeps to the
911 // exact-hash match only.
912 wait->target_node_key = !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module)
913 ? dt_supervisor_node_key(pipe->type, piece->module->op,
914 piece->module->multi_priority)
916 wait->restart = restart;
917 wait->user_data = restart_data;
918 if(IS_NULL_PTR(wait->owner_tag))
919 wait->owner_tag = !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module) ? piece->module->op : "gui-backbuf";
920 if(IS_NULL_PTR(wait->owner_object))
921 wait->owner_object = restart_data;
922 wait->connected = TRUE;
923
924 // The queue assigns the request id and reports whether it just went from empty to
925 // non-empty; the subscription and the cursor are ours, and both hang off exactly that
926 // transition -- which is why they were one flag each in the old manager.
927 const gboolean queue_became_busy = dt_pixelpipe_cache_wait_enqueue(wait);
928 if(queue_became_busy)
929 {
931 {
935 }
937 {
939 activate_wait_cursor = TRUE;
940 }
941 }
942 if(activate_wait_cursor)
943 g_main_context_invoke(NULL, _cache_wait_cursor_progress, NULL);
944
946 "[cache-wait] queued id=%" PRIu64 " owner=%s hash=%" PRIu64 " target=%s\n",
947 wait->request_id,
948 !IS_NULL_PTR(wait->owner_tag) ? wait->owner_tag : "(unknown)",
949 wait->hash,
950 !IS_NULL_PTR(wait->module) ? wait->module_op : "backbuf");
951
954 !IS_NULL_PTR(wait->module) ? wait->module_op : NULL,
956 (int)pipe->type, pipe->imgid, TRUE, NULL);
957 }
958 else
959 {
960 /* The GUI already waits for this exact cacheline. Re-emitting CACHE_REQUEST from
961 * each expose keeps retrying an unsatisfied target forever when the pipeline cannot
962 * publish it, for example after an OpenCL memory pre-check failure on very large images. */
963 request_cacheline = FALSE;
964
965 /* A deduped re-poll: the consumer asked again for a wait already in flight, and no
966 * new CACHE_REQUEST was emitted. A run of these with no matching serve (and no
967 * cacheline ever published under wait->hash) is the "never processed, never
968 * finishes" signature. */
971 !IS_NULL_PTR(wait->module) ? wait->module_op : NULL,
973 (int)pipe->type, pipe->imgid, FALSE, "dedup-poll");
974 }
975 }
976
977 if(request_cacheline)
978 {
982 !IS_NULL_PTR(piece) ? piece->module : NULL);
984
985 dt_print(DT_DEBUG_DEV, "[pixelpipe/gui] request host cache pipe=%s target=%s hash=%" PRIu64 "\n",
987 !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module) ? piece->module->op : "backbuf", hash);
988 }
989
990 return FALSE;
991}
992
994 void **data, dt_pixel_cache_entry_t **cache_entry,
997 gpointer restart_data)
998{
999 return _peek_gui(pipe, piece, data, cache_entry, wait, restart, restart_data, FALSE);
1000}
1001
1003 void **data, dt_pixel_cache_entry_t **cache_entry,
1006 gpointer restart_data)
1007{
1008 return _peek_gui(pipe, piece, data, cache_entry, wait, restart, restart_data, TRUE);
1009}
1010
1011
1039static void _dt_dev_pixelpipe_cache_wait_ready_callback(gpointer instance, const guint64 hash,
1040 const guint64 producer_node_key,
1041 gpointer user_data)
1042{
1043 gboolean restore_wait_cursor = FALSE;
1044 gboolean drained = FALSE;
1045
1046 // The queue decides who this publication satisfies and hands them back; running their
1047 // callbacks is ours, and stays OUTSIDE any queue lock, because a restart handler queues
1048 // redraws and asks for new cachelines.
1049 GList *to_restart = dt_pixelpipe_cache_wait_take_matching(hash, producer_node_key, &drained);
1050
1051 if(drained && _cache_wait_connected)
1052 {
1056 }
1057 if(drained && _cache_wait_cursor_active)
1058 {
1060 restore_wait_cursor = TRUE;
1061 }
1062 if(restore_wait_cursor)
1063 g_main_context_invoke(NULL, _cache_wait_cursor_restore, NULL);
1064
1065 for(GList *iter = to_restart; iter; iter = g_list_next(iter))
1066 {
1067 dt_dev_pixelpipe_cache_wait_t *wait = iter->data;
1069 gpointer restart_data = wait->user_data;
1070 // A drift serve: the awaited hash never published, but the target module did
1071 // (matched via producer_node_key). Record it as such so the supervisor timeline
1072 // shows the recovery instead of a phantom exact hit.
1073 const gboolean drift_serve = wait->hash != hash;
1074
1076 "[cache-wait] served id=%" PRIu64 " owner=%s awaited=%" PRIu64 " published=%" PRIu64
1077 " target=%s%s\n",
1078 wait->request_id,
1079 !IS_NULL_PTR(wait->owner_tag) ? wait->owner_tag : "(unknown)",
1080 wait->hash, hash,
1081 !IS_NULL_PTR(wait->module) ? wait->module_op : "backbuf",
1082 drift_serve ? " (drift: node-key)" : "");
1083
1086 !IS_NULL_PTR(wait->module) ? wait->module_op : NULL,
1088 !IS_NULL_PTR(wait->pipe) ? (int)wait->pipe_type : -1,
1089 !IS_NULL_PTR(wait->pipe) ? wait->pipe_imgid : -1, FALSE,
1090 drift_serve ? "served (drift: node-key)" : "served");
1091
1092 wait->pipe = NULL;
1093 wait->module = NULL;
1096 wait->restart = NULL;
1097 wait->user_data = NULL;
1098 wait->request_id = 0;
1099
1100 if(!IS_NULL_PTR(restart)) restart(restart_data);
1101 }
1102 g_list_free(to_restart);
1103}
1104
1106{
1107 if(IS_NULL_PTR(wait) || !wait->connected) return;
1108 gboolean restore_wait_cursor = FALSE;
1109 const uint64_t request_id = wait->request_id;
1110 const uint64_t hash = wait->hash;
1111 const char *owner_tag = wait->owner_tag;
1112 // Snapshot the identity and its labels together, before the reset below clears them. Nothing
1113 // here dereferences the module or the pipe: the labels were copied when the wait was queued,
1114 // precisely because either object can be gone by the time a wait is cancelled.
1115 const gboolean had_module = !IS_NULL_PTR(wait->module);
1116 const gboolean had_pipe = !IS_NULL_PTR(wait->pipe);
1117 const char *module_op = had_module ? wait->module_op : NULL;
1118 const int module_multi_priority = wait->module_multi_priority;
1119 const int cancel_pipe_type = wait->pipe_type;
1120 const int32_t cancel_pipe_imgid = wait->pipe_imgid;
1121 const char *cancel_reason = !IS_NULL_PTR(reason) ? reason : "unspecified";
1122
1123 gboolean drained = FALSE;
1124 dt_pixelpipe_cache_wait_cancel(wait, &drained);
1125
1126 if(drained && _cache_wait_connected)
1127 {
1131 }
1132 if(drained && _cache_wait_cursor_active)
1133 {
1135 restore_wait_cursor = TRUE;
1136 }
1137 if(restore_wait_cursor)
1138 g_main_context_invoke(NULL, _cache_wait_cursor_restore, NULL);
1139
1140 // -1: the queue owns the enqueue timestamp, and a cancellation is not worth an extra query
1141 // into it. The dump reports real ages for everything still pending.
1142 const int64_t age_ms = -1;
1144 "[cache-wait] cancelled id=%" PRIu64 " owner=%s hash=%" PRIu64
1145 " target=%s age_ms=%" PRId64 " reason=%s\n",
1146 request_id,
1147 !IS_NULL_PTR(owner_tag) ? owner_tag : "(unknown)",
1148 hash,
1149 had_module ? module_op : "backbuf",
1150 age_ms,
1151 cancel_reason);
1152
1154 {
1155 char note[64];
1156 g_snprintf(note, sizeof(note), "cancelled: %s", cancel_reason);
1157 dt_supervisor_cache_wait(DT_SV_DELETE, request_id, hash, owner_tag,
1158 module_op, module_multi_priority,
1159 had_pipe ? cancel_pipe_type : -1,
1160 had_pipe ? cancel_pipe_imgid : -1, FALSE, note);
1161 }
1162
1163 wait->pipe = NULL;
1164 wait->module = NULL;
1167 wait->restart = NULL;
1168 wait->user_data = NULL;
1169 wait->request_id = 0;
1170 wait->connected = FALSE;
1171}
1172
1174 const char *owner_tag,
1175 gpointer owner_object)
1176{
1177 if(IS_NULL_PTR(wait)) return;
1178 wait->owner_tag = owner_tag;
1179 wait->owner_object = owner_object;
1180}
1181
1182/* Establish the *input* side of a piece's buffer contract from the upstream descriptor by
1183 * asking the module's input_format(). This does NOT decide compatibility or finalize the
1184 * output: the single authoritative pass dt_dev_pixelpipe_propagate_formats() owns the mismatch/auto-disable
1185 * decision so it is taken once, on the fully-committed and consistently-threaded chain, never
1186 * on the stale partial state a synch_top / realtime edit can leave behind (issue #733).
1187 *
1188 * It is split out from that pass because commit_params() of a few modules (exposure, highlights,
1189 * invert, temperature) reads piece->dsc_in (filters, processed_maximum), so the commit loop must
1190 * set dsc_in before committing. */
1191// Short colorspace name for the [dsc] format-propagation debug log (-d pipe). Knowing the cst of
1192// each node's input/output is essential to spot when the pixelpipe's automatic RGB<->Lab conversion
1193// should fire: a Lab-domain module whose dsc_in.cst is not "lab" will silently consume RGB.
1194static const char *_dsc_cst_name(const int cst)
1195{
1196 switch(cst)
1197 {
1198 case IOP_CS_RAW: return "raw";
1199 case IOP_CS_LAB: return "lab";
1200 case IOP_CS_RGB: return "rgb";
1201 case IOP_CS_RGB_DISPLAY: return "display-rgb";
1202 case IOP_CS_LCH: return "lch";
1203 case IOP_CS_HSL: return "hsl";
1204 case IOP_CS_JZCZHZ: return "jzczhz";
1205 case IOP_CS_NONE: return "none";
1206 default: return "?";
1207 }
1208}
1209
1211 const dt_iop_buffer_dsc_t *upstream_dsc)
1212{
1213 piece->dsc_in = *upstream_dsc;
1214 piece->dsc_out = *upstream_dsc;
1217
1218 /* Disabled modules are strict pass-through stages. Their advertised contracts must
1219 * not rewrite the upstream descriptor, otherwise a disabled RGB-only module can make
1220 * downstream RAW stages such as demosaic believe the stream was already converted. */
1221 if(!piece->enabled) return;
1222
1223 piece->module->input_format(piece->module, pipe, piece, &piece->dsc_in);
1225 piece->dsc_out = piece->dsc_in;
1227}
1228
1231 dt_iop_buffer_dsc_t *upstream_dsc)
1232{
1233 const dt_iop_buffer_dsc_t actual_input_dsc = *upstream_dsc;
1234
1235 // Set dsc_in before committing (commit_params() of some modules reads it). Compatibility and
1236 // the final output descriptor are settled later by dt_dev_pixelpipe_propagate_formats(), so this loop
1237 // never auto-disables a module on a possibly-stale upstream descriptor.
1239
1240 // This should run even if the module is disabled because
1241 // some modules with no history self-enable based on runtime context
1242 dt_iop_commit_params(piece->module, params, blend_params, pipe, piece);
1243
1244 if(piece->enabled)
1245 {
1246 piece->module->output_format(piece->module, pipe, piece, &piece->dsc_out);
1248 }
1249 else
1250 {
1251 piece->dsc_in = actual_input_dsc;
1252 piece->dsc_out = actual_input_dsc;
1255 }
1256
1257 *upstream_dsc = piece->dsc_out;
1258}
1259
1260/* Direction-independent forward format pass.
1261 *
1262 * The buffer-format contract (channels / datatype / colorspace / CFA presence flowing from one
1263 * module to the next) is conceptually independent of ROI planning, which runs in both
1264 * directions (end->start for a target size, start->end for the native size). Deriving the
1265 * contract inside the history-sync commit loop made it sensitive to partial syncs
1266 * (synch_top / realtime top edits): a downstream module could read a stale upstream descriptor
1267 * and get wrongly disabled for an "unexpected input buffer format" (issue #733). Worse, the
1268 * auto-disable is one-way, so a later pass could not undo a false positive.
1269 *
1270 * This single forward pass re-threads the whole chain from the input image descriptor after
1271 * params have been committed, and is the *only* place that disables a module for a genuinely
1272 * incompatible input. Disabling here is safe because the chain is consistent and fully threaded
1273 * from pipe->dev->image_storage.dsc. Only the first stage (basebuffer) reads the input image
1274 * type; every later node derives its contract solely from its upstream piece. The lone
1275 * ROI-dependent refinement, rawprepare's CFA phase shift, is finalized later in modify_roi_*().
1276 *
1277 * It is deliberately NON-destructive for compatible enabled modules: it verifies the input
1278 * contract and re-threads dsc_in, but keeps the dsc_out that commit_params() produced. dsc_out
1279 * carries runtime fields (processed_maximum, rawprepare black/white, temperature coeffs, CFA
1280 * phase) that input/output_format cannot reconstruct, so recomputing them here would wipe the
1281 * tonal scaling and render images black/white. */
1283{
1284 if(IS_NULL_PTR(pipe)) return;
1285
1286 dt_iop_buffer_dsc_t upstream_dsc = pipe->dev->image_storage.dsc;
1287 gchar *pipe_name = (dt_get_debug_flags() & DT_DEBUG_PIPE) ? _get_debug_pipe_name(pipe, NULL) : NULL;
1288
1289 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1290 {
1292 if(IS_NULL_PTR(piece) || IS_NULL_PTR(piece->module)) continue;
1293
1294 const dt_iop_buffer_dsc_t actual_input_dsc = upstream_dsc;
1295
1296 if(!piece->enabled)
1297 {
1298 // Disabled modules are strict pass-through stages.
1299 piece->dsc_in = actual_input_dsc;
1300 piece->dsc_out = actual_input_dsc;
1303 }
1304 else
1305 {
1306 // Ask the module what input it expects, into a scratch descriptor. We must NOT recompute
1307 // piece->dsc_out from input/output_format here: commit_params() already produced dsc_out
1308 // with runtime fields that those methods cannot reconstruct (processed_maximum, rawprepare
1309 // black/white, temperature coeffs, CFA phase). Recomputing would reset processed_maximum to
1310 // the input image's value and blow up downstream tone handling (black/white renders).
1311 dt_iop_buffer_dsc_t declared_in = actual_input_dsc;
1312 dt_iop_buffer_dsc_update_bpp(&declared_in);
1313 piece->module->input_format(piece->module, pipe, piece, &declared_in);
1314 dt_iop_buffer_dsc_update_bpp(&declared_in);
1315
1316 // A module whose declared input does not match what the previous stage actually publishes
1317 // cannot run: disable it (in the pipe only; history is untouched) and turn it into a
1318 // pass-through so the contract keeps flowing to the next stage.
1319 const gboolean input_mismatch
1320 = (declared_in.bpp != actual_input_dsc.bpp
1321 || declared_in.channels != actual_input_dsc.channels
1322 || declared_in.filters != actual_input_dsc.filters);
1323
1324 if(input_mismatch)
1325 {
1326 dt_control_log(_("disabled module `%s`: unexpected input buffer format"),
1327 piece->module->name());
1329 "[pixelpipe] disabling module %s because input format expects %" G_GSIZE_FORMAT
1330 " B/px, %u channels, filters %u but upstream publishes %" G_GSIZE_FORMAT
1331 " B/px, %u channels, filters %u\n",
1332 piece->module->op, declared_in.bpp, declared_in.channels, declared_in.filters,
1333 actual_input_dsc.bpp, actual_input_dsc.channels, actual_input_dsc.filters);
1334
1335 piece->enabled = FALSE;
1337 piece->blendop_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1339 piece->global_mask_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1340 piece->upstream_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1341 piece->dsc_in = actual_input_dsc;
1342 piece->dsc_out = actual_input_dsc;
1345 }
1346 else
1347 {
1348 // Input is compatible: publish the module's DECLARED input descriptor. `declared_in` was
1349 // seeded from the upstream descriptor and then run through input_format(), so it already
1350 // carries the upstream runtime fields (processed_maximum, rawprepare, temperature, CFA
1351 // phase) AND the colorspace/channel layout the module actually consumes.
1352 //
1353 // Critically, we must NOT fall back to `actual_input_dsc` here: that would reset dsc_in.cst
1354 // to the upstream colorspace and make it equal to the buffer's cst, so the pixelpipe's
1355 // automatic colorspace conversion (pixelpipe_cpu.c / pixelpipe_gpu.c) would believe no
1356 // conversion is needed. Lab-domain modules (old color balance, color checker, atrous, ...)
1357 // inserted after an RGB stage would then receive RGB data interpreted as Lab and render
1358 // garbled/solid colors. The full-resync path keeps dsc_in from input_format() for the same
1359 // reason; this incremental (synch_top) path must match it.
1360 piece->dsc_in = declared_in;
1362 }
1363 }
1364
1365 if(pipe_name)
1367 "[dsc] pipe=%s module=%s enabled=%d in=(cst=%s ch=%i bpp=%" G_GSIZE_FORMAT
1368 " filters=%u) out=(cst=%s ch=%i bpp=%" G_GSIZE_FORMAT " filters=%u)\n",
1369 pipe_name, piece->module->op, piece->enabled,
1370 _dsc_cst_name(piece->dsc_in.cst), piece->dsc_in.channels, piece->dsc_in.bpp, piece->dsc_in.filters,
1371 _dsc_cst_name(piece->dsc_out.cst), piece->dsc_out.channels, piece->dsc_out.bpp, piece->dsc_out.filters);
1372
1373 upstream_dsc = piece->dsc_out;
1374 }
1375
1376 dt_free(pipe_name);
1377}
1378
1379/* `history_list` is the snapshot's list (dt_dev_history_snapshot_t::items), never dev->history:
1380 * this runs OUTSIDE history_mutex, and the snapshot's references are what keep every item it
1381 * walks alive and unmodified (see dt_dev_history_snapshot_take). */
1383 const uint32_t history_end, const char *debug_label)
1384{
1385 dt_iop_buffer_dsc_t upstream_dsc = pipe->dev->image_storage.dsc;
1386 const gboolean previous_want_detail_mask = (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE);
1387
1388 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1389 {
1391 if(IS_NULL_PTR(piece)) continue;
1392
1394 piece->blendop_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1396 piece->global_mask_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1397 piece->upstream_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1398 piece->enabled = piece->module->default_enabled;
1399 piece->detail_mask = FALSE;
1400
1401 dt_iop_params_t *params = piece->module->default_params;
1402 dt_develop_blend_params_t *blend_params = piece->module->default_blendop_params;
1403 uint64_t history_param_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1404 gboolean found_history = FALSE;
1405
1406 for(GList *history = g_list_nth(history_list, history_end - 1);
1407 history;
1408 history = g_list_previous(history))
1409 {
1410 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1411 if(piece->module == hist->module)
1412 {
1413 piece->enabled = hist->enabled;
1414 params = hist->params;
1415 blend_params = hist->blend_params;
1416 history_param_hash = hist->hash; // the history entry key (module->hash)
1417 found_history = TRUE;
1418 break;
1419 }
1420 }
1421
1422 piece->detail_mask = blend_params && blend_params->details != 0.0f;
1423 if(!strcmp(piece->module->op, "detailmask"))
1425 _commit_piece_contract(pipe, piece, params, blend_params, &upstream_dsc);
1426
1427 // Tie this node to the history item it was just synchronized with, so the
1428 // cachelines it produces resolve to that history state through the node.
1429 // Use the history entry key (module->hash), NOT piece->hash: the latter folds
1430 // in runtime_data_hash() for some modules and then no longer matches history.
1431 if(dt_supervisor_active() && found_history)
1433 dt_supervisor_node_key(pipe->type, piece->module->op, piece->module->multi_priority),
1434 history_param_hash, DT_PIXELPIPE_CACHE_HASH_INVALID, piece->module->op,
1435 piece->module->multi_priority, piece->module->iop_order, pipe->type, pipe->imgid);
1436
1437 if(!found_history)
1438 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] info: committed default params for %s (%s) in pipe %s\n",
1439 piece->module->op, piece->module->multi_name, debug_label);
1440 }
1441
1443 if(previous_want_detail_mask != (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE))
1444 {
1445 GList *detailmask_node = _find_detailmask_node(pipe);
1446 if(detailmask_node)
1447 _sync_pipe_nodes_from_history_from_node(pipe, history_list, history_end, detailmask_node, debug_label);
1448 }
1449}
1450
1452 const uint32_t history_end, GList *start_node,
1453 const char *debug_label)
1454{
1455 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(start_node)) return;
1456
1457 dt_iop_buffer_dsc_t upstream_dsc = pipe->dev->image_storage.dsc;
1458 const gboolean previous_want_detail_mask = (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE);
1459 for(GList *node = g_list_first(pipe->nodes); node && node != start_node; node = g_list_next(node))
1460 {
1462 if(IS_NULL_PTR(piece)) continue;
1463
1464 upstream_dsc = piece->dsc_out;
1465 }
1466
1467 for(GList *nodes = start_node; nodes; nodes = g_list_next(nodes))
1468 {
1470 if(IS_NULL_PTR(piece)) continue;
1471
1473 piece->blendop_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1475 piece->global_mask_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1476 piece->upstream_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1477
1478 piece->enabled = piece->module->default_enabled;
1479 piece->detail_mask = FALSE;
1480
1481 dt_iop_params_t *params = piece->module->default_params;
1482 dt_develop_blend_params_t *blend_params = piece->module->default_blendop_params;
1483 uint64_t history_param_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1484 gboolean found_history = FALSE;
1485
1486 for(GList *history = g_list_nth(history_list, history_end - 1);
1487 history;
1488 history = g_list_previous(history))
1489 {
1490 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1491 if(piece->module == hist->module)
1492 {
1493 piece->enabled = hist->enabled;
1494 params = hist->params;
1495 blend_params = hist->blend_params;
1496 history_param_hash = hist->hash; // the history entry key (module->hash)
1497 found_history = TRUE;
1498 break;
1499 }
1500 }
1501
1502 piece->detail_mask = blend_params && blend_params->details != 0.0f;
1503 if(!strcmp(piece->module->op, "detailmask"))
1505 _commit_piece_contract(pipe, piece, params, blend_params, &upstream_dsc);
1506
1507 // Tie this node to the history item it was just synchronized with, so the
1508 // cachelines it produces resolve to that history state through the node.
1509 // Use the history entry key (module->hash), NOT piece->hash: the latter folds
1510 // in runtime_data_hash() for some modules and then no longer matches history.
1511 if(dt_supervisor_active() && found_history)
1513 dt_supervisor_node_key(pipe->type, piece->module->op, piece->module->multi_priority),
1514 history_param_hash, DT_PIXELPIPE_CACHE_HASH_INVALID, piece->module->op,
1515 piece->module->multi_priority, piece->module->iop_order, pipe->type, pipe->imgid);
1516
1517 if(!found_history)
1518 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] info: committed default params for %s (%s) in pipe %s\n",
1519 piece->module->op, piece->module->multi_name, debug_label);
1520 }
1521
1523 if(previous_want_detail_mask != (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE))
1524 {
1525 GList *detailmask_node = _find_detailmask_node(pipe);
1526 if(detailmask_node && detailmask_node != start_node)
1527 _sync_pipe_nodes_from_history_from_node(pipe, history_list, history_end, detailmask_node, debug_label);
1528 }
1529}
1530
1532{
1533 /* Traverse the pipeline node by node and compute the cumulative (global) hash of each module.
1534 * This hash takes into account the hashes of the previous modules and the size of the current ROI.
1535 * It is used to map pipeline cache states to current parameters.
1536 * It represents the state of internal modules params as well as their position in the pipe and their output size.
1537 * It is to be called at pipe init, not at runtime.
1538 */
1539
1540 // bernstein hash (djb2)
1542 gboolean passthrough_preview = FALSE;
1543
1544 // see dt_dev_pixelpipe_iop_t.upstream_hash: its own pass, and not derived from `hash` below,
1545 // which folds each piece's ROI
1547
1548 // Bypassing cache contaminates downstream modules, starting at the module requesting it.
1549 // Usecase : crop, clip, ashift, etc. that need the uncropped image ;
1550 // mask displays ; overexposed/clipping alerts and all other transient previews.
1551 gboolean bypass_cache = FALSE;
1552
1553 for(GList *node = g_list_first(pipe->nodes); node; node = g_list_next(node))
1554 {
1556 if(!piece->enabled) continue;
1557
1558 // Combine with the previous bypass states
1559 bypass_cache |= piece->module->bypass_cache;
1560 piece->bypass_cache = bypass_cache;
1561
1562 // Combine with the previous modules hashes
1563 uint64_t local_hash = piece->hash;
1564
1565 // Some GUI previews author their final display inside the active module,
1566 // then runtime forwards that exact buffer through later pass-through stages.
1567 // Keep the planned hash contract aligned with the published cacheline so the
1568 // GUI does not keep requesting a downstream output hash that will never exist.
1569 if(passthrough_preview
1570 && !(piece->module->operation_tags() & IOP_TAG_DISTORT)
1571 && (piece->dsc_in.bpp == piece->dsc_out.bpp)
1572 && !memcmp(&piece->roi_in, &piece->roi_out, sizeof(dt_iop_roi_t)))
1573 {
1574 piece->global_mask_hash = hash;
1575 piece->global_hash = hash;
1576 continue;
1577 }
1578
1579 // Panning and zooming change the ROI. Some GUI modes (crop in editing mode) too.
1580 // dt_dev_get_roi_in() should have run before
1581 //
1582 // These two folds are ALSO how the darkroom viewport reaches this hash, which is why the
1583 // pipe's latched dt_dev_roi_request_t (pixelpipe_hb.h) deliberately does NOT contribute its
1584 // generation here. Every field of that record reaches the ROI by construction, in
1585 // _update_darkroom_roi(): zoom and the fit scale become roi.scale (their product), the
1586 // centre becomes roi.x/roi.y, the box and the processed size become roi.width/height. Even
1587 // finalscale's piece->enabled -- the one consumer that reads the record rather than the ROI
1588 // -- is a function of that same product, and a disabled piece is skipped above, so a toggle
1589 // changes the chain anyway.
1590 //
1591 // Folding the generation on top would therefore add no discriminating power and would only
1592 // over-invalidate: a republication that advances the generation while producing identical
1593 // ROIs (a sub-pixel pan that rounds to the same roi.x, a box change the fminf clamp absorbs)
1594 // would rekey the whole FULL-pipe cache chain for pixels that are bit-identical. Content,
1595 // not version, is the right key here -- unlike mask_preview_settings_revision below, which
1596 // hashes a revision precisely because its state reaches no ROI at all.
1597 local_hash = dt_hash(local_hash, (const char *)&piece->roi_in, sizeof(dt_iop_roi_t));
1598 local_hash = dt_hash(local_hash, (const char *)&piece->roi_out, sizeof(dt_iop_roi_t));
1599
1600 local_hash = dt_hash(local_hash, (const char *)&piece->dsc_in, sizeof(dt_iop_buffer_dsc_t));
1601 local_hash = dt_hash(local_hash, (const char *)&piece->dsc_out, sizeof(dt_iop_buffer_dsc_t));
1602
1603 /* The pipe's drawn-mask rasterisation step changes the mask a module blends with, so it
1604 * changes that module's pixels. It belongs HERE, with request_mask_display below, and for
1605 * the same reason: it is global GUI state that no module's history carries, so
1606 * dt_iop_commit_params() would only fold it when that module's own parameters changed --
1607 * flipping the preference from auto to never would leave every cached line valid-looking
1608 * and stale.
1609 *
1610 * Only for a module that actually draws a mask: the raw stages upstream cannot carry one,
1611 * and rehashing them would cost cache misses for pixels that cannot move. */
1612 // From piece->blendop_data, NOT piece->module->blend_params. This function runs on the
1613 // darkroom worker thread, and blend_params belongs to the GUI thread -- the rule this file
1614 // states twice above ("never the live GUI module->params"). Reading it here dereferences a
1615 // module the GUI can be tearing down or reallocating underneath us, which is Sentry
1616 // 142904894: EXCEPTION_ACCESS_VIOLATION at this line, from resync_pipe_with_history() on
1617 // the pipeline thread. blendop_data is the per-piece copy dt_iop_commit_params() writes
1618 // from the history snapshot, which is what blend.c itself reads.
1619 const dt_develop_blend_params_t *const blend_data
1620 = (const dt_develop_blend_params_t *)piece->blendop_data;
1621 if(!IS_NULL_PTR(blend_data)
1622 && (blend_data->mask_mode & DEVELOP_MASK_SHAPE)
1623 && blend_data->mask_id > 0)
1624 local_hash = dt_hash(local_hash, (const char *)&pipe->mask_rasterization_step, sizeof(int));
1625
1626/*
1627 fprintf(stdout, "start->end : %-17s | ROI in: %4ix%-4i @%2.4f | ROI out: %4ix%-4i @%2.4f\n", piece->module->op,
1628 piece->buf_in.width, piece->buf_in.height, piece->buf_in.scale, piece->buf_out.width,
1629 piece->buf_out.height, piece->buf_out.scale);
1630 fprintf(stdout, "end->start : %-17s | ROI in: %4ix%-4i @%2.4f | ROI out: %4ix%-4i @%2.4f\n", piece->module->op,
1631 piece->roi_in.width, piece->roi_in.height, piece->planned_roi_in.scale,
1632 piece->roi_out.width, piece->roi_out.height, piece->roi_out.scale);
1633*/
1634 // Mask preview display doesn't re-commit params, so we need to keep that of it here
1635 // Too much GUI stuff interleaved with pipeline stuff...
1636 // Mask display applies only to main preview in darkroom.
1638 {
1639 local_hash = dt_hash(local_hash, (const char *)&piece->module->request_mask_display, sizeof(int));
1640
1641 /* Mask-preview appearance is global GUI state, not module history. Hash
1642 * its revision at the module producing the active preview so upstream
1643 * cache entries remain reusable while this module and gamma are rerun. */
1644 if(pipe->dev->gui_attached
1645 && piece->module == pipe->dev->gui_module
1646 && piece->module->request_mask_display != DT_DEV_PIXELPIPE_DISPLAY_NONE)
1647 {
1649 local_hash = dt_hash(local_hash, (const char *)&revision, sizeof(revision));
1650 }
1651
1652 // bypass_cache_variant (see dt_iop_module_t.bypass_cache_variant) is, like
1653 // request_mask_display above, module-owned GUI state whose real effect a module such as
1654 // retouch applies only to this main/FULL pipe -- never to the preview pipe, which always
1655 // renders as if none of these toggles were active. But the field itself lives on
1656 // the shared dt_iop_module_t, so it reads the same non-zero value from every pipe type. Left
1657 // ungated, a preview-pipe run with the same ROI/params (e.g. at zoom == fit) can compute the
1658 // exact same hash chain as the FULL pipe despite publishing different pixels, and either
1659 // pipe's cache entry then gets silently reused by the other via the cross-pipe "another pipe
1660 // already owns this hash" exact-hit path -- exactly like request_mask_display's own hazard.
1661 local_hash = dt_hash(local_hash, (const char *)&piece->module->bypass_cache_variant, sizeof(int));
1662 }
1663 else
1664 {
1665 const int zero = 0;
1666 local_hash = dt_hash(local_hash, (const char *)&zero, sizeof(int));
1667 local_hash = dt_hash(local_hash, (const char *)&zero, sizeof(int));
1668 }
1669
1670 // Keep track of distortion bypass in GUI. That may affect upstream modules in the stack,
1671 // while bypass_cache only affects downstream ones.
1672 // In theory, distortion bypass should already affect planned ROI in/out, but it depends whether
1673 // internal params are committed. Anyway, make it more reliable.
1675 local_hash = dt_hash(local_hash, (const char *)&bypass_distort, sizeof(int));
1676
1677 // If the cache bypass is on, the corresponding cache lines will be freed immediately after use,
1678 // we need to track that. It somewhat overlaps module->request_mask_display, but...
1679 local_hash = dt_hash(local_hash, (const char *)&piece->bypass_cache, sizeof(gboolean));
1680
1681 // Update global hash for this stage
1682 hash = dt_hash(hash, (const char *)&local_hash, sizeof(uint64_t));
1683
1685 {
1688 "[pixelpipe] global hash for %20s (%s) in pipe %s enabled=%d bypass_cache=%d with hash %lu\n",
1689 piece->module->op, piece->module->multi_name, type, piece->enabled, piece->bypass_cache,
1690 (long unsigned int)hash);
1691 dt_free(type);
1692 }
1693 // In case of drawn masks, we would need to account only for the distortions of previous modules.
1694 // Aka conditional to: if((piece->module->operation_tags() & IOP_TAG_DISTORT) == IOP_TAG_DISTORT)
1695 // But in case of parametric masks, they depend on previous modules parameters.
1696 // So, all in all, (parametric | drawn | raster) masking depends on everything :
1697 // - if masking on output, internal params + blendop params + all previous modules internal params + ROI size,
1698 // - if masking on input, blendop params + all previous modules internal params + ROI size
1699 // So we use all that ot once :
1700 piece->global_mask_hash = dt_hash(hash, (const char *)&piece->blendop_hash, sizeof(uint64_t));
1701
1702 // Finally, the output of the module also depends on the mask:
1703 hash = dt_hash(hash, (const char *)&piece->global_mask_hash, sizeof(uint64_t));
1704 piece->global_hash = hash;
1705
1707 && pipe->dev->gui_attached
1708 && (piece->module == pipe->dev->gui_module)
1709 && (piece->module->request_mask_display != DT_DEV_PIXELPIPE_DISPLAY_NONE))
1710 {
1711 passthrough_preview = TRUE;
1712 }
1713 }
1714
1715 // The pipe hash is the hash of its last module.
1717 pipe->bypass_cache = bypass_cache;
1718}
1719
1730/* Resync every node against a history snapshot. Runs outside history_mutex: everything it reads
1731 * is owned by `snap`, whose references keep the items alive and, through the writer's
1732 * copy-on-write gate, unmodified. */
1734 const char *caller_func)
1735{
1737 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch all modules with history for pipe %s called from %s\n", type, caller_func);
1738
1739 const uint32_t history_end = (uint32_t)snap->history_end;
1740 _sync_pipe_nodes_from_history(pipe, snap->items, history_end, type);
1741
1742 // Keep track of the last history item to have been synced
1743 GList *last_item = g_list_nth(snap->items, history_end - 1);
1744 if(last_item)
1745 {
1746 dt_dev_history_item_t *last_hist = (dt_dev_history_item_t *)last_item->data;
1747 pipe->last_history_hash = last_hist->hash;
1749 }
1750 else
1751 {
1754 }
1755
1756 dt_free(type);
1757}
1758
1759/* Public entry: export, snapshots and the focus overlay drive a throwaway pipe through this
1760 * without going through dt_dev_pixelpipe_change(), and none of them holds history_mutex. Take
1761 * the snapshot here, under a read lock held only for the copy. A caller that DOES already hold
1762 * the lock (as reader or writer) re-enters it on the same thread, which dt_pthread_rwlock_t
1763 * permits (see dtpthread.h). */
1775
1776/* Resync only the nodes touched since the last sync, against a history snapshot. Runs outside
1777 * history_mutex, same contract as _synch_all_snapshot(). */
1779{
1781
1782 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch top modules with history for pipe %s\n", type);
1783
1784 const uint32_t history_end = (uint32_t)snap->history_end;
1785 GList *last_item = g_list_nth(snap->items, history_end - 1);
1786 if(last_item)
1787 {
1788 // The identity marker is read once: the GUI thread may re-point it at a clone (see
1789 // dt_dev_history_cow_touch) while this walk runs, and the reference the pipe holds on it
1790 // is what makes comparing against a possibly-departed item safe.
1791 const dt_dev_history_item_t *last_synced
1793
1794 GList *first_item = NULL;
1795 for(GList *history = last_item; history; history = g_list_previous(history))
1796 {
1797 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1798 first_item = history;
1799
1800 if(hist->hash == pipe->last_history_hash || hist == last_synced)
1801 break;
1802 }
1803
1804 GList *fence_item = g_list_nth(snap->items, history_end);
1805 for(GList *history = first_item; history && history != fence_item; history = g_list_next(history))
1806 {
1807 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1808 if(!hist || !hist->module) continue;
1809 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] synch top history module `%s` (%s) for pipe %s\n",
1810 hist->module->op, hist->module->multi_name, type);
1811 for(GList *nodes = g_list_last(pipe->nodes); nodes; nodes = g_list_previous(nodes))
1812 {
1814 if(IS_NULL_PTR(piece) || piece->module != hist->module) continue;
1815
1816 _sync_pipe_nodes_from_history_from_node(pipe, snap->items, history_end, nodes, type);
1817 break;
1818 }
1819 }
1820
1821 dt_dev_history_item_t *last_hist = (dt_dev_history_item_t *)last_item->data;
1822 pipe->last_history_hash = last_hist->hash;
1824 }
1825 else
1826 {
1827 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch top history module missing error for pipe %s\n", type);
1830 }
1831
1832 dt_free(type);
1833}
1834
1846
1847// Modules without history need to be resynced unconditionnally with their internal params
1848// because some of them are self-enabled/disabled from commit_params() methods
1850{
1851 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1852 {
1854 if(IS_NULL_PTR(piece) || IS_NULL_PTR(piece->module)) continue;
1855 dt_iop_module_t *module = piece->module;
1856 if(module->flags() & IOP_FLAGS_NO_HISTORY_STACK)
1857 dt_iop_commit_params(module, module->default_params, module->default_blendop_params, pipe, piece);
1858 }
1859}
1860
1862{
1863 dt_times_t start;
1864 dt_get_times(&start);
1865
1876
1878 char *status_str = g_strdup_printf("%s%s%s%s%s%s%s%s",
1879 (status & DT_DEV_PIPE_UNCHANGED) ? "UNCHANGED " : "",
1880 (status & DT_DEV_PIPE_REMOVE) ? "REMOVE " : "",
1881 (status & DT_DEV_PIPE_TOP_CHANGED) ? "TOP_CHANGED " : "",
1882 (status & DT_DEV_PIPE_SYNCH) ? "SYNCH " : "",
1883 (status & DT_DEV_PIPE_ZOOMED) ? "ZOOMED " : "",
1884 (status & DT_DEV_PIPE_CACHE_REQUEST) ? "CACHE_REQUEST " : "",
1885 (status & DT_DEV_PIPE_REENTRY) ? "REENTRY " : "",
1886 (status & DT_DEV_PIPE_SWITCHED) ? "SWITCHED " : "");
1887
1888 dt_print(DT_DEBUG_DEV, "[dt_dev_pixelpipe_change] pipeline state changing for pipe %s, flag %s\n",
1889 type, status_str);
1890
1891 dt_free(status_str);
1892
1893 // mask display off as a starting point
1895 pipe->bypass_blendif = 0;
1896
1897 // Only raised here: whatever changes next, the run that renders the switched-to state must
1898 // leave the previous state's outputs alone. dt_dev_pixelpipe_process() lowers it once it did.
1900
1901 /* Zoom/pan only replans ROI and hashes, it does not replay history sync.
1902 * Rebuild the aggregate detail-mask demand from the already synchronized
1903 * pieces in that case, otherwise the pipe forgets that the hidden
1904 * `detailmask` stage is needed before the next processing run starts. */
1907 else
1909
1910 // Snapshot history under the read lock, then resync against the snapshot with the lock
1911 // RELEASED. The resync is O(nodes x history) and runs every module's commit_params() --
1912 // tens of ms routinely, over 200 ms under mask-heavy history -- and it used to hold this
1913 // lock for all of it. The GUI thread needs the writer side of the same lock on every
1914 // commit (each slider tick, each throttled mask-drag commit), and glibc's writer-preferring
1915 // policy then also blocks every NEW reader behind the queued writer: one slow resync stalled
1916 // the whole application until it finished. The snapshot holds a reference on each item, and
1917 // the writer's copy-on-write gate (dt_dev_history_cow_touch) clones a shared item before
1918 // touching it, so what this resync reads can neither move nor change under it.
1919 //
1920 // A call chain that already holds history_mutex as writer on this same thread can re-enter
1921 // here (e.g. history-commit paths that resync a pipe while still holding the write lock);
1922 // dt_pthread_rwlock_rdlock is same-thread-recursive for exactly that (see dtpthread.h).
1923 const double _history_mutex_hold_start = dt_get_wtime();
1928 const double _history_mutex_hold_ms = (dt_get_wtime() - _history_mutex_hold_start) * 1000.0;
1929 if(_history_mutex_hold_ms > 1.0)
1931 "[dt_dev_pixelpipe_change] tid %lu pipe %s held history_mutex for %.2f ms (status 0x%x)\n",
1932 (unsigned long)pthread_self(), type, _history_mutex_hold_ms, (unsigned)status);
1933
1934 // The realtime in-place path settles the format contract and the global hash itself (it must,
1935 // since that hash is cumulative over enabled nodes); the other paths defer it to the
1936 // unconditional pass below.
1937 gboolean formats_propagated = FALSE;
1938
1939 // case DT_DEV_PIPE_UNCHANGED: case DT_DEV_PIPE_ZOOMED:
1941 {
1942 // modules have been added in between or removed. need to rebuild the whole pipeline.
1946 _synch_all_snapshot(pipe, &snap, __FUNCTION__);
1947 }
1948 else if(status & DT_DEV_PIPE_SYNCH)
1949 {
1950 // pipeline topology remains intact, only change all params.
1952 _synch_all_snapshot(pipe, &snap, __FUNCTION__);
1953 }
1955 {
1956 // only top history item(s) changed, or a focused module published transient/realtime params
1957 if(_sync_focused_in_place(pipe, &snap))
1958 {
1959 formats_propagated = TRUE;
1960 }
1961 else
1962 {
1964 _synch_top_snapshot(pipe, &snap);
1965 }
1966 }
1967 else // DT_DEV_PIPE_ZOOMED DT_DEV_PIPE_CACHE_REQUEST DT_DEV_PIPE_REENTRY
1968 {
1969 // Finalscale will need to self-enable/disable depending on zoom level
1971 }
1972 // The hash the SNAPSHOT carried, not the live one: a commit may have landed since the lock
1973 // was released, and this pipe is synced to what it just resynced against, nothing newer. The
1974 // worker loop compares this against the live hash and re-flags the pipe if they differ.
1977 const double _resync_ms = (dt_get_wtime() - _history_mutex_hold_start) * 1000.0;
1978 if(_resync_ms > 1.0)
1980 "[dt_dev_pixelpipe_change] tid %lu pipe %s resynced from snapshot in %.2f ms, lock-free (status 0x%x)\n",
1981 (unsigned long)pthread_self(), type, _resync_ms, (unsigned)status);
1982
1983 // Re-establish the buffer-format contract of the whole chain once, after whichever sync path
1984 // (full, top-only or realtime-in-place) committed params. This is the single authoritative,
1985 // direction-independent place where per-node dsc_in/dsc_out are settled and incompatible
1986 // modules are disabled (issue #733), so the OpenCL cache policy and ROI planning below see a
1987 // consistent contract. The realtime path already did this before computing its global hash.
1988 if(!formats_propagated)
1990
1992
1993 // Update theoritical final scale based on distorting modules
1994 // This also writes piece->buf_in/out for each pipe->nodes piece,
1995 // so it's not nearly a matter of getting processed_width/height
1998
1999 dt_show_times_f(&start, "[dev_pixelpipe] pipeline resync with history", "for pipe %s", type);
2000 dt_free(type);
2001}
2002
2010
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:970
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:975
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:654
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:526
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:2184
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
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:320
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:2094
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
dt_imageio_retval_t status
Definition mipmap_cache.c:8
uint32_t height
Definition mipmap_cache.c:1
int dt_opencl_is_inited(void)
Definition opencl.c:3328
#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
const char * dt_pixelpipe_cache_set_current_image(const char *image)
Set the image the current module runs on, for the message that reports a refused allocation (thread-l...
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.
const char * dt_pixelpipe_cache_set_current_module(const char *module)
Set the current module name for cache diagnostics (thread-local).
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
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:619
@ 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.
dt_iop_buffer_dsc_t dsc_in
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:428
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:395
int32_t id
Definition image.h:410
uint32_t filters
Definition format.h:89
unsigned int channels
Definition format.h:83
uint8_t xtrans[6][6]
Definition format.h:99
dt_iop_buffer_type_t datatype
Definition format.h:85
int32_t params_size
Definition imageop.h:335
dt_dev_operation_t op
Definition imageop.h:259
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)