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
43
51
60
74
96
103
104static gboolean _paint_build_dab_cb(void *user_data, dt_drawlayer_paint_stroke_t *state,
106static gboolean _paint_layer_to_widget_cb(void *user_data, float lx, float ly, float *wx, float *wy);
107static void _paint_stroke_seed_cb(void *user_data, uint64_t stroke_seed);
108static void _publish_backend_progress(drawlayer_paint_backend_ctx_t *ctx, gboolean flush_pending);
112 const dt_drawlayer_cache_patch_t *sample_patch,
114 dt_drawlayer_cache_patch_t *stroke_mask,
115 dt_drawlayer_damaged_rect_t *batch_damage);
118static inline gint64 _live_publish_interval_us(void);
119static inline gboolean _live_publish_deadline_reached(const dt_drawlayer_worker_t *rt, const gint64 input_ts,
120 const gint64 interval_us);
123static guint _rasterize_pending_dab_batch(drawlayer_paint_backend_ctx_t *ctx, gint64 budget_us);
126static guint _worker_batch_min_size(void);
127static void _log_worker_batch_timing(const char *tag, guint processed_dabs, guint thread_count, double elapsed_ms,
128 gboolean outer_loop);
129static gboolean _dab_bounds_in_patch(const dt_drawlayer_cache_patch_t *patch, float scale,
131#if defined(_OPENMP) && OUTER_LOOP
132static guint _rasterize_dab_batch_outer_loop(const GArray *dabs, guint max_dabs, float distance_percent,
133 const dt_drawlayer_cache_patch_t *sample_patch,
134 dt_drawlayer_cache_patch_t *patch, float scale,
135 dt_drawlayer_cache_patch_t *stroke_mask,
136 dt_drawlayer_damaged_rect_t *batch_damage,
137 const char *tag);
138static gboolean _dab_batch_supports_outer_loop(const GArray *dabs, guint count);
139#endif
140
141/* GUI worker painting into uint8 buffers is deimplemented. Realtime preview
142 * now relies on the regular pipeline/backbuffer path. */
143
144#if defined(_OPENMP) && OUTER_LOOP
145#include <omp.h>
146#endif
147
148#define DRAWLAYER_BATCH_TILE_SIZE 128
149#define DRAWLAYER_HEARTBEAT_PATCH_NAME "drawlayer heartbeat patch"
150#define DRAWLAYER_HEARTBEAT_MASK_NAME "drawlayer heartbeat stroke mask"
151#define DRAWLAYER_OUTER_LIVE_BATCH_MULTIPLIER 2u
152
156{
157 if(IS_NULL_PTR(self) || IS_NULL_PTR(state) || IS_NULL_PTR(input) || IS_NULL_PTR(dab)) return FALSE;
158
159 float lx = input->lx;
160 float ly = input->ly;
161 if(!input->have_layer_coords && !dt_drawlayer_widget_to_layer_coords(self, input->wx, input->wy, &lx, &ly))
162 return FALSE;
163
164 const float pressure_norm = _clamp01(input->pressure);
165 const float tilt_norm = _clamp01(input->tilt);
166 const float accel_norm = _clamp01(input->acceleration);
167 const gboolean map_pressure_size = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_SIZE) != 0u;
168 const gboolean map_pressure_opacity = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_OPACITY) != 0u;
169 const gboolean map_pressure_flow = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_FLOW) != 0u;
170 const gboolean map_pressure_softness = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_SOFTNESS) != 0u;
171 const gboolean map_tilt_size = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_SIZE) != 0u;
172 const gboolean map_tilt_opacity = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_OPACITY) != 0u;
173 const gboolean map_tilt_flow = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_FLOW) != 0u;
174 const gboolean map_tilt_softness = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_SOFTNESS) != 0u;
175 const gboolean map_accel_size = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_SIZE) != 0u;
176 const gboolean map_accel_opacity = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_OPACITY) != 0u;
177 const gboolean map_accel_flow = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_FLOW) != 0u;
178 const gboolean map_accel_softness = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_SOFTNESS) != 0u;
179 const drawlayer_mapping_profile_t pressure_profile = (drawlayer_mapping_profile_t)CLAMP(
183 const drawlayer_mapping_profile_t accel_profile = (drawlayer_mapping_profile_t)CLAMP(
185 const float pressure_coeff = _mapping_profile_value(pressure_profile, pressure_norm);
186 const float tilt_coeff = _mapping_profile_value(tilt_profile, tilt_norm);
187 const float accel_coeff = _mapping_profile_value(accel_profile, accel_norm);
188
189 float radius = fmaxf(input->brush_radius, 0.5f);
190 float opacity = _clamp01(input->brush_opacity);
191 float flow = _clamp01(input->brush_flow);
192 const float sprinkles = _clamp01(input->brush_sprinkles);
193 float hardness = _clamp01(input->brush_hardness);
194 const float base_radius = radius;
195 const float base_opacity = opacity;
196 const float base_flow = flow;
197 const float base_hardness = hardness;
198
199 if(map_pressure_size) radius *= pressure_coeff;
200 if(map_pressure_opacity) opacity *= pressure_coeff;
201 if(map_pressure_flow) flow *= pressure_coeff;
202 if(map_pressure_softness) hardness *= pressure_coeff;
203
204 if(map_tilt_size) radius *= tilt_coeff;
205 if(map_tilt_opacity) opacity *= tilt_coeff;
206 if(map_tilt_flow) flow *= tilt_coeff;
207 if(map_tilt_softness) hardness *= tilt_coeff;
208
209 if(map_accel_size) radius *= accel_coeff;
210 if(map_accel_opacity) opacity *= accel_coeff;
211 if(map_accel_flow) flow *= accel_coeff;
212 if(map_accel_softness) hardness *= accel_coeff;
213
214 radius = fmaxf(radius, 0.5f);
215 hardness = _clamp01(hardness);
216 float dir_x = 0.0f;
217 float dir_y = 0.0f;
218 if(state->have_last_input_dab)
219 {
220 const float dx = lx - state->last_input_dab.x;
221 const float dy = ly - state->last_input_dab.y;
222 const float dir_len = hypotf(dx, dy);
223 if(dir_len > 1e-6f)
224 {
225 dir_x = dx / dir_len;
226 dir_y = dy / dir_len;
227 }
228 }
229
231 .x = lx,
232 .y = ly,
233 .wx = input->wx,
234 .wy = input->wy,
235 .radius = radius,
236 .dir_x = dir_x,
237 .dir_y = dir_y,
238 .opacity = _clamp01(opacity),
239 .flow = _clamp01(flow),
240 .sprinkles = _clamp01(sprinkles),
241 .sprinkle_size = input->brush_sprinkle_size,
242 .sprinkle_coarseness = _clamp01(input->brush_sprinkle_coarseness),
243 .hardness = hardness,
244 .color = { input->color[0], input->color[1], input->color[2], 1.0f },
245 .display_color = { input->display_color[0], input->display_color[1], input->display_color[2] },
246 .shape = input->brush_shape,
247 .mode = input->brush_mode,
248 .stroke_batch = input->stroke_batch,
249 .stroke_pos = input->stroke_pos,
250 };
251
252 if((map_pressure_size || map_pressure_opacity || map_pressure_flow || map_pressure_softness || map_tilt_size
253 || map_tilt_opacity || map_tilt_flow || map_tilt_softness || map_accel_size || map_accel_opacity
254 || map_accel_flow || map_accel_softness)
256 || ((state->history && (state->history->len & 15u) == 0u))))
257 {
259 "[drawlayer] map p=%.4f t=%.4f a=%.4f coeff[p=%.4f t=%.4f a=%.4f] "
260 "base[r=%.2f o=%.3f f=%.3f h=%.3f] out[r=%.2f o=%.3f f=%.3f h=%.3f] "
261 "flags[p=%d%d%d%d t=%d%d%d%d a=%d%d%d%d]\n",
262 pressure_norm, tilt_norm, accel_norm, pressure_coeff, tilt_coeff, accel_coeff, base_radius,
263 base_opacity, base_flow, base_hardness, radius, _clamp01(opacity), _clamp01(flow), _clamp01(hardness),
264 map_pressure_size ? 1 : 0, map_pressure_opacity ? 1 : 0, map_pressure_flow ? 1 : 0,
265 map_pressure_softness ? 1 : 0, map_tilt_size ? 1 : 0, map_tilt_opacity ? 1 : 0, map_tilt_flow ? 1 : 0,
266 map_tilt_softness ? 1 : 0, map_accel_size ? 1 : 0, map_accel_opacity ? 1 : 0, map_accel_flow ? 1 : 0,
267 map_accel_softness ? 1 : 0);
268 }
269
270 return TRUE;
271}
272
273static gboolean _paint_build_dab_cb(void *user_data, dt_drawlayer_paint_stroke_t *state,
275{
277 return (ctx && ctx->self)
279 (dt_drawlayer_brush_dab_t *)out_dab)
280 : FALSE;
281}
282
283static gboolean _paint_layer_to_widget_cb(void *user_data, float lx, float ly, float *wx, float *wy)
284{
286 return (ctx && ctx->self) ? dt_drawlayer_layer_to_widget_coords(ctx->self, lx, ly, wx, wy) : FALSE;
287}
288
289static void _paint_stroke_seed_cb(void *user_data, uint64_t stroke_seed)
290{
292 if(IS_NULL_PTR(ctx)) return;
293 if(ctx->stroke) dt_drawlayer_paint_runtime_set_stroke_seed(ctx->stroke, stroke_seed);
294}
295
296static void _publish_backend_progress(drawlayer_paint_backend_ctx_t *ctx, gboolean flush_pending)
297{
298 if(IS_NULL_PTR(ctx) || IS_NULL_PTR(ctx->self) || !flush_pending) return;
299
300 dt_iop_module_t *self = ctx->self;
303 dt_develop_t *dev = self->dev;
304 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;
305
308 ctx->worker->live_publish_ts = g_get_monotonic_time();
309
310 const int sample_count = (int)g->stroke.stroke_sample_count;
311 dt_drawlayer_touch_stroke_commit_hash(params, sample_count, g->stroke.last_dab_valid, g->stroke.last_dab_x,
312 g->stroke.last_dab_y, ctx->worker->live_publish_serial);
313
318
319 /* Heartbeat publish only updates drawlayer history ownership here. Realtime
320 * full-pipe bypassing of history resynchronization is handled inside the
321 * pipeline code itself, where pipe pieces may be updated legally. */
323}
324
327{
329 if(IS_NULL_PTR(g) || IS_NULL_PTR(input) || IS_NULL_PTR(stroke)) return;
330
331 drawlayer_paint_backend_ctx_t ctx = _make_backend_ctx(self, g->stroke.worker, stroke);
332 const dt_drawlayer_paint_callbacks_t callbacks = {
334 .layer_to_widget = _paint_layer_to_widget_cb,
335 .on_stroke_seed = _paint_stroke_seed_cb,
336 };
337 if(!dt_drawlayer_paint_queue_raw_input(stroke, input)) return;
338 dt_drawlayer_paint_interpolate_path(stroke, &callbacks, &ctx);
339
340 if(stroke->pending_dabs && stroke->pending_dabs->len > 0)
341 {
342 const gint64 live_publish_interval_us = _live_publish_interval_us();
343 const gint64 input_ts = input->event_ts ? input->event_ts : g_get_monotonic_time();
344 if(ctx.worker && ctx.worker->live_publish_ts == 0)
345 ctx.worker->live_publish_ts = input_ts - live_publish_interval_us;
346
347 if(_live_publish_deadline_reached(ctx.worker, input_ts, live_publish_interval_us))
348 {
349 const guint processed_dabs = _rasterize_pending_dab_batch(&ctx, live_publish_interval_us);
350 if(processed_dabs > 0) _publish_backend_progress(&ctx, TRUE);
351 }
352 }
353}
354
356{
358 if(IS_NULL_PTR(g)) return;
359
360 dt_drawlayer_damaged_rect_t backend_damage = { 0 };
361 if(g->stroke.worker && dt_drawlayer_paint_merge_runtime_stroke_damage(g->stroke.worker->backend_path, &backend_damage))
362 {
363 g->process.cache_dirty = TRUE;
364 dt_drawlayer_paint_runtime_note_dab_damage(&g->process.cache_dirty_rect, &backend_damage);
365 }
366}
367
369 const dt_drawlayer_cache_patch_t *sample_patch,
371 dt_drawlayer_cache_patch_t *stroke_mask,
372 dt_drawlayer_damaged_rect_t *batch_damage)
373{
374 dt_drawlayer_paint_stroke_t *stroke = ctx ? ctx->stroke : NULL;
375 if(IS_NULL_PTR(ctx) || !stroke || !stroke->dab_window || IS_NULL_PTR(dab) || IS_NULL_PTR(patch) || IS_NULL_PTR(patch->pixels) || !stroke_mask
376 || !stroke_mask->pixels)
377 return FALSE;
378
379 dt_drawlayer_damaged_rect_t step_damage = { 0 };
381 patch, 1.0f, stroke_mask, &step_damage, stroke))
382 return FALSE;
383
384 if(step_damage.valid && batch_damage)
385 dt_drawlayer_paint_runtime_note_dab_damage(batch_damage, &step_damage);
386
387 return step_damage.valid;
388}
389
392
395{
396 if(IS_NULL_PTR(rt)) return;
397 if(rt->stroke && rt->stroke->pending_dabs)
398 {
399 g_array_free(rt->stroke->pending_dabs, TRUE);
400 rt->stroke->pending_dabs = NULL;
401 }
402 if(rt->stroke && rt->stroke->dab_window)
403 {
404 g_array_free(rt->stroke->dab_window, TRUE);
405 rt->stroke->dab_window = NULL;
406 }
408}
409
412{
413 if(IS_NULL_PTR(rt)) return FALSE;
414 if(rt->stroke) return TRUE;
416 if(IS_NULL_PTR(rt->stroke)) return FALSE;
417 rt->stroke->history = rt->backend_history;
418 rt->stroke->pending_dabs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
419 if(!rt->stroke->pending_dabs)
420 {
422 return FALSE;
423 }
424 rt->stroke->dab_window = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
425 if(!rt->stroke->dab_window)
426 {
427 g_array_free(rt->stroke->pending_dabs, TRUE);
428 rt->stroke->pending_dabs = NULL;
430 return FALSE;
431 }
432 return TRUE;
433}
434
437{
438 if(IS_NULL_PTR(rt)) return FALSE;
439 if(!rt->backend_history)
440 rt->backend_history = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
441 if(IS_NULL_PTR(rt->backend_history)) return FALSE;
442 if(!rt->stroke_raw_inputs)
443 rt->stroke_raw_inputs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_paint_raw_input_t));
444 if(IS_NULL_PTR(rt->stroke_raw_inputs)) return FALSE;
445 if(!_stroke_create(rt)) return FALSE;
446 rt->stroke->history = rt->backend_history;
449 g_array_set_size(rt->backend_history, 0);
450 g_array_set_size(rt->stroke_raw_inputs, 0);
451 return TRUE;
452}
453
456{
457 if(IS_NULL_PTR(rt)) return;
458 if(rt->backend_history) g_array_set_size(rt->backend_history, 0);
459 if(rt->stroke_raw_inputs) g_array_set_size(rt->stroke_raw_inputs, 0);
460 if(rt->stroke)
461 {
464 }
465}
466
471
479
481{
482 return rt ? &rt->workers[DRAWLAYER_RT_WORKER_BACKEND] : NULL;
483}
484
486{
487 return rt ? &rt->workers[DRAWLAYER_RT_WORKER_BACKEND] : NULL;
488}
489
490static inline gint64 _live_publish_interval_us(void)
491{
492 return MAX((gint64)dt_gui_throttle_get_pipe_runtime_us(DT_DEV_PIXELPIPE_FULL), (gint64)20000);
493}
494
495static guint _worker_batch_min_size(void)
496{
497#if defined(_OPENMP) && OUTER_LOOP
498 return MAX(1, omp_get_max_threads());
499#else
500 return 1;
501#endif
502}
503
504#if defined(_OPENMP) && OUTER_LOOP
505static gboolean _dab_batch_supports_outer_loop(const GArray *dabs, const guint count)
506{
507 if(IS_NULL_PTR(dabs) || count == 0) return FALSE;
508 for(guint i = 0; i < count; i++)
509 {
510 const dt_drawlayer_brush_dab_t *dab = &g_array_index(dabs, dt_drawlayer_brush_dab_t, i);
511 if(dab->mode == DT_DRAWLAYER_BRUSH_MODE_SMUDGE) return FALSE;
512 }
513 return TRUE;
514}
515#endif
516
517static void _log_worker_batch_timing(const char *tag, const guint processed_dabs, const guint thread_count,
518 const double elapsed_ms, const gboolean outer_loop)
519{
520 if(!(darktable.unmuted & DT_DEBUG_PERF)) return;
521 dt_print(DT_DEBUG_PERF, "[drawlayer] batch worker=%s dabs=%u threads=%u outer=%d ms=%.3f\n",
522 tag ? tag : "unknown", processed_dabs, thread_count, outer_loop ? 1 : 0, elapsed_ms);
523}
524
525static inline gboolean _live_publish_deadline_reached(const dt_drawlayer_worker_t *rt, const gint64 input_ts,
526 const gint64 interval_us)
527{
528 return rt && input_ts - rt->live_publish_ts >= interval_us;
529}
530
533{
535 .self = self,
536 .worker = worker,
537 .stroke = stroke,
538 };
539}
540
541static gboolean _dab_bounds_in_patch(const dt_drawlayer_cache_patch_t *patch, const float scale,
543{
544 if(bounds) *bounds = (dt_drawlayer_damaged_rect_t){ 0 };
545 if(IS_NULL_PTR(patch) || IS_NULL_PTR(dab) || IS_NULL_PTR(bounds) || IS_NULL_PTR(patch->pixels) || patch->width <= 0 || patch->height <= 0
546 || dab->radius <= 0.0f || dab->opacity <= 0.0f || scale <= 0.0f)
547 return FALSE;
548
549 const float support_radius = dab->radius;
550 bounds->valid = TRUE;
551 bounds->nw[0] = MAX(0, (int)floorf((dab->x - support_radius) * scale) - patch->x);
552 bounds->nw[1] = MAX(0, (int)floorf((dab->y - support_radius) * scale) - patch->y);
553 bounds->se[0] = MIN(patch->width, (int)ceilf((dab->x + support_radius) * scale) - patch->x + 1);
554 bounds->se[1] = MIN(patch->height, (int)ceilf((dab->y + support_radius) * scale) - patch->y + 1);
555 return bounds->se[0] > bounds->nw[0] && bounds->se[1] > bounds->nw[1];
556}
557
559static gboolean _collect_batch_bounds(const GArray *dabs, const guint max_dabs,
560 const dt_drawlayer_cache_patch_t *patch,
561 dt_drawlayer_damaged_rect_t *batch_bounds)
562{
563 if(batch_bounds) *batch_bounds = (dt_drawlayer_damaged_rect_t){ 0 };
564 if(IS_NULL_PTR(dabs) || max_dabs == 0 || IS_NULL_PTR(patch) || IS_NULL_PTR(batch_bounds)) return FALSE;
565
566 /* Walk the upcoming batch in FIFO order and union every dab footprint that
567 * can touch the destination patch. This keeps the worker scratch patch as
568 * small as possible while still covering all writes for the current heartbeat. */
569 for(guint i = 0; i < max_dabs; i++)
570 {
571 dt_drawlayer_damaged_rect_t dab_bounds = { 0 };
572 const dt_drawlayer_brush_dab_t *dab = &g_array_index(dabs, dt_drawlayer_brush_dab_t, i);
573 if(_dab_bounds_in_patch(patch, 1.0f, dab, &dab_bounds))
574 dt_drawlayer_paint_runtime_note_dab_damage(batch_bounds, &dab_bounds);
575 }
576
577 return batch_bounds->valid;
578}
579
582 const dt_drawlayer_damaged_rect_t *batch_bounds)
583{
584 if(IS_NULL_PTR(rt) || IS_NULL_PTR(batch_bounds) || !batch_bounds->valid) return FALSE;
585
586 const int width = batch_bounds->se[0] - batch_bounds->nw[0];
587 const int height = batch_bounds->se[1] - batch_bounds->nw[1];
588 if(width <= 0 || height <= 0) return FALSE;
592 return FALSE;
593
594 rt->heartbeat_patch.x = batch_bounds->nw[0];
595 rt->heartbeat_patch.y = batch_bounds->nw[1];
596 rt->heartbeat_stroke_mask.x = batch_bounds->nw[0];
597 rt->heartbeat_stroke_mask.y = batch_bounds->nw[1];
598 return TRUE;
599}
600
604{
605 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;
606
607 const int src_x0 = dst->x - src->x;
608 const int src_y0 = dst->y - src->y;
609 if(src_x0 < 0 || src_y0 < 0 || src_x0 + dst->width > src->width || src_y0 + dst->height > src->height) return;
610
611 for(int y = 0; y < dst->height; y++)
612 {
613 const float *src_row = src->pixels + 4 * ((size_t)(src_y0 + y) * src->width + src_x0);
614 float *dst_row = dst->pixels + 4 * ((size_t)y * dst->width);
615 memcpy(dst_row, src_row, (size_t)dst->width * 4 * sizeof(float));
616 }
617}
618
622{
623 if(IS_NULL_PTR(dst) || IS_NULL_PTR(dst->pixels) || dst->width <= 0 || dst->height <= 0) return;
624 if(IS_NULL_PTR(src) || IS_NULL_PTR(src->pixels))
625 {
626 memset(dst->pixels, 0, (size_t)dst->width * dst->height * sizeof(float));
627 return;
628 }
629
630 const int src_x0 = dst->x - src->x;
631 const int src_y0 = dst->y - src->y;
632 if(src_x0 < 0 || src_y0 < 0 || src_x0 + dst->width > src->width || src_y0 + dst->height > src->height)
633 {
634 memset(dst->pixels, 0, (size_t)dst->width * dst->height * sizeof(float));
635 return;
636 }
637
638 for(int y = 0; y < dst->height; y++)
639 {
640 const float *src_row = src->pixels + (size_t)(src_y0 + y) * src->width + src_x0;
641 float *dst_row = dst->pixels + (size_t)y * dst->width;
642 memcpy(dst_row, src_row, (size_t)dst->width * sizeof(float));
643 }
644}
645
648 const dt_drawlayer_damaged_rect_t *local_damage,
649 dt_drawlayer_damaged_rect_t *absolute_damage)
650{
651 if(absolute_damage) *absolute_damage = (dt_drawlayer_damaged_rect_t){ 0 };
652 if(IS_NULL_PTR(patch) || IS_NULL_PTR(local_damage) || !local_damage->valid || IS_NULL_PTR(absolute_damage)) return FALSE;
653
654 *absolute_damage = *local_damage;
655 absolute_damage->nw[0] += patch->x;
656 absolute_damage->nw[1] += patch->y;
657 absolute_damage->se[0] += patch->x;
658 absolute_damage->se[1] += patch->y;
659 return TRUE;
660}
661
664 const dt_drawlayer_damaged_rect_t *local_damage,
666{
667 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;
668
669 const int copy_w = local_damage->se[0] - local_damage->nw[0];
670 const int copy_h = local_damage->se[1] - local_damage->nw[1];
671 if(copy_w <= 0 || copy_h <= 0) return;
672
673 const int dst_x0 = src->x + local_damage->nw[0] - dst->x;
674 const int dst_y0 = src->y + local_damage->nw[1] - dst->y;
675 if(dst_x0 < 0 || dst_y0 < 0 || dst_x0 + copy_w > dst->width || dst_y0 + copy_h > dst->height) return;
676
677 for(int y = 0; y < copy_h; y++)
678 {
679 const float *src_row = src->pixels + 4 * ((size_t)(local_damage->nw[1] + y) * src->width + local_damage->nw[0]);
680 float *dst_row = dst->pixels + 4 * ((size_t)(dst_y0 + y) * dst->width + dst_x0);
681 memcpy(dst_row, src_row, (size_t)copy_w * 4 * sizeof(float));
682 }
683}
684
687 const dt_drawlayer_damaged_rect_t *local_damage,
689{
690 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;
691
692 const int copy_w = local_damage->se[0] - local_damage->nw[0];
693 const int copy_h = local_damage->se[1] - local_damage->nw[1];
694 if(copy_w <= 0 || copy_h <= 0) return;
695
696 const int dst_x0 = src->x + local_damage->nw[0] - dst->x;
697 const int dst_y0 = src->y + local_damage->nw[1] - dst->y;
698 if(dst_x0 < 0 || dst_y0 < 0 || dst_x0 + copy_w > dst->width || dst_y0 + copy_h > dst->height) return;
699
700 for(int y = 0; y < copy_h; y++)
701 {
702 const float *src_row = src->pixels + (size_t)(local_damage->nw[1] + y) * src->width + local_damage->nw[0];
703 float *dst_row = dst->pixels + (size_t)(dst_y0 + y) * dst->width + dst_x0;
704 memcpy(dst_row, src_row, (size_t)copy_w * sizeof(float));
705 }
706}
707
710 const dt_drawlayer_damaged_rect_t *local_damage)
711{
712 if(IS_NULL_PTR(patch) || IS_NULL_PTR(local_damage) || !local_damage->valid || IS_NULL_PTR(patch->pixels)) return;
713
714 const int clear_w = local_damage->se[0] - local_damage->nw[0];
715 const int clear_h = local_damage->se[1] - local_damage->nw[1];
716 if(clear_w <= 0 || clear_h <= 0) return;
717
718 for(int y = 0; y < clear_h; y++)
719 {
720 float *row = patch->pixels + 4 * ((size_t)(local_damage->nw[1] + y) * patch->width + local_damage->nw[0]);
721 memset(row, 0, (size_t)clear_w * 4 * sizeof(float));
722 }
723}
724
727 const dt_drawlayer_damaged_rect_t *local_damage)
728{
729 if(IS_NULL_PTR(patch) || IS_NULL_PTR(local_damage) || !local_damage->valid || IS_NULL_PTR(patch->pixels)) return;
730
731 const int clear_w = local_damage->se[0] - local_damage->nw[0];
732 const int clear_h = local_damage->se[1] - local_damage->nw[1];
733 if(clear_w <= 0 || clear_h <= 0) return;
734
735 for(int y = 0; y < clear_h; y++)
736 {
737 float *row = patch->pixels + (size_t)(local_damage->nw[1] + y) * patch->width + local_damage->nw[0];
738 memset(row, 0, (size_t)clear_w * sizeof(float));
739 }
740}
741
742#if defined(_OPENMP) && OUTER_LOOP
743static dt_drawlayer_paint_stroke_t *_create_batch_runtime(void)
744{
746 if(IS_NULL_PTR(runtime)) return NULL;
747 runtime->dab_window = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
748 if(!runtime->dab_window)
749 {
751 return NULL;
752 }
753 return runtime;
754}
755
756static void _destroy_batch_runtime(dt_drawlayer_paint_stroke_t **runtime)
757{
758 if(!runtime || !*runtime) return;
759 if((*runtime)->dab_window) g_array_free((*runtime)->dab_window, TRUE);
760 (*runtime)->dab_window = NULL;
762}
763
764static void _lock_batch_tiles(omp_lock_t *locks, const int tile_cols, const int tile_origin_x,
765 const int tile_origin_y, const dt_drawlayer_damaged_rect_t *bounds)
766{
767 if(!locks || tile_cols <= 0 || IS_NULL_PTR(bounds) || !bounds->valid) return;
768 const int tx0 = bounds->nw[0] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x;
769 const int ty0 = bounds->nw[1] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y;
770 const int tx1 = MAX(tx0, (bounds->se[0] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x);
771 const int ty1 = MAX(ty0, (bounds->se[1] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y);
772 for(int ty = ty0; ty <= ty1; ty++)
773 for(int tx = tx0; tx <= tx1; tx++)
774 omp_set_lock(&locks[ty * tile_cols + tx]);
775}
776
777static void _unlock_batch_tiles(omp_lock_t *locks, const int tile_cols, const int tile_origin_x,
778 const int tile_origin_y, const dt_drawlayer_damaged_rect_t *bounds)
779{
780 if(!locks || tile_cols <= 0 || IS_NULL_PTR(bounds) || !bounds->valid) return;
781 const int tx0 = bounds->nw[0] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x;
782 const int ty0 = bounds->nw[1] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y;
783 const int tx1 = MAX(tx0, (bounds->se[0] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x);
784 const int ty1 = MAX(ty0, (bounds->se[1] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y);
785 for(int ty = ty1; ty >= ty0; ty--)
786 for(int tx = tx1; tx >= tx0; tx--)
787 omp_unset_lock(&locks[ty * tile_cols + tx]);
788}
789
790static guint _rasterize_dab_batch_outer_loop(const GArray *dabs, const guint max_dabs, const float distance_percent,
791 const dt_drawlayer_cache_patch_t *sample_patch,
792 dt_drawlayer_cache_patch_t *patch, const float scale,
793 dt_drawlayer_cache_patch_t *stroke_mask,
794 dt_drawlayer_damaged_rect_t *batch_damage,
795 const char *tag)
796{
797 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;
798
799 const guint thread_count = _worker_batch_min_size();
800 dt_drawlayer_damaged_rect_t batch_bounds = { 0 };
801 for(guint i = 0; i < max_dabs; i++)
802 {
803 dt_drawlayer_damaged_rect_t dab_bounds = { 0 };
804 const dt_drawlayer_brush_dab_t *dab = &g_array_index(dabs, dt_drawlayer_brush_dab_t, i);
805 if(_dab_bounds_in_patch(patch, scale, dab, &dab_bounds))
806 dt_drawlayer_paint_runtime_note_dab_damage(&batch_bounds, &dab_bounds);
807 }
808 if(!batch_bounds.valid) return 0;
809
810 const int tile_origin_x = batch_bounds.nw[0] / DRAWLAYER_BATCH_TILE_SIZE;
811 const int tile_origin_y = batch_bounds.nw[1] / DRAWLAYER_BATCH_TILE_SIZE;
812 const int tile_cols = MAX(1, (batch_bounds.se[0] + DRAWLAYER_BATCH_TILE_SIZE - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x);
813 const int tile_rows = MAX(1, (batch_bounds.se[1] + DRAWLAYER_BATCH_TILE_SIZE - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y);
814 dt_drawlayer_paint_stroke_t **thread_runtime = g_malloc0((size_t)thread_count * sizeof(*thread_runtime));
815 dt_drawlayer_damaged_rect_t *thread_damage = g_malloc0((size_t)thread_count * sizeof(*thread_damage));
816 omp_lock_t *tile_locks = g_malloc0((size_t)tile_cols * tile_rows * sizeof(*tile_locks));
817 if(!thread_runtime || IS_NULL_PTR(thread_damage) || !tile_locks)
818 {
819 g_free(thread_runtime);
820 g_free(thread_damage);
821 g_free(tile_locks);
822 return 0;
823 }
824
825 for(guint i = 0; i < thread_count; i++)
826 {
827 thread_runtime[i] = _create_batch_runtime();
828 if(!thread_runtime[i])
829 {
830 for(guint k = 0; k < i; k++) _destroy_batch_runtime(&thread_runtime[k]);
831 g_free(thread_runtime);
832 g_free(thread_damage);
833 g_free(tile_locks);
834 return 0;
835 }
836 }
837
838 for(int i = 0; i < tile_cols * tile_rows; i++)
839 omp_init_lock(&tile_locks[i]);
840
841 const double t0 = dt_get_wtime();
842#pragma omp parallel for default(shared)
843 for(guint i = 0; i < max_dabs; i++)
844 {
845 const int tid = omp_get_thread_num();
846 dt_drawlayer_paint_stroke_t *runtime = thread_runtime[tid];
847 dt_drawlayer_damaged_rect_t runtime_damage = { 0 };
848 dt_drawlayer_damaged_rect_t dab_damage = { 0 };
849 dt_drawlayer_damaged_rect_t bounds = { 0 };
850 const dt_drawlayer_brush_dab_t *dab = &g_array_index(dabs, dt_drawlayer_brush_dab_t, i);
851
854 if(runtime->dab_window) g_array_set_size(runtime->dab_window, 0);
855 if(!_dab_bounds_in_patch(patch, scale, dab, &bounds))
856 continue;
857
858 _lock_batch_tiles(tile_locks, tile_cols, tile_origin_x, tile_origin_y, &bounds);
859 dt_drawlayer_paint_rasterize_segment_to_buffer(dab, distance_percent, sample_patch, patch, scale, stroke_mask,
860 &runtime_damage, runtime);
861 _unlock_batch_tiles(tile_locks, tile_cols, tile_origin_x, tile_origin_y, &bounds);
862
863 if(dt_drawlayer_paint_runtime_get_stroke_damage(&runtime_damage, &dab_damage))
864 dt_drawlayer_paint_runtime_note_dab_damage(&thread_damage[tid], &dab_damage);
865 }
866 const double t1 = dt_get_wtime();
867
868 if(batch_damage) dt_drawlayer_paint_runtime_state_reset(batch_damage);
869 for(guint i = 0; i < thread_count; i++)
870 {
871 if(batch_damage) dt_drawlayer_paint_runtime_note_dab_damage(batch_damage, &thread_damage[i]);
872 _destroy_batch_runtime(&thread_runtime[i]);
873 }
874 for(int i = 0; i < tile_cols * tile_rows; i++)
875 omp_destroy_lock(&tile_locks[i]);
876
877 g_free(thread_runtime);
878 g_free(thread_damage);
879 g_free(tile_locks);
880
881 _log_worker_batch_timing(tag, max_dabs, thread_count, 1000.0 * (t1 - t0), TRUE);
882 return max_dabs;
883}
884#endif
885
887{
889 dt_drawlayer_paint_stroke_t *stroke = ctx ? ctx->stroke : NULL;
890 dt_drawlayer_worker_t *worker = ctx ? ctx->worker : NULL;
891 if(IS_NULL_PTR(g) || IS_NULL_PTR(stroke) || IS_NULL_PTR(worker) || !stroke->pending_dabs || stroke->pending_dabs->len == 0) return 0;
892 if(IS_NULL_PTR(g->process.base_patch.pixels) || g->process.base_patch.width <= 0 || g->process.base_patch.height <= 0)
893 return 0;
894
895 const guint min_batch = _worker_batch_min_size();
896 const guint remaining_dabs = stroke->pending_dabs->len;
897 const guint batch_dabs = (budget_us > 0)
898 ? MIN(remaining_dabs, min_batch * DRAWLAYER_OUTER_LIVE_BATCH_MULTIPLIER)
899 : remaining_dabs;
900 if(batch_dabs == 0) return 0;
901
902 dt_drawlayer_damaged_rect_t batch_bounds = { 0 };
903 if(!_collect_batch_bounds(stroke->pending_dabs, batch_dabs, &g->process.base_patch, &batch_bounds))
904 {
905 g_array_remove_range(stroke->pending_dabs, 0, batch_dabs);
906 return batch_dabs;
907 }
908 if(!_ensure_heartbeat_batch_buffers(worker, &batch_bounds)) return 0;
909
910 dt_drawlayer_cache_patch_t *const heartbeat_patch = &worker->heartbeat_patch;
911 dt_drawlayer_cache_patch_t *const heartbeat_mask = &worker->heartbeat_stroke_mask;
912 dt_drawlayer_damaged_rect_t batch_damage = { 0 };
913 guint processed_dabs = 0;
914 gboolean used_outer_loop = FALSE;
915 const double batch_t0 = dt_get_wtime();
916
917 /* Build the heartbeat scratch patch from the latest committed layer state,
918 * then keep the authoritative base patch read-locked while blur/smudge
919 * sample from it during rasterization. */
920 dt_drawlayer_cache_patch_rdlock(&g->process.base_patch);
921 dt_drawlayer_cache_patch_rdlock(&g->process.stroke_mask);
922 _copy_rgba_batch_from_locked_patch(&g->process.base_patch, heartbeat_patch);
923 _copy_mask_batch_from_locked_patch(&g->process.stroke_mask, heartbeat_mask);
924#if defined(_OPENMP) && OUTER_LOOP
925 if(batch_dabs >= min_batch && _dab_batch_supports_outer_loop(stroke->pending_dabs, batch_dabs))
926 {
927 used_outer_loop = TRUE;
928 processed_dabs = _rasterize_dab_batch_outer_loop(stroke->pending_dabs, batch_dabs,
929 _clamp01(stroke->distance_percent),
930 &g->process.base_patch, heartbeat_patch, 1.0f,
931 heartbeat_mask, &batch_damage, "heartbeat");
932 }
933#endif
934 while(processed_dabs < batch_dabs)
935 {
936 const dt_drawlayer_brush_dab_t *dab
937 = &g_array_index(stroke->pending_dabs, dt_drawlayer_brush_dab_t, processed_dabs);
938 _process_backend_dab(dab, ctx, &g->process.base_patch, heartbeat_patch, heartbeat_mask, &batch_damage);
939 processed_dabs++;
940
941 if(budget_us > 0)
942 {
943 const gint64 elapsed_us = (gint64)(1000000.0 * (dt_get_wtime() - batch_t0));
944 if(processed_dabs >= min_batch && elapsed_us >= budget_us) break;
945 }
946 }
947 dt_drawlayer_cache_patch_rdunlock(&g->process.stroke_mask);
948 dt_drawlayer_cache_patch_rdunlock(&g->process.base_patch);
949
950 if(processed_dabs == 0) return 0;
951
952 const dt_drawlayer_brush_dab_t *last_dab
953 = &g_array_index(stroke->pending_dabs, dt_drawlayer_brush_dab_t, processed_dabs - 1);
954 g_array_remove_range(stroke->pending_dabs, 0, processed_dabs);
955
956 if(batch_damage.valid)
957 {
958 dt_drawlayer_damaged_rect_t absolute_damage = { 0 };
959 if(_translate_batch_damage(heartbeat_patch, &batch_damage, &absolute_damage))
960 {
961 dt_drawlayer_cache_patch_wrlock(&g->process.base_patch);
962 dt_drawlayer_cache_patch_wrlock(&g->process.stroke_mask);
963 _copy_rgba_damage_to_locked_patch(heartbeat_patch, &batch_damage, &g->process.base_patch);
964 _copy_mask_damage_to_locked_patch(heartbeat_mask, &batch_damage, &g->process.stroke_mask);
965 _clear_rgba_damage_in_patch(heartbeat_patch, &batch_damage);
966 _clear_mask_damage_in_patch(heartbeat_mask, &batch_damage);
968 g->process.base_patch.cache_entry, -1);
969 dt_drawlayer_cache_patch_wrunlock(&g->process.stroke_mask);
970 dt_drawlayer_cache_patch_wrunlock(&g->process.base_patch);
971
972 g->process.cache_dirty = TRUE;
973 dt_drawlayer_paint_runtime_note_dab_damage(&g->process.cache_dirty_rect, &absolute_damage);
976 g->stroke.last_dab_valid = TRUE;
977 g->stroke.last_dab_x = last_dab->x;
978 g->stroke.last_dab_y = last_dab->y;
979 }
980 }
981
982 if(!used_outer_loop)
983 _log_worker_batch_timing("heartbeat", processed_dabs, 1, 1000.0 * (dt_get_wtime() - batch_t0), FALSE);
984 return processed_dabs;
985}
986
989{
991 if(IS_NULL_PTR(worker)) return;
992 worker->ring_head = 0;
993 worker->ring_tail = 0;
994 worker->ring_count = 0;
995}
996
998static gboolean _rt_queue_empty(const dt_drawlayer_worker_t *rt)
999{
1000 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1001 return !worker || worker->ring_count == 0;
1002}
1003
1005static gboolean _rt_queue_full(const dt_drawlayer_worker_t *rt)
1006{
1007 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1008 return !worker || (worker->ring_capacity > 0 && worker->ring_count >= worker->ring_capacity);
1009}
1010
1013 const dt_drawlayer_paint_raw_input_t *event)
1014{
1016 if(IS_NULL_PTR(worker) || IS_NULL_PTR(event) || IS_NULL_PTR(worker->ring) || worker->ring_capacity == 0 || _rt_queue_full(rt)) return FALSE;
1017 worker->ring[worker->ring_tail] = *event;
1018 worker->ring_tail = (worker->ring_tail + 1) % worker->ring_capacity;
1019 worker->ring_count++;
1020 return TRUE;
1021}
1022
1026{
1028 if(IS_NULL_PTR(worker) || IS_NULL_PTR(event) || IS_NULL_PTR(worker->ring) || worker->ring_capacity == 0 || _rt_queue_empty(rt)) return FALSE;
1029 *event = worker->ring[worker->ring_head];
1030 worker->ring_head = (worker->ring_head + 1) % worker->ring_capacity;
1031 worker->ring_count--;
1032 return TRUE;
1033}
1034
1035static inline gboolean _worker_is_started(const drawlayer_rt_worker_t *worker)
1036{
1037 return worker && worker->thread_started;
1038}
1039
1040static inline gboolean _worker_is_busy(const drawlayer_rt_worker_t *worker)
1041{
1042 return worker && worker->state == DT_DRAWLAYER_WORKER_STATE_BUSY;
1043}
1044
1045static inline gboolean _worker_pause_requested(const drawlayer_rt_worker_t *worker)
1046{
1047 return worker
1050}
1051
1053{
1054 return rt && rt->stroke && rt->stroke->pending_dabs && rt->stroke->pending_dabs->len > 0;
1055}
1056
1059{
1061 if(!IS_NULL_PTR(worker)) worker->state = state;
1062}
1063
1066{
1067#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
1068 struct sched_param param = { 0 };
1069 const int max_prio = sched_get_priority_max(SCHED_FIFO);
1070 if(max_prio > 0)
1071 {
1072 param.sched_priority = MIN(max_prio, 80);
1073 pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
1074 }
1075#endif
1076}
1077
1080{
1081 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1082 return worker && (_worker_is_busy(worker) || worker->ring_count > 0
1085}
1086
1089{
1090 return _workers_active_locked(rt);
1091}
1092
1095{
1096 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1097 return worker && rt->finish_commit_pending && rt->painting
1098 && *rt->finish_commit_pending && !*rt->painting
1099 && !_worker_is_busy(worker) && worker->ring_count == 0
1101}
1102
1105{
1106 gboolean active = FALSE;
1107 if(IS_NULL_PTR(rt)) return FALSE;
1109 active = _workers_active_locked(rt);
1111 return active;
1112}
1113
1116{
1117 gboolean active = FALSE;
1118 if(IS_NULL_PTR(rt)) return FALSE;
1120 active = _workers_any_active_locked(rt);
1122 return active;
1123}
1124
1127{
1129 drawlayer_paint_backend_ctx_t ctx = _make_backend_ctx(self, rt, rt ? rt->stroke : NULL);
1130 if(!IS_NULL_PTR(self) && g && !IS_NULL_PTR(rt) && rt->stroke && rt->stroke->pending_dabs && rt->stroke->pending_dabs->len > 0)
1131 {
1132 /* Once a stroke has emitted dabs, keep draining the heartbeat backlog in
1133 * this same idle phase instead of returning to the outer loop after each
1134 * batch. Otherwise long strokes can momentarily "stall" between batches as
1135 * the worker bounces through the queue/idle state machine waiting for the
1136 * next loop turn. */
1137 while(rt->stroke && rt->stroke->pending_dabs && rt->stroke->pending_dabs->len > 0)
1138 {
1139 guint processed_dabs = 0;
1141 const gboolean should_stop = _worker_pause_requested(_backend_worker_const(rt))
1144 if(should_stop) break;
1145
1147 if(processed_dabs == 0) break;
1149 }
1150 }
1151
1152 if(IS_NULL_PTR(rt)) return;
1153 gboolean should_commit = FALSE;
1155 should_commit = _workers_ready_for_commit_locked(rt);
1157 if(should_commit) _commit_dabs(self, TRUE);
1158}
1159
1162 const dt_drawlayer_paint_raw_input_t *input)
1163{
1164 if(IS_NULL_PTR(rt) || IS_NULL_PTR(input)) return;
1165 if(!IS_NULL_PTR(rt) && !IS_NULL_PTR(input) && input->stroke_pos == DT_DRAWLAYER_PAINT_STROKE_FIRST)
1166 {
1167 _stroke_begin(rt);
1168 }
1169
1170 if(!rt->stroke && !_stroke_begin(rt)) return;
1171 if(rt->stroke_raw_inputs) g_array_append_val(rt->stroke_raw_inputs, *input);
1172 _process_backend_input(self, input, rt->stroke);
1173}
1174
1177{
1178 if(IS_NULL_PTR(rt)) return;
1179
1180 /* Stroke end only finalizes path generation and asks for commit. Pending dabs
1181 * still drain through the same heartbeat batches as live painting, so the
1182 * worker keeps alternating raster and pipeline refresh until the stroke is
1183 * fully materialized. */
1184 if(rt->stroke)
1185 {
1187 }
1190 pthread_cond_broadcast(&rt->worker_cond);
1192}
1193
1194static const drawlayer_rt_callbacks_t _rt_callbacks[] = {
1196 .thread_name = "draw-back",
1197 .process_sample = _backend_worker_process_sample,
1198 .process_stroke_end = _backend_worker_process_stroke_end,
1199 .on_idle = _backend_worker_on_idle,
1200 },
1201};
1202
1205{
1206 if(IS_NULL_PTR(rt_out) || !*rt_out) return;
1207 dt_drawlayer_worker_t *rt = *rt_out;
1209 _stop_worker(self ? self : rt->self, rt);
1210 _stroke_destroy(rt);
1211 if(rt->backend_history) g_array_free(rt->backend_history, TRUE);
1212 if(rt->stroke_raw_inputs) g_array_free(rt->stroke_raw_inputs, TRUE);
1216 dt_free(worker->ring);
1217 pthread_cond_destroy(&rt->worker_cond);
1219 dt_free(rt);
1220 *rt_out = NULL;
1221}
1222
1225 gboolean *painting, gboolean *finish_commit_pending,
1226 guint *stroke_sample_count, uint32_t *current_stroke_batch)
1227{
1228 if(IS_NULL_PTR(rt_out)) return;
1229 _rt_destroy_state(self, rt_out);
1230 dt_drawlayer_worker_t *rt = NULL;
1231 rt = g_malloc0(sizeof(*rt));
1232 *rt_out = rt;
1233 if(IS_NULL_PTR(rt)) return;
1234
1235 rt->self = self;
1236 rt->painting = painting;
1237 rt->finish_commit_pending = finish_commit_pending;
1238 rt->stroke_sample_count = stroke_sample_count;
1239 rt->current_stroke_batch = current_stroke_batch;
1242 pthread_cond_init(&rt->worker_cond, NULL);
1243
1246 worker->ring = g_malloc_n(worker->ring_capacity, sizeof(dt_drawlayer_paint_raw_input_t));
1247 rt->backend_history = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
1248 rt->stroke_raw_inputs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_paint_raw_input_t));
1250 _stroke_create(rt);
1251}
1252
1255{
1256 _rt_destroy_state(NULL, rt_out);
1257}
1258
1261{
1262 (void)self;
1263 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1264 if(IS_NULL_PTR(rt) || IS_NULL_PTR(worker) || !_worker_is_started(worker)) return TRUE;
1265
1267 while(_worker_is_busy(worker) || worker->ring_count > 0)
1268 {
1269 if(worker->stop) break;
1271 }
1273 return TRUE;
1274}
1275
1280
1282static void *_drawlayer_worker_main(void *user_data)
1283{
1285 dt_drawlayer_worker_t *rt = ctx ? ctx->rt : NULL;
1286 dt_free(ctx);
1287
1288 dt_iop_module_t *self = rt ? rt->self : NULL;
1291 if(IS_NULL_PTR(self) || IS_NULL_PTR(rt) || IS_NULL_PTR(worker) || IS_NULL_PTR(cb)) return NULL;
1292
1295
1296 while(TRUE)
1297 {
1298 dt_drawlayer_paint_raw_input_t event = { 0 };
1299 gboolean have_event = FALSE;
1300 gboolean have_backlog = FALSE;
1301
1303 while(!worker->stop && (_worker_pause_requested(worker)
1304 || (worker->ring_count == 0 && !_backend_pending_dabs_locked(rt))))
1305 {
1308 pthread_cond_broadcast(&rt->worker_cond);
1310 if(cb->on_idle) cb->on_idle(self, rt);
1312 if(!worker->stop && (_worker_pause_requested(worker)
1313 || (worker->ring_count == 0 && !_backend_pending_dabs_locked(rt))))
1315 }
1316
1317 if(worker->stop)
1318 {
1320 pthread_cond_broadcast(&rt->worker_cond);
1322 break;
1323 }
1324
1325 have_backlog = _backend_pending_dabs_locked(rt);
1326 have_event = _rt_queue_pop_locked(rt, &event);
1327 _rt_set_worker_state(rt, (have_event || have_backlog) ? DT_DRAWLAYER_WORKER_STATE_BUSY
1330
1331 if(!have_event)
1332 {
1333 if(cb->on_idle) cb->on_idle(self, rt);
1334 continue;
1335 }
1336
1337 if(event.stroke_pos == DT_DRAWLAYER_PAINT_STROKE_END)
1338 {
1339 /* If stroke-end carries a real raw sample (button release), process it
1340 * before closing the stroke so the final segment is not lost. */
1341 if(cb->process_sample && event.event_ts != 0)
1342 cb->process_sample(self, rt, &event);
1343 if(cb->process_stroke_end) cb->process_stroke_end(self, rt);
1344 }
1345 else
1346 {
1347 if(cb->process_sample) cb->process_sample(self, rt, &event);
1348 }
1349
1351 have_backlog = _backend_pending_dabs_locked(rt);
1353 : (have_backlog ? DT_DRAWLAYER_WORKER_STATE_BUSY
1355 pthread_cond_broadcast(&rt->worker_cond);
1357 if(cb->on_idle) cb->on_idle(self, rt);
1358 }
1359
1360 return NULL;
1361}
1362
1365{
1367 if(IS_NULL_PTR(self) || IS_NULL_PTR(rt) || IS_NULL_PTR(worker)) return FALSE;
1368 if(_worker_is_started(worker))
1369 return TRUE;
1370
1371 worker->stop = FALSE;
1373
1374 drawlayer_rt_thread_ctx_t *ctx = g_malloc(sizeof(*ctx));
1375 if(IS_NULL_PTR(ctx)) return FALSE;
1376 ctx->rt = rt;
1377
1378 const int err = dt_pthread_create(&worker->thread, _drawlayer_worker_main, ctx, TRUE);
1379 if(err != 0)
1380 {
1381 dt_free(ctx);
1382 worker->thread_started = FALSE;
1384 return FALSE;
1385 }
1386
1387 worker->thread_started = TRUE;
1389 return TRUE;
1390}
1391
1401
1404{
1405 if(IS_NULL_PTR(rt)) return;
1408
1409 if(worker && _worker_is_started(worker))
1410 {
1411 _wait_worker_idle(self, rt);
1412
1414 worker->stop = TRUE;
1415 pthread_cond_broadcast(&rt->worker_cond);
1417
1418 pthread_join(worker->thread, NULL);
1419 memset(&worker->thread, 0, sizeof(worker->thread));
1420 worker->thread_started = FALSE;
1422 worker->stop = FALSE;
1424 _stroke_clear(rt);
1425 }
1426}
1427
1430{
1431 (void)self;
1433 if(IS_NULL_PTR(rt) || IS_NULL_PTR(worker) || !_worker_is_started(worker)) return;
1434
1438 while(_worker_is_busy(worker) && !worker->stop)
1441}
1442
1445{
1446 (void)self;
1448 if(IS_NULL_PTR(rt) || IS_NULL_PTR(worker) || !_worker_is_started(worker)) return;
1449
1452 pthread_cond_broadcast(&rt->worker_cond);
1454}
1455
1458 const dt_drawlayer_paint_raw_input_t *event)
1459{
1460 if(IS_NULL_PTR(rt) || IS_NULL_PTR(event)) return FALSE;
1461 if(!_start_worker(self, rt)) return FALSE;
1462
1464 const gboolean ok = _rt_queue_push_locked(rt, event);
1465 pthread_cond_broadcast(&rt->worker_cond);
1467 return ok;
1468}
1469
1472 const dt_drawlayer_paint_raw_input_t *input)
1473{
1474 if(IS_NULL_PTR(rt) || IS_NULL_PTR(input)) return FALSE;
1475 if(!_start_worker(self, rt)) return FALSE;
1476
1477 const dt_drawlayer_paint_raw_input_t event = *input;
1478
1479 gboolean ok = FALSE;
1481 if(!_rt_queue_full(rt))
1482 {
1483 ok = _rt_queue_push_locked(rt, &event);
1484 if(ok && rt->stroke_sample_count) (*rt->stroke_sample_count)++;
1485 }
1486 if(!ok)
1487 {
1488 /* Saturated queue policy: abort current stroke deterministically instead of
1489 * silently dropping or coalescing raw input events. Keep FIFO backlog and
1490 * force a stroke end at the newest queued slot. */
1492 dt_drawlayer_paint_raw_input_t end_input = *input;
1494 end_input.event_index = input->event_index + 1u;
1495 const dt_drawlayer_paint_raw_input_t end_event = end_input;
1496 if(worker->ring && worker->ring_count > 0)
1497 {
1498 const guint last_index = (worker->ring_tail + worker->ring_capacity - 1) % worker->ring_capacity;
1499 worker->ring[last_index] = end_event;
1500 ok = TRUE;
1501 }
1502 else
1503 ok = _rt_queue_push_locked(rt, &end_event);
1504
1506 dt_control_log(_("drawing worker queue is full, stroke aborted"));
1507 }
1508 pthread_cond_broadcast(&rt->worker_cond);
1510 return ok;
1511}
1512
1515 const dt_drawlayer_paint_raw_input_t *input)
1516{
1517 if(IS_NULL_PTR(rt)) return FALSE;
1518
1519 dt_drawlayer_paint_raw_input_t end_input = { 0 };
1520 if(input) end_input = *input;
1521 if(end_input.stroke_batch == 0u && rt->current_stroke_batch)
1522 end_input.stroke_batch = *rt->current_stroke_batch;
1524
1525 if(_enqueue_event(self, rt, &end_input)) return TRUE;
1526 dt_control_log(_("drawing worker queue is full"));
1527 return FALSE;
1528}
1529
1532 gboolean *painting, gboolean *finish_commit_pending,
1533 guint *stroke_sample_count, uint32_t *current_stroke_batch)
1534{
1535 _rt_init_state(self, worker, painting, finish_commit_pending, stroke_sample_count, current_stroke_batch);
1536}
1537
1543
1546{
1547 return _rt_workers_active((dt_drawlayer_worker_t *)worker);
1548}
1549
1555
1557{
1558 return _start_worker(self, rt);
1559}
1560
1565
1568{
1569 if(snapshot) *snapshot = (dt_drawlayer_worker_snapshot_t){ 0 };
1571 if(IS_NULL_PTR(rt) || IS_NULL_PTR(snapshot)) return;
1572
1574 const drawlayer_rt_worker_t *backend = _backend_worker_const(rt);
1575 snapshot->backend_state = backend->state;
1576 snapshot->backend_queue_count = backend->ring_count;
1579}
1580
1583{
1584 if(IS_NULL_PTR(worker)) return;
1586 if(worker->finish_commit_pending) *worker->finish_commit_pending = TRUE;
1587 pthread_cond_broadcast(&worker->worker_cond);
1589}
1590
1593{
1594 if(IS_NULL_PTR(worker) || IS_NULL_PTR(worker->self)) return;
1595 _wait_worker_idle(worker->self, worker);
1596}
1597
1602
1610
1618
1621{
1622 if(IS_NULL_PTR(worker)) return;
1624 _stroke_clear(worker);
1625 _reset_live_publish(worker);
1627}
1628
1631{
1632 return worker ? worker->stroke_raw_inputs : NULL;
1633}
1634
1637{
1638 return worker ? worker->stroke : NULL;
1639}
1640
1642{
1644 if(IS_NULL_PTR(rt)) return 0;
1645
1646 guint len = 0;
1648 if(rt->stroke && rt->stroke->pending_dabs) len = rt->stroke->pending_dabs->len;
1650 return len;
1651}
1652
1655 const dt_drawlayer_paint_raw_input_t *input)
1656{
1657 return _enqueue_input(worker ? worker->self : NULL, worker, (const dt_drawlayer_paint_raw_input_t *)input);
1658}
1659
1662 const dt_drawlayer_paint_raw_input_t *input)
1663{
1664 return _enqueue_stroke_end(worker ? worker->self : NULL, worker, input);
1665}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
@ DT_DRAWLAYER_BRUSH_MODE_SMUDGE
Definition brush.h:51
const float i
Definition colorspaces_inline_conversions.h:440
const float g
Definition colorspaces_inline_conversions.h:674
static const int row
Definition colorspaces_inline_conversions.h:35
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_control_log(const char *msg,...)
Definition control.c:530
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:630
gboolean dt_drawlayer_widget_to_layer_coords(dt_iop_module_t *self, const double wx, const double wy, float *lx, float *ly)
Definition coordinates.c:81
gboolean dt_drawlayer_layer_to_widget_coords(dt_iop_module_t *self, const float x, const float y, float *wx, float *wy)
Definition coordinates.c:94
darktable_t darktable
Definition darktable.c:173
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1448
@ DT_DEBUG_INPUT
Definition darktable.h:728
@ DT_DEBUG_PERF
Definition darktable.h:718
#define omp_get_max_threads()
Definition darktable.h:254
#define dt_free(ptr)
Definition darktable.h:456
#define omp_get_thread_num()
Definition darktable.h:255
static double dt_get_wtime(void)
Definition darktable.h:913
#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 darktable.h:281
uint64_t dt_dev_history_compute_hash(dt_develop_t *dev)
Get the integrity checksum of the whole history stack. This should be done ONLY when history is chang...
Definition dev_history.c:848
gboolean dt_dev_add_history_item_ext(dt_develop_t *dev, struct dt_iop_module_t *module, gboolean enable, gboolean force_new_item)
Append or update a history item for a module.
Definition dev_history.c:745
static void dt_dev_set_history_hash(dt_develop_t *dev, const uint64_t history_hash)
Definition develop.h:478
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:1521
#define _commit_dabs
Definition drawlayer.c:117
int dt_pthread_create(pthread_t *thread, void *(*start_routine)(void *), void *arg, const gboolean realtime)
Definition dtpthread.c:45
void dt_pthread_setname(const char *name)
Definition dtpthread.c:118
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:359
#define dt_pthread_rwlock_wrlock
Definition dtpthread.h:394
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:379
static int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
Definition dtpthread.h:384
#define dt_pthread_rwlock_unlock
Definition dtpthread.h:392
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:364
int dt_gui_throttle_get_pipe_runtime_us(const dt_dev_pixelpipe_type_t pipe_type)
Definition gui_throttle.c:217
static float _clamp01(const float v)
Clamp scalar to [0,1].
Definition iop/drawlayer/brush.c:38
static float _mapping_profile_value(const drawlayer_mapping_profile_t profile, const float x)
Definition iop/drawlayer/module.h:77
@ DRAWLAYER_INPUT_MAP_ACCEL_SIZE
Definition iop/drawlayer/module.h:34
@ DRAWLAYER_INPUT_MAP_ACCEL_FLOW
Definition iop/drawlayer/module.h:36
@ DRAWLAYER_INPUT_MAP_PRESSURE_SOFTNESS
Definition iop/drawlayer/module.h:29
@ DRAWLAYER_INPUT_MAP_TILT_SOFTNESS
Definition iop/drawlayer/module.h:33
@ DRAWLAYER_INPUT_MAP_TILT_OPACITY
Definition iop/drawlayer/module.h:31
@ DRAWLAYER_INPUT_MAP_PRESSURE_OPACITY
Definition iop/drawlayer/module.h:27
@ DRAWLAYER_INPUT_MAP_TILT_FLOW
Definition iop/drawlayer/module.h:32
@ DRAWLAYER_INPUT_MAP_ACCEL_OPACITY
Definition iop/drawlayer/module.h:35
@ DRAWLAYER_INPUT_MAP_TILT_SIZE
Definition iop/drawlayer/module.h:30
@ DRAWLAYER_INPUT_MAP_PRESSURE_FLOW
Definition iop/drawlayer/module.h:28
@ DRAWLAYER_INPUT_MAP_ACCEL_SOFTNESS
Definition iop/drawlayer/module.h:37
@ DRAWLAYER_INPUT_MAP_PRESSURE_SIZE
Definition iop/drawlayer/module.h:26
drawlayer_mapping_profile_t
Definition iop/drawlayer/module.h:15
@ DRAWLAYER_PROFILE_INV_QUADRATIC
Definition iop/drawlayer/module.h:21
@ DRAWLAYER_PROFILE_LINEAR
Definition iop/drawlayer/module.h:16
#define DRAWLAYER_WORKER_RING_CAPACITY
Definition iop/drawlayer/module.h:11
void dt_drawlayer_paint_path_state_reset(dt_drawlayer_paint_stroke_t *state)
Reset full stroke state including queued raw input events.
Definition iop/drawlayer/paint.c:476
void dt_drawlayer_paint_runtime_state_destroy(dt_drawlayer_damaged_rect_t **state)
Destroy stroke-damage accumulator state and null pointer.
Definition iop/drawlayer/paint.c:796
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).
Definition iop/drawlayer/paint.c:642
void dt_drawlayer_paint_runtime_private_destroy(dt_drawlayer_paint_stroke_t **state)
Destroy stroke runtime payload and null pointer.
Definition iop/drawlayer/paint.c:820
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.
Definition iop/drawlayer/paint.c:666
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.
Definition iop/drawlayer/paint.c:839
void dt_drawlayer_paint_finalize_path(dt_drawlayer_paint_stroke_t *state)
Finalize stroke by force-emitting the pending first sample if needed.
Definition iop/drawlayer/paint.c:527
void dt_drawlayer_paint_runtime_private_reset(dt_drawlayer_paint_stroke_t *state)
Reset transient stroke runtime payload between strokes.
Definition iop/drawlayer/paint.c:828
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.
Definition iop/drawlayer/paint.c:940
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.
Definition iop/drawlayer/paint.c:714
void dt_drawlayer_paint_runtime_state_reset(dt_drawlayer_damaged_rect_t *state)
Reset stroke-damage accumulator to empty/invalid.
Definition iop/drawlayer/paint.c:802
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.
Definition iop/drawlayer/paint.c:922
dt_drawlayer_damaged_rect_t * dt_drawlayer_paint_runtime_state_create(void)
Allocate zero-initialized stroke-damage accumulator state.
Definition iop/drawlayer/paint.c:788
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.
Definition iop/drawlayer/paint.c:967
dt_drawlayer_paint_stroke_t * dt_drawlayer_paint_runtime_private_create(void)
Allocate stroke runtime payload object used by paint+brush internals.
Definition iop/drawlayer/paint.c:812
@ DT_DRAWLAYER_PAINT_STROKE_MIDDLE
Definition iop/drawlayer/paint.h:38
@ DT_DRAWLAYER_PAINT_STROKE_FIRST
Definition iop/drawlayer/paint.h:37
@ DT_DRAWLAYER_PAINT_STROKE_END
Definition iop/drawlayer/paint.h:39
float *const restrict const size_t k
Definition luminance_mask.h:78
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:39
gboolean dt_dev_pixelpipe_cache_flush_host_pinned_image(dt_dev_pixelpipe_cache_t *cache, void *host_ptr, dt_pixel_cache_entry_t *entry_hint, int devid)
Drop cached pinned OpenCL images associated with a given host buffer.
Definition pixelpipe_cache.c:845
const float uint32_t state[4]
Definition src/develop/noise_generator.h:72
const float const float param
Definition src/develop/noise_generator.h:108
void dt_drawlayer_cache_patch_wrunlock(const dt_drawlayer_cache_patch_t *patch)
Release write lock on shared patch cache entry.
Definition src/iop/drawlayer/cache.c:180
void dt_drawlayer_cache_patch_wrlock(const dt_drawlayer_cache_patch_t *patch)
Acquire write lock on shared patch cache entry.
Definition src/iop/drawlayer/cache.c:173
void dt_drawlayer_cache_patch_clear(dt_drawlayer_cache_patch_t *patch, const char *external_alloc_name)
Release patch storage and reset patch metadata.
Definition src/iop/drawlayer/cache.c:65
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.
Definition src/iop/drawlayer/cache.c:200
void dt_drawlayer_cache_patch_rdlock(const dt_drawlayer_cache_patch_t *patch)
Acquire read lock on shared patch cache entry.
Definition src/iop/drawlayer/cache.c:159
void dt_drawlayer_cache_patch_rdunlock(const dt_drawlayer_cache_patch_t *patch)
Release read lock on shared patch cache entry.
Definition src/iop/drawlayer/cache.c:166
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_dev_pixelpipe_cache_t * pixelpipe_cache
Definition darktable.h:789
int32_t unmuted
Definition darktable.h:759
Definition worker.c:98
dt_drawlayer_paint_stroke_t * stroke
Definition worker.c:101
dt_iop_module_t * self
Definition worker.c:99
dt_drawlayer_worker_t * worker
Definition worker.c:100
Per-worker callback vtable.
Definition worker.c:54
drawlayer_rt_sample_cb process_sample
Definition worker.c:56
const char * thread_name
Definition worker.c:55
drawlayer_rt_stroke_end_cb process_stroke_end
Definition worker.c:57
drawlayer_rt_idle_cb on_idle
Definition worker.c:58
Definition worker.c:1277
dt_drawlayer_worker_t * rt
Definition worker.c:1278
One worker thread runtime including event ring buffer.
Definition worker.c:63
guint ring_head
Definition worker.c:68
guint ring_capacity
Definition worker.c:67
guint ring_count
Definition worker.c:70
pthread_t thread
Definition worker.c:64
guint ring_tail
Definition worker.c:69
gboolean stop
Definition worker.c:72
gboolean thread_started
Definition worker.c:65
dt_drawlayer_worker_state_t state
Definition worker.c:71
dt_drawlayer_paint_raw_input_t * ring
Definition worker.c:66
Definition develop.h:159
dt_pthread_rwlock_t history_mutex
Definition develop.h:252
Fully resolved input dab descriptor.
Definition brush.h:64
float x
Definition brush.h:65
float radius
Definition brush.h:69
int mode
Definition brush.h:83
float y
Definition brush.h:66
float opacity
Definition brush.h:74
Generic float RGBA patch stored either in malloc memory or pixel cache.
Definition iop/drawlayer/cache.h:37
int y
Definition iop/drawlayer/cache.h:39
int x
Definition iop/drawlayer/cache.h:38
int height
Definition iop/drawlayer/cache.h:41
float * pixels
Definition iop/drawlayer/cache.h:42
int width
Definition iop/drawlayer/cache.h:40
Integer axis-aligned rectangle in buffer coordinates.
Definition iop/drawlayer/paint.h:87
int se[2]
Definition iop/drawlayer/paint.h:90
int nw[2]
Definition iop/drawlayer/paint.h:89
gboolean valid
Definition iop/drawlayer/paint.h:88
Callback bundle used by stroke processing entry points.
Definition iop/drawlayer/paint.h:145
dt_drawlayer_paint_build_dab_cb build_dab
Definition iop/drawlayer/paint.h:146
One raw pointer event queued to stroke processing.
Definition iop/drawlayer/paint.h:49
float brush_opacity
Definition iop/drawlayer/paint.h:69
uint32_t map_flags
Definition iop/drawlayer/paint.h:65
float acceleration
Definition iop/drawlayer/paint.h:56
float color[3]
Definition iop/drawlayer/paint.h:77
float brush_hardness
Definition iop/drawlayer/paint.h:71
float brush_sprinkle_coarseness
Definition iop/drawlayer/paint.h:74
float wy
Definition iop/drawlayer/paint.h:51
int brush_shape
Definition iop/drawlayer/paint.h:75
float wx
Definition iop/drawlayer/paint.h:50
uint32_t event_index
Definition iop/drawlayer/paint.h:59
uint8_t tilt_profile
Definition iop/drawlayer/paint.h:63
uint32_t stroke_batch
Definition iop/drawlayer/paint.h:58
uint8_t pressure_profile
Definition iop/drawlayer/paint.h:62
gint64 event_ts
Definition iop/drawlayer/paint.h:57
float tilt
Definition iop/drawlayer/paint.h:55
float lx
Definition iop/drawlayer/paint.h:52
float brush_sprinkle_size
Definition iop/drawlayer/paint.h:73
float brush_sprinkles
Definition iop/drawlayer/paint.h:72
float brush_radius
Definition iop/drawlayer/paint.h:68
float brush_flow
Definition iop/drawlayer/paint.h:70
uint8_t stroke_pos
Definition iop/drawlayer/paint.h:60
uint8_t have_layer_coords
Definition iop/drawlayer/paint.h:61
float display_color[3]
Definition iop/drawlayer/paint.h:78
uint8_t accel_profile
Definition iop/drawlayer/paint.h:64
float pressure
Definition iop/drawlayer/paint.h:54
float ly
Definition iop/drawlayer/paint.h:53
int brush_mode
Definition iop/drawlayer/paint.h:76
Mutable stroke runtime state owned by worker/backend code.
Definition iop/drawlayer/paint.h:100
GArray * pending_dabs
Definition iop/drawlayer/paint.h:103
GArray * history
Definition iop/drawlayer/paint.h:101
float distance_percent
Definition iop/drawlayer/paint.h:112
GArray * dab_window
Definition iop/drawlayer/paint.h:105
Definition worker.h:39
dt_drawlayer_worker_state_t backend_state
Definition worker.h:40
guint backend_queue_count
Definition worker.h:41
gboolean commit_pending
Definition worker.h:42
Drawlayer worker global state shared with drawlayer module.
Definition worker.c:77
gboolean * finish_commit_pending
Definition worker.c:80
dt_drawlayer_damaged_rect_t * backend_path
Definition worker.c:89
uint32_t * current_stroke_batch
Definition worker.c:82
dt_drawlayer_cache_patch_t heartbeat_patch
Definition worker.c:90
guint * stroke_sample_count
Definition worker.c:81
GArray * stroke_raw_inputs
Definition worker.c:87
dt_drawlayer_cache_patch_t heartbeat_stroke_mask
Definition worker.c:91
drawlayer_rt_worker_t workers[DRAWLAYER_RT_WORKER_COUNT]
Definition worker.c:85
pthread_cond_t worker_cond
Definition worker.c:84
dt_drawlayer_damaged_rect_t live_publish_damage
Definition worker.c:94
uint32_t live_publish_serial
Definition worker.c:93
gint64 live_publish_ts
Definition worker.c:92
dt_pthread_mutex_t worker_mutex
Definition worker.c:83
GArray * backend_history
Definition worker.c:86
dt_iop_module_t * self
Definition worker.c:78
dt_drawlayer_paint_stroke_t * stroke
Definition worker.c:88
gboolean * painting
Definition worker.c:79
Definition runtime.h:254
Definition src/iop/drawlayer/common.h:14
Definition imageop.h:246
struct dt_develop_t * dev
Definition imageop.h:298
dt_iop_gui_data_t * gui_data
Definition imageop.h:313
dt_iop_params_t * params
Definition imageop.h:309
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static void _backend_worker_on_idle(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Backend-worker idle hook.
Definition worker.c:1126
static gboolean _stroke_create(dt_drawlayer_worker_t *rt)
Create stroke runtime if missing.
Definition worker.c:411
#define DRAWLAYER_BATCH_TILE_SIZE
Definition worker.c:148
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:48
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:1566
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:1176
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:1079
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:686
static gboolean _worker_is_busy(const drawlayer_rt_worker_t *worker)
Definition worker.c:1040
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:647
static void _pause_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Pause worker processing after current callback returns.
Definition worker.c:1429
static gboolean _rt_queue_full(const dt_drawlayer_worker_t *rt)
Test whether event queue is full.
Definition worker.c:1005
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:1561
static guint _worker_batch_min_size(void)
Definition worker.c:495
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:1204
dt_drawlayer_paint_stroke_t * dt_drawlayer_worker_stroke(dt_drawlayer_worker_t *worker)
Read-only access to preserved stroke runtime.
Definition worker.c:1636
void dt_drawlayer_worker_cleanup(dt_drawlayer_worker_t **worker)
Public worker cleanup entry point.
Definition worker.c:1539
void dt_drawlayer_worker_reset_backend_path(dt_drawlayer_worker_t *worker)
Reset worker-owned backend damage accumulator.
Definition worker.c:1603
static guint _rasterize_pending_dab_batch(drawlayer_paint_backend_ctx_t *ctx, gint64 budget_us)
Definition worker.c:886
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:1012
drawlayer_rt_worker_kind_t
Internal worker slot kinds (currently backend only).
Definition worker.c:39
@ DRAWLAYER_RT_WORKER_COUNT
Definition worker.c:41
@ DRAWLAYER_RT_WORKER_BACKEND
Definition worker.c:40
static gboolean _backend_pending_dabs_locked(const dt_drawlayer_worker_t *rt)
Definition worker.c:1052
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:1556
static gboolean _live_publish_deadline_reached(const dt_drawlayer_worker_t *rt, const gint64 input_ts, const gint64 interval_us)
Definition worker.c:525
static gboolean _start_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Start backend worker thread if not running.
Definition worker.c:1364
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:1457
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:726
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:709
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:620
static void _reset_backend_path(dt_drawlayer_worker_t *rt)
Definition worker.c:467
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:1661
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:325
#define DRAWLAYER_HEARTBEAT_MASK_NAME
Definition worker.c:150
static void _cancel_async_commit(dt_drawlayer_worker_t *rt)
Cancel deferred commit request state if any.
Definition worker.c:1393
#define DRAWLAYER_HEARTBEAT_PATCH_NAME
Definition worker.c:149
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:1531
static drawlayer_rt_worker_t * _backend_worker(dt_drawlayer_worker_t *rt)
Definition worker.c:480
static gboolean _workers_any_active_locked(const dt_drawlayer_worker_t *rt)
Check whether any worker activity remains.
Definition worker.c:1088
static void * _drawlayer_worker_main(void *user_data)
Worker main loop: FIFO dequeue, process, and idle scheduling.
Definition worker.c:1282
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:559
static void _paint_stroke_seed_cb(void *user_data, uint64_t stroke_seed)
Definition worker.c:289
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:1058
void dt_drawlayer_worker_flush_pending(dt_drawlayer_worker_t *worker)
Flush pending backend stroke inputs synchronously.
Definition worker.c:1592
static gboolean _rt_workers_any_active(dt_drawlayer_worker_t *rt)
Thread-safe wrapper for any worker activity.
Definition worker.c:1115
static void _rt_cleanup_state(dt_drawlayer_worker_t **rt_out)
Destroy worker state object and all owned resources.
Definition worker.c:1254
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:45
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:602
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:541
static void _reset_live_publish(dt_drawlayer_worker_t *rt)
Definition worker.c:472
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:1471
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:1161
static gboolean _worker_pause_requested(const drawlayer_rt_worker_t *worker)
Definition worker.c:1045
static gboolean _rt_workers_active(dt_drawlayer_worker_t *rt)
Thread-safe wrapper for active-workers status.
Definition worker.c:1104
gboolean dt_drawlayer_worker_active(const dt_drawlayer_worker_t *worker)
Public status query: TRUE when worker has pending activity.
Definition worker.c:1545
static void _set_current_thread_realtime_best_effort(void)
Try elevating current thread scheduling policy for lower-latency input.
Definition worker.c:1065
static void _stop_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Stop worker thread and clear transient state.
Definition worker.c:1403
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:1094
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:355
static void _rt_queue_clear_locked(dt_drawlayer_worker_t *rt)
Clear queued events (lock must be held).
Definition worker.c:988
void dt_drawlayer_worker_request_commit(dt_drawlayer_worker_t *worker)
Public commit request helper.
Definition worker.c:1582
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:1551
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:1654
void dt_drawlayer_worker_reset_live_publish(dt_drawlayer_worker_t *worker)
Reset worker-owned transient live-publish state.
Definition worker.c:1611
guint dt_drawlayer_worker_pending_dab_count(const dt_drawlayer_worker_t *worker)
Return the number of interpolated-but-not-yet-rasterized dabs in the current stroke batch.
Definition worker.c:1641
GArray * dt_drawlayer_worker_raw_inputs(dt_drawlayer_worker_t *worker)
Read-only access to preserved raw input history.
Definition worker.c:1630
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:368
static void _publish_backend_progress(drawlayer_paint_backend_ctx_t *ctx, gboolean flush_pending)
Definition worker.c:296
static gboolean _rt_queue_empty(const dt_drawlayer_worker_t *rt)
Test whether event queue is empty.
Definition worker.c:998
static gboolean _paint_layer_to_widget_cb(void *user_data, float lx, float ly, float *wx, float *wy)
Definition worker.c:283
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:1514
static void _resume_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Resume worker processing and wake sleeping thread.
Definition worker.c:1444
static gint64 _live_publish_interval_us(void)
Definition worker.c:490
void dt_drawlayer_worker_seal_for_commit(dt_drawlayer_worker_t *worker)
Seal current stroke for synchronous commit.
Definition worker.c:1598
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:273
static const drawlayer_rt_worker_t * _backend_worker_const(const dt_drawlayer_worker_t *rt)
Definition worker.c:485
static void _log_worker_batch_timing(const char *tag, guint processed_dabs, guint thread_count, double elapsed_ms, gboolean outer_loop)
Definition worker.c:517
static gboolean _worker_is_started(const drawlayer_rt_worker_t *worker)
Definition worker.c:1035
void dt_drawlayer_worker_reset_stroke(dt_drawlayer_worker_t *worker)
Clear preserved stroke runtime/history after commit completed.
Definition worker.c:1620
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:581
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:50
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:1224
static gboolean _stroke_begin(dt_drawlayer_worker_t *rt)
Start new stroke runtime and reset history/path state.
Definition worker.c:436
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:153
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:531
static void _stroke_destroy(dt_drawlayer_worker_t *rt)
Destroy stroke runtime and owned dab window.
Definition worker.c:394
static gboolean _wait_worker_idle(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Wait until worker queue is drained and not busy.
Definition worker.c:1260
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:1024
static const drawlayer_rt_callbacks_t _rt_callbacks[DRAWLAYER_RT_WORKER_COUNT]
Definition worker.c:390
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:663
#define DRAWLAYER_OUTER_LIVE_BATCH_MULTIPLIER
Definition worker.c:151
static void _stroke_clear(dt_drawlayer_worker_t *rt)
Clear current stroke state while preserving allocations.
Definition worker.c:455
dt_drawlayer_worker_state_t
Definition worker.h:31
@ DT_DRAWLAYER_WORKER_STATE_STOPPED
Definition worker.h:32
@ DT_DRAWLAYER_WORKER_STATE_IDLE
Definition worker.h:33
@ DT_DRAWLAYER_WORKER_STATE_BUSY
Definition worker.h:34
@ DT_DRAWLAYER_WORKER_STATE_PAUSED
Definition worker.h:36
@ DT_DRAWLAYER_WORKER_STATE_PAUSING
Definition worker.h:35