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 * The drawlayer module uses two worker roles:
23 * - a realtime backend worker consuming GUI raw input into the live process tile,
24 * - a deferred full-resolution worker replaying finished strokes into `base_patch`.
25 */
26
37
45
54
67
73
100
107
116
117static gboolean _paint_build_dab_cb(void *user_data, dt_drawlayer_paint_stroke_t *state,
119static gboolean _paint_layer_to_widget_cb(void *user_data, float lx, float ly, float *wx, float *wy);
120static void _paint_emit_backend_dab_cb(void *user_data, const dt_drawlayer_brush_dab_t *dab);
121static void _paint_emit_noop_cb(void *user_data, const dt_drawlayer_brush_dab_t *dab);
122static void _paint_stroke_seed_cb(void *user_data, uint64_t stroke_seed);
123static void _publish_backend_progress(drawlayer_paint_backend_ctx_t *ctx, gboolean flush_pending);
130static inline gint64 _live_publish_interval_us(void);
131static inline gboolean _live_publish_deadline_reached(const dt_drawlayer_worker_t *rt, const gint64 input_ts,
132 const gint64 interval_us);
135static guint _rasterize_pending_dab_batch(drawlayer_paint_backend_ctx_t *ctx, gint64 budget_us);
140static float *_ensure_fullres_replay_float_buffer(float **buffer, size_t *capacity_values, size_t needed_values);
141static guint _worker_batch_min_size(void);
142static gboolean _dab_batch_supports_outer_loop(const GArray *dabs, guint count);
143static void _log_worker_batch_timing(const char *tag, guint processed_dabs, guint thread_count, double elapsed_ms,
144 gboolean outer_loop);
145#if defined(_OPENMP) && OUTER_LOOP
146static guint _rasterize_dab_batch_outer_loop(const GArray *dabs, guint max_dabs, float distance_percent,
147 dt_drawlayer_cache_patch_t *patch, float scale,
148 dt_drawlayer_cache_patch_t *stroke_mask,
149 dt_drawlayer_damaged_rect_t *batch_damage,
150 const char *tag);
151#endif
152
153/* GUI worker painting into uint8 buffers is deimplemented. Realtime preview
154 * now relies on the regular pipeline/backbuffer path. */
155
156#if defined(_OPENMP) && OUTER_LOOP
157#include <omp.h>
158#endif
159
160#define DRAWLAYER_BATCH_TILE_SIZE 128
161#define DRAWLAYER_OUTER_LIVE_BATCH_MULTIPLIER 2u
162#define DRAWLAYER_OUTER_FULLRES_BATCH_MULTIPLIER 4u
163
167{
168 if(!self || !state || !input || !dab) return FALSE;
169
170 float lx = input->lx;
171 float ly = input->ly;
172 if(!input->have_layer_coords && !dt_drawlayer_widget_to_layer_coords(self, input->wx, input->wy, &lx, &ly))
173 return FALSE;
174
175 const float pressure_norm = _clamp01(input->pressure);
176 const float tilt_norm = _clamp01(input->tilt);
177 const float accel_norm = _clamp01(input->acceleration);
178 const gboolean map_pressure_size = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_SIZE) != 0u;
179 const gboolean map_pressure_opacity = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_OPACITY) != 0u;
180 const gboolean map_pressure_flow = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_FLOW) != 0u;
181 const gboolean map_pressure_softness = (input->map_flags & DRAWLAYER_INPUT_MAP_PRESSURE_SOFTNESS) != 0u;
182 const gboolean map_tilt_size = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_SIZE) != 0u;
183 const gboolean map_tilt_opacity = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_OPACITY) != 0u;
184 const gboolean map_tilt_flow = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_FLOW) != 0u;
185 const gboolean map_tilt_softness = (input->map_flags & DRAWLAYER_INPUT_MAP_TILT_SOFTNESS) != 0u;
186 const gboolean map_accel_size = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_SIZE) != 0u;
187 const gboolean map_accel_opacity = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_OPACITY) != 0u;
188 const gboolean map_accel_flow = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_FLOW) != 0u;
189 const gboolean map_accel_softness = (input->map_flags & DRAWLAYER_INPUT_MAP_ACCEL_SOFTNESS) != 0u;
190 const drawlayer_mapping_profile_t pressure_profile = (drawlayer_mapping_profile_t)CLAMP(
194 const drawlayer_mapping_profile_t accel_profile = (drawlayer_mapping_profile_t)CLAMP(
196 const float pressure_coeff = _mapping_profile_value(pressure_profile, pressure_norm);
197 const float tilt_coeff = _mapping_profile_value(tilt_profile, tilt_norm);
198 const float accel_coeff = _mapping_profile_value(accel_profile, accel_norm);
199
200 float radius = fmaxf(input->brush_radius, 0.5f);
201 float opacity = _clamp01(input->brush_opacity);
202 float flow = _clamp01(input->brush_flow);
203 const float sprinkles = _clamp01(input->brush_sprinkles);
204 float hardness = _clamp01(input->brush_hardness);
205 const float base_radius = radius;
206 const float base_opacity = opacity;
207 const float base_flow = flow;
208 const float base_hardness = hardness;
209
210 if(map_pressure_size) radius *= pressure_coeff;
211 if(map_pressure_opacity) opacity *= pressure_coeff;
212 if(map_pressure_flow) flow *= pressure_coeff;
213 if(map_pressure_softness) hardness *= pressure_coeff;
214
215 if(map_tilt_size) radius *= tilt_coeff;
216 if(map_tilt_opacity) opacity *= tilt_coeff;
217 if(map_tilt_flow) flow *= tilt_coeff;
218 if(map_tilt_softness) hardness *= tilt_coeff;
219
220 if(map_accel_size) radius *= accel_coeff;
221 if(map_accel_opacity) opacity *= accel_coeff;
222 if(map_accel_flow) flow *= accel_coeff;
223 if(map_accel_softness) hardness *= accel_coeff;
224
225 radius = fmaxf(radius, 0.5f);
226 hardness = _clamp01(hardness);
227 float dir_x = 0.0f;
228 float dir_y = 0.0f;
229 if(state->have_last_input_dab)
230 {
231 const float dx = lx - state->last_input_dab.x;
232 const float dy = ly - state->last_input_dab.y;
233 const float dir_len = hypotf(dx, dy);
234 if(dir_len > 1e-6f)
235 {
236 dir_x = dx / dir_len;
237 dir_y = dy / dir_len;
238 }
239 }
240
242 .x = lx,
243 .y = ly,
244 .wx = input->wx,
245 .wy = input->wy,
246 .radius = radius,
247 .dir_x = dir_x,
248 .dir_y = dir_y,
249 .opacity = _clamp01(opacity),
250 .flow = _clamp01(flow),
251 .sprinkles = _clamp01(sprinkles),
252 .sprinkle_size = input->brush_sprinkle_size,
253 .sprinkle_coarseness = _clamp01(input->brush_sprinkle_coarseness),
254 .hardness = hardness,
255 .color = { input->color[0], input->color[1], input->color[2], 1.0f },
256 .display_color = { input->display_color[0], input->display_color[1], input->display_color[2] },
257 .shape = input->brush_shape,
258 .mode = input->brush_mode,
259 .stroke_batch = input->stroke_batch,
260 .stroke_pos = input->stroke_pos,
261 };
262
263 if((map_pressure_size || map_pressure_opacity || map_pressure_flow || map_pressure_softness || map_tilt_size
264 || map_tilt_opacity || map_tilt_flow || map_tilt_softness || map_accel_size || map_accel_opacity
265 || map_accel_flow || map_accel_softness)
267 || ((state->history && (state->history->len & 15u) == 0u))))
268 {
270 "[drawlayer] map p=%.4f t=%.4f a=%.4f coeff[p=%.4f t=%.4f a=%.4f] "
271 "base[r=%.2f o=%.3f f=%.3f h=%.3f] out[r=%.2f o=%.3f f=%.3f h=%.3f] "
272 "flags[p=%d%d%d%d t=%d%d%d%d a=%d%d%d%d]\n",
273 pressure_norm, tilt_norm, accel_norm, pressure_coeff, tilt_coeff, accel_coeff, base_radius,
274 base_opacity, base_flow, base_hardness, radius, _clamp01(opacity), _clamp01(flow), _clamp01(hardness),
275 map_pressure_size ? 1 : 0, map_pressure_opacity ? 1 : 0, map_pressure_flow ? 1 : 0,
276 map_pressure_softness ? 1 : 0, map_tilt_size ? 1 : 0, map_tilt_opacity ? 1 : 0, map_tilt_flow ? 1 : 0,
277 map_tilt_softness ? 1 : 0, map_accel_size ? 1 : 0, map_accel_opacity ? 1 : 0, map_accel_flow ? 1 : 0,
278 map_accel_softness ? 1 : 0);
279 }
280
281 return TRUE;
282}
283
284static gboolean _paint_build_dab_cb(void *user_data, dt_drawlayer_paint_stroke_t *state,
286{
288 return (ctx && ctx->self)
290 (dt_drawlayer_brush_dab_t *)out_dab)
291 : FALSE;
292}
293
294static gboolean _paint_layer_to_widget_cb(void *user_data, float lx, float ly, float *wx, float *wy)
295{
297 return (ctx && ctx->self) ? dt_drawlayer_layer_to_widget_coords(ctx->self, lx, ly, wx, wy) : FALSE;
298}
299
300static void _paint_emit_backend_dab_cb(void *user_data, const dt_drawlayer_brush_dab_t *dab)
301{
303 if(ctx && ctx->self) _process_backend_dab(ctx->self, (const dt_drawlayer_brush_dab_t *)dab, ctx);
304}
305
306static void _paint_emit_noop_cb(void *user_data, const dt_drawlayer_brush_dab_t *dab)
307{
308 (void)user_data;
309 (void)dab;
310}
311
312static void _paint_stroke_seed_cb(void *user_data, uint64_t stroke_seed)
313{
315 if(!ctx) return;
316 if(ctx->stroke) dt_drawlayer_paint_runtime_set_stroke_seed(ctx->stroke, stroke_seed);
317}
318
319static void _publish_backend_progress(drawlayer_paint_backend_ctx_t *ctx, gboolean flush_pending)
320{
321 if(!ctx || !ctx->self || !flush_pending) return;
322
324 if(!ctx->worker || !g || !ctx->worker->live_publish_damage.valid) return;
325
328
329 dt_develop_t *dev = ctx->self->dev;
330 if(!dev) return;
331
333 const int sample_count = (int)g->stroke.stroke_sample_count;
334 dt_drawlayer_touch_stroke_commit_hash(params, sample_count, g->stroke.last_dab_valid, g->stroke.last_dab_x,
335 g->stroke.last_dab_y, ctx->worker->live_publish_serial);
336
342 ctx->worker->live_publish_ts = g_get_monotonic_time();
343}
344
347{
349 if(!g || !input || !stroke) return;
350
351 drawlayer_paint_backend_ctx_t ctx = _make_backend_ctx(self, g->stroke.worker, stroke);
352 const dt_drawlayer_paint_callbacks_t callbacks = {
354 .layer_to_widget = _paint_layer_to_widget_cb,
355 .emit_dab = _paint_emit_backend_dab_cb,
356 .on_stroke_seed = _paint_stroke_seed_cb,
357 };
358 if(!dt_drawlayer_paint_queue_raw_input(stroke, input)) return;
359 dt_drawlayer_paint_interpolate_path(stroke, &callbacks, &ctx);
360
361 if(stroke->pending_dabs && stroke->pending_dabs->len > 0)
362 {
363 const gint64 live_publish_interval_us = _live_publish_interval_us();
364 const gint64 input_ts = input->event_ts ? input->event_ts : g_get_monotonic_time();
365 if(ctx.worker && ctx.worker->live_publish_ts == 0)
366 ctx.worker->live_publish_ts = input_ts - live_publish_interval_us;
367
368 if(_live_publish_deadline_reached(ctx.worker, input_ts, live_publish_interval_us))
369 {
370 const guint processed_dabs = _rasterize_pending_dab_batch(&ctx, live_publish_interval_us);
371 if(processed_dabs > 0) _publish_backend_progress(&ctx, TRUE);
372 }
373 }
374}
375
377{
379 if(!g) return;
380
381 dt_drawlayer_damaged_rect_t backend_damage = { 0 };
382 if(g->stroke.worker && dt_drawlayer_paint_merge_runtime_stroke_damage(g->stroke.worker->backend_path, &backend_damage))
383 g->process.cache_dirty = TRUE;
384}
385
386static void _destroy_fullres_replay_scratch(gpointer data)
387{
389 if(!scratch) return;
391 dt_free_align(scratch->stroke_mask);
392 if(scratch->stroke)
393 {
394 if(scratch->stroke->history) g_array_free(scratch->stroke->history, TRUE);
395 if(scratch->stroke->pending_dabs) g_array_free(scratch->stroke->pending_dabs, TRUE);
396 if(scratch->stroke->dab_window) g_array_free(scratch->stroke->dab_window, TRUE);
398 }
399 dt_free(scratch);
400}
401
403
405{
408 if(scratch) return scratch;
409
410 scratch = g_malloc0(sizeof(*scratch));
411 if(!scratch) return NULL;
412
414 if(!scratch->stroke)
415 {
416 dt_free(scratch);
417 return NULL;
418 }
419 scratch->stroke->history = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
420 if(!scratch->stroke->history)
421 {
423 dt_free(scratch);
424 return NULL;
425 }
426 scratch->stroke->pending_dabs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
427 if(!scratch->stroke->pending_dabs)
428 {
429 g_array_free(scratch->stroke->history, TRUE);
430 scratch->stroke->history = NULL;
432 dt_free(scratch);
433 return NULL;
434 }
435 scratch->stroke->dab_window = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
436 if(!scratch->stroke->dab_window)
437 {
438 g_array_free(scratch->stroke->pending_dabs, TRUE);
439 scratch->stroke->pending_dabs = NULL;
440 g_array_free(scratch->stroke->history, TRUE);
441 scratch->stroke->history = NULL;
443 dt_free(scratch);
444 return NULL;
445 }
446
447 g_private_set(&_drawlayer_fullres_replay_scratch_key, scratch);
448 return scratch;
449}
450
451static float *_ensure_fullres_replay_float_buffer(float **buffer, size_t *capacity_values, size_t needed_values)
452{
453 if(!buffer || !capacity_values || needed_values == 0) return NULL;
454 if(*capacity_values < needed_values)
455 {
456 dt_free_align(*buffer);
457 *buffer = dt_alloc_align(needed_values * sizeof(float));
458 if(!*buffer)
459 {
460 *capacity_values = 0;
461 return NULL;
462 }
463 *capacity_values = needed_values;
464 }
465 return *buffer;
466}
467
469{
471 if(!g || !raw_inputs || raw_inputs->len == 0) return FALSE;
472 if(!g->process.base_patch.pixels || g->process.base_patch.width <= 0 || g->process.base_patch.height <= 0)
473 return FALSE;
474
476 if(!scratch || !scratch->stroke) return FALSE;
477
478 dt_drawlayer_paint_stroke_t *stroke = scratch->stroke;
481 if(!stroke->history)
482 {
483 stroke->history = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
484 if(!stroke->history) return FALSE;
485 }
486 g_array_set_size(stroke->history, 0);
487 if(!stroke->pending_dabs)
488 {
489 stroke->pending_dabs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
490 if(!stroke->pending_dabs) return FALSE;
491 }
492 g_array_set_size(stroke->pending_dabs, 0);
493 if(stroke->dab_window) g_array_set_size(stroke->dab_window, 0);
494
495 drawlayer_paint_backend_ctx_t replay_ctx = {
496 .self = self,
497 .worker = g->stroke.worker,
498 .stroke = stroke,
499 };
500 const dt_drawlayer_paint_callbacks_t callbacks = {
502 .layer_to_widget = _paint_layer_to_widget_cb,
503 .emit_dab = _paint_emit_noop_cb,
504 .on_stroke_seed = _paint_stroke_seed_cb,
505 };
506 for(guint i = 0; i < raw_inputs->len; i++)
507 {
508 const dt_drawlayer_paint_raw_input_t *input = &g_array_index(raw_inputs, dt_drawlayer_paint_raw_input_t, i);
509 if(!dt_drawlayer_paint_queue_raw_input(stroke, input)) return FALSE;
510 }
511 dt_drawlayer_paint_interpolate_path(stroke, &callbacks, &replay_ctx);
512 dt_drawlayer_paint_finalize_path(stroke, &callbacks, &replay_ctx);
513 if(!stroke->history || stroke->history->len == 0 || !stroke->pending_dabs || stroke->pending_dabs->len == 0)
514 return FALSE;
515
516 dt_drawlayer_damaged_rect_t replay_bounds = { 0 };
517 for(guint i = 0; i < stroke->history->len; i++)
518 {
519 const dt_drawlayer_brush_dab_t *dab = &g_array_index(stroke->history, dt_drawlayer_brush_dab_t, i);
520 const int x0 = CLAMP((int)floorf(dab->x - dab->radius - 1.0f), 0, g->process.base_patch.width - 1);
521 const int y0 = CLAMP((int)floorf(dab->y - dab->radius - 1.0f), 0, g->process.base_patch.height - 1);
522 const int x1 = CLAMP((int)ceilf(dab->x + dab->radius + 1.0f), 0, g->process.base_patch.width);
523 const int y1 = CLAMP((int)ceilf(dab->y + dab->radius + 1.0f), 0, g->process.base_patch.height);
524 const dt_drawlayer_damaged_rect_t dab_bounds = {
525 .valid = TRUE,
526 .nw = { x0, y0 },
527 .se = { x1, y1 },
528 };
529 dt_drawlayer_paint_runtime_note_dab_damage(&replay_bounds, &dab_bounds);
530 }
531 if(!replay_bounds.valid) return FALSE;
532
533 const int replay_width = replay_bounds.se[0] - replay_bounds.nw[0];
534 const int replay_height = replay_bounds.se[1] - replay_bounds.nw[1];
535 if(replay_width <= 0 || replay_height <= 0) return FALSE;
536
537 float *replay_pixels = _ensure_fullres_replay_float_buffer(
538 &scratch->replay_pixels, &scratch->replay_pixels_capacity, (size_t)replay_width * replay_height * 4);
539 if(!replay_pixels) return FALSE;
540
541 float *stroke_mask = _ensure_fullres_replay_float_buffer(&scratch->stroke_mask, &scratch->stroke_mask_capacity,
542 (size_t)replay_width * replay_height);
543 if(!stroke_mask) return FALSE;
544 memset(stroke_mask, 0, (size_t)replay_width * replay_height * sizeof(float));
545 dt_drawlayer_damaged_rect_t replay_damage = { 0 };
546 dt_drawlayer_cache_patch_t replay_patch = {
547 .x = replay_bounds.nw[0],
548 .y = replay_bounds.nw[1],
549 .width = replay_width,
550 .height = replay_height,
551 .pixels = replay_pixels,
552 .external_alloc = TRUE,
553 };
554 dt_drawlayer_cache_patch_t replay_stroke_mask = {
555 .x = replay_bounds.nw[0],
556 .y = replay_bounds.nw[1],
557 .width = replay_width,
558 .height = replay_height,
559 .pixels = stroke_mask,
560 .external_alloc = TRUE,
561 };
562
563 dt_drawlayer_cache_patch_rdlock(&g->process.base_patch);
564#ifdef _OPENMP
565#pragma omp parallel for default(none) schedule(static) \
566 dt_omp_firstprivate(g, replay_bounds, replay_height, replay_pixels, replay_width) if(replay_height > 8)
567#endif
568 for(int yy = 0; yy < replay_height; yy++)
569 {
570 const float *src = g->process.base_patch.pixels
571 + ((size_t)(replay_bounds.nw[1] + yy) * g->process.base_patch.width + replay_bounds.nw[0]) * 4;
572 float *dst = replay_pixels + (size_t)yy * replay_width * 4;
573 memcpy(dst, src, (size_t)replay_width * 4 * sizeof(float));
574 }
575 dt_drawlayer_cache_patch_rdunlock(&g->process.base_patch);
576
577 gboolean wrote_replay_tile = FALSE;
578#if defined(_OPENMP) && OUTER_LOOP
579 if(stroke->pending_dabs->len >= _worker_batch_min_size()
581 {
582 while(stroke->pending_dabs->len > 0)
583 {
584 const guint batch_dabs
586 dt_drawlayer_damaged_rect_t batch_damage = { 0 };
587 const guint processed_dabs = _rasterize_dab_batch_outer_loop(
588 stroke->pending_dabs, batch_dabs, _clamp01(stroke->distance_percent), &replay_patch, 1.0f,
589 &replay_stroke_mask, &batch_damage, "fullres");
590 if(processed_dabs == 0) break;
591 g_array_remove_range(stroke->pending_dabs, 0, processed_dabs);
592 dt_drawlayer_paint_runtime_note_dab_damage(&replay_damage, &batch_damage);
593 wrote_replay_tile = wrote_replay_tile || batch_damage.valid;
594 dt_iop_nap(100);
595 }
596 }
597 else
598#endif
599 {
600 const double replay_t0 = dt_get_wtime();
601 wrote_replay_tile = dt_drawlayer_paint_raster_path(stroke->pending_dabs, _clamp01(stroke->distance_percent),
602 &replay_patch, 1.0f, &replay_stroke_mask, &replay_damage,
603 stroke);
604 if(wrote_replay_tile)
605 _log_worker_batch_timing("fullres", stroke->pending_dabs->len, 1, 1000.0 * (dt_get_wtime() - replay_t0),
606 FALSE);
607 }
608 if(wrote_replay_tile)
609 {
610 dt_drawlayer_cache_patch_wrlock(&g->process.base_patch);
611#ifdef _OPENMP
612#pragma omp parallel for default(none) schedule(static) \
613 dt_omp_firstprivate(g, replay_bounds, replay_height, replay_pixels, replay_width) if(replay_height > 8)
614#endif
615 for(int yy = 0; yy < replay_height; yy++)
616 {
617 const float *src = replay_pixels + (size_t)yy * replay_width * 4;
618 float *dst = g->process.base_patch.pixels
619 + ((size_t)(replay_bounds.nw[1] + yy) * g->process.base_patch.width + replay_bounds.nw[0]) * 4;
620 memcpy(dst, src, (size_t)replay_width * 4 * sizeof(float));
621 }
622#ifdef HAVE_OPENCL
624 g->process.base_patch.cache_entry, -1);
625#endif
626 dt_drawlayer_cache_patch_wrunlock(&g->process.base_patch);
627 }
628 return wrote_replay_tile;
629}
630
633{
634 dt_drawlayer_paint_stroke_t *stroke = ctx ? ctx->stroke : NULL;
636 if(!g || !stroke || !stroke->dab_window || !dab) return;
637
638 gboolean have_process_damage = FALSE;
639 dt_drawlayer_damaged_rect_t process_step_path = { 0 };
640 dt_drawlayer_damaged_rect_t process_step_damage = { 0 };
641 dt_drawlayer_paint_runtime_state_reset(&process_step_path);
642
643 if(g->process.process_patch_valid && g->process.process_patch.pixels && g->process.process_patch.width > 0
644 && g->process.process_patch.height > 0 && g->process.process_combined_roi.scale > 1e-6f)
645 {
646 dt_drawlayer_cache_patch_t process_patch = g->process.process_patch;
647 process_patch.x = g->process.process_combined_roi.x;
648 process_patch.y = g->process.process_combined_roi.y;
649 dt_drawlayer_cache_patch_t process_stroke_mask = g->process.process_stroke_mask;
650 process_stroke_mask.x = process_patch.x;
651 process_stroke_mask.y = process_patch.y;
653 dab, _clamp01(stroke->distance_percent), &process_patch, g->process.process_combined_roi.scale,
654 &process_stroke_mask, &process_step_path, stroke);
655 have_process_damage = dt_drawlayer_paint_runtime_get_stroke_damage(&process_step_path, &process_step_damage);
656 if(have_process_damage)
657 {
658 g->process.process_patch_dirty = TRUE;
659 dt_drawlayer_paint_runtime_note_dab_damage(&g->process.process_dirty_rect, &process_step_damage);
660 }
661 }
662
663 if(have_process_damage)
664 {
665 g->process.cache_dirty = TRUE;
666 if(ctx && ctx->worker)
668 g->stroke.last_dab_valid = TRUE;
669 g->stroke.last_dab_x = dab->x;
670 g->stroke.last_dab_y = dab->y;
671 }
672}
673
677
680{
681 if(!job) return;
682 if(job->raw_inputs) g_array_free(job->raw_inputs, TRUE);
683 dt_free(job);
684}
685
689{
690 if(!rt) return;
691 if(rt->stroke && rt->stroke->pending_dabs)
692 {
693 g_array_free(rt->stroke->pending_dabs, TRUE);
694 rt->stroke->pending_dabs = NULL;
695 }
696 if(rt->stroke && rt->stroke->dab_window)
697 {
698 g_array_free(rt->stroke->dab_window, TRUE);
699 rt->stroke->dab_window = NULL;
700 }
702}
703
706{
707 if(!rt) return FALSE;
708 if(rt->stroke) return TRUE;
710 if(!rt->stroke) return FALSE;
711 rt->stroke->history = rt->backend_history;
712 rt->stroke->pending_dabs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
713 if(!rt->stroke->pending_dabs)
714 {
716 return FALSE;
717 }
718 rt->stroke->dab_window = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
719 if(!rt->stroke->dab_window)
720 {
721 g_array_free(rt->stroke->pending_dabs, TRUE);
722 rt->stroke->pending_dabs = NULL;
724 return FALSE;
725 }
726 return TRUE;
727}
728
731{
732 if(!rt) return FALSE;
733 if(!rt->backend_history)
734 rt->backend_history = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
735 if(!rt->backend_history) return FALSE;
736 if(!rt->stroke_raw_inputs)
737 rt->stroke_raw_inputs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_paint_raw_input_t));
738 if(!rt->stroke_raw_inputs) return FALSE;
739 if(!_stroke_create(rt)) return FALSE;
740 rt->stroke->history = rt->backend_history;
743 g_array_set_size(rt->backend_history, 0);
744 g_array_set_size(rt->stroke_raw_inputs, 0);
746 return TRUE;
747}
748
751{
752 if(!rt) return;
753 if(rt->backend_history) g_array_set_size(rt->backend_history, 0);
754 if(rt->stroke_raw_inputs) g_array_set_size(rt->stroke_raw_inputs, 0);
755 if(rt->stroke)
756 {
759 }
761}
762
767
769{
770 if(!rt) return;
771 rt->live_publish_ts = 0;
772 rt->live_publish_serial = 0;
774}
775
777{
778 return rt ? &rt->workers[DRAWLAYER_RT_WORKER_BACKEND] : NULL;
779}
780
782{
783 return rt ? &rt->workers[DRAWLAYER_RT_WORKER_BACKEND] : NULL;
784}
785
786static inline gint64 _live_publish_interval_us(void)
787{
788 return MAX((gint64)dt_gui_throttle_get_pipe_runtime_us(DT_DEV_PIXELPIPE_FULL), (gint64)20000);
789}
790
791static guint _worker_batch_min_size(void)
792{
793#if defined(_OPENMP) && OUTER_LOOP
794 return MAX(1, omp_get_max_threads());
795#else
796 return 1;
797#endif
798}
799
800static gboolean _dab_batch_supports_outer_loop(const GArray *dabs, const guint count)
801{
802 if(!dabs || count == 0) return FALSE;
803 for(guint i = 0; i < count; i++)
804 {
805 const dt_drawlayer_brush_dab_t *dab = &g_array_index(dabs, dt_drawlayer_brush_dab_t, i);
806 if(dab->mode == DT_DRAWLAYER_BRUSH_MODE_SMUDGE) return FALSE;
807 }
808 return TRUE;
809}
810
811static void _log_worker_batch_timing(const char *tag, const guint processed_dabs, const guint thread_count,
812 const double elapsed_ms, const gboolean outer_loop)
813{
814 if(!(darktable.unmuted & DT_DEBUG_PERF)) return;
815 dt_print(DT_DEBUG_PERF, "[drawlayer] batch worker=%s dabs=%u threads=%u outer=%d ms=%.3f\n",
816 tag ? tag : "unknown", processed_dabs, thread_count, outer_loop ? 1 : 0, elapsed_ms);
817}
818
819static inline gboolean _live_publish_deadline_reached(const dt_drawlayer_worker_t *rt, const gint64 input_ts,
820 const gint64 interval_us)
821{
822 return rt && input_ts - rt->live_publish_ts >= interval_us;
823}
824
827{
829 .self = self,
830 .worker = worker,
831 .stroke = stroke,
832 };
833}
834
835#if defined(_OPENMP) && OUTER_LOOP
836static gboolean _dab_bounds_in_patch(const dt_drawlayer_cache_patch_t *patch, const float scale,
838{
839 if(bounds) *bounds = (dt_drawlayer_damaged_rect_t){ 0 };
840 if(!patch || !dab || !bounds || !patch->pixels || patch->width <= 0 || patch->height <= 0
841 || dab->radius <= 0.0f || dab->opacity <= 0.0f || scale <= 0.0f)
842 return FALSE;
843
844 const float support_radius = dab->radius;
845 bounds->valid = TRUE;
846 bounds->nw[0] = MAX(0, (int)floorf((dab->x - support_radius) * scale) - patch->x);
847 bounds->nw[1] = MAX(0, (int)floorf((dab->y - support_radius) * scale) - patch->y);
848 bounds->se[0] = MIN(patch->width, (int)ceilf((dab->x + support_radius) * scale) - patch->x + 1);
849 bounds->se[1] = MIN(patch->height, (int)ceilf((dab->y + support_radius) * scale) - patch->y + 1);
850 return bounds->se[0] > bounds->nw[0] && bounds->se[1] > bounds->nw[1];
851}
852
853static dt_drawlayer_paint_stroke_t *_create_batch_runtime(void)
854{
856 if(!runtime) return NULL;
857 runtime->dab_window = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
858 if(!runtime->dab_window)
859 {
861 return NULL;
862 }
863 return runtime;
864}
865
866static void _destroy_batch_runtime(dt_drawlayer_paint_stroke_t **runtime)
867{
868 if(!runtime || !*runtime) return;
869 if((*runtime)->dab_window) g_array_free((*runtime)->dab_window, TRUE);
870 (*runtime)->dab_window = NULL;
872}
873
874static void _lock_batch_tiles(omp_lock_t *locks, const int tile_cols, const int tile_origin_x,
875 const int tile_origin_y, const dt_drawlayer_damaged_rect_t *bounds)
876{
877 if(!locks || tile_cols <= 0 || !bounds || !bounds->valid) return;
878 const int tx0 = bounds->nw[0] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x;
879 const int ty0 = bounds->nw[1] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y;
880 const int tx1 = MAX(tx0, (bounds->se[0] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x);
881 const int ty1 = MAX(ty0, (bounds->se[1] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y);
882 for(int ty = ty0; ty <= ty1; ty++)
883 for(int tx = tx0; tx <= tx1; tx++)
884 omp_set_lock(&locks[ty * tile_cols + tx]);
885}
886
887static void _unlock_batch_tiles(omp_lock_t *locks, const int tile_cols, const int tile_origin_x,
888 const int tile_origin_y, const dt_drawlayer_damaged_rect_t *bounds)
889{
890 if(!locks || tile_cols <= 0 || !bounds || !bounds->valid) return;
891 const int tx0 = bounds->nw[0] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x;
892 const int ty0 = bounds->nw[1] / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y;
893 const int tx1 = MAX(tx0, (bounds->se[0] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x);
894 const int ty1 = MAX(ty0, (bounds->se[1] - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y);
895 for(int ty = ty1; ty >= ty0; ty--)
896 for(int tx = tx1; tx >= tx0; tx--)
897 omp_unset_lock(&locks[ty * tile_cols + tx]);
898}
899
900static guint _rasterize_dab_batch_outer_loop(const GArray *dabs, const guint max_dabs, const float distance_percent,
901 dt_drawlayer_cache_patch_t *patch, const float scale,
902 dt_drawlayer_cache_patch_t *stroke_mask,
903 dt_drawlayer_damaged_rect_t *batch_damage,
904 const char *tag)
905{
906 if(!dabs || max_dabs == 0 || !patch || !patch->pixels || patch->width <= 0 || patch->height <= 0) return 0;
907
908 const guint thread_count = _worker_batch_min_size();
909 dt_drawlayer_damaged_rect_t batch_bounds = { 0 };
910 for(guint i = 0; i < max_dabs; i++)
911 {
912 dt_drawlayer_damaged_rect_t dab_bounds = { 0 };
913 const dt_drawlayer_brush_dab_t *dab = &g_array_index(dabs, dt_drawlayer_brush_dab_t, i);
914 if(_dab_bounds_in_patch(patch, scale, dab, &dab_bounds))
915 dt_drawlayer_paint_runtime_note_dab_damage(&batch_bounds, &dab_bounds);
916 }
917 if(!batch_bounds.valid) return 0;
918
919 const int tile_origin_x = batch_bounds.nw[0] / DRAWLAYER_BATCH_TILE_SIZE;
920 const int tile_origin_y = batch_bounds.nw[1] / DRAWLAYER_BATCH_TILE_SIZE;
921 const int tile_cols = MAX(1, (batch_bounds.se[0] + DRAWLAYER_BATCH_TILE_SIZE - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_x);
922 const int tile_rows = MAX(1, (batch_bounds.se[1] + DRAWLAYER_BATCH_TILE_SIZE - 1) / DRAWLAYER_BATCH_TILE_SIZE - tile_origin_y);
923 dt_drawlayer_paint_stroke_t **thread_runtime = g_malloc0((size_t)thread_count * sizeof(*thread_runtime));
924 dt_drawlayer_damaged_rect_t *thread_damage = g_malloc0((size_t)thread_count * sizeof(*thread_damage));
925 omp_lock_t *tile_locks = g_malloc0((size_t)tile_cols * tile_rows * sizeof(*tile_locks));
926 if(!thread_runtime || !thread_damage || !tile_locks)
927 {
928 g_free(thread_runtime);
929 g_free(thread_damage);
930 g_free(tile_locks);
931 return 0;
932 }
933
934 for(guint i = 0; i < thread_count; i++)
935 {
936 thread_runtime[i] = _create_batch_runtime();
937 if(!thread_runtime[i])
938 {
939 for(guint k = 0; k < i; k++) _destroy_batch_runtime(&thread_runtime[k]);
940 g_free(thread_runtime);
941 g_free(thread_damage);
942 g_free(tile_locks);
943 return 0;
944 }
945 }
946
947 for(int i = 0; i < tile_cols * tile_rows; i++)
948 omp_init_lock(&tile_locks[i]);
949
950 const double t0 = dt_get_wtime();
951#pragma omp parallel for schedule(static) default(none) \
952 shared(dabs, patch, stroke_mask, thread_runtime, thread_damage, tile_locks, tile_cols, tile_origin_x, tile_origin_y, max_dabs, distance_percent, scale)
953 for(guint i = 0; i < max_dabs; i++)
954 {
955 const int tid = omp_get_thread_num();
956 dt_drawlayer_paint_stroke_t *runtime = thread_runtime[tid];
957 dt_drawlayer_damaged_rect_t runtime_damage = { 0 };
958 dt_drawlayer_damaged_rect_t dab_damage = { 0 };
959 dt_drawlayer_damaged_rect_t bounds = { 0 };
960 const dt_drawlayer_brush_dab_t *dab = &g_array_index(dabs, dt_drawlayer_brush_dab_t, i);
961
964 if(runtime->dab_window) g_array_set_size(runtime->dab_window, 0);
965 if(!_dab_bounds_in_patch(patch, scale, dab, &bounds))
966 continue;
967
968 _lock_batch_tiles(tile_locks, tile_cols, tile_origin_x, tile_origin_y, &bounds);
969 dt_drawlayer_paint_rasterize_segment_to_buffer(dab, distance_percent, patch, scale, stroke_mask,
970 &runtime_damage, runtime);
971 _unlock_batch_tiles(tile_locks, tile_cols, tile_origin_x, tile_origin_y, &bounds);
972
973 if(dt_drawlayer_paint_runtime_get_stroke_damage(&runtime_damage, &dab_damage))
974 dt_drawlayer_paint_runtime_note_dab_damage(&thread_damage[tid], &dab_damage);
975 }
976 const double t1 = dt_get_wtime();
977
978 if(batch_damage) dt_drawlayer_paint_runtime_state_reset(batch_damage);
979 for(guint i = 0; i < thread_count; i++)
980 {
981 if(batch_damage) dt_drawlayer_paint_runtime_note_dab_damage(batch_damage, &thread_damage[i]);
982 _destroy_batch_runtime(&thread_runtime[i]);
983 }
984 for(int i = 0; i < tile_cols * tile_rows; i++)
985 omp_destroy_lock(&tile_locks[i]);
986
987 g_free(thread_runtime);
988 g_free(thread_damage);
989 g_free(tile_locks);
990
991 _log_worker_batch_timing(tag, max_dabs, thread_count, 1000.0 * (t1 - t0), TRUE);
992 return max_dabs;
993}
994#endif
995
997{
999 dt_drawlayer_paint_stroke_t *stroke = ctx ? ctx->stroke : NULL;
1000 if(!g || !stroke || !stroke->pending_dabs || stroke->pending_dabs->len == 0) return 0;
1001
1002 const guint remaining_dabs = stroke->pending_dabs->len;
1003 const guint min_batch = _worker_batch_min_size();
1004
1005#if defined(_OPENMP) && OUTER_LOOP
1006 if(remaining_dabs >= min_batch && _dab_batch_supports_outer_loop(stroke->pending_dabs, remaining_dabs))
1007 {
1008 const guint batch_dabs = (budget_us > 0)
1009 ? MIN(remaining_dabs, min_batch * DRAWLAYER_OUTER_LIVE_BATCH_MULTIPLIER)
1010 : remaining_dabs;
1011 dt_drawlayer_damaged_rect_t batch_damage = { 0 };
1012 dt_drawlayer_cache_patch_t process_patch = g->process.process_patch;
1013 dt_drawlayer_cache_patch_t process_stroke_mask = g->process.process_stroke_mask;
1014 process_patch.x = g->process.process_combined_roi.x;
1015 process_patch.y = g->process.process_combined_roi.y;
1016 process_stroke_mask.x = process_patch.x;
1017 process_stroke_mask.y = process_patch.y;
1018
1019 dt_drawlayer_cache_patch_wrlock(&g->process.process_patch);
1020 const guint processed_dabs = _rasterize_dab_batch_outer_loop(
1021 stroke->pending_dabs, batch_dabs, _clamp01(stroke->distance_percent), &process_patch,
1022 g->process.process_combined_roi.scale, &process_stroke_mask, &batch_damage, "realtime");
1023 dt_drawlayer_cache_patch_wrunlock(&g->process.process_patch);
1024
1025 if(processed_dabs > 0)
1026 {
1027 const dt_drawlayer_brush_dab_t *last_dab
1028 = &g_array_index(stroke->pending_dabs, dt_drawlayer_brush_dab_t, processed_dabs - 1);
1029 g_array_remove_range(stroke->pending_dabs, 0, processed_dabs);
1030 if(batch_damage.valid)
1031 {
1032 g->process.process_patch_dirty = TRUE;
1033 g->process.cache_dirty = TRUE;
1034 dt_drawlayer_paint_runtime_note_dab_damage(&g->process.process_dirty_rect, &batch_damage);
1035 if(ctx->worker)
1037 g->stroke.last_dab_valid = TRUE;
1038 g->stroke.last_dab_x = last_dab->x;
1039 g->stroke.last_dab_y = last_dab->y;
1040 }
1041 }
1042 return processed_dabs;
1043 }
1044#endif
1045
1046 dt_drawlayer_cache_patch_wrlock(&g->process.process_patch);
1047 guint processed_dabs = 0;
1048 const double batch_t0 = dt_get_wtime();
1049 while(processed_dabs < stroke->pending_dabs->len)
1050 {
1051 const dt_drawlayer_brush_dab_t *dab
1052 = &g_array_index(stroke->pending_dabs, dt_drawlayer_brush_dab_t, processed_dabs);
1053 _process_backend_dab(ctx->self, dab, ctx);
1054 processed_dabs++;
1055
1056 if(budget_us > 0)
1057 {
1058 const gint64 elapsed_us = (gint64)(1000000.0 * (dt_get_wtime() - batch_t0));
1059 if(processed_dabs >= min_batch && elapsed_us >= budget_us) break;
1060 }
1061 }
1062 if(processed_dabs > 0) g_array_remove_range(stroke->pending_dabs, 0, processed_dabs);
1063 dt_drawlayer_cache_patch_wrunlock(&g->process.process_patch);
1064 if(processed_dabs > 0)
1065 _log_worker_batch_timing("realtime", processed_dabs, 1, 1000.0 * (dt_get_wtime() - batch_t0), FALSE);
1066 return processed_dabs;
1067}
1068
1070{
1071 if(!worker->stroke_raw_inputs)
1072 worker->stroke_raw_inputs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_paint_raw_input_t));
1073
1074 dt_drawlayer_paint_raw_input_t event = { 0 };
1075 while(worker->stroke_raw_inputs && _rt_queue_pop_locked(worker, &event))
1076 g_array_append_val(worker->stroke_raw_inputs, event);
1077}
1078
1081{
1083 if(!worker) return;
1084 worker->ring_head = 0;
1085 worker->ring_tail = 0;
1086 worker->ring_count = 0;
1087}
1088
1090static gboolean _rt_queue_empty(const dt_drawlayer_worker_t *rt)
1091{
1092 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1093 return !worker || worker->ring_count == 0;
1094}
1095
1097static gboolean _rt_queue_full(const dt_drawlayer_worker_t *rt)
1098{
1099 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1100 return !worker || (worker->ring_capacity > 0 && worker->ring_count >= worker->ring_capacity);
1101}
1102
1105 const dt_drawlayer_paint_raw_input_t *event)
1106{
1108 if(!worker || !event || !worker->ring || worker->ring_capacity == 0 || _rt_queue_full(rt)) return FALSE;
1109 worker->ring[worker->ring_tail] = *event;
1110 worker->ring_tail = (worker->ring_tail + 1) % worker->ring_capacity;
1111 worker->ring_count++;
1112 return TRUE;
1113}
1114
1118{
1120 if(!worker || !event || !worker->ring || worker->ring_capacity == 0 || _rt_queue_empty(rt)) return FALSE;
1121 *event = worker->ring[worker->ring_head];
1122 worker->ring_head = (worker->ring_head + 1) % worker->ring_capacity;
1123 worker->ring_count--;
1124 return TRUE;
1125}
1126
1127static inline gboolean _worker_is_started(const drawlayer_rt_worker_t *worker)
1128{
1129 return worker && worker->state != DT_DRAWLAYER_WORKER_STATE_STOPPED;
1130}
1131
1132static inline gboolean _worker_is_busy(const drawlayer_rt_worker_t *worker)
1133{
1134 return worker && worker->state == DT_DRAWLAYER_WORKER_STATE_BUSY;
1135}
1136
1137static inline gboolean _worker_pause_requested(const drawlayer_rt_worker_t *worker)
1138{
1139 return worker
1142}
1143
1145{
1146 return rt && rt->stroke && rt->stroke->pending_dabs && rt->stroke->pending_dabs->len > 0;
1147}
1148
1149static inline gboolean _fullres_worker_started(const dt_drawlayer_worker_t *rt)
1150{
1152}
1153
1154static inline gboolean _fullres_worker_busy(const dt_drawlayer_worker_t *rt)
1155{
1157}
1158
1161{
1163 if(worker) worker->state = state;
1164}
1165
1168{
1169#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
1170 struct sched_param param = { 0 };
1171 const int max_prio = sched_get_priority_max(SCHED_FIFO);
1172 if(max_prio > 0)
1173 {
1174 param.sched_priority = MIN(max_prio, 80);
1175 pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
1176 }
1177#endif
1178}
1179
1182{
1183 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1184 return worker && (_worker_is_busy(worker) || worker->ring_count > 0
1186}
1187
1190{
1191 return rt && (_fullres_worker_busy(rt) || rt->fullres_stop
1192 || (rt->finished_stroke_queue && !g_queue_is_empty(rt->finished_stroke_queue)));
1193}
1194
1197{
1199}
1200
1203{
1204 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1205 return worker && rt->finish_commit_pending && rt->painting
1206 && *rt->finish_commit_pending && !*rt->painting
1207 && !_worker_is_busy(worker) && worker->ring_count == 0;
1208}
1209
1212{
1213 gboolean active = FALSE;
1214 if(!rt) return FALSE;
1216 active = _workers_active_locked(rt);
1218 return active;
1219}
1220
1223{
1224 gboolean active = FALSE;
1225 if(!rt) return FALSE;
1227 active = _workers_any_active_locked(rt);
1229 return active;
1230}
1231
1233static gboolean _async_commit_idle(gpointer user_data)
1234{
1236 dt_iop_module_t *self = rt ? rt->self : NULL;
1237 if(!rt || !self || !self->dev) return G_SOURCE_REMOVE;
1238
1239 gboolean should_commit = FALSE;
1242 should_commit = _workers_ready_for_commit_locked(rt);
1244
1245 if(should_commit) _commit_dabs(self, TRUE);
1246 return G_SOURCE_REMOVE;
1247}
1248
1251{
1252 if(!rt || !rt->self || !rt->self->dev) return;
1254 rt->finish_commit_source_id = g_idle_add(_async_commit_idle, rt);
1255}
1256
1259{
1261 drawlayer_paint_backend_ctx_t ctx = _make_backend_ctx(self, rt, rt ? rt->stroke : NULL);
1262 if(self && g && rt && rt->stroke && rt->stroke->pending_dabs && rt->stroke->pending_dabs->len > 0)
1263 {
1264 const guint processed_dabs = _rasterize_pending_dab_batch(&ctx, _live_publish_interval_us());
1265 if(processed_dabs > 0) _publish_backend_progress(&ctx, TRUE);
1266 return;
1267 }
1268
1269 if(!rt) return;
1273}
1274
1277 const dt_drawlayer_paint_raw_input_t *input)
1278{
1279 if(!rt || !input) return;
1280 if(rt && input && input->stroke_pos == DT_DRAWLAYER_PAINT_STROKE_FIRST)
1281 {
1282 _stroke_begin(rt);
1283 }
1284
1285 if(!rt->stroke && !_stroke_begin(rt)) return;
1286 if(rt->stroke_raw_inputs) g_array_append_val(rt->stroke_raw_inputs, *input);
1287 _process_backend_input(self, input, rt->stroke);
1288}
1289
1292{
1293 if(!rt) return;
1294
1295 /* Worker only drains per-stroke pending samples. Stroke-damage rectangles are
1296 * consumed/applied by drawlayer during commit, after worker idle is reached. */
1297 if(rt->stroke)
1298 {
1301 const dt_drawlayer_paint_callbacks_t callbacks = {
1303 .layer_to_widget = _paint_layer_to_widget_cb,
1304 .emit_dab = _paint_emit_backend_dab_cb,
1305 .on_stroke_seed = _paint_stroke_seed_cb,
1306 };
1307 if(g)
1308 {
1309 dt_drawlayer_paint_finalize_path(rt->stroke, &callbacks, &ctx);
1312 }
1313 }
1318 pthread_cond_broadcast(&rt->worker_cond);
1320}
1321
1322static const drawlayer_rt_callbacks_t _rt_callbacks[] = {
1324 .thread_name = "draw-back",
1325 .process_sample = _backend_worker_process_sample,
1326 .process_stroke_end = _backend_worker_process_stroke_end,
1327 .on_idle = _backend_worker_on_idle,
1328 },
1329};
1330
1333{
1334 if(!rt_out || !*rt_out) return;
1335 dt_drawlayer_worker_t *rt = *rt_out;
1337 _stop_worker(self ? self : rt->self, rt);
1338 _stroke_destroy(rt);
1339 if(rt->backend_history) g_array_free(rt->backend_history, TRUE);
1340 if(rt->stroke_raw_inputs) g_array_free(rt->stroke_raw_inputs, TRUE);
1341 if(rt->finished_stroke_queue)
1342 {
1343 while(!g_queue_is_empty(rt->finished_stroke_queue))
1345 g_queue_free(rt->finished_stroke_queue);
1346 }
1348 dt_free(worker->ring);
1349 pthread_cond_destroy(&rt->worker_cond);
1351 dt_free(rt);
1352 *rt_out = NULL;
1353}
1354
1357 gboolean *painting, gboolean *finish_commit_pending,
1358 guint *stroke_sample_count, uint32_t *current_stroke_batch,
1359 dt_drawlayer_worker_finished_stroke_cb finished_stroke_cb)
1360{
1361 if(!rt_out) return;
1362 _rt_destroy_state(self, rt_out);
1363 dt_drawlayer_worker_t *rt = NULL;
1364 rt = g_malloc0(sizeof(*rt));
1365 *rt_out = rt;
1366 if(!rt) return;
1367
1368 rt->self = self;
1369 rt->painting = painting;
1370 rt->finish_commit_pending = finish_commit_pending;
1371 rt->stroke_sample_count = stroke_sample_count;
1372 rt->current_stroke_batch = current_stroke_batch;
1373 rt->finished_stroke_cb = finished_stroke_cb;
1376 pthread_cond_init(&rt->worker_cond, NULL);
1377
1380 worker->ring = g_malloc_n(worker->ring_capacity, sizeof(dt_drawlayer_paint_raw_input_t));
1381 rt->backend_history = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_brush_dab_t));
1382 rt->stroke_raw_inputs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_paint_raw_input_t));
1383 rt->finished_stroke_queue = g_queue_new();
1385 _stroke_create(rt);
1386}
1387
1390{
1391 _rt_destroy_state(NULL, rt_out);
1392}
1393
1396{
1397 (void)self;
1398 const drawlayer_rt_worker_t *worker = _backend_worker_const(rt);
1399 if(!rt || !worker || !_worker_is_started(worker)) return TRUE;
1400
1402 while(_worker_is_busy(worker) || worker->ring_count > 0)
1403 {
1404 if(worker->stop) break;
1406 }
1408 return TRUE;
1409}
1410
1415
1417static void *_drawlayer_fullres_worker_main(void *user_data)
1418{
1420 dt_drawlayer_worker_t *rt = ctx ? ctx->rt : NULL;
1421 dt_free(ctx);
1422
1423 dt_iop_module_t *self = rt ? rt->self : NULL;
1424 if(!self || !rt) return NULL;
1425
1426 dt_pthread_setname("draw-base");
1427
1428 while(TRUE)
1429 {
1431
1433 while(!rt->fullres_stop && (!rt->finished_stroke_queue || g_queue_is_empty(rt->finished_stroke_queue)))
1434 {
1436 pthread_cond_broadcast(&rt->worker_cond);
1438 }
1439
1440 if(rt->fullres_stop)
1441 {
1443 pthread_cond_broadcast(&rt->worker_cond);
1445 break;
1446 }
1447
1448 job = (drawlayer_finished_stroke_job_t *)g_queue_pop_head(rt->finished_stroke_queue);
1451
1452 if(job && rt->finished_stroke_cb)
1453 rt->finished_stroke_cb(self, job->raw_inputs);
1454
1456
1459 pthread_cond_broadcast(&rt->worker_cond);
1461 }
1462
1463 return NULL;
1464}
1465
1468{
1469 if(!rt) return FALSE;
1470 if(_fullres_worker_started(rt)) return TRUE;
1471
1472 rt->fullres_stop = FALSE;
1474
1475 drawlayer_rt_thread_ctx_t *ctx = g_malloc(sizeof(*ctx));
1476 if(!ctx) return FALSE;
1477 ctx->rt = rt;
1478
1480 if(err != 0)
1481 {
1482 dt_free(ctx);
1483 return FALSE;
1484 }
1485
1487 return TRUE;
1488}
1489
1492{
1493 if(!rt || !_fullres_worker_started(rt)) return;
1494
1496 while(_fullres_worker_busy(rt) || (rt->finished_stroke_queue && !g_queue_is_empty(rt->finished_stroke_queue)))
1497 {
1498 if(rt->fullres_stop) break;
1500 }
1502}
1503
1506{
1507 if(!rt || rt->finished_stroke_queued) return TRUE;
1508 if(!rt->finished_stroke_cb || !rt->stroke_raw_inputs || rt->stroke_raw_inputs->len == 0) return FALSE;
1509
1510 if(!_start_fullres_worker(rt))
1511 {
1512 return FALSE;
1513 }
1514
1515 GQueue *new_queue = NULL;
1516 if(!rt->finished_stroke_queue)
1517 {
1518 new_queue = g_queue_new();
1519 if(!new_queue)
1520 return FALSE;
1521 }
1522
1523 GArray *next_raw_inputs = g_array_new(FALSE, FALSE, sizeof(dt_drawlayer_paint_raw_input_t));
1524 if(!next_raw_inputs)
1525 {
1526 if(new_queue) g_queue_free(new_queue);
1527 return FALSE;
1528 }
1529
1530 drawlayer_finished_stroke_job_t *job = g_malloc0(sizeof(*job));
1531 if(!job)
1532 {
1533 if(new_queue) g_queue_free(new_queue);
1534 g_array_free(next_raw_inputs, TRUE);
1535 return FALSE;
1536 }
1537
1538 gboolean queued = FALSE;
1540 if(!rt->finished_stroke_queue && new_queue)
1541 {
1542 rt->finished_stroke_queue = new_queue;
1543 new_queue = NULL;
1544 }
1546 queued = TRUE;
1547 else if(rt->finished_stroke_queue)
1548 {
1549 job->raw_inputs = rt->stroke_raw_inputs;
1550 g_queue_push_tail(rt->finished_stroke_queue, job);
1551 rt->stroke_raw_inputs = next_raw_inputs;
1553 queued = TRUE;
1554 next_raw_inputs = NULL;
1555 job = NULL;
1556 pthread_cond_broadcast(&rt->worker_cond);
1557 }
1559
1560 if(new_queue) g_queue_free(new_queue);
1561 if(next_raw_inputs) g_array_free(next_raw_inputs, TRUE);
1563 return queued;
1564}
1565
1567static void *_drawlayer_worker_main(void *user_data)
1568{
1570 dt_drawlayer_worker_t *rt = ctx ? ctx->rt : NULL;
1571 dt_free(ctx);
1572
1573 dt_iop_module_t *self = rt ? rt->self : NULL;
1576 if(!self || !rt || !worker || !cb) return NULL;
1577
1580
1581 while(TRUE)
1582 {
1583 dt_drawlayer_paint_raw_input_t event = { 0 };
1584 gboolean have_event = FALSE;
1585 gboolean have_backlog = FALSE;
1586
1588 while(!worker->stop && (_worker_pause_requested(worker)
1589 || (worker->ring_count == 0 && !_backend_pending_dabs_locked(rt))))
1590 {
1593 pthread_cond_broadcast(&rt->worker_cond);
1595 if(cb->on_idle) cb->on_idle(self, rt);
1597 if(!worker->stop && (_worker_pause_requested(worker)
1598 || (worker->ring_count == 0 && !_backend_pending_dabs_locked(rt))))
1600 }
1601
1602 if(worker->stop)
1603 {
1605 pthread_cond_broadcast(&rt->worker_cond);
1607 break;
1608 }
1609
1610 have_backlog = _backend_pending_dabs_locked(rt);
1611 have_event = _rt_queue_pop_locked(rt, &event);
1612 _rt_set_worker_state(rt, (have_event || have_backlog) ? DT_DRAWLAYER_WORKER_STATE_BUSY
1615
1616 if(!have_event)
1617 {
1618 if(cb->on_idle) cb->on_idle(self, rt);
1619 continue;
1620 }
1621
1622 if(event.stroke_pos == DT_DRAWLAYER_PAINT_STROKE_END)
1623 {
1624 /* If stroke-end carries a real raw sample (button release), process it
1625 * before closing the stroke so the final segment is not lost. */
1626 if(cb->process_sample && event.event_ts != 0)
1627 cb->process_sample(self, rt, &event);
1628 if(cb->process_stroke_end) cb->process_stroke_end(self, rt);
1629 }
1630 else
1631 {
1632 if(cb->process_sample) cb->process_sample(self, rt, &event);
1633 }
1634
1636 have_backlog = _backend_pending_dabs_locked(rt);
1638 : (have_backlog ? DT_DRAWLAYER_WORKER_STATE_BUSY
1640 pthread_cond_broadcast(&rt->worker_cond);
1642 if(cb->on_idle) cb->on_idle(self, rt);
1643 }
1644
1645 return NULL;
1646}
1647
1650{
1652 if(!self || !rt || !worker) return FALSE;
1653 if(_worker_is_started(worker))
1654 {
1656 return TRUE;
1657 }
1658
1659 worker->stop = FALSE;
1661
1662 drawlayer_rt_thread_ctx_t *ctx = g_malloc(sizeof(*ctx));
1663 if(!ctx) return FALSE;
1664 ctx->rt = rt;
1665
1666 const int err = dt_pthread_create(&worker->thread, _drawlayer_worker_main, ctx, TRUE);
1667 if(err != 0)
1668 {
1669 dt_free(ctx);
1670 return FALSE;
1671 }
1672
1675 return TRUE;
1676}
1677
1680{
1681 if(!rt) return;
1682
1683 guint source_id = 0;
1686 source_id = rt->finish_commit_source_id;
1689
1690 if(source_id != 0) g_source_remove(source_id);
1691}
1692
1695{
1698 if(!rt) return;
1699
1700 if(worker && _worker_is_started(worker))
1701 {
1702 _wait_worker_idle(self, rt);
1703
1705 worker->stop = TRUE;
1706 pthread_cond_broadcast(&rt->worker_cond);
1708
1709 pthread_join(worker->thread, NULL);
1711 worker->stop = FALSE;
1713 _stroke_clear(rt);
1714 }
1715
1717 {
1719
1721 rt->fullres_stop = TRUE;
1722 pthread_cond_broadcast(&rt->worker_cond);
1724
1725 pthread_join(rt->fullres_thread, NULL);
1727 rt->fullres_stop = FALSE;
1728 }
1729}
1730
1733{
1734 (void)self;
1736 if(!rt || !worker || !_worker_is_started(worker)) return;
1737
1741 while(_worker_is_busy(worker) && !worker->stop)
1744}
1745
1748{
1749 (void)self;
1751 if(!rt || !worker || !_worker_is_started(worker)) return;
1752
1755 pthread_cond_broadcast(&rt->worker_cond);
1757}
1758
1761 const dt_drawlayer_paint_raw_input_t *event)
1762{
1763 if(!rt || !event) return FALSE;
1764 if(!_start_worker(self, rt)) return FALSE;
1765
1767 const gboolean ok = _rt_queue_push_locked(rt, event);
1768 pthread_cond_broadcast(&rt->worker_cond);
1770 return ok;
1771}
1772
1775 const dt_drawlayer_paint_raw_input_t *input)
1776{
1777 if(!rt || !input) return FALSE;
1778 if(!_start_worker(self, rt)) return FALSE;
1779
1780 const dt_drawlayer_paint_raw_input_t event = *input;
1781
1782 gboolean ok = FALSE;
1784 if(!_rt_queue_full(rt))
1785 {
1786 ok = _rt_queue_push_locked(rt, &event);
1787 if(ok && rt->stroke_sample_count) (*rt->stroke_sample_count)++;
1788 }
1789 if(!ok)
1790 {
1791 /* Saturated queue policy: abort current stroke deterministically instead of
1792 * silently dropping or coalescing raw input events. Keep FIFO backlog and
1793 * force a stroke end at the newest queued slot. */
1795 dt_drawlayer_paint_raw_input_t end_input = *input;
1797 end_input.event_index = input->event_index + 1u;
1798 const dt_drawlayer_paint_raw_input_t end_event = end_input;
1799 if(worker->ring && worker->ring_count > 0)
1800 {
1801 const guint last_index = (worker->ring_tail + worker->ring_capacity - 1) % worker->ring_capacity;
1802 worker->ring[last_index] = end_event;
1803 ok = TRUE;
1804 }
1805 else
1806 ok = _rt_queue_push_locked(rt, &end_event);
1807
1809 dt_control_log(_("drawing worker queue is full, stroke aborted"));
1810 }
1811 pthread_cond_broadcast(&rt->worker_cond);
1813 return ok;
1814}
1815
1818 const dt_drawlayer_paint_raw_input_t *input)
1819{
1820 if(!rt) return FALSE;
1821
1822 dt_drawlayer_paint_raw_input_t end_input = { 0 };
1823 if(input) end_input = *input;
1824 if(end_input.stroke_batch == 0u && rt->current_stroke_batch)
1825 end_input.stroke_batch = *rt->current_stroke_batch;
1827
1828 if(_enqueue_event(self, rt, &end_input)) return TRUE;
1829 dt_control_log(_("drawing worker queue is full"));
1830 return FALSE;
1831}
1832
1835 gboolean *painting, gboolean *finish_commit_pending,
1836 guint *stroke_sample_count, uint32_t *current_stroke_batch,
1837 dt_drawlayer_worker_finished_stroke_cb finished_stroke_cb)
1838{
1839 _rt_init_state(self, worker, painting, finish_commit_pending, stroke_sample_count, current_stroke_batch,
1840 finished_stroke_cb);
1841}
1842
1848
1851{
1852 return _rt_workers_active((dt_drawlayer_worker_t *)worker);
1853}
1854
1860
1862{
1863 return _start_worker(self, rt);
1864}
1865
1870
1873{
1874 if(snapshot) *snapshot = (dt_drawlayer_worker_snapshot_t){ 0 };
1876 if(!rt || !snapshot) return;
1877
1879 const drawlayer_rt_worker_t *backend = _backend_worker_const(rt);
1880 snapshot->backend_state = backend->state;
1881 snapshot->backend_queue_count = backend->ring_count;
1882 snapshot->fullres_state = rt->fullres_state;
1883 snapshot->fullres_queue_count = (rt->finished_stroke_queue) ? (guint)rt->finished_stroke_queue->length : 0u;
1886}
1887
1890{
1891 if(!worker) return;
1893 if(worker->finish_commit_pending) *worker->finish_commit_pending = TRUE;
1895 pthread_cond_broadcast(&worker->worker_cond);
1897}
1898
1901{
1902 if(!worker || !worker->self) return;
1903 _wait_worker_idle(worker->self, worker);
1904}
1905
1907{
1908 if(!worker || !worker->self) return;
1909
1910 /* Quiet commit paths do not need the live raster worker to finish stamping
1911 * every display-sized dab. The authoritative stroke result is replayed later
1912 * from preserved raw inputs, so we pause after the current callback, fold any
1913 * queued raw inputs into that preserved history, and discard unreplayed live
1914 * dab backlog. */
1915 _pause_worker(worker->self, worker);
1916
1919
1920 if(worker->stroke && worker->stroke->pending_dabs)
1921 g_array_set_size(worker->stroke->pending_dabs, 0);
1922 if(worker->stroke && worker->stroke->dab_window)
1923 g_array_set_size(worker->stroke->dab_window, 0);
1924
1925 drawlayer_rt_worker_t *backend = _backend_worker(worker);
1927 pthread_cond_broadcast(&worker->worker_cond);
1929}
1930
1936
1938{
1939 if(!worker) return;
1941 _reset_backend_path(worker);
1943}
1944
1946{
1947 if(!worker) return;
1949 _reset_live_publish(worker);
1951}
1952
1955{
1956 if(!worker) return;
1958 _stroke_clear(worker);
1959 _reset_live_publish(worker);
1961}
1962
1965{
1966 return worker ? worker->stroke_raw_inputs : NULL;
1967}
1968
1971{
1972 return worker ? worker->stroke : NULL;
1973}
1974
1976{
1978 if(!rt) return 0;
1979
1980 guint len = 0;
1982 if(rt->stroke && rt->stroke->pending_dabs) len = rt->stroke->pending_dabs->len;
1984 return len;
1985}
1986
1989{
1990 return worker ? worker->finished_stroke_queued : FALSE;
1991}
1992
1995 const dt_drawlayer_paint_raw_input_t *input)
1996{
1997 return _enqueue_input(worker ? worker->self : NULL, worker, (const dt_drawlayer_paint_raw_input_t *)input);
1998}
1999
2002 const dt_drawlayer_paint_raw_input_t *input)
2003{
2004 return _enqueue_stroke_end(worker ? worker->self : NULL, worker, input);
2005}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
@ DT_DRAWLAYER_BRUSH_MODE_SMUDGE
Definition brush.h:53
const float i
Definition colorspaces_inline_conversions.h:669
const float g
Definition colorspaces_inline_conversions.h:925
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_control_log(const char *msg,...)
Definition control.c:530
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:82
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:95
darktable_t darktable
Definition darktable.c:178
void * dt_alloc_align(size_t size)
Definition darktable.c:447
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1528
#define dt_free_align(ptr)
Definition darktable.h:405
@ DT_DEBUG_INPUT
Definition darktable.h:649
@ DT_DEBUG_PERF
Definition darktable.h:639
#define omp_get_max_threads()
Definition darktable.h:260
#define dt_free(ptr)
Definition darktable.h:380
#define omp_get_thread_num()
Definition darktable.h:261
static double dt_get_wtime(void)
Definition darktable.h:845
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:831
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:725
static void dt_dev_set_history_hash(dt_develop_t *dev, const uint64_t history_hash)
Definition develop.h:407
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:2299
#define _commit_dabs
Definition drawlayer.c:197
static float _mapping_profile_value(const drawlayer_mapping_profile_t profile, const float x)
Definition drawlayer.c:289
static float _clamp01(const float value)
Definition drawlayer.c:244
@ DRAWLAYER_INPUT_MAP_ACCEL_SIZE
Definition drawlayer.c:123
@ DRAWLAYER_INPUT_MAP_ACCEL_FLOW
Definition drawlayer.c:125
@ DRAWLAYER_INPUT_MAP_PRESSURE_SOFTNESS
Definition drawlayer.c:118
@ DRAWLAYER_INPUT_MAP_TILT_SOFTNESS
Definition drawlayer.c:122
@ DRAWLAYER_INPUT_MAP_TILT_OPACITY
Definition drawlayer.c:120
@ DRAWLAYER_INPUT_MAP_PRESSURE_OPACITY
Definition drawlayer.c:116
@ DRAWLAYER_INPUT_MAP_TILT_FLOW
Definition drawlayer.c:121
@ DRAWLAYER_INPUT_MAP_ACCEL_OPACITY
Definition drawlayer.c:124
@ DRAWLAYER_INPUT_MAP_TILT_SIZE
Definition drawlayer.c:119
@ DRAWLAYER_INPUT_MAP_PRESSURE_FLOW
Definition drawlayer.c:117
@ DRAWLAYER_INPUT_MAP_ACCEL_SOFTNESS
Definition drawlayer.c:126
@ DRAWLAYER_INPUT_MAP_PRESSURE_SIZE
Definition drawlayer.c:115
drawlayer_mapping_profile_t
Definition drawlayer.c:104
@ DRAWLAYER_PROFILE_INV_QUADRATIC
Definition drawlayer.c:110
@ DRAWLAYER_PROFILE_LINEAR
Definition drawlayer.c:105
#define DRAWLAYER_WORKER_RING_CAPACITY
Definition drawlayer.c:100
int dt_pthread_create(pthread_t *thread, void *(*start_routine)(void *), void *arg, const gboolean realtime)
Definition dtpthread.c:42
void dt_pthread_setname(const char *name)
Definition dtpthread.c:113
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
void dt_iop_nap(int32_t usec)
Definition imageop.c:2525
void dt_drawlayer_cache_patch_wrunlock(const dt_drawlayer_cache_patch_t *patch)
Release write lock on shared patch cache entry.
Definition iop/drawlayer/cache.c:153
void dt_drawlayer_cache_patch_wrlock(const dt_drawlayer_cache_patch_t *patch)
Acquire write lock on shared patch cache entry.
Definition iop/drawlayer/cache.c:145
void dt_drawlayer_cache_patch_rdlock(const dt_drawlayer_cache_patch_t *patch)
Acquire read lock on shared patch cache entry.
Definition iop/drawlayer/cache.c:129
void dt_drawlayer_cache_patch_rdunlock(const dt_drawlayer_cache_patch_t *patch)
Release read lock on shared patch cache entry.
Definition iop/drawlayer/cache.c:137
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:821
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:649
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:845
gboolean dt_drawlayer_paint_rasterize_segment_to_buffer(const dt_drawlayer_brush_dab_t *dab, const float distance_percent, 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:741
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:673
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:864
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:853
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:965
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:827
gboolean dt_drawlayer_paint_raster_path(const GArray *dabs, const float distance_percent, 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)
Rasterize a precomputed dab list into one float RGBA patch.
Definition iop/drawlayer/paint.c:691
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:947
dt_drawlayer_damaged_rect_t * dt_drawlayer_paint_runtime_state_create(void)
Allocate zero-initialized stroke-damage accumulator state.
Definition iop/drawlayer/paint.c:813
void dt_drawlayer_paint_finalize_path(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_paint_callbacks_t *callbacks, void *user_data)
Finalize stroke by force-emitting the pending first sample if needed.
Definition iop/drawlayer/paint.c:529
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:992
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:837
@ 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
k
Definition derive_filmic_v6_gamut_mapping.py:67
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:39
void 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:775
gboolean dt_drawlayer_process_state_publish_locked(dt_drawlayer_process_state_t *state, const dt_drawlayer_damaged_rect_t *damage, const gboolean full_copy)
Definition runtime.c:1068
unsigned __int64 uint64_t
Definition strptime.c:74
struct dt_dev_pixelpipe_cache_t * pixelpipe_cache
Definition darktable.h:717
int32_t unmuted
Definition darktable.h:687
One finished stroke queued for deferred full-resolution replay.
Definition worker.c:70
GArray * raw_inputs
Definition worker.c:71
Definition worker.c:109
dt_drawlayer_paint_stroke_t * stroke
Definition worker.c:114
size_t replay_pixels_capacity
Definition worker.c:111
float * replay_pixels
Definition worker.c:110
size_t stroke_mask_capacity
Definition worker.c:113
float * stroke_mask
Definition worker.c:112
Definition worker.c:102
dt_drawlayer_paint_stroke_t * stroke
Definition worker.c:105
dt_iop_module_t * self
Definition worker.c:103
dt_drawlayer_worker_t * worker
Definition worker.c:104
Per-worker callback vtable.
Definition worker.c:48
drawlayer_rt_sample_cb process_sample
Definition worker.c:50
const char * thread_name
Definition worker.c:49
drawlayer_rt_stroke_end_cb process_stroke_end
Definition worker.c:51
drawlayer_rt_idle_cb on_idle
Definition worker.c:52
Definition worker.c:1412
dt_drawlayer_worker_t * rt
Definition worker.c:1413
One worker thread runtime including event ring buffer.
Definition worker.c:57
guint ring_head
Definition worker.c:61
guint ring_capacity
Definition worker.c:60
guint ring_count
Definition worker.c:63
pthread_t thread
Definition worker.c:58
guint ring_tail
Definition worker.c:62
gboolean stop
Definition worker.c:65
dt_drawlayer_worker_state_t state
Definition worker.c:64
dt_drawlayer_paint_raw_input_t * ring
Definition worker.c:59
Definition develop.h:151
dt_pthread_rwlock_t history_mutex
Definition develop.h:243
Fully resolved input dab descriptor.
Definition brush.h:66
float x
Definition brush.h:67
float radius
Definition brush.h:71
int mode
Definition brush.h:85
float y
Definition brush.h:68
float opacity
Definition brush.h:76
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:147
dt_drawlayer_paint_build_dab_cb build_dab
Definition iop/drawlayer/paint.h:148
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
dt_drawlayer_brush_dab_t last_input_dab
Definition iop/drawlayer/paint.h:106
GArray * dab_window
Definition iop/drawlayer/paint.h:105
gboolean have_last_input_dab
Definition iop/drawlayer/paint.h:107
Definition worker.h:39
guint fullres_queue_count
Definition worker.h:43
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:44
dt_drawlayer_worker_state_t fullres_state
Definition worker.h:42
Drawlayer worker global state shared with drawlayer module.
Definition worker.c:76
dt_drawlayer_worker_state_t fullres_state
Definition worker.c:95
gboolean * finish_commit_pending
Definition worker.c:79
dt_drawlayer_damaged_rect_t * backend_path
Definition worker.c:89
uint32_t * current_stroke_batch
Definition worker.c:81
gboolean fullres_stop
Definition worker.c:96
guint * stroke_sample_count
Definition worker.c:80
GArray * stroke_raw_inputs
Definition worker.c:87
drawlayer_rt_worker_t workers[DRAWLAYER_RT_WORKER_COUNT]
Definition worker.c:84
pthread_cond_t worker_cond
Definition worker.c:83
pthread_t fullres_thread
Definition worker.c:93
dt_drawlayer_damaged_rect_t live_publish_damage
Definition worker.c:92
uint32_t live_publish_serial
Definition worker.c:91
gint64 live_publish_ts
Definition worker.c:90
dt_pthread_mutex_t worker_mutex
Definition worker.c:82
GArray * backend_history
Definition worker.c:86
dt_iop_module_t * self
Definition worker.c:77
GQueue * finished_stroke_queue
Definition worker.c:94
dt_drawlayer_paint_stroke_t * stroke
Definition worker.c:88
gboolean finished_stroke_queued
Definition worker.c:97
dt_drawlayer_worker_finished_stroke_cb finished_stroke_cb
Definition worker.c:98
guint finish_commit_source_id
Definition worker.c:85
gboolean * painting
Definition worker.c:78
Definition runtime.h:282
Definition src/iop/drawlayer/common.h:14
Definition imageop.h:217
struct dt_develop_t * dev
Definition imageop.h:262
dt_iop_gui_data_t * gui_data
Definition imageop.h:277
dt_iop_params_t * params
Definition imageop.h:273
#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:1258
gboolean dt_drawlayer_worker_finished_stroke_queued(const dt_drawlayer_worker_t *worker)
Report whether current preserved stroke was already queued for deferred replay.
Definition worker.c:1988
static gboolean _stroke_create(dt_drawlayer_worker_t *rt)
Create stroke runtime if missing.
Definition worker.c:705
static void _finished_stroke_job_destroy(drawlayer_finished_stroke_job_t *job)
Destroy one finished-stroke replay job and owned history.
Definition worker.c:679
#define DRAWLAYER_BATCH_TILE_SIZE
Definition worker.c:160
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:42
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:1871
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:1291
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:1181
static void _paint_emit_backend_dab_cb(void *user_data, const dt_drawlayer_brush_dab_t *dab)
Definition worker.c:300
static gboolean _dab_batch_supports_outer_loop(const GArray *dabs, guint count)
Definition worker.c:800
static gboolean _worker_is_busy(const drawlayer_rt_worker_t *worker)
Definition worker.c:1132
static void _pause_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Pause worker processing after current callback returns.
Definition worker.c:1732
static float * _ensure_fullres_replay_float_buffer(float **buffer, size_t *capacity_values, size_t needed_values)
Definition worker.c:451
static gboolean _rt_queue_full(const dt_drawlayer_worker_t *rt)
Test whether event queue is full.
Definition worker.c:1097
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:1866
static guint _worker_batch_min_size(void)
Definition worker.c:791
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:1332
dt_drawlayer_paint_stroke_t * dt_drawlayer_worker_stroke(dt_drawlayer_worker_t *worker)
Read-only access to preserved stroke runtime.
Definition worker.c:1970
void dt_drawlayer_worker_cleanup(dt_drawlayer_worker_t **worker)
Public worker cleanup entry point.
Definition worker.c:1844
void dt_drawlayer_worker_reset_backend_path(dt_drawlayer_worker_t *worker)
Reset worker-owned backend damage accumulator.
Definition worker.c:1937
static guint _rasterize_pending_dab_batch(drawlayer_paint_backend_ctx_t *ctx, gint64 budget_us)
Definition worker.c:996
static void _paint_emit_noop_cb(void *user_data, const dt_drawlayer_brush_dab_t *dab)
Definition worker.c:306
static gboolean _fullres_worker_started(const dt_drawlayer_worker_t *rt)
Definition worker.c:1149
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:1104
drawlayer_rt_worker_kind_t
Internal worker slot kinds (currently backend only).
Definition worker.c:33
@ DRAWLAYER_RT_WORKER_COUNT
Definition worker.c:35
@ DRAWLAYER_RT_WORKER_BACKEND
Definition worker.c:34
static gboolean _backend_pending_dabs_locked(const dt_drawlayer_worker_t *rt)
Definition worker.c:1144
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:1861
static gboolean _live_publish_deadline_reached(const dt_drawlayer_worker_t *rt, const gint64 input_ts, const gint64 interval_us)
Definition worker.c:819
static gboolean _start_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Start backend worker thread if not running.
Definition worker.c:1649
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:1760
static void _reset_backend_path(dt_drawlayer_worker_t *rt)
Definition worker.c:763
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:2001
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:345
static void _cancel_async_commit(dt_drawlayer_worker_t *rt)
Cancel pending async commit idle callback if any.
Definition worker.c:1679
static gboolean _start_fullres_worker(dt_drawlayer_worker_t *rt)
Start deferred full-resolution replay worker if not running.
Definition worker.c:1467
static gboolean _fullres_worker_busy(const dt_drawlayer_worker_t *rt)
Definition worker.c:1154
static GPrivate _drawlayer_fullres_replay_scratch_key
Definition worker.c:402
static drawlayer_rt_worker_t * _backend_worker(dt_drawlayer_worker_t *rt)
Definition worker.c:776
static void _drain_queued_raw_inputs_locked(dt_drawlayer_worker_t *worker)
Definition worker.c:1069
static void * _drawlayer_fullres_worker_main(void *user_data)
Deferred full-resolution replay worker main loop.
Definition worker.c:1417
#define DRAWLAYER_OUTER_FULLRES_BATCH_MULTIPLIER
Definition worker.c:162
static gboolean _workers_any_active_locked(const dt_drawlayer_worker_t *rt)
Check whether any worker activity remains (backend or deferred replay).
Definition worker.c:1196
static void * _drawlayer_worker_main(void *user_data)
Worker main loop: FIFO dequeue, process, and idle scheduling.
Definition worker.c:1567
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, dt_drawlayer_worker_finished_stroke_cb finished_stroke_cb)
Allocate and initialize worker state object and buffers.
Definition worker.c:1356
static gboolean _async_commit_idle(gpointer user_data)
Idle callback committing pending stroke once workers are fully idle.
Definition worker.c:1233
static void _paint_stroke_seed_cb(void *user_data, uint64_t stroke_seed)
Definition worker.c:312
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:1160
void dt_drawlayer_worker_flush_pending(dt_drawlayer_worker_t *worker)
Flush pending backend stroke inputs synchronously.
Definition worker.c:1900
static gboolean _fullres_active_locked(const dt_drawlayer_worker_t *rt)
Check whether deferred full-resolution replay still has pending activity (lock must be held).
Definition worker.c:1189
static gboolean _rt_workers_any_active(dt_drawlayer_worker_t *rt)
Thread-safe wrapper for any worker activity, including deferred replay.
Definition worker.c:1222
static void _rt_cleanup_state(dt_drawlayer_worker_t **rt_out)
Destroy worker state object and all owned resources.
Definition worker.c:1389
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:39
static void _reset_live_publish(dt_drawlayer_worker_t *rt)
Definition worker.c:768
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:1774
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:1276
static gboolean _worker_pause_requested(const drawlayer_rt_worker_t *worker)
Definition worker.c:1137
static gboolean _rt_workers_active(dt_drawlayer_worker_t *rt)
Thread-safe wrapper for active-workers status.
Definition worker.c:1211
gboolean dt_drawlayer_worker_active(const dt_drawlayer_worker_t *worker)
Public status query: TRUE when worker has pending activity.
Definition worker.c:1850
static void _set_current_thread_realtime_best_effort(void)
Try elevating current thread scheduling policy for lower-latency input.
Definition worker.c:1167
static void _stop_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Stop worker thread and clear transient state.
Definition worker.c:1694
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:1202
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, dt_drawlayer_worker_finished_stroke_cb finished_stroke_cb)
Public worker initialization entry point.
Definition worker.c:1834
static gboolean _enqueue_finished_stroke(dt_drawlayer_worker_t *rt)
Queue preserved finished stroke for deferred full-resolution replay (lock must be held).
Definition worker.c:1505
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:376
static void _rt_queue_clear_locked(dt_drawlayer_worker_t *rt)
Clear queued events (lock must be held).
Definition worker.c:1080
void dt_drawlayer_worker_request_commit(dt_drawlayer_worker_t *worker)
Public commit request helper.
Definition worker.c:1889
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:1856
static drawlayer_fullres_replay_scratch_t * _get_fullres_replay_scratch(void)
Definition worker.c:404
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:1994
void dt_drawlayer_worker_reset_live_publish(dt_drawlayer_worker_t *worker)
Reset worker-owned transient live-publish state.
Definition worker.c:1945
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:1975
GArray * dt_drawlayer_worker_raw_inputs(dt_drawlayer_worker_t *worker)
Read-only access to preserved raw input history.
Definition worker.c:1964
static void _publish_backend_progress(drawlayer_paint_backend_ctx_t *ctx, gboolean flush_pending)
Definition worker.c:319
static gboolean _rt_queue_empty(const dt_drawlayer_worker_t *rt)
Test whether event queue is empty.
Definition worker.c:1090
static gboolean _paint_layer_to_widget_cb(void *user_data, float lx, float ly, float *wx, float *wy)
Definition worker.c:294
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:1817
static void _resume_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Resume worker processing and wake sleeping thread.
Definition worker.c:1747
static gint64 _live_publish_interval_us(void)
Definition worker.c:786
void dt_drawlayer_worker_seal_for_commit(dt_drawlayer_worker_t *worker)
Seal current stroke for synchronous commit by folding queued raw inputs into preserved history.
Definition worker.c:1906
static void _process_backend_dab(dt_iop_module_t *self, const dt_drawlayer_brush_dab_t *dab, drawlayer_paint_backend_ctx_t *ctx)
Definition worker.c:631
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:284
static const drawlayer_rt_worker_t * _backend_worker_const(const dt_drawlayer_worker_t *rt)
Definition worker.c:781
static void _log_worker_batch_timing(const char *tag, guint processed_dabs, guint thread_count, double elapsed_ms, gboolean outer_loop)
Definition worker.c:811
static gboolean _worker_is_started(const drawlayer_rt_worker_t *worker)
Definition worker.c:1127
void dt_drawlayer_worker_reset_stroke(dt_drawlayer_worker_t *worker)
Clear preserved stroke runtime/history after commit completed.
Definition worker.c:1954
static void _wait_fullres_idle(dt_drawlayer_worker_t *rt)
Wait until deferred full-resolution replay queue is drained and idle.
Definition worker.c:1491
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:44
static void _destroy_fullres_replay_scratch(gpointer data)
Definition worker.c:386
static gboolean _stroke_begin(dt_drawlayer_worker_t *rt)
Start new stroke runtime and reset history/path state.
Definition worker.c:730
gboolean dt_drawlayer_worker_replay_finished_stroke_to_base_patch(dt_iop_module_t *self, const GArray *raw_inputs)
Replay one finished stroke into the authoritative base patch from preserved raw inputs.
Definition worker.c:468
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:164
void dt_drawlayer_worker_flush_finished_strokes(dt_drawlayer_worker_t *worker)
Wait until deferred full-resolution replay queue becomes idle.
Definition worker.c:1932
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:825
static void _stroke_destroy(dt_drawlayer_worker_t *rt)
Deep-copy preserved stroke history into one deferred replay job.
Definition worker.c:688
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:1395
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:1116
static const drawlayer_rt_callbacks_t _rt_callbacks[DRAWLAYER_RT_WORKER_COUNT]
Definition worker.c:674
#define DRAWLAYER_OUTER_LIVE_BATCH_MULTIPLIER
Definition worker.c:161
static void _schedule_async_commit_if_ready_locked(dt_drawlayer_worker_t *rt)
Schedule async commit when lock-state indicates readiness.
Definition worker.c:1250
static void _stroke_clear(dt_drawlayer_worker_t *rt)
Clear current stroke state while preserving allocations.
Definition worker.c:750
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
gboolean(* dt_drawlayer_worker_finished_stroke_cb)(dt_iop_module_t *self, const GArray *raw_inputs)
Callback processing one finished stroke on the deferred full-resolution worker.
Definition worker.h:47