Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
dev_roi_request.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel.
3 Copyright (C) 2026 Aurélien Pierre.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
20#include "common/conf.h"
23#include "develop/develop.h"
25
26#include <string.h>
27
28/* Same publication protocol as the geometry record and the viewport: one writer (the GUI
29 * thread), a generation that is odd mid-publication, readers that copy and retry.
30 */
31
46static inline gboolean _payload_equal(const dt_dev_roi_request_t *a, const dt_dev_roi_request_t *b)
47{
48 return a->box_width == b->box_width
49 && a->box_height == b->box_height
50 && a->processed_width == b->processed_width
51 && a->processed_height == b->processed_height
52 && a->preview_width == b->preview_width
53 && a->preview_height == b->preview_height
54 && a->natural_scale == b->natural_scale
55 && a->scaling == b->scaling
56 && a->center_x == b->center_x
57 && a->center_y == b->center_y
58 && a->valid == b->valid;
59}
60
62{
64 memset(&request, 0, sizeof(request));
65 request.natural_scale = -1.f;
66 request.scaling = 1.f;
67 request.center_x = 0.5f;
68 request.center_y = 0.5f;
69 return request;
70}
71
79
81{
83 memset(&request, 0, sizeof(request));
84 if(IS_NULL_PTR(dev)) return request;
85
87
88 for(;;)
89 {
90 const uint64_t before = dt_atomic_get_uint64(&store->generation);
91 if(before & 1) continue;
92
93 request = store->value;
94
95 const uint64_t after = dt_atomic_get_uint64(&store->generation);
96 if(before == after) break;
97 }
98
99 return request;
100}
101
103{
104 if(IS_NULL_PTR(dev)) return 0;
105
106 // One coherent read of each input, so the request cannot mix two viewport states or two
107 // geometries -- the whole point of publishing them as records in the first place.
108 const dt_dev_viewport_state_t viewport = dt_dev_viewport_get(dev);
110
112
113 next.box_width = viewport.box_width;
114 next.box_height = viewport.box_height;
115 next.scaling = viewport.scaling;
116 next.center_x = viewport.center_x;
117 next.center_y = viewport.center_y;
118 next.processed_width = geometry.processed_width;
119 next.processed_height = geometry.processed_height;
120
121 // dt_dev_get_natural_scale()'s guard, reproduced: no viewport allocation or no raw geometry
122 // means there is nothing to fit the image into, and -1 is the sentinel every consumer of the
123 // product already handles.
124 next.natural_scale = (viewport.configured && geometry.raw_inited)
125 ? dt_dev_roi_natural_scale(viewport.box_width, viewport.box_height,
126 geometry.processed_width, geometry.processed_height)
127 : -1.f;
128
129 // roundf(), NOT a truncation: these must match the ROI the worker requests, which rounds too.
130 // A truncation here disagreed by 1px whenever the fraction was >= 0.5, which is image-dependent,
131 // and silently broke dt_dev_pixelpipe_has_preview_output() -- hence ashift structure detection
132 // and drawing -- on some images but not others.
135
136 // DERIVED, never asserted. This used to be an unconditional TRUE, which made the record claim
137 // it was usable from dt_dev_init() onwards -- before any viewport, any image, any pipe -- and
138 // made dt_dev_reset_roi()'s invalidation unobservable, since the viewport reset republishes on
139 // the very next line.
140 //
141 // Each of these three flags is FALSE only while its own numbers are still zero: box_* is
142 // written only by dt_dev_viewport_set_box(), which sets `configured' in the same publication;
143 // raw_* only by _dt_dev_mipmap_prefetch_full(), which pairs a FALSE with 0x0; processed_* only
144 // by dt_dev_geometry_set_processed_size(), which always sets its flag. So a FALSE here always
145 // travels with a zero, which is what keeps _darkroom_pipeline_inputs_ready() (it tests the
146 // numbers, not the flags) rejecting exactly the states this reports as unusable -- the worker
147 // naps and retries instead of reaching _update_darkroom_roi(), whose !valid branch returns
148 // without writing its out-params and would leave the caller planning a 0x0 ROI.
149 //
150 // Do not clear one of these flags without zeroing its numbers, and do not gate this on
151 // anything a publication cannot re-derive.
152 next.valid = viewport.configured && geometry.raw_inited && geometry.processed_inited;
153
154 // Advance the generation only on a real change, so a consumer can use it as a cache key
155 // without being invalidated by every republication of identical numbers.
157 if(_payload_equal(&dev->roi_request.value, &next)) return published;
158
159 /* Drawn-mask rasterisation step, decided HERE because this is the GUI thread and the only
160 * place that knows both scales: the main pipe renders at natural_scale * scaling (the zoom
161 * the user set) and the preview at natural_scale (always fit). It must not be decided in the
162 * pipeline thread -- that would read a user preference and a viewport from a thread that owns
163 * neither, and would recompute per frame what changes only when the viewport does.
164 *
165 * "never" pins both to 1. Absent a GUI, nothing calls this at all, so the pipes keep the
166 * pixel-accurate step their init gave them. */
167 int main_step = 1;
168 int preview_step = 1;
169 if(dev->gui_attached && next.valid && next.natural_scale > 0.f)
170 {
171 /* The preference governs the MAIN pipe only. The preview is always rendered at fit size and
172 * is never what the user inspects for pixel accuracy, so its step follows its own ROI
173 * unconditionally. */
174 gchar *mode = dt_conf_get_string("plugins/darkroom/masks/rasterization");
175 const gboolean fast = IS_NULL_PTR(mode) || strcmp(mode, "never") != 0;
176 g_free(mode);
177
178 const float main_scale = next.natural_scale * next.scaling;
179 if(fast && main_scale > 0.f) main_step = (int)floorf(1.f / main_scale);
180 preview_step = (int)floorf(1.f / next.natural_scale);
181 }
184
185 /* Which pipes' PLANS does this change invalidate? Decided from the old payload while we still
186 * hold it. The preview plans from (natural_scale, processed, box) only -- _update_darkroom_roi()
187 * ignores scaling and centre for it -- so a zoom or pan tick must not cost a preview run. The
188 * main pipe plans from all of it. `valid' flipping is a plan change for both: the first usable
189 * record is what lets a pipe plan at all. */
190 const dt_dev_roi_request_t previous = dev->roi_request.value;
191 const gboolean preview_plan_changed = previous.processed_width != next.processed_width
192 || previous.processed_height != next.processed_height
193 || previous.box_width != next.box_width
194 || previous.box_height != next.box_height
195 || previous.natural_scale != next.natural_scale
196 || previous.valid != next.valid;
197
198 next.generation = published + 2; // stays even: the store's counter is the odd/even flag
200 dev->roi_request.value = next;
202
203 /* A record nobody replans from is a lie waiting to be displayed. This is the fix for #1157:
204 * the darkroom worker latches this record ONCE per iteration, and its history resync loop
205 * spans 100-400 ms -- so a history commit whose SYNCH flag lands inside that loop gets its
206 * flag consumed by a run still planned from the PREVIOUS latch. The killswitch raised with
207 * the flag does not save that run either: the worker resets it when processing starts. The
208 * run then renders the new history into the old geometry -- the border-clamped smear of
209 * issue #1157 -- publishes it, and, since this function used to flag nothing, NOTHING forced
210 * a corrective render: the frame was terminal until the user zoomed.
211 *
212 * So the publisher re-arms the pipes itself, which also makes the viewport setters' callers'
213 * manual flagging (dt_dev_pixelpipe_change_zoom_main, dt_dev_configure_real) a guarantee
214 * instead of a convention.
215 *
216 * ORDER MATTERS: the flags are raised AFTER the seqlock store above completes. A worker woken
217 * by the flag re-latches, and must not be able to re-latch the old record -- flag observed
218 * implies store visible. Raising them before the store would recreate the bug one level down.
219 *
220 * The shutdown is the same killswitch _change_pipe() raises: an in-flight run planned from
221 * the previous record is superseded and should stop wasting the device. */
222 if(dev->gui_attached)
223 {
225 {
228 }
229 /* Any payload change alters the main plan: _payload_equal() already returned FALSE, and
230 * every field it compares is either a main-plan input or derived from one. */
231 if(!IS_NULL_PTR(dev->pipe))
232 {
235 }
236 }
237
238 return next.generation;
239}
240
250
252{
254 if(IS_NULL_PTR(pipe)) return request;
255
256 for(;;)
257 {
259 if(before & 1) continue;
260
261 request = pipe->roi_request.value;
262
264 if(before == after) break;
265 }
266
267 return request;
268}
269
274
279
284
286{
287 return dt_dev_roi_request_get(dev).valid;
288}
289
290// clang-format off
291// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
292// vim: shiftwidth=2 expandtab tabstop=2 cindent
293// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
294// clang-format on
#define TRUE
Definition ashift_lsd.c:162
void dt_atomic_set_int(dt_atomic_int *var, int value)
uint64_t dt_atomic_get_uint64(const dt_atomic_uint64 *var)
void dt_atomic_set_uint64(dt_atomic_uint64 *var, uint64_t value)
gchar * dt_conf_get_string(const char *name)
Read the stored string for name as a private copy.
dt_dev_image_geometry_t dt_dev_geometry_snapshot(const dt_develop_t *dev)
dt_dev_roi_request_t dt_dev_roi_request_neutral(void)
The value a pipe carries before the worker has latched anything.
int32_t dt_dev_roi_request_preview_height(const dt_develop_t *dev)
void dt_dev_roi_request_latch(dt_dev_pixelpipe_t *pipe, const dt_dev_roi_request_t *request)
Publish onto a pipe the request its next run is planned from. Darkroom worker only.
float dt_dev_roi_request_natural_scale(const dt_develop_t *dev)
int32_t dt_dev_roi_request_preview_width(const dt_develop_t *dev)
dt_dev_roi_request_t dt_dev_roi_request_of_pipe(const dt_dev_pixelpipe_t *pipe)
static gboolean _payload_equal(const dt_dev_roi_request_t *a, const dt_dev_roi_request_t *b)
Do these two records carry the same numbers, ignoring the generation stamp?
uint64_t dt_dev_roi_request_publish(dt_develop_t *dev)
Recompute the derived members from the viewport and the geometry record, and publish if anything chan...
void dt_dev_roi_request_init(dt_develop_t *dev)
gboolean dt_dev_roi_request_valid(const dt_develop_t *dev)
dt_dev_roi_request_t dt_dev_roi_request_get(const dt_develop_t *dev)
static float dt_dev_roi_natural_scale(const int32_t box_width, const int32_t box_height, const int32_t processed_width, const int32_t processed_height)
natural scaling = MIN(box / processed, 1): the image fits the widget minus its borders.
dt_dev_viewport_state_t dt_dev_viewport_get(const dt_develop_t *dev)
GtkWidget * geometry
its size, under the preview
GtkTreeStore * store
its model, owned by the view
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
#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
void dt_dev_pixelpipe_set_mask_rasterization_step(dt_dev_pixelpipe_t *pipe, const int step)
Set the drawn-mask rasterisation step for the frame this pipe is about to run.
static void dt_dev_pixelpipe_or_changed(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_change_t flags)
@ DT_DEV_PIPE_ZOOMED
unsigned __int64 uint64_t
Definition strptime.c:75
Objective facts about the image a dev is working on.
dt_atomic_int shutdown
dt_dev_roi_request_store_t roi_request
The viewport snapshot this run was planned from.
dt_dev_roi_request_t value
The coherent set of numbers a darkroom pipe plans its ROI from.
What the darkroom view asks the pipeline to show: the window onto the image.
int32_t gui_attached
Definition develop.h:167
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:214
dt_dev_roi_request_store_t roi_request
What the darkroom pipes plan their ROI from: the viewport and the geometry, combined and derived once...
Definition develop.h:210
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214