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"
35#include "develop/pixelpipe.h"
37#include "develop/supervisor.h"
38#include "develop/blend.h"
39#include "control/control.h"
40#include "control/signal.h"
41#include <stdint.h>
42#include <stdlib.h>
43
44static void _sync_pipe_nodes_from_history_from_node(dt_dev_pixelpipe_t *pipe, GList *history_list,
45 const uint32_t history_end, GList *start_node,
46 const char *debug_label);
47// dt_dev_pixelpipe_propagate_formats() is the authoritative forward buffer-format pass; it is
48// declared in dev_pixelpipe.h. It must run before any dt_pixelpipe_get_global_hash() because the
49// auto-disable it performs feeds the cumulative global hash (see its definition).
50static void _dt_dev_pixelpipe_cache_wait_ready_callback(gpointer instance, const guint64 hash,
51 const guint64 producer_node_key,
52 gpointer user_data);
53
54/* What stays here is the TRANSPORT and the PRESENTATION of the wait queue, not the queue:
55 * whether we are currently subscribed to the ready signal, and whether the busy cursor is up.
56 * The queue, its lock, its counters and its matching rule are cache state and live in
57 * caches/pixelpipe_cache_wait.{h,c}.
58 *
59 * Both flags are GUI-thread-only, like every accessor below: DT_SIGNAL_CACHELINE_READY is
60 * declared asynchronous (control/signal.c), so its handler is marshalled to the main loop, and
61 * every enqueue site is an expose, a widget callback or an idle. That asynchrony is load-bearing
62 * and must not be optimised away -- see the file comment in caches/pixelpipe_cache_wait.h. */
63static gboolean _cache_wait_connected = FALSE;
65
66static gboolean _cache_wait_cursor_progress(gpointer user_data)
67{
69 return G_SOURCE_REMOVE;
70
72 return G_SOURCE_REMOVE;
73}
74
75static gboolean _cache_wait_cursor_restore(gpointer user_data)
76{
78 return G_SOURCE_REMOVE;
79
82 return G_SOURCE_REMOVE;
83}
84
85static void _dump_one_pending(const dt_dev_pixelpipe_cache_wait_t *wait, const int64_t age_us,
86 gpointer user_data)
87{
88 (void)user_data;
90 "[cache-wait] pending id=%" PRIu64 " owner=%s hash=%" PRIu64
91 " target=%s connected=%d age_ms=%" PRId64 "\n",
92 wait->request_id,
93 !IS_NULL_PTR(wait->owner_tag) ? wait->owner_tag : "(unknown)",
94 wait->hash,
95 !IS_NULL_PTR(wait->module) ? wait->module_op : "backbuf",
96 wait->connected,
97 MAX(age_us / 1000, 0));
98}
99
101{
102 const char *context = !IS_NULL_PTR(reason) ? reason : "unspecified";
103
104 uint64_t queued = 0, served = 0, cancelled = 0, immediate = 0, misses = 0;
105 dt_pixelpipe_cache_wait_get_stats(&queued, &served, &cancelled, &immediate, &misses);
106
108 "[cache-wait] dump reason=%s pending=%u queued=%" PRIu64 " served=%" PRIu64
109 " cancelled=%" PRIu64 " immediate=%" PRIu64 " misses=%" PRIu64 "\n",
110 context, dt_pixelpipe_cache_wait_pending_count(), queued, served, cancelled,
111 immediate, misses);
112
114}
115
117 const dt_iop_module_t *module)
118{
119 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(module)) return FALSE;
120 if(pipe->type != DT_DEV_PIXELPIPE_PREVIEW) return FALSE;
121 if(dt_dev_pixelpipe_get_realtime(pipe)) return FALSE;
122 if(!pipe->gui_observable_source) return FALSE;
123
124 return !strcmp(module->op, "initialscale") || !strcmp(module->op, "colorout");
125}
126
128 const dt_iop_module_t *module)
129{
130 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(module)) return FALSE;
131 if(pipe->type != DT_DEV_PIXELPIPE_PREVIEW) return FALSE;
132 if(dt_dev_pixelpipe_get_realtime(pipe)) return FALSE;
133 if(!pipe->gui_observable_source) return FALSE;
134
135 return !strcmp(module->op, "gamma");
136}
137
151 const dt_iop_module_t *module,
152 const dt_iop_module_t *picker_module)
153{
154 if(IS_NULL_PTR(picker_module) || IS_NULL_PTR(module)) return FALSE;
155 if(module == picker_module) return TRUE;
156
157 const dt_dev_pixelpipe_iop_t *const piece = dt_dev_pixelpipe_get_module_piece(pipe, picker_module);
158 const dt_dev_pixelpipe_iop_t *const previous_piece = dt_dev_pixelpipe_get_prev_enabled_piece(pipe, piece);
159
160 return !IS_NULL_PTR(previous_piece) && previous_piece->module == module;
161}
162
163static gchar *_get_debug_pipe_name(const dt_dev_pixelpipe_t *pipe, const dt_develop_t *dev)
164{
165 (void)dev;
166 return g_strdup(dt_pixelpipe_get_pipe_name(!IS_NULL_PTR(pipe) ? pipe->type : DT_DEV_PIXELPIPE_NONE));
167}
168
170{
171 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
172 {
174 if(!IS_NULL_PTR(piece) && !strcmp(piece->module->op, "detailmask")) return nodes;
175 }
176
177 return NULL;
178}
179
181{
182 dt_dev_pixelpipe_iop_t *detailmask_piece = NULL;
184
185 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
186 {
188 if(IS_NULL_PTR(piece)) continue;
189
190 if(!strcmp(piece->module->op, "detailmask")) detailmask_piece = piece;
191 if(piece->detail_mask)
192 {
194 break;
195 }
196 }
197
198 if(!IS_NULL_PTR(detailmask_piece))
199 {
200 const gboolean enabled = (pipe->want_detail_mask == DT_DEV_DETAIL_MASK_ENABLED)
201 && (detailmask_piece->dsc_in.channels == 4)
202 && (detailmask_piece->dsc_in.datatype == TYPE_FLOAT)
203 && (detailmask_piece->dsc_in.cst == IOP_CS_RGB);
204 detailmask_piece->enabled = enabled;
205 detailmask_piece->process_tiling_ready = !enabled;
206 if(!IS_NULL_PTR(detailmask_piece->data)) *((int *)detailmask_piece->data) = enabled ? 1 : 0;
207 }
208}
209
216
234/* Record the history item a resync last synchronized to, holding a reference on it.
235 *
236 * The resync runs against a snapshot outside history_mutex, so by the time it writes this the
237 * item may already have been cloned out of dev->history by the GUI thread's copy-on-write.
238 * Two things follow. The reference keeps the item alive for as long as the pipe names it, so an
239 * identity compare on the pointer can never hit a recycled address. The atomic exchange is
240 * what makes the write safe against dt_dev_history_cow_touch() re-pointing the same slot at the
241 * clone from the GUI thread: whichever side loses the race still ends up releasing exactly the
242 * reference it took, never the other side's. */
250
251/* `snap` is the caller's history snapshot (dt_dev_history_snapshot_take): this runs outside
252 * history_mutex and must not touch dev->history or dev->history_end directly. */
254{
255 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(pipe->dev) || IS_NULL_PTR(pipe->dev->gui_module)) return FALSE;
256 dt_develop_t *dev = pipe->dev;
257 dt_iop_module_t *focus = dev->gui_module;
258
259 const gboolean transient = dt_dev_transient_params_active(dev, focus);
260 const gboolean realtime = dt_dev_pixelpipe_get_realtime(pipe);
261 if(!transient && !realtime) return FALSE;
262
263 const uint32_t history_end = (uint32_t)snap->history_end;
264 if(history_end == 0) return FALSE;
265
266 // Enable/blend state comes from the focused module's history item (transient edits are params-only).
267 dt_dev_history_item_t *hist = dt_dev_history_get_last_item_by_module(snap->items, focus, history_end);
268 if(IS_NULL_PTR(hist) || IS_NULL_PTR(hist->module) || IS_NULL_PTR(hist->params)) return FALSE;
269
271 if(IS_NULL_PTR(piece)) return FALSE;
272
273 // Resolve params: thread-safe transient snapshot when published, else the history snapshot. A copy is
274 // taken so the slot lock is not held across commit_params(); never the live GUI module->params.
275 dt_iop_params_t *params = hist->params;
276 void *tbuf = NULL;
277 if(transient && focus->params_size > 0)
278 {
279 tbuf = g_malloc0(focus->params_size);
280 if(!IS_NULL_PTR(tbuf)
281 && dt_dev_transient_params_get(dev, focus, tbuf, (size_t)focus->params_size, NULL, 0, NULL))
282 params = (dt_iop_params_t *)tbuf;
283 else
284 dt_free(tbuf);
285 }
286
287 const gboolean previous_want_detail_mask = (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE);
288 piece->enabled = hist->enabled;
289 piece->detail_mask = !IS_NULL_PTR(hist->blend_params) && hist->blend_params->details != 0.0f;
290 dt_iop_commit_params(focus, params, hist->blend_params, pipe, piece);
291 dt_free(tbuf);
293 if(previous_want_detail_mask != (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE)) return FALSE;
294
295 // The global hash is cumulative and skips disabled nodes, so the format contract (which may
296 // disable a node on an incompatible input) must be settled first.
299
300 // Only advance the synch_top fence when the focused module is the newest history item (the realtime
301 // drawlayer case, where each heartbeat appended a top item). For a transient edit of a non-top module
302 // no history item changed, so the fence must stay where the last real history sync left it.
303 GList *last_item = g_list_nth(snap->items, history_end - 1);
304 if(last_item && (dt_dev_history_item_t *)last_item->data == hist)
305 {
306 pipe->last_history_hash = hist->hash;
307 _pipe_set_last_history_item(pipe, hist);
308 }
309 return TRUE;
310}
311
344{
345 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(pipe->nodes)) return;
346
347 // The scopes/global histogram are not refreshed while a realtime stroke is in progress (the preview
348 // pipe is paused), so do not force any module to publish a host copy of its output just to feed them.
349 // This drops the per-frame RAM copy of `colorout` (and the histogram input/module caches) during
350 // realtime drawing, where it is pure overhead.
351 const gboolean realtime = dt_dev_pixelpipe_get_realtime(pipe);
352
353 // ONE read each, for the whole walk. Both belong to the GUI thread, and both were read LIVE at
354 // every node: a focus change or a picker toggle landing mid-walk gave the early nodes one
355 // answer and the later nodes another, so the sealed pipe described no single moment. Sampling
356 // them here does not make the pipeline the owner of these facts -- having the GUI publish them
357 // instead needs the layer flip, since gui/ may not reach up into develop/ today -- but it does
358 // make one resync describe one moment.
359 const dt_iop_module_t *const focused_module = pipe->dev->gui_module;
360 const dt_iop_module_t *const picker_module = pipe->dev->color_picker.module;
361
362 gboolean current_output_must_cache_host = TRUE;
363
364 for(GList *pieces = g_list_last(pipe->nodes); pieces; pieces = g_list_previous(pieces))
365 {
366 dt_dev_pixelpipe_iop_t *piece = pieces->data;
367 dt_iop_module_t *module = !IS_NULL_PTR(piece) ? piece->module : NULL;
368 if(IS_NULL_PTR(piece) || IS_NULL_PTR(module) || !piece->enabled) continue;
369
370 gboolean supports_opencl = FALSE;
371#ifdef HAVE_OPENCL
372 supports_opencl = dt_opencl_is_inited() && piece->process_cl_ready && module->process_cl;
373#endif
374
375 gchar *string = g_strdup_printf("/plugins/%s/cache", module->op);
376 if(!dt_conf_key_exists(string) || !dt_conf_key_not_empty(string))
378
379 const gboolean authored_cache = piece->cache_output_on_ram;
380 const gboolean user_requested_cache = dt_conf_get_bool(string);
381 dt_free(string);
382
383 const gboolean color_picker_on = _module_feeds_color_picker(pipe, module, picker_module);
384 const gboolean global_hist_output_on
385 = !realtime && _module_requires_global_histogram_output_cache(pipe, module);
386 const gboolean global_hist_input_on
387 = !realtime && _module_requires_global_histogram_input_cache(pipe, module);
388 const gboolean module_hist_on
389 = !realtime
390 && (pipe->type == DT_DEV_PIXELPIPE_PREVIEW
391 && pipe->gui_observable_source
393 && (piece->request_histogram & DT_REQUEST_ON));
394 const gboolean active_in_gui
396 && focused_module == module;
397
398 const gboolean has_autoset = pipe->autoset && !IS_NULL_PTR(module->autoset);
399
400 // The decision is a pure function of these nine facts (develop/pipe_cache_policy.h), pinned
401 // by tests/unittests/test_pipe_cache_policy.c. What stays here is GATHERING them, which
402 // is the part that needs a pipe and a dev to look at.
403 const dt_dev_pipe_cache_policy_inputs_t inputs = { .authored_cache = authored_cache,
404 .user_requested_cache = user_requested_cache,
405 .supports_opencl = supports_opencl,
406 .color_picker_on = color_picker_on,
407 .global_hist_output_on = global_hist_output_on,
408 .global_hist_input_on = global_hist_input_on,
409 .module_hist_on = module_hist_on,
410 .active_in_gui = active_in_gui,
411 .has_autoset = has_autoset };
412
414 = dt_dev_pipe_cache_policy_decide(&inputs, current_output_must_cache_host,
415 &current_output_must_cache_host);
416 }
417}
418
420{
421 if(IS_NULL_PTR(dev) || !dev->gui_attached) return;
424 /* Rebuilt here, and deliberately not PUBLISHED here: these are the flag-raising paths, and
425 * nothing may observe geometry before the publication that belongs with it -- the ordering
426 * rule from the fix for #1157. */
428}
429
435
442
449
455
462
469
476
478{
479 if(IS_NULL_PTR(dev) || !dev->gui_attached) return;
480 /* Zoom/pan updates must run through the normal validity/ROI flow.
481 * If a module left the main pipe in realtime mode, force it back to
482 * standard mode here so zoom changes cannot get visually stuck on a
483 * best-effort backbuffer policy. */
487}
488
495
497{
498 if (IS_NULL_PTR(dev) || !dev->gui_attached) return;
499 /* Entering a zoom/pan change always exits realtime rendering policy.
500 * Realtime is stroke-scoped and should never control darkroom navigation. */
502 // Slightly different logic: killswitch ASAP,
503 // then redraw UI ASAP for feedback,
504 // finally flag the pipe as dirty for later recompute.
505 // Remember GUI responsiveness is paramount, since a laggy UI
506 // will make user repeat their order for lack of feedback,
507 // meaning relaunching a pipe recompute, meaning working more
508 // for the same contract.
511 gtk_widget_queue_draw(dt_gui_center_widget());
514}
515
517{
518 return (dev // don't segfault
519 && dev->gui_attached // don't run on background/export pipes
520 && dev->gui_module // don't segfault
521 && dev->gui_module != current_module
522 // current_module is not the active one (capturing edit mode)
523 && dev->gui_module->operation_tags_filter() & current_module->operation_tags())
524 // current_module does operation(s) that active module doesn't want
526 // cache bypass is our hint that the active module is in "editing" mode
527}
528
529
531 const int width_in, const int height_in,
532 int *width, int *height)
533{
534 dt_iop_roi_t roi_in = (dt_iop_roi_t){ 0, 0, width_in, height_in, 1.0 };
535 dt_iop_roi_t roi_out = roi_in;
536 gchar *pipe_name = NULL;
538 pipe_name = _get_debug_pipe_name(pipe, pipe->dev);
539
540 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
541 {
543 dt_iop_module_t *module = piece->module;
544
545 piece->buf_in = roi_in;
546
547 // If in GUI and using a module that needs a full, undistorterted image,
548 // we need to shutdown temporarily any module distorting the image.
550 piece->enabled = FALSE;
551
552 // If module is disabled, modify_roi_out() is a no-op
553 if(piece->enabled)
554 module->modify_roi_out(module, pipe, piece, &roi_out, &roi_in);
555 else
556 roi_out = roi_in;
557
558 // Forward ROI planning answers "what output rectangle does this module
559 // produce from the previous one ?". Logging the tuple here makes each
560 // module-local geometry change visible on `-d pipe`.
561 if(piece->enabled && (dt_get_debug_flags() & DT_DEBUG_PIPE))
563 "[roi-out] pipe=%-15s module=%-18s enabled=%d in =(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)"
564 " out=(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)\n",
565 pipe_name, module->op, piece->enabled,
566 roi_in.x, roi_in.y, roi_in.width, roi_in.height, roi_in.scale,
567 roi_out.x, roi_out.y, roi_out.width, roi_out.height, roi_out.scale);
568
569 piece->buf_out = roi_out;
570 roi_in = roi_out;
571 }
572
573 if(pipe_name) dt_free(pipe_name);
574 *width = roi_out.width;
575 *height = roi_out.height;
576}
577
579{
580 // while module->modify_roi_out describes how the current module will change the size of
581 // the output buffer depending on its parameters (pretty intuitive),
582 // module->modify_roi_in describes "how much material" the current module needs from the previous one,
583 // because some modules (lens correction) need a padding on their input.
584 // The tricky part is therefore that the effect of the current module->modify_roi_in() needs to be repercuted
585 // upstream in the pipeline for proper pipeline cache invalidation, so we need to browse the pipeline
586 // backwards.
587
588 // This function does not support NULL pipes or ad-hoc temp nodes.
589
590 dt_iop_roi_t roi_out_temp = roi_out;
591 dt_iop_roi_t roi_in;
592 gchar *pipe_name = NULL;
594 pipe_name = _get_debug_pipe_name(pipe, pipe->dev);
595 for(GList *nodes = g_list_last(pipe->nodes); nodes; nodes = g_list_previous(nodes))
596 {
598 dt_iop_module_t *module = piece->module;
599
600 piece->roi_out = roi_out_temp;
601
602 // If in GUI and using a module that needs a full, undistorterted image,
603 // we need to shutdown temporarily any module distorting the image.
605 piece->enabled = FALSE;
606
607 // If module is disabled, modify_roi_in() is a no-op
608 if(piece->enabled)
609 module->modify_roi_in(module, pipe, piece, &roi_out_temp, &roi_in);
610 else
611 roi_in = roi_out_temp;
612
613 // Backward ROI planning answers "how much input rectangle does this
614 // module need from upstream to deliver the requested downstream output ?".
615 // Logging that request before and after modify_roi_in() makes ROI growth
616 // and padding traceable module-by-module on `-d pipe`.
617 if(piece->enabled && (dt_get_debug_flags() & DT_DEBUG_PIPE))
619 "[roi-in ] pipe=%-15s module=%-18s enabled=%d out=(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)"
620 " in=(x=%5d y=%5d w=%5d h=%5d scale=%2.2f)\n",
621 pipe_name, module->op, piece->enabled,
622 roi_out_temp.x, roi_out_temp.y, roi_out_temp.width, roi_out_temp.height, roi_out_temp.scale,
623 roi_in.x, roi_in.y, roi_in.width, roi_in.height, roi_in.scale);
624
625 piece->roi_in = roi_in;
626 roi_out_temp = roi_in;
627 }
628
629 if(pipe_name) dt_free(pipe_name);
630
631 /* ROI planning runs backwards, but rawprepare seals the effective Bayer/X-Trans phase only once
632 * the real input crop is known. Forward that authored RAW descriptor now so the downstream RAW
633 * modules process with the same CFA layout that rawprepare just computed for this run. */
634 dt_iop_buffer_dsc_t upstream_dsc = pipe->dev->image_storage.dsc;
635 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
636 {
638
639 if(piece->dsc_in.cst == IOP_CS_RAW && piece->dsc_in.channels == 1)
640 {
641 piece->dsc_in.filters = upstream_dsc.filters;
642 memcpy(piece->dsc_in.xtrans, upstream_dsc.xtrans, sizeof(piece->dsc_in.xtrans));
643 }
644
645 if(piece->dsc_out.cst == IOP_CS_RAW && piece->dsc_out.channels == 1
646 && (!piece->enabled || strcmp(piece->module->op, "rawprepare")))
647 {
648 piece->dsc_out.filters = piece->dsc_in.filters;
649 memcpy(piece->dsc_out.xtrans, piece->dsc_in.xtrans, sizeof(piece->dsc_out.xtrans));
650 }
651
652 upstream_dsc = piece->dsc_out;
653 }
654
655}
656
658{
659 // Start with a hash that is unique, image-wise.
660 return dt_hash(5381, (const char *)&pipe->dev->image_storage.filename, DT_MAX_FILENAME_LEN);
661}
662
664 const dt_iop_roi_t roi_out, const int pos)
665{
666 // to be called at runtime, not at pipe init.
667
668 // Only at the first step of pipe, we don't have a module because we init the base buffer.
669 if(!IS_NULL_PTR(piece))
670 return piece->global_hash;
671 else
672 {
673 // This is used for the first step of the pipe, before modules, when initing base buffer
674 // We need to take care of the ROI manually
675 uint64_t hash = _default_pipe_hash(pipe);
676 hash = dt_hash(hash, (const char *)&roi_out, sizeof(dt_iop_roi_t));
677 return dt_hash(hash, (const char *)&pos, sizeof(int));
678 }
679}
680
682 const dt_iop_module_t *module)
683{
684 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(module)) return NULL;
685
686 for(GList *node = g_list_first(pipe->nodes); node; node = g_list_next(node))
687 {
688 dt_dev_pixelpipe_iop_t *const piece = node->data;
689 if(piece && piece->enabled && piece->module == module)
690 return piece;
691 }
692
693 return NULL;
694}
695
697 const dt_dev_pixelpipe_iop_t *piece)
698{
699 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(piece)) return NULL;
700
701 GList *node = g_list_find(pipe->nodes, (gpointer)piece);
702 if(IS_NULL_PTR(node)) return NULL;
703
704 for(node = g_list_previous(node); node; node = g_list_previous(node))
705 {
706 dt_dev_pixelpipe_iop_t *const previous = node->data;
707 if(previous && previous->enabled)
708 return previous;
709 }
710
711 return NULL;
712}
713
714static gboolean _peek_gui(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece,
715 void **data, dt_pixel_cache_entry_t **cache_entry,
718 gpointer restart_data, const gboolean retain)
719{
720 if(!IS_NULL_PTR(data)) *data = NULL;
721 if(!IS_NULL_PTR(cache_entry)) *cache_entry = NULL;
722 if(IS_NULL_PTR(pipe)) return FALSE;
723
724 // Module-output cache requests are not satisfiable when the target piece
725 // itself bypasses cache retention. Re-queueing those from GUI redraws would
726 // keep transient edit modes in a self-feeding recompute loop.
727 // Upstream targets (for example color-picker input sampling) stay allowed even if a
728 // downstream piece enabled bypass mode.
729 if(!IS_NULL_PTR(piece)
730 && (dt_dev_pixelpipe_get_realtime(pipe) || pipe->no_cache || piece->bypass_cache))
731 return FALSE;
732
733 const uint64_t hash = !IS_NULL_PTR(piece) ? piece->global_hash : dt_dev_pixelpipe_get_hash(pipe);
734
735 // For the final backbuffer (piece == NULL), GUI consumers display the last *published* frame,
736 // identified by pipe->backbuf.hash. The pipeline plans the next frame ahead of publishing it, so
737 // pipe->hash (the planned final hash) runs ahead of the backbuffer while a recompute is in flight.
738 // This is the normal steady state of realtime drawing: every heartbeat plans a new frame. Peeking
739 // the planned hash would then miss the perfectly valid published frame, the darkroom main-surface
740 // lock would fail, and the view would drop to the paused preview pipe (which lacks the in-progress
741 // stroke) — the flicker between "drawing applied" and "original". So look up the published backbuf
742 // hash for display, and keep the planned hash only to drive recompute on a genuine miss below.
743 uint64_t display_hash = hash;
744 if(IS_NULL_PTR(piece))
745 {
746 const uint64_t backbuf_hash = dt_dev_backbuf_get_hash(&pipe->backbuf);
747 if(backbuf_hash != DT_PIXELPIPE_CACHE_HASH_INVALID) display_hash = backbuf_hash;
748 }
749
750 void *buffer = NULL;
751 dt_pixel_cache_entry_t *entry = NULL;
752 // GUI consumers run on the CPU and never own the OpenCL device lock, so we pass preferred_devid = -1:
753 // dt_dev_pixelpipe_cache_peek() then returns host-resident cachelines directly but refuses to
754 // materialize device-only ones from the GPU (which would enqueue hidden GPU work from the GUI thread,
755 // racing whichever pipeline thread currently owns the device — the clReleaseEvent crash). A device-only
756 // entry is reported as a miss, so the request below waits for the pipeline to publish a host copy
757 // (modules whose output the GUI samples, e.g. initialscale, already cache to RAM).
758 //
759 // `retain` picks between the two ownership models a GUI consumer can have on a cacheline, and
760 // they are not interchangeable. A consumer that merely BORROWS the currently published
761 // backbuffer (the darkroom surface, the navigation thumbnail, the scopes) is covered by the
762 // keepalive reference its publisher holds, and must not take one of its own. A consumer that
763 // samples an INTERMEDIATE module cacheline (color picker, module histograms, autoset,
764 // colorequal) has no such cover: those entries sit at refcount 0 between renders and the
765 // pipeline may evict one at any moment. Looking one up and taking the reference afterwards
766 // leaves a window in which it is freed under the caller, so that acquisition happens under one
767 // hold of the cache lock -- the hazard `dt_dev_pixelpipe_process_rec()` already avoids for its
768 // own exact-hit lookups, for exactly the same reason.
769 const gboolean got = display_hash != DT_PIXELPIPE_CACHE_HASH_INVALID
770 && (retain ? dt_dev_pixelpipe_cache_ref_host_entry_by_hash(display_hash, &buffer, &entry)
771 : dt_dev_pixelpipe_cache_peek(display_hash, &buffer, &entry, -1, NULL));
772 if(got && !IS_NULL_PTR(buffer) && !IS_NULL_PTR(entry))
773 {
775 dt_dev_pixelpipe_cache_wait_cleanup(wait, "peek-gui-immediate-hit");
776 if(!IS_NULL_PTR(data)) *data = buffer;
777 if(!IS_NULL_PTR(cache_entry)) *cache_entry = entry;
778 return TRUE;
779 }
780
781 /* A retained entry the caller cannot be handed back (it asked for neither the data nor the
782 * entry) would leak its reference: nobody would ever release it. */
783 if(retain && got && !IS_NULL_PTR(entry))
784 {
786 entry = NULL;
787 buffer = NULL;
788 }
789
791
792 gboolean request_cacheline = TRUE;
793 if(!IS_NULL_PTR(wait) && !IS_NULL_PTR(restart) && hash != DT_PIXELPIPE_CACHE_HASH_INVALID)
794 {
795 const gboolean changed_target = !wait->connected
796 || wait->pipe != pipe
797 || wait->module != (!IS_NULL_PTR(piece) ? piece->module : NULL)
798 || wait->hash != hash
799 || wait->restart != restart;
800 if(changed_target)
801 {
802 gboolean activate_wait_cursor = FALSE;
803 dt_dev_pixelpipe_cache_wait_cleanup(wait, "peek-gui-target-changed");
804 wait->pipe = pipe;
805 wait->module = !IS_NULL_PTR(piece) ? piece->module : NULL;
806
807 // Copy the labels now, while both objects are certainly alive. The queue never reads
808 // through the two identity pointers above; everything it reports about a waiter comes
809 // from here.
810 const dt_iop_module_t *target_module = !IS_NULL_PTR(piece) ? piece->module : NULL;
811 if(!IS_NULL_PTR(target_module))
812 {
813 g_strlcpy(wait->module_op, target_module->op, sizeof(wait->module_op));
814 wait->module_multi_priority = target_module->multi_priority;
815 }
816 else
817 {
818 wait->module_op[0] = '\0';
819 wait->module_multi_priority = 0;
820 }
821 wait->pipe_type = (int)pipe->type;
822 wait->pipe_imgid = pipe->imgid;
823
824 wait->hash = hash;
825 // Record the producing node identity so CACHELINE_READY can serve this waiter by
826 // its target module even if the exact awaited hash drifts before publish (§8). A
827 // backbuf target (piece == NULL) has no single producing node, so it keeps to the
828 // exact-hash match only.
829 wait->target_node_key = !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module)
830 ? dt_supervisor_node_key(pipe->type, piece->module->op,
831 piece->module->multi_priority)
833 wait->restart = restart;
834 wait->user_data = restart_data;
835 if(IS_NULL_PTR(wait->owner_tag))
836 wait->owner_tag = !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module) ? piece->module->op : "gui-backbuf";
837 if(IS_NULL_PTR(wait->owner_object))
838 wait->owner_object = restart_data;
839 wait->connected = TRUE;
840
841 // The queue assigns the request id and reports whether it just went from empty to
842 // non-empty; the subscription and the cursor are ours, and both hang off exactly that
843 // transition -- which is why they were one flag each in the old manager.
844 const gboolean queue_became_busy = dt_pixelpipe_cache_wait_enqueue(wait);
845 if(queue_became_busy)
846 {
848 {
852 }
854 {
856 activate_wait_cursor = TRUE;
857 }
858 }
859 if(activate_wait_cursor)
860 g_main_context_invoke(NULL, _cache_wait_cursor_progress, NULL);
861
863 "[cache-wait] queued id=%" PRIu64 " owner=%s hash=%" PRIu64 " target=%s\n",
864 wait->request_id,
865 !IS_NULL_PTR(wait->owner_tag) ? wait->owner_tag : "(unknown)",
866 wait->hash,
867 !IS_NULL_PTR(wait->module) ? wait->module_op : "backbuf");
868
871 !IS_NULL_PTR(wait->module) ? wait->module_op : NULL,
873 (int)pipe->type, pipe->imgid, TRUE, NULL);
874 }
875 else
876 {
877 /* The GUI already waits for this exact cacheline. Re-emitting CACHE_REQUEST from
878 * each expose keeps retrying an unsatisfied target forever when the pipeline cannot
879 * publish it, for example after an OpenCL memory pre-check failure on very large images. */
880 request_cacheline = FALSE;
881
882 /* A deduped re-poll: the consumer asked again for a wait already in flight, and no
883 * new CACHE_REQUEST was emitted. A run of these with no matching serve (and no
884 * cacheline ever published under wait->hash) is the "never processed, never
885 * finishes" signature. */
888 !IS_NULL_PTR(wait->module) ? wait->module_op : NULL,
890 (int)pipe->type, pipe->imgid, FALSE, "dedup-poll");
891 }
892 }
893
894 if(request_cacheline)
895 {
899 !IS_NULL_PTR(piece) ? piece->module : NULL);
901
902 dt_print(DT_DEBUG_DEV, "[pixelpipe/gui] request host cache pipe=%s target=%s hash=%" PRIu64 "\n",
904 !IS_NULL_PTR(piece) && !IS_NULL_PTR(piece->module) ? piece->module->op : "backbuf", hash);
905 }
906
907 return FALSE;
908}
909
911 void **data, dt_pixel_cache_entry_t **cache_entry,
914 gpointer restart_data)
915{
916 return _peek_gui(pipe, piece, data, cache_entry, wait, restart, restart_data, FALSE);
917}
918
920 void **data, dt_pixel_cache_entry_t **cache_entry,
923 gpointer restart_data)
924{
925 return _peek_gui(pipe, piece, data, cache_entry, wait, restart, restart_data, TRUE);
926}
927
928
956static void _dt_dev_pixelpipe_cache_wait_ready_callback(gpointer instance, const guint64 hash,
957 const guint64 producer_node_key,
958 gpointer user_data)
959{
960 gboolean restore_wait_cursor = FALSE;
961 gboolean drained = FALSE;
962
963 // The queue decides who this publication satisfies and hands them back; running their
964 // callbacks is ours, and stays OUTSIDE any queue lock, because a restart handler queues
965 // redraws and asks for new cachelines.
966 GList *to_restart = dt_pixelpipe_cache_wait_take_matching(hash, producer_node_key, &drained);
967
968 if(drained && _cache_wait_connected)
969 {
973 }
974 if(drained && _cache_wait_cursor_active)
975 {
977 restore_wait_cursor = TRUE;
978 }
979 if(restore_wait_cursor)
980 g_main_context_invoke(NULL, _cache_wait_cursor_restore, NULL);
981
982 for(GList *iter = to_restart; iter; iter = g_list_next(iter))
983 {
984 dt_dev_pixelpipe_cache_wait_t *wait = iter->data;
986 gpointer restart_data = wait->user_data;
987 // A drift serve: the awaited hash never published, but the target module did
988 // (matched via producer_node_key). Record it as such so the supervisor timeline
989 // shows the recovery instead of a phantom exact hit.
990 const gboolean drift_serve = wait->hash != hash;
991
993 "[cache-wait] served id=%" PRIu64 " owner=%s awaited=%" PRIu64 " published=%" PRIu64
994 " target=%s%s\n",
995 wait->request_id,
996 !IS_NULL_PTR(wait->owner_tag) ? wait->owner_tag : "(unknown)",
997 wait->hash, hash,
998 !IS_NULL_PTR(wait->module) ? wait->module_op : "backbuf",
999 drift_serve ? " (drift: node-key)" : "");
1000
1003 !IS_NULL_PTR(wait->module) ? wait->module_op : NULL,
1005 !IS_NULL_PTR(wait->pipe) ? (int)wait->pipe_type : -1,
1006 !IS_NULL_PTR(wait->pipe) ? wait->pipe_imgid : -1, FALSE,
1007 drift_serve ? "served (drift: node-key)" : "served");
1008
1009 wait->pipe = NULL;
1010 wait->module = NULL;
1013 wait->restart = NULL;
1014 wait->user_data = NULL;
1015 wait->request_id = 0;
1016
1017 if(!IS_NULL_PTR(restart)) restart(restart_data);
1018 }
1019 g_list_free(to_restart);
1020}
1021
1023{
1024 if(IS_NULL_PTR(wait) || !wait->connected) return;
1025 gboolean restore_wait_cursor = FALSE;
1026 const uint64_t request_id = wait->request_id;
1027 const uint64_t hash = wait->hash;
1028 const char *owner_tag = wait->owner_tag;
1029 // Snapshot the identity and its labels together, before the reset below clears them. Nothing
1030 // here dereferences the module or the pipe: the labels were copied when the wait was queued,
1031 // precisely because either object can be gone by the time a wait is cancelled.
1032 const gboolean had_module = !IS_NULL_PTR(wait->module);
1033 const gboolean had_pipe = !IS_NULL_PTR(wait->pipe);
1034 const char *module_op = had_module ? wait->module_op : NULL;
1035 const int module_multi_priority = wait->module_multi_priority;
1036 const int cancel_pipe_type = wait->pipe_type;
1037 const int32_t cancel_pipe_imgid = wait->pipe_imgid;
1038 const char *cancel_reason = !IS_NULL_PTR(reason) ? reason : "unspecified";
1039
1040 gboolean drained = FALSE;
1041 dt_pixelpipe_cache_wait_cancel(wait, &drained);
1042
1043 if(drained && _cache_wait_connected)
1044 {
1048 }
1049 if(drained && _cache_wait_cursor_active)
1050 {
1052 restore_wait_cursor = TRUE;
1053 }
1054 if(restore_wait_cursor)
1055 g_main_context_invoke(NULL, _cache_wait_cursor_restore, NULL);
1056
1057 // -1: the queue owns the enqueue timestamp, and a cancellation is not worth an extra query
1058 // into it. The dump reports real ages for everything still pending.
1059 const int64_t age_ms = -1;
1061 "[cache-wait] cancelled id=%" PRIu64 " owner=%s hash=%" PRIu64
1062 " target=%s age_ms=%" PRId64 " reason=%s\n",
1063 request_id,
1064 !IS_NULL_PTR(owner_tag) ? owner_tag : "(unknown)",
1065 hash,
1066 had_module ? module_op : "backbuf",
1067 age_ms,
1068 cancel_reason);
1069
1071 {
1072 char note[64];
1073 g_snprintf(note, sizeof(note), "cancelled: %s", cancel_reason);
1074 dt_supervisor_cache_wait(DT_SV_DELETE, request_id, hash, owner_tag,
1075 module_op, module_multi_priority,
1076 had_pipe ? cancel_pipe_type : -1,
1077 had_pipe ? cancel_pipe_imgid : -1, FALSE, note);
1078 }
1079
1080 wait->pipe = NULL;
1081 wait->module = NULL;
1084 wait->restart = NULL;
1085 wait->user_data = NULL;
1086 wait->request_id = 0;
1087 wait->connected = FALSE;
1088}
1089
1091 const char *owner_tag,
1092 gpointer owner_object)
1093{
1094 if(IS_NULL_PTR(wait)) return;
1095 wait->owner_tag = owner_tag;
1096 wait->owner_object = owner_object;
1097}
1098
1099/* Establish the *input* side of a piece's buffer contract from the upstream descriptor by
1100 * asking the module's input_format(). This does NOT decide compatibility or finalize the
1101 * output: the single authoritative pass dt_dev_pixelpipe_propagate_formats() owns the mismatch/auto-disable
1102 * decision so it is taken once, on the fully-committed and consistently-threaded chain, never
1103 * on the stale partial state a synch_top / realtime edit can leave behind (issue #733).
1104 *
1105 * It is split out from that pass because commit_params() of a few modules (exposure, highlights,
1106 * invert, temperature) reads piece->dsc_in (filters, processed_maximum), so the commit loop must
1107 * set dsc_in before committing. */
1108// Short colorspace name for the [dsc] format-propagation debug log (-d pipe). Knowing the cst of
1109// each node's input/output is essential to spot when the pixelpipe's automatic RGB<->Lab conversion
1110// should fire: a Lab-domain module whose dsc_in.cst is not "lab" will silently consume RGB.
1111static const char *_dsc_cst_name(const int cst)
1112{
1113 switch(cst)
1114 {
1115 case IOP_CS_RAW: return "raw";
1116 case IOP_CS_LAB: return "lab";
1117 case IOP_CS_RGB: return "rgb";
1118 case IOP_CS_RGB_DISPLAY: return "display-rgb";
1119 case IOP_CS_LCH: return "lch";
1120 case IOP_CS_HSL: return "hsl";
1121 case IOP_CS_JZCZHZ: return "jzczhz";
1122 case IOP_CS_NONE: return "none";
1123 default: return "?";
1124 }
1125}
1126
1128 const dt_iop_buffer_dsc_t *upstream_dsc)
1129{
1130 piece->dsc_in = *upstream_dsc;
1131 piece->dsc_out = *upstream_dsc;
1134
1135 /* Disabled modules are strict pass-through stages. Their advertised contracts must
1136 * not rewrite the upstream descriptor, otherwise a disabled RGB-only module can make
1137 * downstream RAW stages such as demosaic believe the stream was already converted. */
1138 if(!piece->enabled) return;
1139
1140 piece->module->input_format(piece->module, pipe, piece, &piece->dsc_in);
1142 piece->dsc_out = piece->dsc_in;
1144}
1145
1147 dt_iop_params_t *params, dt_develop_blend_params_t *blend_params,
1148 dt_iop_buffer_dsc_t *upstream_dsc)
1149{
1150 const dt_iop_buffer_dsc_t actual_input_dsc = *upstream_dsc;
1151
1152 // Set dsc_in before committing (commit_params() of some modules reads it). Compatibility and
1153 // the final output descriptor are settled later by dt_dev_pixelpipe_propagate_formats(), so this loop
1154 // never auto-disables a module on a possibly-stale upstream descriptor.
1155 _prepare_piece_input_contract(pipe, piece, upstream_dsc);
1156
1157 // This should run even if the module is disabled because
1158 // some modules with no history self-enable based on runtime context
1159 dt_iop_commit_params(piece->module, params, blend_params, pipe, piece);
1160
1161 if(piece->enabled)
1162 {
1163 piece->module->output_format(piece->module, pipe, piece, &piece->dsc_out);
1165 }
1166 else
1167 {
1168 piece->dsc_in = actual_input_dsc;
1169 piece->dsc_out = actual_input_dsc;
1172 }
1173
1174 *upstream_dsc = piece->dsc_out;
1175}
1176
1177/* Direction-independent forward format pass.
1178 *
1179 * The buffer-format contract (channels / datatype / colorspace / CFA presence flowing from one
1180 * module to the next) is conceptually independent of ROI planning, which runs in both
1181 * directions (end->start for a target size, start->end for the native size). Deriving the
1182 * contract inside the history-sync commit loop made it sensitive to partial syncs
1183 * (synch_top / realtime top edits): a downstream module could read a stale upstream descriptor
1184 * and get wrongly disabled for an "unexpected input buffer format" (issue #733). Worse, the
1185 * auto-disable is one-way, so a later pass could not undo a false positive.
1186 *
1187 * This single forward pass re-threads the whole chain from the input image descriptor after
1188 * params have been committed, and is the *only* place that disables a module for a genuinely
1189 * incompatible input. Disabling here is safe because the chain is consistent and fully threaded
1190 * from pipe->dev->image_storage.dsc. Only the first stage (basebuffer) reads the input image
1191 * type; every later node derives its contract solely from its upstream piece. The lone
1192 * ROI-dependent refinement, rawprepare's CFA phase shift, is finalized later in modify_roi_*().
1193 *
1194 * It is deliberately NON-destructive for compatible enabled modules: it verifies the input
1195 * contract and re-threads dsc_in, but keeps the dsc_out that commit_params() produced. dsc_out
1196 * carries runtime fields (processed_maximum, rawprepare black/white, temperature coeffs, CFA
1197 * phase) that input/output_format cannot reconstruct, so recomputing them here would wipe the
1198 * tonal scaling and render images black/white. */
1200{
1201 if(IS_NULL_PTR(pipe)) return;
1202
1203 dt_iop_buffer_dsc_t upstream_dsc = pipe->dev->image_storage.dsc;
1204 gchar *pipe_name = (dt_get_debug_flags() & DT_DEBUG_PIPE) ? _get_debug_pipe_name(pipe, NULL) : NULL;
1205
1206 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1207 {
1209 if(IS_NULL_PTR(piece) || IS_NULL_PTR(piece->module)) continue;
1210
1211 const dt_iop_buffer_dsc_t actual_input_dsc = upstream_dsc;
1212
1213 if(!piece->enabled)
1214 {
1215 // Disabled modules are strict pass-through stages.
1216 piece->dsc_in = actual_input_dsc;
1217 piece->dsc_out = actual_input_dsc;
1220 }
1221 else
1222 {
1223 // Ask the module what input it expects, into a scratch descriptor. We must NOT recompute
1224 // piece->dsc_out from input/output_format here: commit_params() already produced dsc_out
1225 // with runtime fields that those methods cannot reconstruct (processed_maximum, rawprepare
1226 // black/white, temperature coeffs, CFA phase). Recomputing would reset processed_maximum to
1227 // the input image's value and blow up downstream tone handling (black/white renders).
1228 dt_iop_buffer_dsc_t declared_in = actual_input_dsc;
1229 dt_iop_buffer_dsc_update_bpp(&declared_in);
1230 piece->module->input_format(piece->module, pipe, piece, &declared_in);
1231 dt_iop_buffer_dsc_update_bpp(&declared_in);
1232
1233 // A module whose declared input does not match what the previous stage actually publishes
1234 // cannot run: disable it (in the pipe only; history is untouched) and turn it into a
1235 // pass-through so the contract keeps flowing to the next stage.
1236 const gboolean input_mismatch
1237 = (declared_in.bpp != actual_input_dsc.bpp
1238 || declared_in.channels != actual_input_dsc.channels
1239 || declared_in.filters != actual_input_dsc.filters);
1240
1241 if(input_mismatch)
1242 {
1243 dt_control_log(_("disabled module `%s`: unexpected input buffer format"),
1244 piece->module->name());
1246 "[pixelpipe] disabling module %s because input format expects %" G_GSIZE_FORMAT
1247 " B/px, %u channels, filters %u but upstream publishes %" G_GSIZE_FORMAT
1248 " B/px, %u channels, filters %u\n",
1249 piece->module->op, declared_in.bpp, declared_in.channels, declared_in.filters,
1250 actual_input_dsc.bpp, actual_input_dsc.channels, actual_input_dsc.filters);
1251
1252 piece->enabled = FALSE;
1257 piece->dsc_in = actual_input_dsc;
1258 piece->dsc_out = actual_input_dsc;
1261 }
1262 else
1263 {
1264 // Input is compatible: publish the module's DECLARED input descriptor. `declared_in` was
1265 // seeded from the upstream descriptor and then run through input_format(), so it already
1266 // carries the upstream runtime fields (processed_maximum, rawprepare, temperature, CFA
1267 // phase) AND the colorspace/channel layout the module actually consumes.
1268 //
1269 // Critically, we must NOT fall back to `actual_input_dsc` here: that would reset dsc_in.cst
1270 // to the upstream colorspace and make it equal to the buffer's cst, so the pixelpipe's
1271 // automatic colorspace conversion (pixelpipe_cpu.c / pixelpipe_gpu.c) would believe no
1272 // conversion is needed. Lab-domain modules (old color balance, color checker, atrous, ...)
1273 // inserted after an RGB stage would then receive RGB data interpreted as Lab and render
1274 // garbled/solid colors. The full-resync path keeps dsc_in from input_format() for the same
1275 // reason; this incremental (synch_top) path must match it.
1276 piece->dsc_in = declared_in;
1278 }
1279 }
1280
1281 if(pipe_name)
1283 "[dsc] pipe=%s module=%s enabled=%d in=(cst=%s ch=%i bpp=%" G_GSIZE_FORMAT
1284 " filters=%u) out=(cst=%s ch=%i bpp=%" G_GSIZE_FORMAT " filters=%u)\n",
1285 pipe_name, piece->module->op, piece->enabled,
1286 _dsc_cst_name(piece->dsc_in.cst), piece->dsc_in.channels, piece->dsc_in.bpp, piece->dsc_in.filters,
1287 _dsc_cst_name(piece->dsc_out.cst), piece->dsc_out.channels, piece->dsc_out.bpp, piece->dsc_out.filters);
1288
1289 upstream_dsc = piece->dsc_out;
1290 }
1291
1292 dt_free(pipe_name);
1293}
1294
1295/* `history_list` is the snapshot's list (dt_dev_history_snapshot_t::items), never dev->history:
1296 * this runs OUTSIDE history_mutex, and the snapshot's references are what keep every item it
1297 * walks alive and unmodified (see dt_dev_history_snapshot_take). */
1298static void _sync_pipe_nodes_from_history(dt_dev_pixelpipe_t *pipe, GList *history_list,
1299 const uint32_t history_end, const char *debug_label)
1300{
1301 dt_iop_buffer_dsc_t upstream_dsc = pipe->dev->image_storage.dsc;
1302 const gboolean previous_want_detail_mask = (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE);
1303
1304 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1305 {
1307 if(IS_NULL_PTR(piece)) continue;
1308
1313 piece->enabled = piece->module->default_enabled;
1314 piece->detail_mask = FALSE;
1315
1316 dt_iop_params_t *params = piece->module->default_params;
1317 dt_develop_blend_params_t *blend_params = piece->module->default_blendop_params;
1318 uint64_t history_param_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1319 gboolean found_history = FALSE;
1320
1321 for(GList *history = g_list_nth(history_list, history_end - 1);
1322 history;
1323 history = g_list_previous(history))
1324 {
1325 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1326 if(piece->module == hist->module)
1327 {
1328 piece->enabled = hist->enabled;
1329 params = hist->params;
1330 blend_params = hist->blend_params;
1331 history_param_hash = hist->hash; // the history entry key (module->hash)
1332 found_history = TRUE;
1333 break;
1334 }
1335 }
1336
1337 piece->detail_mask = blend_params && blend_params->details != 0.0f;
1338 if(!strcmp(piece->module->op, "detailmask"))
1340 _commit_piece_contract(pipe, piece, params, blend_params, &upstream_dsc);
1341
1342 // Tie this node to the history item it was just synchronized with, so the
1343 // cachelines it produces resolve to that history state through the node.
1344 // Use the history entry key (module->hash), NOT piece->hash: the latter folds
1345 // in runtime_data_hash() for some modules and then no longer matches history.
1346 if(dt_supervisor_active() && found_history)
1348 dt_supervisor_node_key(pipe->type, piece->module->op, piece->module->multi_priority),
1349 history_param_hash, DT_PIXELPIPE_CACHE_HASH_INVALID, piece->module->op,
1350 piece->module->multi_priority, piece->module->iop_order, pipe->type, pipe->imgid);
1351
1352 if(!found_history)
1353 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] info: committed default params for %s (%s) in pipe %s\n",
1354 piece->module->op, piece->module->multi_name, debug_label);
1355 }
1356
1358 if(previous_want_detail_mask != (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE))
1359 {
1360 GList *detailmask_node = _find_detailmask_node(pipe);
1361 if(detailmask_node)
1362 _sync_pipe_nodes_from_history_from_node(pipe, history_list, history_end, detailmask_node, debug_label);
1363 }
1364}
1365
1367 const uint32_t history_end, GList *start_node,
1368 const char *debug_label)
1369{
1370 if(IS_NULL_PTR(pipe) || IS_NULL_PTR(start_node)) return;
1371
1372 dt_iop_buffer_dsc_t upstream_dsc = pipe->dev->image_storage.dsc;
1373 const gboolean previous_want_detail_mask = (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE);
1374 for(GList *node = g_list_first(pipe->nodes); node && node != start_node; node = g_list_next(node))
1375 {
1377 if(IS_NULL_PTR(piece)) continue;
1378
1379 upstream_dsc = piece->dsc_out;
1380 }
1381
1382 for(GList *nodes = start_node; nodes; nodes = g_list_next(nodes))
1383 {
1385 if(IS_NULL_PTR(piece)) continue;
1386
1391
1392 piece->enabled = piece->module->default_enabled;
1393 piece->detail_mask = FALSE;
1394
1395 dt_iop_params_t *params = piece->module->default_params;
1396 dt_develop_blend_params_t *blend_params = piece->module->default_blendop_params;
1397 uint64_t history_param_hash = DT_PIXELPIPE_CACHE_HASH_INVALID;
1398 gboolean found_history = FALSE;
1399
1400 for(GList *history = g_list_nth(history_list, history_end - 1);
1401 history;
1402 history = g_list_previous(history))
1403 {
1404 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1405 if(piece->module == hist->module)
1406 {
1407 piece->enabled = hist->enabled;
1408 params = hist->params;
1409 blend_params = hist->blend_params;
1410 history_param_hash = hist->hash; // the history entry key (module->hash)
1411 found_history = TRUE;
1412 break;
1413 }
1414 }
1415
1416 piece->detail_mask = blend_params && blend_params->details != 0.0f;
1417 if(!strcmp(piece->module->op, "detailmask"))
1419 _commit_piece_contract(pipe, piece, params, blend_params, &upstream_dsc);
1420
1421 // Tie this node to the history item it was just synchronized with, so the
1422 // cachelines it produces resolve to that history state through the node.
1423 // Use the history entry key (module->hash), NOT piece->hash: the latter folds
1424 // in runtime_data_hash() for some modules and then no longer matches history.
1425 if(dt_supervisor_active() && found_history)
1427 dt_supervisor_node_key(pipe->type, piece->module->op, piece->module->multi_priority),
1428 history_param_hash, DT_PIXELPIPE_CACHE_HASH_INVALID, piece->module->op,
1429 piece->module->multi_priority, piece->module->iop_order, pipe->type, pipe->imgid);
1430
1431 if(!found_history)
1432 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] info: committed default params for %s (%s) in pipe %s\n",
1433 piece->module->op, piece->module->multi_name, debug_label);
1434 }
1435
1437 if(previous_want_detail_mask != (pipe->want_detail_mask != DT_DEV_DETAIL_MASK_NONE))
1438 {
1439 GList *detailmask_node = _find_detailmask_node(pipe);
1440 if(detailmask_node && detailmask_node != start_node)
1441 _sync_pipe_nodes_from_history_from_node(pipe, history_list, history_end, detailmask_node, debug_label);
1442 }
1443}
1444
1446{
1447 /* Traverse the pipeline node by node and compute the cumulative (global) hash of each module.
1448 * This hash takes into account the hashes of the previous modules and the size of the current ROI.
1449 * It is used to map pipeline cache states to current parameters.
1450 * It represents the state of internal modules params as well as their position in the pipe and their output size.
1451 * It is to be called at pipe init, not at runtime.
1452 */
1453
1454 // bernstein hash (djb2)
1455 uint64_t hash = _default_pipe_hash(pipe);
1456 gboolean passthrough_preview = FALSE;
1457
1458 // Bypassing cache contaminates downstream modules, starting at the module requesting it.
1459 // Usecase : crop, clip, ashift, etc. that need the uncropped image ;
1460 // mask displays ; overexposed/clipping alerts and all other transient previews.
1461 gboolean bypass_cache = FALSE;
1462
1463 for(GList *node = g_list_first(pipe->nodes); node; node = g_list_next(node))
1464 {
1466 if(!piece->enabled) continue;
1467
1468 // Combine with the previous bypass states
1469 bypass_cache |= piece->module->bypass_cache;
1470 piece->bypass_cache = bypass_cache;
1471
1472 // Combine with the previous modules hashes
1473 uint64_t local_hash = piece->hash;
1474
1475 // Some GUI previews author their final display inside the active module,
1476 // then runtime forwards that exact buffer through later pass-through stages.
1477 // Keep the planned hash contract aligned with the published cacheline so the
1478 // GUI does not keep requesting a downstream output hash that will never exist.
1479 if(passthrough_preview
1480 && !(piece->module->operation_tags() & IOP_TAG_DISTORT)
1481 && (piece->dsc_in.bpp == piece->dsc_out.bpp)
1482 && !memcmp(&piece->roi_in, &piece->roi_out, sizeof(dt_iop_roi_t)))
1483 {
1484 piece->global_mask_hash = hash;
1485 piece->global_hash = hash;
1486 continue;
1487 }
1488
1489 // Panning and zooming change the ROI. Some GUI modes (crop in editing mode) too.
1490 // dt_dev_get_roi_in() should have run before
1491 //
1492 // These two folds are ALSO how the darkroom viewport reaches this hash, which is why the
1493 // pipe's latched dt_dev_roi_request_t (pixelpipe_hb.h) deliberately does NOT contribute its
1494 // generation here. Every field of that record reaches the ROI by construction, in
1495 // _update_darkroom_roi(): zoom and the fit scale become roi.scale (their product), the
1496 // centre becomes roi.x/roi.y, the box and the processed size become roi.width/height. Even
1497 // finalscale's piece->enabled -- the one consumer that reads the record rather than the ROI
1498 // -- is a function of that same product, and a disabled piece is skipped above, so a toggle
1499 // changes the chain anyway.
1500 //
1501 // Folding the generation on top would therefore add no discriminating power and would only
1502 // over-invalidate: a republication that advances the generation while producing identical
1503 // ROIs (a sub-pixel pan that rounds to the same roi.x, a box change the fminf clamp absorbs)
1504 // would rekey the whole FULL-pipe cache chain for pixels that are bit-identical. Content,
1505 // not version, is the right key here -- unlike mask_preview_settings_revision below, which
1506 // hashes a revision precisely because its state reaches no ROI at all.
1507 local_hash = dt_hash(local_hash, (const char *)&piece->roi_in, sizeof(dt_iop_roi_t));
1508 local_hash = dt_hash(local_hash, (const char *)&piece->roi_out, sizeof(dt_iop_roi_t));
1509
1510 local_hash = dt_hash(local_hash, (const char *)&piece->dsc_in, sizeof(dt_iop_buffer_dsc_t));
1511 local_hash = dt_hash(local_hash, (const char *)&piece->dsc_out, sizeof(dt_iop_buffer_dsc_t));
1512
1513 /* The pipe's drawn-mask rasterisation step changes the mask a module blends with, so it
1514 * changes that module's pixels. It belongs HERE, with request_mask_display below, and for
1515 * the same reason: it is global GUI state that no module's history carries, so
1516 * dt_iop_commit_params() would only fold it when that module's own parameters changed --
1517 * flipping the preference from auto to never would leave every cached line valid-looking
1518 * and stale.
1519 *
1520 * Only for a module that actually draws a mask: the raw stages upstream cannot carry one,
1521 * and rehashing them would cost cache misses for pixels that cannot move. */
1522 // From piece->blendop_data, NOT piece->module->blend_params. This function runs on the
1523 // darkroom worker thread, and blend_params belongs to the GUI thread -- the rule this file
1524 // states twice above ("never the live GUI module->params"). Reading it here dereferences a
1525 // module the GUI can be tearing down or reallocating underneath us, which is Sentry
1526 // 142904894: EXCEPTION_ACCESS_VIOLATION at this line, from resync_pipe_with_history() on
1527 // the pipeline thread. blendop_data is the per-piece copy dt_iop_commit_params() writes
1528 // from the history snapshot, which is what blend.c itself reads.
1529 const dt_develop_blend_params_t *const blend_data
1530 = (const dt_develop_blend_params_t *)piece->blendop_data;
1531 if(!IS_NULL_PTR(blend_data)
1532 && (blend_data->mask_mode & DEVELOP_MASK_SHAPE)
1533 && blend_data->mask_id > 0)
1534 local_hash = dt_hash(local_hash, (const char *)&pipe->mask_rasterization_step, sizeof(int));
1535
1536/*
1537 fprintf(stdout, "start->end : %-17s | ROI in: %4ix%-4i @%2.4f | ROI out: %4ix%-4i @%2.4f\n", piece->module->op,
1538 piece->buf_in.width, piece->buf_in.height, piece->buf_in.scale, piece->buf_out.width,
1539 piece->buf_out.height, piece->buf_out.scale);
1540 fprintf(stdout, "end->start : %-17s | ROI in: %4ix%-4i @%2.4f | ROI out: %4ix%-4i @%2.4f\n", piece->module->op,
1541 piece->roi_in.width, piece->roi_in.height, piece->planned_roi_in.scale,
1542 piece->roi_out.width, piece->roi_out.height, piece->roi_out.scale);
1543*/
1544 // Mask preview display doesn't re-commit params, so we need to keep that of it here
1545 // Too much GUI stuff interleaved with pipeline stuff...
1546 // Mask display applies only to main preview in darkroom.
1547 if(pipe->type == DT_DEV_PIXELPIPE_FULL)
1548 {
1549 local_hash = dt_hash(local_hash, (const char *)&piece->module->request_mask_display, sizeof(int));
1550
1551 /* Mask-preview appearance is global GUI state, not module history. Hash
1552 * its revision at the module producing the active preview so upstream
1553 * cache entries remain reusable while this module and gamma are rerun. */
1554 if(pipe->dev->gui_attached
1555 && piece->module == pipe->dev->gui_module
1556 && piece->module->request_mask_display != DT_DEV_PIXELPIPE_DISPLAY_NONE)
1557 {
1558 const int revision = dt_atomic_get_int(&pipe->dev->mask_preview_settings_revision);
1559 local_hash = dt_hash(local_hash, (const char *)&revision, sizeof(revision));
1560 }
1561
1562 // bypass_cache_variant (see dt_iop_module_t.bypass_cache_variant) is, like
1563 // request_mask_display above, module-owned GUI state whose real effect a module such as
1564 // retouch applies only to this main/FULL pipe -- never to the preview pipe, which always
1565 // renders as if none of these toggles were active. But the field itself lives on
1566 // the shared dt_iop_module_t, so it reads the same non-zero value from every pipe type. Left
1567 // ungated, a preview-pipe run with the same ROI/params (e.g. at zoom == fit) can compute the
1568 // exact same hash chain as the FULL pipe despite publishing different pixels, and either
1569 // pipe's cache entry then gets silently reused by the other via the cross-pipe "another pipe
1570 // already owns this hash" exact-hit path -- exactly like request_mask_display's own hazard.
1571 local_hash = dt_hash(local_hash, (const char *)&piece->module->bypass_cache_variant, sizeof(int));
1572 }
1573 else
1574 {
1575 const int zero = 0;
1576 local_hash = dt_hash(local_hash, (const char *)&zero, sizeof(int));
1577 local_hash = dt_hash(local_hash, (const char *)&zero, sizeof(int));
1578 }
1579
1580 // Keep track of distortion bypass in GUI. That may affect upstream modules in the stack,
1581 // while bypass_cache only affects downstream ones.
1582 // In theory, distortion bypass should already affect planned ROI in/out, but it depends whether
1583 // internal params are committed. Anyway, make it more reliable.
1584 int bypass_distort = dt_dev_pixelpipe_activemodule_disables_currentmodule(pipe->dev, piece->module);
1585 local_hash = dt_hash(local_hash, (const char *)&bypass_distort, sizeof(int));
1586
1587 // If the cache bypass is on, the corresponding cache lines will be freed immediately after use,
1588 // we need to track that. It somewhat overlaps module->request_mask_display, but...
1589 local_hash = dt_hash(local_hash, (const char *)&piece->bypass_cache, sizeof(gboolean));
1590
1591 // Update global hash for this stage
1592 hash = dt_hash(hash, (const char *)&local_hash, sizeof(uint64_t));
1593
1595 {
1596 gchar *type = _get_debug_pipe_name(pipe, pipe->dev);
1598 "[pixelpipe] global hash for %20s (%s) in pipe %s enabled=%d bypass_cache=%d with hash %lu\n",
1599 piece->module->op, piece->module->multi_name, type, piece->enabled, piece->bypass_cache,
1600 (long unsigned int)hash);
1601 dt_free(type);
1602 }
1603 // In case of drawn masks, we would need to account only for the distortions of previous modules.
1604 // Aka conditional to: if((piece->module->operation_tags() & IOP_TAG_DISTORT) == IOP_TAG_DISTORT)
1605 // But in case of parametric masks, they depend on previous modules parameters.
1606 // So, all in all, (parametric | drawn | raster) masking depends on everything :
1607 // - if masking on output, internal params + blendop params + all previous modules internal params + ROI size,
1608 // - if masking on input, blendop params + all previous modules internal params + ROI size
1609 // So we use all that ot once :
1610 piece->global_mask_hash = dt_hash(hash, (const char *)&piece->blendop_hash, sizeof(uint64_t));
1611
1612 // Finally, the output of the module also depends on the mask:
1613 hash = dt_hash(hash, (const char *)&piece->global_mask_hash, sizeof(uint64_t));
1614 piece->global_hash = hash;
1615
1616 if(pipe->type == DT_DEV_PIXELPIPE_FULL
1617 && pipe->dev->gui_attached
1618 && (piece->module == pipe->dev->gui_module)
1619 && (piece->module->request_mask_display != DT_DEV_PIXELPIPE_DISPLAY_NONE))
1620 {
1621 passthrough_preview = TRUE;
1622 }
1623 }
1624
1625 // The pipe hash is the hash of its last module.
1626 dt_dev_pixelpipe_set_hash(pipe, hash);
1627 pipe->bypass_cache = bypass_cache;
1628}
1629
1640/* Resync every node against a history snapshot. Runs outside history_mutex: everything it reads
1641 * is owned by `snap`, whose references keep the items alive and, through the writer's
1642 * copy-on-write gate, unmodified. */
1644 const char *caller_func)
1645{
1646 gchar *type = _get_debug_pipe_name(pipe, pipe->dev);
1647 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch all modules with history for pipe %s called from %s\n", type, caller_func);
1648
1649 const uint32_t history_end = (uint32_t)snap->history_end;
1650 _sync_pipe_nodes_from_history(pipe, snap->items, history_end, type);
1651
1652 // Keep track of the last history item to have been synced
1653 GList *last_item = g_list_nth(snap->items, history_end - 1);
1654 if(last_item)
1655 {
1656 dt_dev_history_item_t *last_hist = (dt_dev_history_item_t *)last_item->data;
1657 pipe->last_history_hash = last_hist->hash;
1658 _pipe_set_last_history_item(pipe, last_hist);
1659 }
1660 else
1661 {
1663 _pipe_set_last_history_item(pipe, NULL);
1664 }
1665
1666 dt_free(type);
1667}
1668
1669/* Public entry: export, snapshots and the focus overlay drive a throwaway pipe through this
1670 * without going through dt_dev_pixelpipe_change(), and none of them holds history_mutex. Take
1671 * the snapshot here, under a read lock held only for the copy. A caller that DOES already hold
1672 * the lock (as reader or writer) re-enters it on the same thread, which dt_pthread_rwlock_t
1673 * permits (see dtpthread.h). */
1674void dt_dev_pixelpipe_synch_all_real(dt_dev_pixelpipe_t *pipe, const char *caller_func)
1675{
1678 dt_dev_history_snapshot_take(pipe->dev, &snap);
1680
1681 _synch_all_snapshot(pipe, &snap, caller_func);
1682
1684}
1685
1686/* Resync only the nodes touched since the last sync, against a history snapshot. Runs outside
1687 * history_mutex, same contract as _synch_all_snapshot(). */
1689{
1690 gchar *type = _get_debug_pipe_name(pipe, pipe->dev);
1691
1692 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch top modules with history for pipe %s\n", type);
1693
1694 const uint32_t history_end = (uint32_t)snap->history_end;
1695 GList *last_item = g_list_nth(snap->items, history_end - 1);
1696 if(last_item)
1697 {
1698 // The identity marker is read once: the GUI thread may re-point it at a clone (see
1699 // dt_dev_history_cow_touch) while this walk runs, and the reference the pipe holds on it
1700 // is what makes comparing against a possibly-departed item safe.
1701 const dt_dev_history_item_t *last_synced
1703
1704 GList *first_item = NULL;
1705 for(GList *history = last_item; history; history = g_list_previous(history))
1706 {
1707 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1708 first_item = history;
1709
1710 if(hist->hash == pipe->last_history_hash || hist == last_synced)
1711 break;
1712 }
1713
1714 GList *fence_item = g_list_nth(snap->items, history_end);
1715 for(GList *history = first_item; history && history != fence_item; history = g_list_next(history))
1716 {
1717 dt_dev_history_item_t *hist = (dt_dev_history_item_t *)history->data;
1718 if(!hist || !hist->module) continue;
1719 dt_print(DT_DEBUG_PARAMS, "[pixelpipe] synch top history module `%s` (%s) for pipe %s\n",
1720 hist->module->op, hist->module->multi_name, type);
1721 for(GList *nodes = g_list_last(pipe->nodes); nodes; nodes = g_list_previous(nodes))
1722 {
1724 if(IS_NULL_PTR(piece) || piece->module != hist->module) continue;
1725
1726 _sync_pipe_nodes_from_history_from_node(pipe, snap->items, history_end, nodes, type);
1727 break;
1728 }
1729 }
1730
1731 dt_dev_history_item_t *last_hist = (dt_dev_history_item_t *)last_item->data;
1732 pipe->last_history_hash = last_hist->hash;
1733 _pipe_set_last_history_item(pipe, last_hist);
1734 }
1735 else
1736 {
1737 dt_print(DT_DEBUG_DEV, "[pixelpipe] synch top history module missing error for pipe %s\n", type);
1739 _pipe_set_last_history_item(pipe, NULL);
1740 }
1741
1742 dt_free(type);
1743}
1744
1756
1757// Modules without history need to be resynced unconditionnally with their internal params
1758// because some of them are self-enabled/disabled from commit_params() methods
1760{
1761 for(GList *nodes = g_list_first(pipe->nodes); nodes; nodes = g_list_next(nodes))
1762 {
1764 if(IS_NULL_PTR(piece) || IS_NULL_PTR(piece->module)) continue;
1765 dt_iop_module_t *module = piece->module;
1766 if(module->flags() & IOP_FLAGS_NO_HISTORY_STACK)
1767 dt_iop_commit_params(module, module->default_params, module->default_blendop_params, pipe, piece);
1768 }
1769}
1770
1772{
1773 dt_times_t start;
1774 dt_get_times(&start);
1775
1786
1787 gchar *type = _get_debug_pipe_name(pipe, pipe->dev);
1788 char *status_str = g_strdup_printf("%s%s%s%s%s%s%s",
1789 (status & DT_DEV_PIPE_UNCHANGED) ? "UNCHANGED " : "",
1790 (status & DT_DEV_PIPE_REMOVE) ? "REMOVE " : "",
1791 (status & DT_DEV_PIPE_TOP_CHANGED) ? "TOP_CHANGED " : "",
1792 (status & DT_DEV_PIPE_SYNCH) ? "SYNCH " : "",
1793 (status & DT_DEV_PIPE_ZOOMED) ? "ZOOMED " : "",
1794 (status & DT_DEV_PIPE_CACHE_REQUEST) ? "CACHE_REQUEST " : "",
1795 (status & DT_DEV_PIPE_REENTRY) ? "REENTRY " : "");
1796
1797 dt_print(DT_DEBUG_DEV, "[dt_dev_pixelpipe_change] pipeline state changing for pipe %s, flag %s\n",
1798 type, status_str);
1799
1800 dt_free(status_str);
1801
1802 // mask display off as a starting point
1804 pipe->bypass_blendif = 0;
1805
1806 /* Zoom/pan only replans ROI and hashes, it does not replay history sync.
1807 * Rebuild the aggregate detail-mask demand from the already synchronized
1808 * pieces in that case, otherwise the pipe forgets that the hidden
1809 * `detailmask` stage is needed before the next processing run starts. */
1812 else
1814
1815 // Snapshot history under the read lock, then resync against the snapshot with the lock
1816 // RELEASED. The resync is O(nodes x history) and runs every module's commit_params() --
1817 // tens of ms routinely, over 200 ms under mask-heavy history -- and it used to hold this
1818 // lock for all of it. The GUI thread needs the writer side of the same lock on every
1819 // commit (each slider tick, each throttled mask-drag commit), and glibc's writer-preferring
1820 // policy then also blocks every NEW reader behind the queued writer: one slow resync stalled
1821 // the whole application until it finished. The snapshot holds a reference on each item, and
1822 // the writer's copy-on-write gate (dt_dev_history_cow_touch) clones a shared item before
1823 // touching it, so what this resync reads can neither move nor change under it.
1824 //
1825 // A call chain that already holds history_mutex as writer on this same thread can re-enter
1826 // here (e.g. history-commit paths that resync a pipe while still holding the write lock);
1827 // dt_pthread_rwlock_rdlock is same-thread-recursive for exactly that (see dtpthread.h).
1828 const double _history_mutex_hold_start = dt_get_wtime();
1831 dt_dev_history_snapshot_take(pipe->dev, &snap);
1833 const double _history_mutex_hold_ms = (dt_get_wtime() - _history_mutex_hold_start) * 1000.0;
1834 if(_history_mutex_hold_ms > 1.0)
1836 "[dt_dev_pixelpipe_change] tid %lu pipe %s held history_mutex for %.2f ms (status 0x%x)\n",
1837 (unsigned long)pthread_self(), type, _history_mutex_hold_ms, (unsigned)status);
1838
1839 // The realtime in-place path settles the format contract and the global hash itself (it must,
1840 // since that hash is cumulative over enabled nodes); the other paths defer it to the
1841 // unconditional pass below.
1842 gboolean formats_propagated = FALSE;
1843
1844 // case DT_DEV_PIPE_UNCHANGED: case DT_DEV_PIPE_ZOOMED:
1846 {
1847 // modules have been added in between or removed. need to rebuild the whole pipeline.
1848 if(pipe->nodes) dt_dev_pixelpipe_cleanup_nodes(pipe);
1851 _synch_all_snapshot(pipe, &snap, __FUNCTION__);
1852 }
1853 else if(status & DT_DEV_PIPE_SYNCH)
1854 {
1855 // pipeline topology remains intact, only change all params.
1857 _synch_all_snapshot(pipe, &snap, __FUNCTION__);
1858 }
1860 {
1861 // only top history item(s) changed, or a focused module published transient/realtime params
1862 if(_sync_focused_in_place(pipe, &snap))
1863 {
1864 formats_propagated = TRUE;
1865 }
1866 else
1867 {
1869 _synch_top_snapshot(pipe, &snap);
1870 }
1871 }
1872 else // DT_DEV_PIPE_ZOOMED DT_DEV_PIPE_CACHE_REQUEST DT_DEV_PIPE_REENTRY
1873 {
1874 // Finalscale will need to self-enable/disable depending on zoom level
1876 }
1877 // The hash the SNAPSHOT carried, not the live one: a commit may have landed since the lock
1878 // was released, and this pipe is synced to what it just resynced against, nothing newer. The
1879 // worker loop compares this against the live hash and re-flags the pipe if they differ.
1882 const double _resync_ms = (dt_get_wtime() - _history_mutex_hold_start) * 1000.0;
1883 if(_resync_ms > 1.0)
1885 "[dt_dev_pixelpipe_change] tid %lu pipe %s resynced from snapshot in %.2f ms, lock-free (status 0x%x)\n",
1886 (unsigned long)pthread_self(), type, _resync_ms, (unsigned)status);
1887
1888 // Re-establish the buffer-format contract of the whole chain once, after whichever sync path
1889 // (full, top-only or realtime-in-place) committed params. This is the single authoritative,
1890 // direction-independent place where per-node dsc_in/dsc_out are settled and incompatible
1891 // modules are disabled (issue #733), so the OpenCL cache policy and ROI planning below see a
1892 // consistent contract. The realtime path already did this before computing its global hash.
1893 if(!formats_propagated)
1895
1897
1898 // Update theoritical final scale based on distorting modules
1899 // This also writes piece->buf_in/out for each pipe->nodes piece,
1900 // so it's not nearly a matter of getting processed_width/height
1902 &pipe->processed_height);
1903
1904 dt_show_times_f(&start, "[dev_pixelpipe] pipeline resync with history", "for pipe %s", type);
1905 dt_free(type);
1906}
1907
1915
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:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
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:929
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
gboolean dt_dev_transient_params_get(dt_develop_t *dev, const dt_iop_module_t *module, void *out_params, const size_t out_params_size, void *out_blend, const size_t out_blend_size, gboolean *out_has_blend)
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 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:2117
static uint64_t dt_dev_get_history_hash(const dt_develop_t *dev)
Definition develop.h:494
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:122
@ DT_DEV_DETAIL_MASK_ENABLED
Definition develop.h:150
@ DT_DEV_DETAIL_MASK_NONE
Definition develop.h:149
gboolean enabled
–doc was passed on the command line
GtkWidget * status
result of the last capture
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:267
@ IOP_CS_RGB_DISPLAY
Definition format.h:71
@ TYPE_FLOAT
Definition format.h:56
void dt_geometry_chain_rebuild(dt_develop_t *dev)
Rebuild the chain from the dev's current modules and history. GUI thread only.
Definition geometry.c:247
Where things are on the image, answered without a pipeline.
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
const char flag
Definition image.h:311
void dt_iop_commit_params(dt_iop_module_t *module, dt_iop_params_t *params, dt_develop_blend_params_t *blendop_params, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Copy params into the pipeline piece, so the next render uses them.
Definition imageop.c:1565
gboolean dt_iop_get_cache_bypass(dt_iop_module_t *module)
Definition imageop.c:1664
@ IOP_FLAGS_NO_HISTORY_STACK
Definition imageop.h:193
@ IOP_TAG_DISTORT
Definition imageop.h:170
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_PIPE
Definition logging.h:76
@ DT_DEBUG_PIPECACHE
Definition logging.h:56
@ DT_DEBUG_HISTORY
Definition logging.h:75
@ DT_DEBUG_VERBOSE
Definition logging.h:78
@ DT_DEBUG_PARAMS
Definition logging.h:71
@ DT_DEBUG_DEV
Definition logging.h:53
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
int dt_opencl_is_inited(void)
Definition opencl.c:3255
#define DT_MAX_FILENAME_LEN
Definition paths.h:35
Whether one pipeline node must keep a host-RAM copy of its output.
static gboolean dt_dev_pipe_cache_policy_decide(const dt_dev_pipe_cache_policy_inputs_t *in, const gboolean inherited_requirement, gboolean *upstream_requirement)
Decide one node's host-cache requirement, walking the pipe from the end backwards.
void dt_iop_buffer_dsc_update_bpp(dt_iop_buffer_dsc_t *dsc)
@ DT_REQUEST_ON
Definition pixelpipe.h:52
@ DT_REQUEST_ONLY_IN_GUI
Definition pixelpipe.h:53
@ DT_DEV_PIXELPIPE_NONE
Definition pixelpipe.h:41
@ DT_DEV_PIXELPIPE_PREVIEW
Definition pixelpipe.h:44
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:43
void dt_dev_pixelpipe_cache_ref_count_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Increase/Decrease the reference count on the cache line as to prevent LRU item removal....
gboolean dt_dev_pixelpipe_cache_peek(const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry, const int preferred_devid, void **cl_mem_output)
Non-owning lookup of an existing cache line.
void dt_dev_pixelpipe_cache_flush(const int id)
Remove cache lines matching id. Entries locked in read/write or having reference count greater than 0...
gboolean dt_dev_pixelpipe_cache_ref_host_entry_by_hash(const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry)
Resolve and retain an existing cacheline that already holds HOST pixels.
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
guint dt_pixelpipe_cache_wait_pending_count(void)
How many requests are outstanding. Diagnostics only.
void dt_pixelpipe_cache_wait_foreach_pending(dt_pixelpipe_cache_wait_visitor_t callback, gpointer user_data)
gboolean dt_pixelpipe_cache_wait_cancel(dt_pixelpipe_cache_wait_t *wait, gboolean *drained)
Remove wait from the queue if it is there, and reset it to an inert state.
GList * dt_pixelpipe_cache_wait_take_matching(const uint64_t hash, const uint64_t producer_node_key, gboolean *drained)
Take every waiter satisfied by a publication of hash from node producer_node_key.
gboolean dt_pixelpipe_cache_wait_enqueue(dt_pixelpipe_cache_wait_t *wait)
Queue wait, or refresh it in place if it is already queued for the same target.
void dt_pixelpipe_cache_wait_count_immediate_hit(void)
Count one cache hit that never needed to queue.
void dt_pixelpipe_cache_wait_get_stats(uint64_t *queued, uint64_t *served, uint64_t *cancelled, uint64_t *immediate_hits, uint64_t *misses)
Snapshot of the lifetime counters, for the dump. Any pointer may be NULL.
void dt_pixelpipe_cache_wait_count_miss(void)
Count one miss that is about to queue.
gboolean dt_dev_pixelpipe_get_realtime(const dt_dev_pixelpipe_t *pipe)
char * dt_pixelpipe_get_pipe_name(dt_dev_pixelpipe_type_t pipe_type)
void dt_dev_pixelpipe_create_nodes(dt_dev_pixelpipe_t *pipe)
void dt_dev_pixelpipe_set_realtime(dt_dev_pixelpipe_t *pipe, gboolean state)
void dt_dev_pixelpipe_cleanup_nodes(dt_dev_pixelpipe_t *pipe)
static void dt_dev_pixelpipe_set_hash(dt_dev_pixelpipe_t *pipe, const uint64_t hash)
static uint64_t dt_dev_backbuf_get_history_hash(const dt_backbuf_t *backbuf)
static uint64_t dt_dev_pixelpipe_get_hash(const dt_dev_pixelpipe_t *pipe)
static void dt_dev_pixelpipe_or_changed(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_change_t flags)
static void dt_dev_pixelpipe_set_cache_request(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_cache_request_t request, const struct dt_iop_module_t *module)
dt_dev_pixelpipe_change_t
@ DT_DEV_PIPE_REENTRY
@ DT_DEV_PIPE_ZOOMED
@ DT_DEV_PIPE_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:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_CACHELINE_READY
This signal is raised when one cacheline write lock is released. 1 : uint64_t cacheline hash no retur...
Definition signal.h:188
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
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_out
dt_dev_request_flags_t request_histogram
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_int shutdown
int mask_rasterization_step
Rasterisation step for drawn masks, in image pixels. Always >= 1.
dt_dev_pixelpipe_type_t type
dt_atomic_int changed
struct dt_develop_t * dev
int32_t gui_attached
Definition develop.h:167
dt_image_t image_storage
Definition develop.h:225
struct dt_develop_t::@13 color_picker
Authoritative darkroom color-picker state.
struct dt_iop_module_t * gui_module
Definition develop.h:170
dt_pthread_rwlock_t history_mutex
Definition develop.h:247
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
dt_atomic_int mask_preview_settings_revision
Revision of the global mask-preview appearance.
Definition develop.h:181
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
dt_iop_buffer_dsc_t dsc
Definition image.h:419
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:386
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)