Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
worker.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
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
19/*
20 * drawlayer realtime worker subsystem
21 *
22 * Realtime painting now advances in heartbeat batches:
23 * - read current layer content from the authoritative full-resolution
24 * `base_patch`,
25 * - rasterize a small dab batch into a worker-private scratch patch,
26 * - copy only the damaged area back into `base_patch`,
27 * - publish one regular pipeline refresh before continuing.
28 *
29 * This keeps base-patch ownership authoritative while avoiding long stretches
30 * where rasterization and pipeline refresh starve each other.
31 */
32
38
39#include "iop/drawlayer/cache.h" // dt_drawlayer_cache_patch_t, ..._wrlock()
40#include "iop/drawlayer/common.h" // dt_drawlayer_commit_dabs(), ..._build_worker_input_dab()
41#include "iop/drawlayer/module.h" // dt_iop_drawlayer_data_t
42#include "iop/drawlayer/runtime.h" // dt_iop_drawlayer_gui_data_t
43
44#include "control/user_message.h" // dt_control_log()
45#include "control/redraw.h" // dt_control_queue_redraw_center()
46#include "develop/develop.h" // dt_develop_t
47#include "develop/dev_history.h" // dt_dev_transient_params_set()
48#include "develop/gui_throttle.h" // dt_gui_throttle_get_pipe_runtime_us(), DT_THROTTLE_SLOT_MAIN
49#include "develop/imageop_gui.h" // dt_iop_gui_data()
50#include "system/dtpthread.h" // dt_pthread_mutex_t
51#include "common/times.h" // dt_get_wtime()
52#include "system/macros.h" // IS_NULL_PTR()
53
60
68
77
91
126
133
134static gboolean _paint_build_dab_cb(void *user_data, dt_drawlayer_paint_stroke_t *state,
136static void _paint_stroke_seed_cb(void *user_data, uint64_t stroke_seed);
137static void _publish_backend_progress(drawlayer_paint_backend_ctx_t *ctx, gboolean flush_pending);
138static gboolean _commit_dabs_on_gui_thread(gpointer user_data);
143 const dt_drawlayer_cache_patch_t *sample_patch,
145 dt_drawlayer_cache_patch_t *stroke_mask,
146 dt_drawlayer_damaged_rect_t *batch_damage);
149static inline gint64 _live_publish_interval_us(void);
150static inline gboolean _live_publish_deadline_reached(const dt_drawlayer_worker_t *rt, const gint64 input_ts,
151 const gint64 interval_us);
154static guint _rasterize_pending_dab_batch(drawlayer_paint_backend_ctx_t *ctx, gint64 budget_us);
157static guint _worker_batch_min_size(void);
158static void _log_worker_batch_timing(const char *tag, guint processed_dabs, guint thread_count, double elapsed_ms,
159 gboolean outer_loop);
160static gboolean _dab_bounds_in_patch(const dt_drawlayer_cache_patch_t *patch, float scale,
162#if defined(_OPENMP) && OUTER_LOOP
163static guint _rasterize_dab_batch_outer_loop(const GArray *dabs, guint max_dabs, float distance_percent,
164 const dt_drawlayer_cache_patch_t *sample_patch,
165 dt_drawlayer_cache_patch_t *patch, float scale,
166 dt_drawlayer_cache_patch_t *stroke_mask,
167 dt_drawlayer_damaged_rect_t *batch_damage,
168 const char *tag);
169static gboolean _dab_batch_supports_outer_loop(const GArray *dabs, guint count);
170#endif
171
172/* GUI worker painting into uint8 buffers is deimplemented. Realtime preview
173 * now relies on the regular pipeline/backbuffer path. */
174
175#if defined(_OPENMP) && OUTER_LOOP
176#include <omp.h>
177#endif
178
179#define DRAWLAYER_BATCH_TILE_SIZE 128
180#define DRAWLAYER_HEARTBEAT_PATCH_NAME "drawlayer heartbeat patch"
181#define DRAWLAYER_HEARTBEAT_MASK_NAME "drawlayer heartbeat stroke mask"
182#define DRAWLAYER_OUTER_LIVE_BATCH_MULTIPLIER 2u
183
187{
188 if(IS_NULL_PTR(self) || IS_NULL_PTR(state) || IS_NULL_PTR(input) || IS_NULL_PTR(dab)) return FALSE;
189
190 float lx = input->lx;
191 float ly = input->ly;
192 if(!input->have_layer_coords && !dt_drawlayer_widget_to_layer_coords(self, input->wx, input->wy, &lx, &ly))
193 return FALSE;
194
195 const float pressure_norm = _clamp01(input->pressure);
196 const float tilt_norm = _clamp01(input->tilt);
197 const float accel_norm = _clamp01(input->acceleration);
198 const gboolean map_pressure_size = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_SIZE) != 0u;
199 const gboolean map_pressure_opacity = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_OPACITY) != 0u;
200 const gboolean map_pressure_flow = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_FLOW) != 0u;
201 const gboolean map_pressure_softness = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_SOFTNESS) != 0u;
202 const gboolean map_tilt_size = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_SIZE) != 0u;
203 const gboolean map_tilt_opacity = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_OPACITY) != 0u;
204 const gboolean map_tilt_flow = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_FLOW) != 0u;
205 const gboolean map_tilt_softness = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_SOFTNESS) != 0u;
206 const gboolean map_accel_size = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_SIZE) != 0u;
207 const gboolean map_accel_opacity = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_OPACITY) != 0u;
208 const gboolean map_accel_flow = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_FLOW) != 0u;
209 const gboolean map_accel_softness = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_SOFTNESS) != 0u;
210 const drawlayer_mapping_profile_t pressure_profile = (drawlayer_mapping_profile_t)CLAMP(
214 const drawlayer_mapping_profile_t accel_profile = (drawlayer_mapping_profile_t)CLAMP(
216 const float pressure_coeff = _mapping_profile_value(pressure_profile, pressure_norm);
217 const float tilt_coeff = _mapping_profile_value(tilt_profile, tilt_norm);
218 const float accel_coeff = _mapping_profile_value(accel_profile, accel_norm);
219
220 float radius = fmaxf(input->brush_radius, 0.5f);
221 float opacity = _clamp01(input->brush_opacity);
222 float flow = _clamp01(input->brush_flow);
223 const float sprinkles = _clamp01(input->brush_sprinkles);
224 float hardness = _clamp01(input->brush_hardness);
225 const float base_radius = radius;
226 const float base_opacity = opacity;
227 const float base_flow = flow;
228 const float base_hardness = hardness;
229
230 if(map_pressure_size) radius *= pressure_coeff;
231 if(map_pressure_opacity) opacity *= pressure_coeff;
232 if(map_pressure_flow) flow *= pressure_coeff;
233 if(map_pressure_softness) hardness *= pressure_coeff;
234
235 if(map_tilt_size) radius *= tilt_coeff;
236 if(map_tilt_opacity) opacity *= tilt_coeff;
237 if(map_tilt_flow) flow *= tilt_coeff;
238 if(map_tilt_softness) hardness *= tilt_coeff;
239
240 if(map_accel_size) radius *= accel_coeff;
241 if(map_accel_opacity) opacity *= accel_coeff;
242 if(map_accel_flow) flow *= accel_coeff;
243 if(map_accel_softness) hardness *= accel_coeff;
244
245 radius = fmaxf(radius, 0.5f);
246 hardness = _clamp01(hardness);
247 float dir_x = 0.0f;
248 float dir_y = 0.0f;
249 if(state->have_last_input_dab)
250 {
251 const float dx = lx - state->last_input_dab.x;
252 const float dy = ly - state->last_input_dab.y;
253 const float dir_len = hypotf(dx, dy);
254 if(dir_len > 1e-6f)
255 {
256 dir_x = dx / dir_len;
257 dir_y = dy / dir_len;
258 }
259 }
260
262 .x = lx,
263 .y = ly,
264 .radius = radius,
265 .dir_x = dir_x,
266 .dir_y = dir_y,
267 .opacity = _clamp01(opacity),
268 .flow = _clamp01(flow),
269 .sprinkles = _clamp01(sprinkles),
270 .sprinkle_size = input->brush_sprinkle_size,
271 .sprinkle_coarseness = _clamp01(input->brush_sprinkle_coarseness),
272 .hardness = hardness,
273 .color = { input->color[0], input->color[1], input->color[2], 1.0f },
274 .display_color = { input->display_color[0], input->display_color[1], input->display_color[2] },
275 .shape = input->brush_shape,
276 .mode = input->brush_mode,
277 .stroke_batch = input->stroke_batch,
278 .stroke_pos = input->stroke_pos,
279 };
280
281 if((map_pressure_size || map_pressure_opacity || map_pressure_flow || map_pressure_softness || map_tilt_size
282 || map_tilt_opacity || map_tilt_flow || map_tilt_softness || map_accel_size || map_accel_opacity
283 || map_accel_flow || map_accel_softness)
285 || ((state->history && (state->history->len & 15u) == 0u))))
286 {
288 "[drawlayer] map p=%.4f t=%.4f a=%.4f coeff[p=%.4f t=%.4f a=%.4f] "
289 "base[r=%.2f o=%.3f f=%.3f h=%.3f] out[r=%.2f o=%.3f f=%.3f h=%.3f] "
290 "flags[p=%d%d%d%d t=%d%d%d%d a=%d%d%d%d]\n",
291 pressure_norm, tilt_norm, accel_norm, pressure_coeff, tilt_coeff, accel_coeff, base_radius,
292 base_opacity, base_flow, base_hardness, radius, _clamp01(opacity), _clamp01(flow), _clamp01(hardness),
293 map_pressure_size ? 1 : 0, map_pressure_opacity ? 1 : 0, map_pressure_flow ? 1 : 0,
294 map_pressure_softness ? 1 : 0, map_tilt_size ? 1 : 0, map_tilt_opacity ? 1 : 0, map_tilt_flow ? 1 : 0,
295 map_tilt_softness ? 1 : 0, map_accel_size ? 1 : 0, map_accel_opacity ? 1 : 0, map_accel_flow ? 1 : 0,
296 map_accel_softness ? 1 : 0);
297 }
298
299 return TRUE;
300}
301
302static gboolean _paint_build_dab_cb(void *user_data, dt_drawlayer_paint_stroke_t *state,
304{
306 return (ctx && ctx->self)
308 (dt_drawlayer_brush_dab_t *)out_dab)
309 : FALSE;
310}
311
312
313static void _paint_stroke_seed_cb(void *user_data, uint64_t stroke_seed)
314{
316 if(IS_NULL_PTR(ctx)) return;
317 if(ctx->stroke) dt_drawlayer_paint_runtime_set_stroke_seed(ctx->stroke, stroke_seed);
318}
319
320static void _publish_backend_progress(drawlayer_paint_backend_ctx_t *ctx, gboolean flush_pending)
321{
322 if(IS_NULL_PTR(ctx) || IS_NULL_PTR(ctx->self) || !flush_pending) return;
323
324 dt_iop_module_t *self = ctx->self;
327 dt_develop_t *dev = self->dev;
328 if(IS_NULL_PTR(ctx->worker) || IS_NULL_PTR(g) || IS_NULL_PTR(params) || IS_NULL_PTR(dev) || !ctx->worker->live_publish_damage.valid) return;
329
332 ctx->worker->live_publish_ts = g_get_monotonic_time();
333
334 const int sample_count = (int)g->stroke.stroke_sample_count;
335 dt_drawlayer_touch_stroke_commit_hash(params, sample_count, g->stroke.last_dab_valid, g->stroke.last_dab_x,
336 g->stroke.last_dab_y, ctx->worker->live_publish_serial);
337
338 /* Realtime heartbeats publish the live params through the thread-safe transient channel instead of
339 * writing permanent history. This re-renders the in-progress stroke (the bumped stroke_commit_hash
340 * re-keys the drawlayer piece's global_hash) without churning the undo stack or the database. The
341 * permanent history is written once, at the real commit (`dt_drawlayer_commit_dabs`), which also
342 * clears the transient slot. drawlayer does not change geometry, so a fast main-pipe top-only resync
343 * (`_sync_focused_in_place`) is enough — flag it explicitly since _set no longer auto-triggers.
344 *
345 * `params` is the WORKER'S OWN blob, seeded from `self->params` on the GUI thread at stroke
346 * begin. It is not `self->params`: that one belongs to the GUI thread, and bumping its hash
347 * here was an unsynchronised read-modify-write of a struct carrying two strings. The transient
348 * channel deep-copies what it is given, so a private blob is all it needs. Consequence worth
349 * knowing: the hash the COMMIT chains from is now the previous commit's rather than the last
350 * heartbeat's, which makes the committed value deterministic instead of a function of how many
351 * heartbeats happened to fire. */
352 dt_dev_transient_params_set(self, params, self->params_size, NULL, 0);
355
356 dt_print(DT_DEBUG_PERF, "[drawlayer] publish serial=%u hash=%u dabs=%d\n",
358}
359
362{
364 if(IS_NULL_PTR(g) || IS_NULL_PTR(input) || IS_NULL_PTR(stroke)) return;
365
366 drawlayer_paint_backend_ctx_t ctx = _make_backend_ctx(self, g->stroke.worker, stroke);
367 const dt_drawlayer_paint_callbacks_t callbacks = {
369 .on_stroke_seed = _paint_stroke_seed_cb,
370 };
371 if(!dt_drawlayer_paint_queue_raw_input(stroke, input)) return;
372 dt_drawlayer_paint_interpolate_path(stroke, &callbacks, &ctx);
373
374 if(stroke->pending_dabs && stroke->pending_dabs->len > 0)
375 {
376 const gint64 live_publish_interval_us = _live_publish_interval_us();
377 const gint64 input_ts = input->event_ts ? input->event_ts : g_get_monotonic_time();
378 if(ctx.worker && ctx.worker->live_publish_ts == 0)
379 ctx.worker->live_publish_ts = input_ts - live_publish_interval_us;
380
381 if(_live_publish_deadline_reached(ctx.worker, input_ts, live_publish_interval_us))
382 {
383 const guint processed_dabs = _rasterize_pending_dab_batch(&ctx, live_publish_interval_us);
384 if(processed_dabs > 0) _publish_backend_progress(&ctx, TRUE);
385 }
386 }
387}
388
390{
392 if(IS_NULL_PTR(g)) return;
393
394 dt_drawlayer_damaged_rect_t backend_damage = { 0 };
395 if(g->stroke.worker && dt_drawlayer_paint_merge_runtime_stroke_damage(g->stroke.worker->backend_path, &backend_damage))
396 {
397 g->process.cache_dirty = TRUE;
398 dt_drawlayer_paint_runtime_note_dab_damage(&g->process.cache_dirty_rect, &backend_damage);
399 }
400}
401
403 const dt_drawlayer_cache_patch_t *sample_patch,
405 dt_drawlayer_cache_patch_t *stroke_mask,
406 dt_drawlayer_damaged_rect_t *batch_damage)
407{
408 dt_drawlayer_paint_stroke_t *stroke = ctx ? ctx->stroke : NULL;
409 if(IS_NULL_PTR(ctx) || !stroke || !stroke->dab_window || IS_NULL_PTR(dab) || IS_NULL_PTR(patch) || IS_NULL_PTR(patch->pixels) || !stroke_mask
410 || !stroke_mask->pixels)
411 return FALSE;
412
413 dt_drawlayer_damaged_rect_t step_damage = { 0 };
415 patch, 1.0f, stroke_mask, &step_damage, stroke))
416 return FALSE;
417
418 if(step_damage.valid && batch_damage)
419 dt_drawlayer_paint_runtime_note_dab_damage(batch_damage, &step_damage);
420
421 return step_damage.valid;
422}
423
426
429{
430 if(IS_NULL_PTR(rt)) return;
431 if(rt->stroke && rt->stroke->pending_dabs)
432 {
433 g_array_free(rt->stroke->pending_dabs, TRUE);
434 rt->stroke->pending_dabs = NULL;
435 }
436 if(rt->stroke && rt->stroke->dab_window)
437 {
438 g_array_free(rt->stroke->dab_window, TRUE);
439 rt->stroke->dab_window = NULL;
440 }
442}
443
446{
447 if(IS_NULL_PTR(rt)) return FALSE;
448 if(rt->stroke) return TRUE;
450 if(IS_NULL_PTR(rt->stroke)) return FALSE;
451 rt->stroke->history = rt->backend_history;
452 rt->stroke->pending_dabs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
453 if(!rt->stroke->pending_dabs)
454 {
456 return FALSE;
457 }
458 rt->stroke->dab_window = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
459 if(!rt->stroke->dab_window)
460 {
461 g_array_free(rt->stroke->pending_dabs, TRUE);
462 rt->stroke->pending_dabs = NULL;
464 return FALSE;
465 }
466 return TRUE;
467}
468
471{
472 if(IS_NULL_PTR(rt)) return FALSE;
473 if(!rt->backend_history)
474 rt->backend_history = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
475 if(IS_NULL_PTR(rt->backend_history)) return FALSE;
476 if(!rt->stroke_raw_inputs)
477 rt->stroke_raw_inputs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_paint_raw_input_t));
478 if(IS_NULL_PTR(rt->stroke_raw_inputs)) return FALSE;
479 if(!_stroke_create(rt)) return FALSE;
480 rt->stroke->history = rt->backend_history;
483 g_array_set_size(rt->backend_history, 0);
484 g_array_set_size(rt->stroke_raw_inputs, 0);
485 return TRUE;
486}
487
490{
491 if(IS_NULL_PTR(rt)) return;
492 if(rt->backend_history) g_array_set_size(rt->backend_history, 0);
493 if(rt->stroke_raw_inputs) g_array_set_size(rt->stroke_raw_inputs, 0);
494 if(rt->stroke)
495 {
498 }
499}
500
505
513
515{
516 return rt ? &rt->workers[DRAWLAYER_RT_WORKER_BACKEND] : NULL;
517}
518
520{
521 return rt ? &rt->workers[DRAWLAYER_RT_WORKER_BACKEND] : NULL;
522}
523
524static inline gint64 _live_publish_interval_us(void)
525{
526 return MAX((gint64)dt_gui_throttle_get_pipe_runtime_us(DT_THROTTLE_SLOT_MAIN), (gint64)20000);
527}
528
529static guint _worker_batch_min_size(void)
530{
531#if defined(_OPENMP) && OUTER_LOOP
532 return MAX(1, omp_get_max_threads());
533#else
534 return 1;
535#endif
536}
537
538#if defined(_OPENMP) && OUTER_LOOP
539static gboolean _dab_batch_supports_outer_loop(const GArray *dabs, const guint count)
540{
541 if(IS_NULL_PTR(dabs) || count == 0) return FALSE;
542 for(guint i = 0; i < count; i++)
543 {
544 const dt_drawlayer_brush_dab_t *dab = &g_array_index(dabs, dt_drawlayer_brush_dab_t, i);
545 if(dab->mode == DT_DRAWLAYER_BRUSH_MODE_SMUDGE) return FALSE;
546 }
547 return TRUE;
548}
549#endif
550
551static void _log_worker_batch_timing(const char *tag, const guint processed_dabs, const guint thread_count,
552 const double elapsed_ms, const gboolean outer_loop)
553{
554 if(!(dt_get_debug_flags() & DT_DEBUG_PERF)) return;
555 dt_print(DT_DEBUG_PERF, "[drawlayer] batch worker=%s dabs=%u threads=%u outer=%d ms=%.3f\n",
556 tag ? tag : "unknown", processed_dabs, thread_count, outer_loop ? 1 : 0, elapsed_ms);
557}
558
559static inline gboolean _live_publish_deadline_reached(const dt_drawlayer_worker_t *rt, const gint64 input_ts,
560 const gint64 interval_us)
561{
562 return rt && input_ts - rt->live_publish_ts >= interval_us;
563}
564
567{
569 .self = self,
570 .worker = worker,
571 .stroke = stroke,
572 };
573}
574
575static gboolean _dab_bounds_in_patch(const dt_drawlayer_cache_patch_t *patch, const float scale,
577{
578 if(bounds) *bounds = (dt_drawlayer_damaged_rect_t){ 0 };
579 if(IS_NULL_PTR(patch) || IS_NULL_PTR(dab) || IS_NULL_PTR(bounds) || IS_NULL_PTR(patch->pixels) || patch->width <= 0 || patch->height <= 0
580 || dab->radius <= 0.0f || dab->opacity <= 0.0f || scale <= 0.0f)
581 return FALSE;
582
583 const float support_radius = dab->radius;
584 bounds->valid = TRUE;
585 bounds->nw[0] = MAX(0, (int)floorf((dab->x - support_radius) * scale) - patch->x);
586 bounds->nw[1] = MAX(0, (int)floorf((dab->y - support_radius) * scale) - patch->y);
587 bounds->se[0] = MIN(patch->width, (int)ceilf((dab->x + support_radius) * scale) - patch->x + 1);
588 bounds->se[1] = MIN(patch->height, (int)ceilf((dab->y + support_radius) * scale) - patch->y + 1);
589 return bounds->se[0] > bounds->nw[0] && bounds->se[1] > bounds->nw[1];
590}
591
593static gboolean _collect_batch_bounds(const GArray *dabs, const guint max_dabs,
594 const dt_drawlayer_cache_patch_t *patch,
595 dt_drawlayer_damaged_rect_t *batch_bounds)
596{
597 if(batch_bounds) *batch_bounds = (dt_drawlayer_damaged_rect_t){ 0 };
598 if(IS_NULL_PTR(dabs) || max_dabs == 0 || IS_NULL_PTR(patch) || IS_NULL_PTR(batch_bounds)) return FALSE;
599
600 /* Walk the upcoming batch in FIFO order and union every dab footprint that
601 * can touch the destination patch. This keeps the worker scratch patch as
602 * small as possible while still covering all writes for the current heartbeat. */
603 for(guint i = 0; i < max_dabs; i++)
604 {
605 dt_drawlayer_damaged_rect_t dab_bounds = { 0 };
606 const dt_drawlayer_brush_dab_t *dab = &g_array_index(dabs, dt_drawlayer_brush_dab_t, i);
607 if(_dab_bounds_in_patch(patch, 1.0f, dab, &dab_bounds))
608 dt_drawlayer_paint_runtime_note_dab_damage(batch_bounds, &dab_bounds);
609 }
610
611 return batch_bounds->valid;
612}
613
616 const dt_drawlayer_damaged_rect_t *batch_bounds)
617{
618 if(IS_NULL_PTR(rt) || IS_NULL_PTR(batch_bounds) || !batch_bounds->valid) return FALSE;
619
620 const int width = batch_bounds->se[0] - batch_bounds->nw[0];
621 const int height = batch_bounds->se[1] - batch_bounds->nw[1];
622 if(width <= 0 || height <= 0) return FALSE;
626 return FALSE;
627
628 const size_t needed = (size_t)width * height;
629 if(rt->heartbeat_transmittance_capacity < needed)
630 {
631 float *const grown = g_realloc_n(rt->heartbeat_transmittance, needed, sizeof(*grown));
632 if(IS_NULL_PTR(grown)) return FALSE;
633 rt->heartbeat_transmittance = grown;
634
635 float *const grown_noise = g_realloc_n(rt->heartbeat_noise, needed, sizeof(*grown_noise));
636 if(IS_NULL_PTR(grown_noise)) return FALSE;
637 rt->heartbeat_noise = grown_noise;
638
640 }
641
642 rt->heartbeat_patch.x = batch_bounds->nw[0];
643 rt->heartbeat_patch.y = batch_bounds->nw[1];
644 rt->heartbeat_stroke_mask.x = batch_bounds->nw[0];
645 rt->heartbeat_stroke_mask.y = batch_bounds->nw[1];
646 return TRUE;
647}
648
652{
653 if(IS_NULL_PTR(src) || IS_NULL_PTR(dst) || IS_NULL_PTR(src->pixels) || IS_NULL_PTR(dst->pixels) || dst->width <= 0 || dst->height <= 0) return;
654
655 const int src_x0 = dst->x - src->x;
656 const int src_y0 = dst->y - src->y;
657 if(src_x0 < 0 || src_y0 < 0 || src_x0 + dst->width > src->width || src_y0 + dst->height > src->height) return;
658
659 for(int y = 0; y < dst->height; y++)
660 {
661 const float *src_row = src->pixels + 4 * ((size_t)(src_y0 + y) * src->width + src_x0);
662 float *dst_row = dst->pixels + 4 * ((size_t)y * dst->width);
663 memcpy(dst_row, src_row, (size_t)dst->width * 4 * sizeof(float));
664 }
665}
666
670{
671 if(IS_NULL_PTR(dst) || IS_NULL_PTR(dst->pixels) || dst->width <= 0 || dst->height <= 0) return;
672 if(IS_NULL_PTR(src) || IS_NULL_PTR(src->pixels))
673 {
674 memset(dst->pixels, 0, (size_t)dst->width * dst->height * sizeof(float));
675 return;
676 }
677
678 const int src_x0 = dst->x - src->x;
679 const int src_y0 = dst->y - src->y;
680 if(src_x0 < 0 || src_y0 < 0 || src_x0 + dst->width > src->width || src_y0 + dst->height > src->height)
681 {
682 memset(dst->pixels, 0, (size_t)dst->width * dst->height * sizeof(float));
683 return;
684 }
685
686 for(int y = 0; y < dst->height; y++)
687 {
688 const float *src_row = src->pixels + (size_t)(src_y0 + y) * src->width + src_x0;
689 float *dst_row = dst->pixels + (size_t)y * dst->width;
690 memcpy(dst_row, src_row, (size_t)dst->width * sizeof(float));
691 }
692}
693
696 const dt_drawlayer_damaged_rect_t *local_damage,
697 dt_drawlayer_damaged_rect_t *absolute_damage)
698{
699 if(absolute_damage) *absolute_damage = (dt_drawlayer_damaged_rect_t){ 0 };
700 if(IS_NULL_PTR(patch) || IS_NULL_PTR(local_damage) || !local_damage->valid || IS_NULL_PTR(absolute_damage)) return FALSE;
701
702 *absolute_damage = *local_damage;
703 absolute_damage->nw[0] += patch->x;
704 absolute_damage->nw[1] += patch->y;
705 absolute_damage->se[0] += patch->x;
706 absolute_damage->se[1] += patch->y;
707 return TRUE;
708}
709
712 const dt_drawlayer_damaged_rect_t *local_damage,
714{
715 if(IS_NULL_PTR(src) || IS_NULL_PTR(dst) || IS_NULL_PTR(local_damage) || !local_damage->valid || IS_NULL_PTR(src->pixels) || IS_NULL_PTR(dst->pixels)) return;
716
717 const int copy_w = local_damage->se[0] - local_damage->nw[0];
718 const int copy_h = local_damage->se[1] - local_damage->nw[1];
719 if(copy_w <= 0 || copy_h <= 0) return;
720
721 const int dst_x0 = src->x + local_damage->nw[0] - dst->x;
722 const int dst_y0 = src->y + local_damage->nw[1] - dst->y;
723 if(dst_x0 < 0 || dst_y0 < 0 || dst_x0 + copy_w > dst->width || dst_y0 + copy_h > dst->height) return;
724
725 for(int y = 0; y < copy_h; y++)
726 {
727 const float *src_row = src->pixels + 4 * ((size_t)(local_damage->nw[1] + y) * src->width + local_damage->nw[0]);
728 float *dst_row = dst->pixels + 4 * ((size_t)(dst_y0 + y) * dst->width + dst_x0);
729 memcpy(dst_row, src_row, (size_t)copy_w * 4 * sizeof(float));
730 }
731}
732
735 const dt_drawlayer_damaged_rect_t *local_damage,
737{
738 if(IS_NULL_PTR(src) || IS_NULL_PTR(dst) || IS_NULL_PTR(local_damage) || !local_damage->valid || IS_NULL_PTR(src->pixels) || IS_NULL_PTR(dst->pixels)) return;
739
740 const int copy_w = local_damage->se[0] - local_damage->nw[0];
741 const int copy_h = local_damage->se[1] - local_damage->nw[1];
742 if(copy_w <= 0 || copy_h <= 0) return;
743
744 const int dst_x0 = src->x + local_damage->nw[0] - dst->x;
745 const int dst_y0 = src->y + local_damage->nw[1] - dst->y;
746 if(dst_x0 < 0 || dst_y0 < 0 || dst_x0 + copy_w > dst->width || dst_y0 + copy_h > dst->height) return;
747
748 for(int y = 0; y < copy_h; y++)
749 {
750 const float *src_row = src->pixels + (size_t)(local_damage->nw[1] + y) * src->width + local_damage->nw[0];
751 float *dst_row = dst->pixels + (size_t)(dst_y0 + y) * dst->width + dst_x0;
752 memcpy(dst_row, src_row, (size_t)copy_w * sizeof(float));
753 }
754}
755
758 const dt_drawlayer_damaged_rect_t *local_damage)
759{
760 if(IS_NULL_PTR(patch) || IS_NULL_PTR(local_damage) || !local_damage->valid || IS_NULL_PTR(patch->pixels)) return;
761
762 const int clear_w = local_damage->se[0] - local_damage->nw[0];
763 const int clear_h = local_damage->se[1] - local_damage->nw[1];
764 if(clear_w <= 0 || clear_h <= 0) return;
765
766 for(int y = 0; y < clear_h; y++)
767 {
768 float *row = patch->pixels + 4 * ((size_t)(local_damage->nw[1] + y) * patch->width + local_damage->nw[0]);
769 memset(row, 0, (size_t)clear_w * 4 * sizeof(float));
770 }
771}
772
775 const dt_drawlayer_damaged_rect_t *local_damage)
776{
777 if(IS_NULL_PTR(patch) || IS_NULL_PTR(local_damage) || !local_damage->valid || IS_NULL_PTR(patch->pixels)) return;
778
779 const int clear_w = local_damage->se[0] - local_damage->nw[0];
780 const int clear_h = local_damage->se[1] - local_damage->nw[1];
781 if(clear_w <= 0 || clear_h <= 0) return;
782
783 for(int y = 0; y < clear_h; y++)
784 {
785 float *row = patch->pixels + (size_t)(local_damage->nw[1] + y) * patch->width + local_damage->nw[0];
786 memset(row, 0, (size_t)clear_w * sizeof(float));
787 }
788}
789
790#if defined(_OPENMP) && OUTER_LOOP
791static dt_drawlayer_paint_stroke_t *_create_batch_runtime(void)
792{
794 if(IS_NULL_PTR(runtime)) return NULL;
795 runtime->dab_window = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
796 if(!runtime->dab_window)
797 {
799 return NULL;
800 }
801 return runtime;
802}
803
804static void _destroy_batch_runtime(dt_drawlayer_paint_stroke_t **runtime)
805{
806 if(!runtime || !*runtime) return;
807 if((*runtime)->dab_window) g_array_free((*runtime)->dab_window, TRUE);
808 (*runtime)->dab_window = NULL;
810}
811
812static void _lock_batch_tiles(omp_lock_t *locks, const int tile_cols, const int tile_origin_x,
813 const int tile_origin_y, const dt_drawlayer_damaged_rect_t *bounds)
814{
815 if(!locks || tile_cols <= 0 || IS_NULL_PTR(bounds) || !bounds->valid) return;
816 const int tx0 = bounds->nw[0] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x;
817 const int ty0 = bounds->nw[1] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y;
818 const int tx1 = MAX(tx0, (bounds->se[0] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x);
819 const int ty1 = MAX(ty0, (bounds->se[1] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y);
820 for(int ty = ty0; ty <= ty1; ty++)
821 for(int tx = tx0; tx <= tx1; tx++)
822 omp_set_lock(&locks[ty * tile_cols + tx]);
823}
824
825static void _unlock_batch_tiles(omp_lock_t *locks, const int tile_cols, const int tile_origin_x,
826 const int tile_origin_y, const dt_drawlayer_damaged_rect_t *bounds)
827{
828 if(!locks || tile_cols <= 0 || IS_NULL_PTR(bounds) || !bounds->valid) return;
829 const int tx0 = bounds->nw[0] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x;
830 const int ty0 = bounds->nw[1] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y;
831 const int tx1 = MAX(tx0, (bounds->se[0] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x);
832 const int ty1 = MAX(ty0, (bounds->se[1] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y);
833 for(int ty = ty1; ty >= ty0; ty--)
834 for(int tx = tx1; tx >= tx0; tx--)
835 omp_unset_lock(&locks[ty * tile_cols + tx]);
836}
837
838static guint _rasterize_dab_batch_outer_loop(const GArray *dabs, const guint max_dabs, const float distance_percent,
839 const dt_drawlayer_cache_patch_t *sample_patch,
840 dt_drawlayer_cache_patch_t *patch, const float scale,
841 dt_drawlayer_cache_patch_t *stroke_mask,
842 dt_drawlayer_damaged_rect_t *batch_damage,
843 const char *tag)
844{
845 if(IS_NULL_PTR(dabs) || max_dabs == 0 || IS_NULL_PTR(patch) || IS_NULL_PTR(patch->pixels) || patch->width <= 0 || patch->height <= 0) return 0;
846
847 const guint thread_count = _worker_batch_min_size();
848 dt_drawlayer_damaged_rect_t batch_bounds = { 0 };
849 for(guint i = 0; i < max_dabs; i++)
850 {
851 dt_drawlayer_damaged_rect_t dab_bounds = { 0 };
852 const dt_drawlayer_brush_dab_t *dab = &g_array_index(dabs, dt_drawlayer_brush_dab_t, i);
853 if(_dab_bounds_in_patch(patch, scale, dab, &dab_bounds))
854 dt_drawlayer_paint_runtime_note_dab_damage(&batch_bounds, &dab_bounds);
855 }
856 if(!batch_bounds.valid) return 0;
857
858 const int tile_origin_x = batch_bounds.nw[0] / DRAWLAYER_BATCH_TILE_SIZE;
859 const int tile_origin_y = batch_bounds.nw[1] / DRAWLAYER_BATCH_TILE_SIZE;
860 const int tile_cols = MAX(1, (batch_bounds.se[0] + DRAWLAYER_BATCH_TILE_SIZE - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x);
861 const int tile_rows = MAX(1, (batch_bounds.se[1] + DRAWLAYER_BATCH_TILE_SIZE - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y);
862 dt_drawlayer_paint_stroke_t **thread_runtime = g_malloc0((size_t)thread_count * sizeof(*thread_runtime));
863 dt_drawlayer_damaged_rect_t *thread_damage = g_malloc0((size_t)thread_count * sizeof(*thread_damage));
864 omp_lock_t *tile_locks = g_malloc0((size_t)tile_cols * tile_rows * sizeof(*tile_locks));
865 if(!thread_runtime || IS_NULL_PTR(thread_damage) || !tile_locks)
866 {
867 g_free(thread_runtime);
868 g_free(thread_damage);
869 g_free(tile_locks);
870 return 0;
871 }
872
873 for(guint i = 0; i < thread_count; i++)
874 {
875 thread_runtime[i] = _create_batch_runtime();
876 if(!thread_runtime[i])
877 {
878 for(guint k = 0; k < i; k++) _destroy_batch_runtime(&thread_runtime[k]);
879 g_free(thread_runtime);
880 g_free(thread_damage);
881 g_free(tile_locks);
882 return 0;
883 }
884 }
885
886 for(int i = 0; i < tile_cols * tile_rows; i++)
887 omp_init_lock(&tile_locks[i]);
888
889 const double t0 = dt_get_wtime();
890#pragma omp parallel for default(shared)
891 for(guint i = 0; i < max_dabs; i++)
892 {
893 const int tid = omp_get_thread_num();
894 dt_drawlayer_paint_stroke_t *runtime = thread_runtime[tid];
895 dt_drawlayer_damaged_rect_t runtime_damage = { 0 };
896 dt_drawlayer_damaged_rect_t dab_damage = { 0 };
897 dt_drawlayer_damaged_rect_t bounds = { 0 };
898 const dt_drawlayer_brush_dab_t *dab = &g_array_index(dabs, dt_drawlayer_brush_dab_t, i);
899
902 if(runtime->dab_window) g_array_set_size(runtime->dab_window, 0);
903 if(!_dab_bounds_in_patch(patch, scale, dab, &bounds))
904 continue;
905
906 _lock_batch_tiles(tile_locks, tile_cols, tile_origin_x, tile_origin_y, &bounds);
907 dt_drawlayer_paint_rasterize_segment_to_buffer(dab, distance_percent, sample_patch, patch, scale, stroke_mask,
908 &runtime_damage, runtime);
909 _unlock_batch_tiles(tile_locks, tile_cols, tile_origin_x, tile_origin_y, &bounds);
910
911 if(dt_drawlayer_paint_runtime_get_stroke_damage(&runtime_damage, &dab_damage))
912 dt_drawlayer_paint_runtime_note_dab_damage(&thread_damage[tid], &dab_damage);
913 }
914 const double t1 = dt_get_wtime();
915
916 if(batch_damage) dt_drawlayer_paint_runtime_state_reset(batch_damage);
917 for(guint i = 0; i < thread_count; i++)
918 {
919 if(batch_damage) dt_drawlayer_paint_runtime_note_dab_damage(batch_damage, &thread_damage[i]);
920 _destroy_batch_runtime(&thread_runtime[i]);
921 }
922 for(int i = 0; i < tile_cols * tile_rows; i++)
923 omp_destroy_lock(&tile_locks[i]);
924
925 g_free(thread_runtime);
926 g_free(thread_damage);
927 g_free(tile_locks);
928
929 _log_worker_batch_timing(tag, max_dabs, thread_count, 1000.0 * (t1 - t0), TRUE);
930 return max_dabs;
931}
932#endif
933
935{
937 dt_drawlayer_paint_stroke_t *stroke = ctx ? ctx->stroke : NULL;
938 dt_drawlayer_worker_t *worker = ctx ? ctx->worker : NULL;
939 if(IS_NULL_PTR(g) || IS_NULL_PTR(stroke) || IS_NULL_PTR(worker) || !stroke->pending_dabs || stroke->pending_dabs->len == 0) return 0;
940 if(IS_NULL_PTR(g->process.base_patch.pixels) || g->process.base_patch.width <= 0 || g->process.base_patch.height <= 0)
941 return 0;
942
943 const guint min_batch = _worker_batch_min_size();
944 const guint remaining_dabs = stroke->pending_dabs->len;
945 const guint batch_dabs = (budget_us > 0)
946 ? MIN(remaining_dabs, min_batch * DRAWLAYER_OUTER_LIVE_BATCH_MULTIPLIER)
947 : remaining_dabs;
948 if(batch_dabs == 0) return 0;
949
950 dt_drawlayer_damaged_rect_t batch_bounds = { 0 };
951 if(!_collect_batch_bounds(stroke->pending_dabs, batch_dabs, &g->process.base_patch, &batch_bounds))
952 {
953 g_array_remove_range(stroke->pending_dabs, 0, batch_dabs);
954 return batch_dabs;
955 }
956 if(!_ensure_heartbeat_batch_buffers(worker, &batch_bounds)) return 0;
957
958 dt_drawlayer_cache_patch_t *const heartbeat_patch = &worker->heartbeat_patch;
959 dt_drawlayer_cache_patch_t *const heartbeat_mask = &worker->heartbeat_stroke_mask;
960 dt_drawlayer_damaged_rect_t batch_damage = { 0 };
961 guint processed_dabs = 0;
962 gboolean used_outer_loop = FALSE;
963 const double batch_t0 = dt_get_wtime();
964
965 /* Build the heartbeat scratch patch from the latest committed layer state,
966 * then keep the authoritative base patch read-locked while blur/smudge
967 * sample from it during rasterization. */
968 dt_drawlayer_cache_patch_rdlock(&g->process.base_patch);
969 dt_drawlayer_cache_patch_rdlock(&g->process.stroke_mask);
970 _copy_rgba_batch_from_locked_patch(&g->process.base_patch, heartbeat_patch);
971 _copy_mask_batch_from_locked_patch(&g->process.stroke_mask, heartbeat_mask);
972 /* Preferred path: one coverage accumulation, one composite. It is the whole batch or
973 * nothing -- `dt_drawlayer_brush_batch_is_uniform` refuses a run whose dabs do not share
974 * the mode, opacity, colour and flow the closed form is derived for, and the serial loop
975 * below then handles every dab as before. */
976 dt_drawlayer_brush_batch_t uniform_batch = { 0 };
978 batch_dabs, &uniform_batch)
980 {
981 if(dt_drawlayer_brush_rasterize_batch(&uniform_batch, heartbeat_patch, 1.0f, heartbeat_mask,
983 &batch_damage))
984 {
985 used_outer_loop = TRUE;
986 processed_dabs = batch_dabs;
987 _log_worker_batch_timing("batch", processed_dabs, _worker_batch_min_size(),
988 1000.0 * (dt_get_wtime() - batch_t0), TRUE);
989 /* The batch path does not feed the 3-dab window, which exists solely so a SMUDGE dab can
990 * see its predecessor. Clear it rather than leave a window with a hole in it: a following
991 * SMUDGE dab then reads as the start of a run, which is the defined conservative state
992 * (`previous_sample` NULL, pickup cleared). The tile-lock path had the same hole and left
993 * it stale. */
994 if(stroke->dab_window) g_array_set_size(stroke->dab_window, 0);
995 }
996 }
997#if defined(_OPENMP) && OUTER_LOOP
998 if(processed_dabs == 0 && batch_dabs >= min_batch
999 && _dab_batch_supports_outer_loop(stroke->pending_dabs, batch_dabs))
1000 {
1001 used_outer_loop = TRUE;
1002 processed_dabs = _rasterize_dab_batch_outer_loop(stroke->pending_dabs, batch_dabs,
1003 _clamp01(stroke->distance_percent),
1004 &g->process.base_patch, heartbeat_patch, 1.0f,
1005 heartbeat_mask, &batch_damage, "heartbeat");
1006 }
1007#endif
1008 while(processed_dabs < batch_dabs)
1009 {
1010 const dt_drawlayer_brush_dab_t *dab
1011 = &g_array_index(stroke->pending_dabs, dt_drawlayer_brush_dab_t, processed_dabs);
1012 _process_backend_dab(dab, ctx, &g->process.base_patch, heartbeat_patch, heartbeat_mask, &batch_damage);
1013 processed_dabs++;
1014
1015 if(budget_us > 0)
1016 {
1017 const gint64 elapsed_us = (gint64)(1000000.0 * (dt_get_wtime() - batch_t0));
1018 if(processed_dabs >= min_batch && elapsed_us >= budget_us) break;
1019 }
1020 }
1021 dt_drawlayer_cache_patch_rdunlock(&g->process.stroke_mask);
1022 dt_drawlayer_cache_patch_rdunlock(&g->process.base_patch);
1023
1024 if(processed_dabs == 0) return 0;
1025
1026 const dt_drawlayer_brush_dab_t *last_dab
1027 = &g_array_index(stroke->pending_dabs, dt_drawlayer_brush_dab_t, processed_dabs - 1);
1028 g_array_remove_range(stroke->pending_dabs, 0, processed_dabs);
1029
1030 if(batch_damage.valid)
1031 {
1032 dt_drawlayer_damaged_rect_t absolute_damage = { 0 };
1033 if(_translate_batch_damage(heartbeat_patch, &batch_damage, &absolute_damage))
1034 {
1035 dt_drawlayer_cache_patch_wrlock(&g->process.base_patch);
1036 dt_drawlayer_cache_patch_wrlock(&g->process.stroke_mask);
1037 _copy_rgba_damage_to_locked_patch(heartbeat_patch, &batch_damage, &g->process.base_patch);
1038 _copy_mask_damage_to_locked_patch(heartbeat_mask, &batch_damage, &g->process.stroke_mask);
1039 _clear_rgba_damage_in_patch(heartbeat_patch, &batch_damage);
1040 _clear_mask_damage_in_patch(heartbeat_mask, &batch_damage);
1041 dt_dev_pixelpipe_cache_flush_host_pinned_image(g->process.base_patch.pixels,
1042 g->process.base_patch.cache_entry, -1);
1043
1044 /* Publish the damage INSIDE the write-lock span that wrote the pixels it describes.
1045 * The pipeline reads and resets this rect under the base-patch READ lock (taken in
1046 * `_update_runtime_state`, released in `_release_runtime_source`), which is mutually
1047 * exclusive with this write lock -- so this was the one access of the three that sat
1048 * outside a lock. Published after the unlock, a pipeline read landing in the gap saw
1049 * pixels without the damage naming them, then cleared the rect: the window was
1050 * uploaded but composited against the wrong rectangle. */
1051 g->process.cache_dirty = TRUE;
1052 dt_drawlayer_paint_runtime_note_dab_damage(&g->process.cache_dirty_rect, &absolute_damage);
1053
1054 dt_drawlayer_cache_patch_wrunlock(&g->process.stroke_mask);
1055 dt_drawlayer_cache_patch_wrunlock(&g->process.base_patch);
1056
1059 g->stroke.last_dab_valid = TRUE;
1060 g->stroke.last_dab_x = last_dab->x;
1061 g->stroke.last_dab_y = last_dab->y;
1062 }
1063 }
1064
1065 if(!used_outer_loop)
1066 _log_worker_batch_timing("heartbeat", processed_dabs, 1, 1000.0 * (dt_get_wtime() - batch_t0), FALSE);
1067 return processed_dabs;
1068}
1069
1072{
1074 if(IS_NULL_PTR(worker)) return;
1075 worker->ring_head = 0;
1076 worker->ring_tail = 0;
1077 worker->ring_count = 0;
1078}
1079
1081static gboolean _rt_queue_empty(const dt_drawlayer_worker_t *rt)
1082{
1083 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1084 return !worker || worker->ring_count == 0;
1085}
1086
1088static gboolean _rt_queue_full(const dt_drawlayer_worker_t *rt)
1089{
1090 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1091 return !worker || (worker->ring_capacity > 0 && worker->ring_count >= worker->ring_capacity);
1092}
1093
1096 const dt_drawlayer_paint_raw_input_t *event)
1097{
1099 if(IS_NULL_PTR(worker) || IS_NULL_PTR(event) || IS_NULL_PTR(worker->ring) || worker->ring_capacity == 0 || _rt_queue_full(rt)) return FALSE;
1100 worker->ring[worker->ring_tail] = *event;
1101 worker->ring_tail = (worker->ring_tail + 1) % worker->ring_capacity;
1102 worker->ring_count++;
1103 return TRUE;
1104}
1105
1109{
1111 if(IS_NULL_PTR(worker) || IS_NULL_PTR(event) || IS_NULL_PTR(worker->ring) || worker->ring_capacity == 0 || _rt_queue_empty(rt)) return FALSE;
1112 *event = worker->ring[worker->ring_head];
1113 worker->ring_head = (worker->ring_head + 1) % worker->ring_capacity;
1114 worker->ring_count--;
1115 return TRUE;
1116}
1117
1118static inline gboolean _worker_is_started(const drawlayer_rt_worker_t *worker)
1119{
1120 return worker && worker->thread_started;
1121}
1122
1123static inline gboolean _worker_is_busy(const drawlayer_rt_worker_t *worker)
1124{
1125 return worker && worker->state == DT_DRAWLAYER_WORKER_STATE_BUSY;
1126}
1127
1128static inline gboolean _worker_pause_requested(const drawlayer_rt_worker_t *worker)
1129{
1130 return worker
1133}
1134
1136{
1137 return rt && rt->stroke && rt->stroke->pending_dabs && rt->stroke->pending_dabs->len > 0;
1138}
1139
1142{
1144 if(!IS_NULL_PTR(worker)) worker->state = state;
1145}
1146
1149{
1150#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
1151 struct sched_param param = { 0 };
1152 const int max_prio = sched_get_priority_max(SCHED_FIFO);
1153 if(max_prio > 0)
1154 {
1155 param.sched_priority = MIN(max_prio, 80);
1156 pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
1157 }
1158#endif
1159}
1160
1163{
1164 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1165 return worker && (_worker_is_busy(worker) || worker->ring_count > 0
1168}
1169
1172{
1173 return _workers_active_locked(rt);
1174}
1175
1178{
1179 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1180 return worker && rt->finish_commit_pending && rt->painting
1181 && *rt->finish_commit_pending && !*rt->painting
1182 && !_worker_is_busy(worker) && worker->ring_count == 0
1184}
1185
1188{
1189 gboolean active = FALSE;
1190 if(IS_NULL_PTR(rt)) return FALSE;
1192 active = _workers_active_locked(rt);
1194 return active;
1195}
1196
1199{
1200 gboolean active = FALSE;
1201 if(IS_NULL_PTR(rt)) return FALSE;
1203 active = _workers_any_active_locked(rt);
1205 return active;
1206}
1207
1220static gboolean _commit_dabs_on_gui_thread(gpointer user_data)
1221{
1222 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1224 dt_drawlayer_worker_t *rt = IS_NULL_PTR(g) ? NULL : g->stroke.worker;
1225 if(IS_NULL_PTR(rt)) return G_SOURCE_REMOVE;
1226
1227 /* Clear the in-flight marker before committing, so a commit that re-arms the request
1228 * can post a fresh one. Both this callback and `_cancel_pending_gui_commit` run on the
1229 * GUI thread, so the source can never be removed while it is dispatching. */
1231 rt->commit_idle_id = 0;
1233
1235 return G_SOURCE_REMOVE;
1236}
1237
1240{
1241 if(IS_NULL_PTR(rt)) return;
1243 const guint source_id = rt->commit_idle_id;
1244 rt->commit_idle_id = 0;
1246 if(source_id) g_source_remove(source_id);
1247}
1248
1251{
1253 drawlayer_paint_backend_ctx_t ctx = _make_backend_ctx(self, rt, rt ? rt->stroke : NULL);
1254 if(!IS_NULL_PTR(self) && g && !IS_NULL_PTR(rt) && rt->stroke && rt->stroke->pending_dabs && rt->stroke->pending_dabs->len > 0)
1255 {
1256 /* Once a stroke has emitted dabs, keep draining the heartbeat backlog in
1257 * this same idle phase instead of returning to the outer loop after each
1258 * batch. Otherwise long strokes can momentarily "stall" between batches as
1259 * the worker bounces through the queue/idle state machine waiting for the
1260 * next loop turn. */
1261 while(rt->stroke && rt->stroke->pending_dabs && rt->stroke->pending_dabs->len > 0)
1262 {
1263 guint processed_dabs = 0;
1265 const gboolean should_stop = _worker_pause_requested(_backend_worker_const(rt))
1268 if(should_stop) break;
1269
1270 const gint64 interval_us = _live_publish_interval_us();
1271 processed_dabs = _rasterize_pending_dab_batch(&ctx, interval_us);
1272 if(processed_dabs == 0) break;
1273
1274 /* Honour the same deadline the input path honours. This loop is where most dabs of a
1275 * fast drag are processed, and it used to publish after EVERY batch: a transient-params
1276 * write, a TOP_CHANGED flag and a `dt_control_queue_redraw_center()` -- the last an
1277 * asynchronous signal raise, so a malloc, a calloc and a GLib idle source each time --
1278 * at up to one per 2 x nthreads dabs. The darkroom loop was dropping the surplus anyway,
1279 * so the frames the user sees are unchanged; what goes away is the flooding.
1280 * `live_publish_damage` is unioned across batches (see `_rasterize_pending_dab_batch`),
1281 * so a skipped publish loses no coverage -- the next one carries it. */
1282 if(_live_publish_deadline_reached(rt, g_get_monotonic_time(), interval_us))
1284 }
1285
1286 /* The last batch of a drain must reach the screen -- but ONLY once the stroke is over.
1287 * This hook runs on every idle transition, which during a live stroke is once per input
1288 * batch, so publishing here unconditionally defeated the deadline above entirely: measured
1289 * on a 10.4 s stroke, 434 publishes at a 19 ms median gap against a main pipe that needed
1290 * 109 ms a frame and delivered 75. The surplus is not free -- each publish raises a centre
1291 * redraw, and the GUI thread was compositing the window 48 times a second to show 9 new
1292 * frames, on the same thread that has to service the pointer.
1293 * Mid-stroke, skipping loses nothing: `live_publish_damage` accumulates and the next
1294 * publish carries it. After the stroke, this is the path that shows the final dabs before
1295 * the commit lands. */
1296 if(rt->painting && !*rt->painting)
1298 }
1299
1300 if(IS_NULL_PTR(rt)) return;
1301 /* Decide and post under the SAME lock acquisition. The old code read the predicate, dropped
1302 * the lock, then committed — and a GUI push landing in that window made the commit's
1303 * `dt_drawlayer_worker_wait_idle` wait on a ring only this thread drains. */
1306 rt->commit_idle_id = g_idle_add_full(G_PRIORITY_HIGH_IDLE, _commit_dabs_on_gui_thread, self, NULL);
1308}
1309
1312 const dt_drawlayer_paint_raw_input_t *input)
1313{
1314 if(IS_NULL_PTR(rt) || IS_NULL_PTR(input)) return;
1315 if(!IS_NULL_PTR(rt) && !IS_NULL_PTR(input) && input->stroke_pos == DT_DRAWLAYER_PAINT_STROKE_FIRST)
1316 {
1317 _stroke_begin(rt);
1318 }
1319
1320 if(!rt->stroke && !_stroke_begin(rt)) return;
1321 if(rt->stroke_raw_inputs) g_array_append_val(rt->stroke_raw_inputs, *input);
1322 _process_backend_input(self, input, rt->stroke);
1323}
1324
1327{
1328 if(IS_NULL_PTR(rt)) return;
1329
1330 /* Stroke end only finalizes path generation and asks for commit. Pending dabs
1331 * still drain through the same heartbeat batches as live painting, so the
1332 * worker keeps alternating raster and pipeline refresh until the stroke is
1333 * fully materialized. */
1334 if(rt->stroke)
1335 {
1337 }
1340 pthread_cond_broadcast(&rt->worker_cond);
1342}
1343
1344static const drawlayer_rt_callbacks_t _rt_callbacks[] = {
1346 .thread_name = "draw-back",
1347 .process_sample = _backend_worker_process_sample,
1348 .process_stroke_end = _backend_worker_process_stroke_end,
1349 .on_idle = _backend_worker_on_idle,
1350 },
1351};
1352
1355{
1356 if(IS_NULL_PTR(rt_out) || !*rt_out) return;
1357 dt_drawlayer_worker_t *rt = *rt_out;
1359 _stop_worker(self ? self : rt->self, rt);
1360 _stroke_destroy(rt);
1361 if(rt->backend_history) g_array_free(rt->backend_history, TRUE);
1362 if(rt->stroke_raw_inputs) g_array_free(rt->stroke_raw_inputs, TRUE);
1366 g_free(rt->heartbeat_transmittance);
1367 rt->heartbeat_transmittance = NULL;
1368 g_free(rt->heartbeat_noise);
1369 rt->heartbeat_noise = NULL;
1371 dt_free(worker->ring);
1372 pthread_cond_destroy(&rt->worker_cond);
1374 dt_free(rt);
1375 *rt_out = NULL;
1376}
1377
1380 gboolean *painting, gboolean *finish_commit_pending,
1381 guint *stroke_sample_count, uint32_t *current_stroke_batch)
1382{
1383 if(IS_NULL_PTR(rt_out)) return;
1384 _rt_destroy_state(self, rt_out);
1385 dt_drawlayer_worker_t *rt = NULL;
1386 rt = g_malloc0(sizeof(*rt));
1387 *rt_out = rt;
1388 if(IS_NULL_PTR(rt)) return;
1389
1390 rt->self = self;
1391 rt->painting = painting;
1392 rt->finish_commit_pending = finish_commit_pending;
1393 rt->stroke_sample_count = stroke_sample_count;
1394 rt->current_stroke_batch = current_stroke_batch;
1397 pthread_cond_init(&rt->worker_cond, NULL);
1398
1401 worker->ring = g_malloc_n(worker->ring_capacity, sizeof(dt_drawlayer_paint_raw_input_t));
1402 rt->backend_history = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
1403 rt->stroke_raw_inputs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_paint_raw_input_t));
1405 _stroke_create(rt);
1406}
1407
1410{
1411 _rt_destroy_state(NULL, rt_out);
1412}
1413
1416{
1417 (void)self;
1418 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1419 if(IS_NULL_PTR(rt) || IS_NULL_PTR(worker) || !_worker_is_started(worker)) return TRUE;
1420
1421 /* TRIPWIRE. This must only ever be called from the GUI thread. The ring's only consumer is
1422 * the worker itself, so waiting here from the worker parks it forever, and `_stop_worker` —
1423 * whose `stop` flag is the sole escape — calls this first, so the GUI thread then hangs on
1424 * the same predicate. Do NOT "fix" a hit by returning early: at that point the ring may hold
1425 * a new stroke's events, and committing would wipe that stroke's session state instead.
1426 * Post the commit to the GUI thread (`_commit_dabs_on_gui_thread`) as `_backend_worker_on_idle`
1427 * does. */
1428 if(worker->thread_started && pthread_equal(pthread_self(), worker->thread))
1430 "[drawlayer] BUG: dt_drawlayer_worker_wait_idle called on the worker thread — it consumes the "
1431 "ring it is about to wait on, and will deadlock with the GUI thread.\n");
1432
1434 while(_worker_is_busy(worker) || worker->ring_count > 0)
1435 {
1436 if(worker->stop) break;
1438 }
1440 return TRUE;
1441}
1442
1447
1449static void *_drawlayer_worker_main(void *user_data)
1450{
1452 dt_drawlayer_worker_t *rt = ctx ? ctx->rt : NULL;
1453 dt_free(ctx);
1454
1455 dt_iop_module_t *self = rt ? rt->self : NULL;
1458 if(IS_NULL_PTR(self) || IS_NULL_PTR(rt) || IS_NULL_PTR(worker) || IS_NULL_PTR(cb)) return NULL;
1459
1462
1463 while(TRUE)
1464 {
1465 dt_drawlayer_paint_raw_input_t event = { 0 };
1466 gboolean have_event = FALSE;
1467 gboolean have_backlog = FALSE;
1468
1470 while(!worker->stop && (_worker_pause_requested(worker)
1471 || (worker->ring_count == 0 && !_backend_pending_dabs_locked(rt))))
1472 {
1475 pthread_cond_broadcast(&rt->worker_cond);
1477 if(cb->on_idle) cb->on_idle(self, rt);
1479 if(!worker->stop && (_worker_pause_requested(worker)
1480 || (worker->ring_count == 0 && !_backend_pending_dabs_locked(rt))))
1482 }
1483
1484 if(worker->stop)
1485 {
1487 pthread_cond_broadcast(&rt->worker_cond);
1489 break;
1490 }
1491
1492 have_backlog = _backend_pending_dabs_locked(rt);
1493 have_event = _rt_queue_pop_locked(rt, &event);
1494 _rt_set_worker_state(rt, (have_event || have_backlog) ? DT_DRAWLAYER_WORKER_STATE_BUSY
1497
1498 if(!have_event)
1499 {
1500 if(cb->on_idle) cb->on_idle(self, rt);
1501 continue;
1502 }
1503
1504 if(event.stroke_pos == DT_DRAWLAYER_PAINT_STROKE_END)
1505 {
1506 /* If stroke-end carries a real raw sample (button release), process it
1507 * before closing the stroke so the final segment is not lost. */
1508 if(cb->process_sample && event.event_ts != 0)
1509 cb->process_sample(self, rt, &event);
1510 if(cb->process_stroke_end) cb->process_stroke_end(self, rt);
1511 }
1512 else
1513 {
1514 if(cb->process_sample) cb->process_sample(self, rt, &event);
1515 }
1516
1518 have_backlog = _backend_pending_dabs_locked(rt);
1520 : (have_backlog ? DT_DRAWLAYER_WORKER_STATE_BUSY
1522 pthread_cond_broadcast(&rt->worker_cond);
1524 if(cb->on_idle) cb->on_idle(self, rt);
1525 }
1526
1527 return NULL;
1528}
1529
1532{
1534 if(IS_NULL_PTR(self) || IS_NULL_PTR(rt) || IS_NULL_PTR(worker)) return FALSE;
1535 if(_worker_is_started(worker))
1536 return TRUE;
1537
1538 worker->stop = FALSE;
1540
1541 drawlayer_rt_thread_ctx_t *ctx = g_malloc(sizeof(*ctx));
1542 if(IS_NULL_PTR(ctx)) return FALSE;
1543 ctx->rt = rt;
1544
1545 const int err = dt_pthread_create(&worker->thread, _drawlayer_worker_main, ctx, TRUE);
1546 if(err != 0)
1547 {
1548 dt_free(ctx);
1549 worker->thread_started = FALSE;
1551 return FALSE;
1552 }
1553
1554 worker->thread_started = TRUE;
1556 return TRUE;
1557}
1558
1568
1571{
1572 if(IS_NULL_PTR(rt)) return;
1575 /* Before joining: a commit the worker posted must not fire against a torn-down module.
1576 * This runs on the GUI thread, as does the callback, so once the source is removed here
1577 * no commit can be in flight. */
1579
1580 if(worker && _worker_is_started(worker))
1581 {
1583
1585 worker->stop = TRUE;
1586 pthread_cond_broadcast(&rt->worker_cond);
1588
1589 pthread_join(worker->thread, NULL);
1590 memset(&worker->thread, 0, sizeof(worker->thread));
1591 worker->thread_started = FALSE;
1593 worker->stop = FALSE;
1595 _stroke_clear(rt);
1596 }
1597}
1598
1613
1616{
1617 (void)self;
1619 if(IS_NULL_PTR(rt) || IS_NULL_PTR(worker) || !_worker_is_started(worker)) return;
1620
1623 pthread_cond_broadcast(&rt->worker_cond);
1625}
1626
1629 const dt_drawlayer_paint_raw_input_t *event)
1630{
1631 if(IS_NULL_PTR(rt) || IS_NULL_PTR(event)) return FALSE;
1632 if(!_start_worker(self, rt)) return FALSE;
1633
1635 const gboolean ok = _rt_queue_push_locked(rt, event);
1636 pthread_cond_broadcast(&rt->worker_cond);
1638 return ok;
1639}
1640
1643 const dt_drawlayer_paint_raw_input_t *input)
1644{
1645 if(IS_NULL_PTR(rt) || IS_NULL_PTR(input)) return FALSE;
1646 if(!_start_worker(self, rt)) return FALSE;
1647
1648 const dt_drawlayer_paint_raw_input_t event = *input;
1649
1650 gboolean ok = FALSE;
1652 if(!_rt_queue_full(rt))
1653 {
1654 ok = _rt_queue_push_locked(rt, &event);
1655 if(ok && rt->stroke_sample_count) (*rt->stroke_sample_count)++;
1656 }
1657 if(!ok)
1658 {
1659 /* Saturated queue policy: abort current stroke deterministically instead of
1660 * silently dropping or coalescing raw input events. Keep FIFO backlog and
1661 * force a stroke end at the newest queued slot. */
1663 dt_drawlayer_paint_raw_input_t end_input = *input;
1665 end_input.event_index = input->event_index + 1u;
1666 const dt_drawlayer_paint_raw_input_t end_event = end_input;
1667 if(worker->ring && worker->ring_count > 0)
1668 {
1669 const guint last_index = (worker->ring_tail + worker->ring_capacity - 1) % worker->ring_capacity;
1670 worker->ring[last_index] = end_event;
1671 ok = TRUE;
1672 }
1673 else
1674 ok = _rt_queue_push_locked(rt, &end_event);
1675
1677 dt_control_alert(_("Drawing not saved"), _("drawing worker queue is full, stroke aborted"), NULL);
1678 }
1679 pthread_cond_broadcast(&rt->worker_cond);
1681 return ok;
1682}
1683
1686 const dt_drawlayer_paint_raw_input_t *input)
1687{
1688 if(IS_NULL_PTR(rt)) return FALSE;
1689
1690 dt_drawlayer_paint_raw_input_t end_input = { 0 };
1691 if(input) end_input = *input;
1692 if(end_input.stroke_batch == 0u && rt->current_stroke_batch)
1693 end_input.stroke_batch = *rt->current_stroke_batch;
1695
1696 if(_enqueue_event(self, rt, &end_input)) return TRUE;
1697 dt_control_log(_("drawing worker queue is full"));
1698 return FALSE;
1699}
1700
1703 gboolean *painting, gboolean *finish_commit_pending,
1704 guint *stroke_sample_count, uint32_t *current_stroke_batch)
1705{
1706 _rt_init_state(self, worker, painting, finish_commit_pending, stroke_sample_count, current_stroke_batch);
1707}
1708
1714
1717{
1718 return _rt_workers_active((dt_drawlayer_worker_t *)worker);
1719}
1720
1726
1728{
1729 return _start_worker(self, rt);
1730}
1731
1736
1739{
1740 if(snapshot) *snapshot = (dt_drawlayer_worker_snapshot_t){ 0 };
1742 if(IS_NULL_PTR(rt) || IS_NULL_PTR(snapshot)) return;
1743
1745 const drawlayer_rt_worker_t *backend = _backend_worker_const(rt);
1746 snapshot->backend_state = backend->state;
1747 snapshot->backend_queue_count = backend->ring_count;
1748 snapshot->commit_pending = rt->finish_commit_pending && *rt->finish_commit_pending;
1750}
1751
1754{
1755 if(IS_NULL_PTR(worker)) return;
1757 if(worker->finish_commit_pending) *worker->finish_commit_pending = TRUE;
1758 pthread_cond_broadcast(&worker->worker_cond);
1760}
1761
1778
1783
1791
1799
1802{
1803 if(IS_NULL_PTR(worker)) return;
1805 _stroke_clear(worker);
1806 _reset_live_publish(worker);
1808}
1809
1812{
1813 return worker ? worker->stroke_raw_inputs : NULL;
1814}
1815
1818 const dt_drawlayer_paint_raw_input_t *input)
1819{
1820 return _enqueue_input(worker ? worker->self : NULL, worker, (const dt_drawlayer_paint_raw_input_t *)input);
1821}
1822
1825 const dt_drawlayer_paint_raw_input_t *input)
1826{
1827 return _enqueue_stroke_end(worker ? worker->self : NULL, worker, input);
1828}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
@ DT_DRAWLAYER_BRUSH_MODE_SMUDGE
Definition brush.h:53
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static const int row
void dt_control_alert(const char *title, const char *message, const char *item)
Tell the user something that must not go unseen: what it says stays true after a toast would have fad...
Definition control.c:883
void dt_control_log(const char *msg,...)
Definition control.c:839
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:970
gboolean dt_drawlayer_widget_to_layer_coords(dt_iop_module_t *self, const double wx, const double wy, float *lx, float *ly)
void dt_dev_transient_params_set(dt_iop_module_t *module, const void *params, const size_t params_size, const void *blend_params, const size_t blend_size)
Out-of-history transient param channel.
void dt_drawlayer_touch_stroke_commit_hash(dt_iop_drawlayer_params_t *params, const int dab_count, const gboolean have_last_dab, const float last_dab_x, const float last_dab_y, const uint32_t publish_serial)
Definition drawlayer.c:1703
gboolean dt_drawlayer_commit_dabs(dt_iop_module_t *self, gboolean record_history)
Definition drawlayer.c:1804
int dt_pthread_create(pthread_t *thread, void *(*start_routine)(void *), void *arg, const gboolean realtime)
Definition dtpthread.c:52
void dt_pthread_setname(const char *name)
Definition dtpthread.c:147
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:127
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
Definition dtpthread.h:104
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:132
static int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
Wait on cond, releasing mutex for the duration.
Definition dtpthread.h:149
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:117
int dt_gui_throttle_get_pipe_runtime_us(const dt_throttle_slot_t slot)
@ DT_THROTTLE_SLOT_MAIN
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
static float _clamp01(const float v)
Clamp scalar to [0,1].
gboolean dt_drawlayer_brush_batch_is_uniform(const dt_drawlayer_brush_dab_t *dabs, const guint count, dt_drawlayer_brush_batch_t *out)
Decide whether a run of dabs can take the batch path, and describe it if so.
gboolean dt_drawlayer_brush_rasterize_batch(const dt_drawlayer_brush_batch_t *batch, dt_drawlayer_cache_patch_t *patch, const float scale, dt_drawlayer_cache_patch_t *stroke_mask, float *const transmittance, float *const noise_scratch, dt_drawlayer_damaged_rect_t *batch_damage)
Rasterize a whole batch in two passes: accumulate transmittance, then composite once.
Patch/cache helpers for drawlayer process and preview buffers.
Private drawlayer module types and lightweight shared helpers.
static float _mapping_profile_value(const drawlayer_mapping_profile_t profile, const float x)
@ DRAWLAYER_INPUT_MAP_ACCEL_SIZE
@ DRAWLAYER_INPUT_MAP_ACCEL_FLOW
@ DRAWLAYER_INPUT_MAP_PRESSURE_SOFTNESS
@ DRAWLAYER_INPUT_MAP_TILT_SOFTNESS
@ DRAWLAYER_INPUT_MAP_TILT_OPACITY
@ DRAWLAYER_INPUT_MAP_PRESSURE_OPACITY
@ DRAWLAYER_INPUT_MAP_TILT_FLOW
@ DRAWLAYER_INPUT_MAP_ACCEL_OPACITY
@ DRAWLAYER_INPUT_MAP_TILT_SIZE
@ DRAWLAYER_INPUT_MAP_PRESSURE_FLOW
@ DRAWLAYER_INPUT_MAP_ACCEL_SOFTNESS
@ DRAWLAYER_INPUT_MAP_PRESSURE_SIZE
drawlayer_mapping_profile_t
@ DRAWLAYER_PROFILE_INV_QUADRATIC
@ DRAWLAYER_PROFILE_LINEAR
#define DRAWLAYER_WORKER_RING_CAPACITY
void dt_drawlayer_paint_path_state_reset(dt_drawlayer_paint_stroke_t *state)
Reset full stroke state including queued raw input events.
void dt_drawlayer_paint_runtime_state_destroy(dt_drawlayer_damaged_rect_t **state)
Destroy stroke-damage accumulator state and null pointer.
gboolean dt_drawlayer_paint_queue_raw_input(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_paint_raw_input_t *input)
Queue one raw input event (FIFO).
void dt_drawlayer_paint_runtime_private_destroy(dt_drawlayer_paint_stroke_t **state)
Destroy stroke runtime payload and null pointer.
void dt_drawlayer_paint_interpolate_path(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_paint_callbacks_t *callbacks, void *user_data)
Drain queued raw input events and append evenly spaced dabs to state->pending_dabs.
void dt_drawlayer_paint_runtime_set_stroke_seed(dt_drawlayer_paint_stroke_t *state, const uint64_t seed)
Set deterministic stroke seed for noise-derived effects.
void dt_drawlayer_paint_finalize_path(dt_drawlayer_paint_stroke_t *state)
Finalize stroke by force-emitting the pending first sample if needed.
void dt_drawlayer_paint_runtime_private_reset(dt_drawlayer_paint_stroke_t *state)
Reset transient stroke runtime payload between strokes.
gboolean dt_drawlayer_paint_runtime_get_stroke_damage(const dt_drawlayer_damaged_rect_t *state, dt_drawlayer_damaged_rect_t *out_rect)
Read accumulated stroke damage rectangle.
gboolean dt_drawlayer_paint_rasterize_segment_to_buffer(const dt_drawlayer_brush_dab_t *dab, const float distance_percent, const dt_drawlayer_cache_patch_t *sample_patch, dt_drawlayer_cache_patch_t *patch, const float scale, dt_drawlayer_cache_patch_t *stroke_mask, dt_drawlayer_damaged_rect_t *runtime_state, dt_drawlayer_paint_stroke_t *runtime_private)
Replay one emitted dab segment into a float buffer through brush API.
void dt_drawlayer_paint_runtime_state_reset(dt_drawlayer_damaged_rect_t *state)
Reset stroke-damage accumulator to empty/invalid.
void dt_drawlayer_paint_runtime_note_dab_damage(dt_drawlayer_damaged_rect_t *state, const dt_drawlayer_damaged_rect_t *dab_rect)
Merge one dab rectangle into an accumulator rectangle.
dt_drawlayer_damaged_rect_t * dt_drawlayer_paint_runtime_state_create(void)
Allocate zero-initialized stroke-damage accumulator state.
gboolean dt_drawlayer_paint_merge_runtime_stroke_damage(dt_drawlayer_damaged_rect_t *path_state, dt_drawlayer_damaged_rect_t *target_rect)
Merge path-state damage into target rectangle and clear path-state accumulator.
dt_drawlayer_paint_stroke_t * dt_drawlayer_paint_runtime_private_create(void)
Allocate stroke runtime payload object used by paint+brush internals.
@ DT_DRAWLAYER_PAINT_STROKE_MIDDLE
@ DT_DRAWLAYER_PAINT_STROKE_FIRST
@ DT_DRAWLAYER_PAINT_STROKE_END
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
@ DT_DEBUG_INPUT
Definition logging.h:64
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_ALWAYS
Definition logging.h:49
int32_t dt_get_debug_flags(void)
Definition darktable.c:2094
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#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
#define omp_get_max_threads()
Definition openmp.h:90
#define omp_get_thread_num()
Definition openmp.h:91
gboolean dt_dev_pixelpipe_cache_flush_host_pinned_image(void *host_ptr, dt_pixel_cache_entry_t *entry_hint, int devid)
Drop cached pinned OpenCL images associated with a given host buffer.
static void dt_dev_pixelpipe_or_changed(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_change_t flags)
@ DT_DEV_PIPE_TOP_CHANGED
Asking the interface to repaint itself.
Private runtime state/helpers shared by drawlayer module entrypoints.
void dt_drawlayer_cache_patch_wrunlock(const dt_drawlayer_cache_patch_t *patch)
Release write lock on shared patch cache entry.
void dt_drawlayer_cache_patch_wrlock(const dt_drawlayer_cache_patch_t *patch)
Acquire write lock on shared patch cache entry.
void dt_drawlayer_cache_patch_clear(dt_drawlayer_cache_patch_t *patch, const char *external_alloc_name)
Release patch storage and reset patch metadata.
gboolean dt_drawlayer_cache_ensure_process_patch_buffer(dt_drawlayer_cache_patch_t *process_patch, dt_drawlayer_cache_patch_t *process_stroke_mask, const int width, const int height, const char *patch_buffer_name, const char *mask_buffer_name)
Ensure process patch and process-stroke-mask backing buffers exist.
void dt_drawlayer_cache_patch_rdlock(const dt_drawlayer_cache_patch_t *patch)
Acquire read lock on shared patch cache entry.
void dt_drawlayer_cache_patch_rdunlock(const dt_drawlayer_cache_patch_t *patch)
Release read lock on shared patch cache entry.
Shared drawlayer runtime helpers used across module/runtime files.
const float uint32_t state[4]
const float const float param
unsigned __int64 uint64_t
Definition strptime.c:75
dt_drawlayer_paint_stroke_t * stroke
Definition worker.c:131
dt_iop_module_t * self
Definition worker.c:129
dt_drawlayer_worker_t * worker
Definition worker.c:130
Per-worker callback vtable.
Definition worker.c:71
drawlayer_rt_sample_cb process_sample
Definition worker.c:73
const char * thread_name
Definition worker.c:72
drawlayer_rt_stroke_end_cb process_stroke_end
Definition worker.c:74
drawlayer_rt_idle_cb on_idle
Definition worker.c:75
dt_drawlayer_worker_t * rt
Definition worker.c:1445
One worker thread runtime including event ring buffer.
Definition worker.c:80
pthread_t thread
Definition worker.c:81
gboolean thread_started
Definition worker.c:82
dt_drawlayer_worker_state_t state
Definition worker.c:88
dt_drawlayer_paint_raw_input_t * ring
Definition worker.c:83
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
A run of dabs that share everything the batch composite needs to be a single step.
Definition brush.h:120
Fully resolved input dab descriptor.
Definition brush.h:66
Generic float RGBA patch stored either in malloc memory or pixel cache.
Integer axis-aligned rectangle in buffer coordinates.
Callback bundle used by stroke processing entry points.
dt_drawlayer_paint_build_dab_cb build_dab
One raw pointer event queued to stroke processing.
Mutable stroke runtime state owned by worker/backend code.
Drawlayer worker global state shared with drawlayer module.
Definition worker.c:94
gboolean * finish_commit_pending
Definition worker.c:97
dt_drawlayer_damaged_rect_t * backend_path
Definition worker.c:106
uint32_t * current_stroke_batch
Definition worker.c:99
dt_drawlayer_cache_patch_t heartbeat_patch
Definition worker.c:107
size_t heartbeat_transmittance_capacity
Definition worker.c:111
guint * stroke_sample_count
Definition worker.c:98
GArray * stroke_raw_inputs
Definition worker.c:104
dt_drawlayer_cache_patch_t heartbeat_stroke_mask
Definition worker.c:108
drawlayer_rt_worker_t workers[DRAWLAYER_RT_WORKER_COUNT]
Definition worker.c:102
pthread_cond_t worker_cond
Definition worker.c:101
float * heartbeat_transmittance
Definition worker.c:109
dt_drawlayer_damaged_rect_t live_publish_damage
Definition worker.c:116
dt_iop_drawlayer_params_t publish_params
Definition worker.c:118
uint32_t live_publish_serial
Definition worker.c:115
dt_pthread_mutex_t worker_mutex
Definition worker.c:100
float * heartbeat_noise
Definition worker.c:110
GArray * backend_history
Definition worker.c:103
dt_iop_module_t * self
Definition worker.c:95
dt_drawlayer_paint_stroke_t * stroke
Definition worker.c:105
gboolean * painting
Definition worker.c:96
struct dt_develop_t * dev
Definition imageop.h:311
int32_t params_size
Definition imageop.h:335
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static double dt_get_wtime(void)
Definition times.h:43
Telling the user something happened.
static void _backend_worker_on_idle(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Backend-worker idle hook.
Definition worker.c:1250
static gboolean _stroke_create(dt_drawlayer_worker_t *rt)
Create stroke runtime if missing.
Definition worker.c:445
#define DRAWLAYER_BATCH_TILE_SIZE
Definition worker.c:179
void(* drawlayer_rt_stroke_end_cb)(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Callback signature for stroke-end event processing.
Definition worker.c:65
void dt_drawlayer_worker_get_snapshot(const dt_drawlayer_worker_t *worker, dt_drawlayer_worker_snapshot_t *snapshot)
Return a thread-safe worker snapshot for runtime scheduling.
Definition worker.c:1737
static void _backend_worker_process_stroke_end(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Handle backend stroke end: flush, reset, and request commit.
Definition worker.c:1326
static gboolean _workers_active_locked(const dt_drawlayer_worker_t *rt)
Check whether workers still have pending activity (lock must be held).
Definition worker.c:1162
static void _copy_mask_damage_to_locked_patch(const dt_drawlayer_cache_patch_t *src, const dt_drawlayer_damaged_rect_t *local_damage, dt_drawlayer_cache_patch_t *dst)
Copy the written scratch mask sub-rectangle back into the locked base stroke mask.
Definition worker.c:734
static gboolean _worker_is_busy(const drawlayer_rt_worker_t *worker)
Definition worker.c:1123
static gboolean _translate_batch_damage(const dt_drawlayer_cache_patch_t *patch, const dt_drawlayer_damaged_rect_t *local_damage, dt_drawlayer_damaged_rect_t *absolute_damage)
Translate scratch-local damage coordinates back into base-patch coordinates.
Definition worker.c:695
static gboolean _rt_queue_full(const dt_drawlayer_worker_t *rt)
Test whether event queue is full.
Definition worker.c:1088
void dt_drawlayer_worker_stop(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Stop realtime and full-resolution worker threads.
Definition worker.c:1732
static guint _worker_batch_min_size(void)
Definition worker.c:529
static void _rt_destroy_state(dt_iop_module_t *self, dt_drawlayer_worker_t **rt_out)
Stop and free an existing worker state object.
Definition worker.c:1354
void dt_drawlayer_worker_cleanup(dt_drawlayer_worker_t **worker)
Public worker cleanup entry point.
Definition worker.c:1710
void dt_drawlayer_worker_reset_backend_path(dt_drawlayer_worker_t *worker)
Reset worker-owned backend damage accumulator.
Definition worker.c:1784
static guint _rasterize_pending_dab_batch(drawlayer_paint_backend_ctx_t *ctx, gint64 budget_us)
Definition worker.c:934
static gboolean _rt_queue_push_locked(dt_drawlayer_worker_t *rt, const dt_drawlayer_paint_raw_input_t *event)
Push one event in ring queue (lock must be held).
Definition worker.c:1095
drawlayer_rt_worker_kind_t
Internal worker slot kinds (currently backend only).
Definition worker.c:56
@ DRAWLAYER_RT_WORKER_COUNT
Definition worker.c:58
@ DRAWLAYER_RT_WORKER_BACKEND
Definition worker.c:57
static gboolean _backend_pending_dabs_locked(const dt_drawlayer_worker_t *rt)
Definition worker.c:1135
gboolean dt_drawlayer_worker_wait_idle(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Wait until worker queue is drained and not busy.
Definition worker.c:1415
void dt_drawlayer_worker_pause(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Pause worker processing after current callback returns.
Definition worker.c:1600
gboolean dt_drawlayer_worker_ensure_running(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Ensure realtime/backend worker threads are started.
Definition worker.c:1727
static gboolean _live_publish_deadline_reached(const dt_drawlayer_worker_t *rt, const gint64 input_ts, const gint64 interval_us)
Definition worker.c:559
static gboolean _start_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Start backend worker thread if not running.
Definition worker.c:1531
void dt_drawlayer_worker_snapshot_params(dt_drawlayer_worker_t *worker, const dt_iop_drawlayer_params_t *params)
Seed the worker's private params blob for the stroke about to start. GUI thread only.
Definition worker.c:1770
static gboolean _enqueue_event(dt_iop_module_t *self, dt_drawlayer_worker_t *rt, const dt_drawlayer_paint_raw_input_t *event)
Generic enqueue helper ensuring worker startup.
Definition worker.c:1628
static void _clear_mask_damage_in_patch(dt_drawlayer_cache_patch_t *patch, const dt_drawlayer_damaged_rect_t *local_damage)
Clear only the flushed alpha-mask sub-rectangle in the heartbeat scratch mask.
Definition worker.c:774
static void _clear_rgba_damage_in_patch(dt_drawlayer_cache_patch_t *patch, const dt_drawlayer_damaged_rect_t *local_damage)
Clear only the flushed RGBA sub-rectangle in the heartbeat scratch patch.
Definition worker.c:757
static void _copy_mask_batch_from_locked_patch(const dt_drawlayer_cache_patch_t *src, dt_drawlayer_cache_patch_t *dst)
Copy one locked alpha-mask source region into the heartbeat scratch mask.
Definition worker.c:668
static void _reset_backend_path(dt_drawlayer_worker_t *rt)
Definition worker.c:501
gboolean dt_drawlayer_worker_enqueue_stroke_end(dt_drawlayer_worker_t *worker, const dt_drawlayer_paint_raw_input_t *input)
Public FIFO enqueue for stroke-end event.
Definition worker.c:1824
static void _process_backend_input(dt_iop_module_t *self, const dt_drawlayer_paint_raw_input_t *input, dt_drawlayer_paint_stroke_t *stroke)
Definition worker.c:360
#define DRAWLAYER_HEARTBEAT_MASK_NAME
Definition worker.c:181
#define DRAWLAYER_HEARTBEAT_PATCH_NAME
Definition worker.c:180
void dt_drawlayer_worker_init(dt_iop_module_t *self, dt_drawlayer_worker_t **worker, gboolean *painting, gboolean *finish_commit_pending, guint *stroke_sample_count, uint32_t *current_stroke_batch)
Public worker initialization entry point.
Definition worker.c:1702
static drawlayer_rt_worker_t * _backend_worker(dt_drawlayer_worker_t *rt)
Definition worker.c:514
static gboolean _workers_any_active_locked(const dt_drawlayer_worker_t *rt)
Check whether any worker activity remains.
Definition worker.c:1171
static void * _drawlayer_worker_main(void *user_data)
Worker main loop: FIFO dequeue, process, and idle scheduling.
Definition worker.c:1449
static gboolean _collect_batch_bounds(const GArray *dabs, const guint max_dabs, const dt_drawlayer_cache_patch_t *patch, dt_drawlayer_damaged_rect_t *batch_bounds)
Build the scratch patch bounds that cover the next heartbeat dab batch.
Definition worker.c:593
static void _paint_stroke_seed_cb(void *user_data, uint64_t stroke_seed)
Definition worker.c:313
static void _rt_set_worker_state(dt_drawlayer_worker_t *rt, const dt_drawlayer_worker_state_t state)
Set worker state atomically under caller synchronization.
Definition worker.c:1141
static gboolean _rt_workers_any_active(dt_drawlayer_worker_t *rt)
Thread-safe wrapper for any worker activity.
Definition worker.c:1198
static void _rt_cleanup_state(dt_drawlayer_worker_t **rt_out)
Destroy worker state object and all owned resources.
Definition worker.c:1409
void(* drawlayer_rt_sample_cb)(dt_iop_module_t *self, dt_drawlayer_worker_t *rt, const dt_drawlayer_paint_raw_input_t *input)
Callback signature for one raw-input event processing.
Definition worker.c:62
static void _cancel_pending_gui_commit(dt_drawlayer_worker_t *rt)
Drop a queued GUI-thread commit request. GUI thread only.
Definition worker.c:1239
static void _copy_rgba_batch_from_locked_patch(const dt_drawlayer_cache_patch_t *src, dt_drawlayer_cache_patch_t *dst)
Copy one locked RGBA source region into the heartbeat scratch patch.
Definition worker.c:650
static gboolean _dab_bounds_in_patch(const dt_drawlayer_cache_patch_t *patch, float scale, const dt_drawlayer_brush_dab_t *dab, dt_drawlayer_damaged_rect_t *bounds)
Definition worker.c:575
static void _reset_live_publish(dt_drawlayer_worker_t *rt)
Definition worker.c:506
static gboolean _enqueue_input(dt_iop_module_t *self, dt_drawlayer_worker_t *rt, const dt_drawlayer_paint_raw_input_t *input)
Enqueue raw input with saturation policy and stroke-abort fallback.
Definition worker.c:1642
static void _backend_worker_process_sample(dt_iop_module_t *self, dt_drawlayer_worker_t *rt, const dt_drawlayer_paint_raw_input_t *input)
Process one backend raw input event.
Definition worker.c:1311
static gboolean _worker_pause_requested(const drawlayer_rt_worker_t *worker)
Definition worker.c:1128
static gboolean _rt_workers_active(dt_drawlayer_worker_t *rt)
Thread-safe wrapper for active-workers status.
Definition worker.c:1187
gboolean dt_drawlayer_worker_active(const dt_drawlayer_worker_t *worker)
Public status query: TRUE when worker has pending activity.
Definition worker.c:1716
static void _set_current_thread_realtime_best_effort(void)
Try elevating current thread scheduling policy for lower-latency input.
Definition worker.c:1148
static void _stop_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Stop worker thread and clear transient state.
Definition worker.c:1570
static gboolean _workers_ready_for_commit_locked(const dt_drawlayer_worker_t *rt)
Check if workers are idle and commit can be safely scheduled.
Definition worker.c:1177
void dt_drawlayer_worker_publish_backend_stroke_damage(dt_iop_module_t *self)
Publish accumulated backend stroke damage into drawlayer process/runtime state.
Definition worker.c:389
static void _rt_queue_clear_locked(dt_drawlayer_worker_t *rt)
Clear queued events (lock must be held).
Definition worker.c:1071
void dt_drawlayer_worker_request_commit(dt_drawlayer_worker_t *worker)
Public commit request helper.
Definition worker.c:1753
gboolean dt_drawlayer_worker_any_active(const dt_drawlayer_worker_t *worker)
Public status query: TRUE when any worker still has pending activity.
Definition worker.c:1722
gboolean dt_drawlayer_worker_enqueue_input(dt_drawlayer_worker_t *worker, const dt_drawlayer_paint_raw_input_t *input)
Public FIFO enqueue for one raw input event.
Definition worker.c:1817
void dt_drawlayer_worker_reset_live_publish(dt_drawlayer_worker_t *worker)
Reset worker-owned transient live-publish state.
Definition worker.c:1792
GArray * dt_drawlayer_worker_raw_inputs(dt_drawlayer_worker_t *worker)
Read-only access to preserved raw input history.
Definition worker.c:1811
static gboolean _process_backend_dab(const dt_drawlayer_brush_dab_t *dab, drawlayer_paint_backend_ctx_t *ctx, const dt_drawlayer_cache_patch_t *sample_patch, dt_drawlayer_cache_patch_t *patch, dt_drawlayer_cache_patch_t *stroke_mask, dt_drawlayer_damaged_rect_t *batch_damage)
Definition worker.c:402
static void _publish_backend_progress(drawlayer_paint_backend_ctx_t *ctx, gboolean flush_pending)
Definition worker.c:320
static gboolean _rt_queue_empty(const dt_drawlayer_worker_t *rt)
Test whether event queue is empty.
Definition worker.c:1081
static gboolean _enqueue_stroke_end(dt_iop_module_t *self, dt_drawlayer_worker_t *rt, const dt_drawlayer_paint_raw_input_t *input)
Enqueue explicit stroke-end event (with optional raw release sample).
Definition worker.c:1685
static gboolean _commit_dabs_on_gui_thread(gpointer user_data)
Run the stroke-end commit on the GUI thread.
Definition worker.c:1220
static gint64 _live_publish_interval_us(void)
Definition worker.c:524
void dt_drawlayer_worker_seal_for_commit(dt_drawlayer_worker_t *worker)
Seal current stroke for synchronous commit.
Definition worker.c:1779
static gboolean _paint_build_dab_cb(void *user_data, dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_paint_raw_input_t *input, dt_drawlayer_brush_dab_t *out_dab)
Definition worker.c:302
void dt_drawlayer_worker_cancel_async_commit(dt_drawlayer_worker_t *rt)
Cancel deferred commit request state if any.
Definition worker.c:1560
static const drawlayer_rt_worker_t * _backend_worker_const(const dt_drawlayer_worker_t *rt)
Definition worker.c:519
static void _log_worker_batch_timing(const char *tag, guint processed_dabs, guint thread_count, double elapsed_ms, gboolean outer_loop)
Definition worker.c:551
static gboolean _worker_is_started(const drawlayer_rt_worker_t *worker)
Definition worker.c:1118
void dt_drawlayer_worker_reset_stroke(dt_drawlayer_worker_t *worker)
Clear preserved stroke runtime/history after commit completed.
Definition worker.c:1801
void dt_drawlayer_worker_resume(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Resume worker processing and wake sleeping thread.
Definition worker.c:1615
static gboolean _ensure_heartbeat_batch_buffers(dt_drawlayer_worker_t *rt, const dt_drawlayer_damaged_rect_t *batch_bounds)
Ensure worker-private heartbeat scratch buffers match the requested batch bounds.
Definition worker.c:615
void(* drawlayer_rt_idle_cb)(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Callback signature for idle transitions inside worker loop.
Definition worker.c:67
static void _rt_init_state(dt_iop_module_t *self, dt_drawlayer_worker_t **rt_out, gboolean *painting, gboolean *finish_commit_pending, guint *stroke_sample_count, uint32_t *current_stroke_batch)
Allocate and initialize worker state object and buffers.
Definition worker.c:1379
static gboolean _stroke_begin(dt_drawlayer_worker_t *rt)
Start new stroke runtime and reset history/path state.
Definition worker.c:470
gboolean dt_drawlayer_build_worker_input_dab(dt_iop_module_t *self, dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_paint_raw_input_t *input, dt_drawlayer_brush_dab_t *dab)
Definition worker.c:184
static drawlayer_paint_backend_ctx_t _make_backend_ctx(dt_iop_module_t *self, dt_drawlayer_worker_t *worker, dt_drawlayer_paint_stroke_t *stroke)
Definition worker.c:565
static void _stroke_destroy(dt_drawlayer_worker_t *rt)
Destroy stroke runtime and owned dab window.
Definition worker.c:428
static gboolean _rt_queue_pop_locked(dt_drawlayer_worker_t *rt, dt_drawlayer_paint_raw_input_t *event)
Pop one event from ring queue (lock must be held).
Definition worker.c:1107
static const drawlayer_rt_callbacks_t _rt_callbacks[DRAWLAYER_RT_WORKER_COUNT]
Definition worker.c:424
static void _copy_rgba_damage_to_locked_patch(const dt_drawlayer_cache_patch_t *src, const dt_drawlayer_damaged_rect_t *local_damage, dt_drawlayer_cache_patch_t *dst)
Copy the written scratch sub-rectangle back into the locked base patch.
Definition worker.c:711
#define DRAWLAYER_OUTER_LIVE_BATCH_MULTIPLIER
Definition worker.c:182
static void _stroke_clear(dt_drawlayer_worker_t *rt)
Clear current stroke state while preserving allocations.
Definition worker.c:489
Background stroke worker API for drawlayer realtime painting.
dt_drawlayer_worker_state_t
Definition worker.h:34
@ DT_DRAWLAYER_WORKER_STATE_STOPPED
Definition worker.h:35
@ DT_DRAWLAYER_WORKER_STATE_IDLE
Definition worker.h:36
@ DT_DRAWLAYER_WORKER_STATE_BUSY
Definition worker.h:37
@ DT_DRAWLAYER_WORKER_STATE_PAUSED
Definition worker.h:39
@ DT_DRAWLAYER_WORKER_STATE_PAUSING
Definition worker.h:38