151 const gint64 interval_us);
159 gboolean outer_loop);
162#if defined(_OPENMP) && OUTER_LOOP
163static guint _rasterize_dab_batch_outer_loop(
const GArray *dabs, guint max_dabs,
float distance_percent,
169static gboolean _dab_batch_supports_outer_loop(
const GArray *dabs, guint count);
175#if defined(_OPENMP) && OUTER_LOOP
179#define DRAWLAYER_BATCH_TILE_SIZE 128
180#define DRAWLAYER_HEARTBEAT_PATCH_NAME "drawlayer heartbeat patch"
181#define DRAWLAYER_HEARTBEAT_MASK_NAME "drawlayer heartbeat stroke mask"
182#define DRAWLAYER_OUTER_LIVE_BATCH_MULTIPLIER 2u
190 float lx = input->
lx;
191 float ly = input->
ly;
225 const float base_radius = radius;
226 const float base_opacity = opacity;
227 const float base_flow = flow;
228 const float base_hardness = hardness;
230 if(map_pressure_size) radius *= pressure_coeff;
231 if(map_pressure_opacity) opacity *= pressure_coeff;
232 if(map_pressure_flow) flow *= pressure_coeff;
233 if(map_pressure_softness) hardness *= pressure_coeff;
235 if(map_tilt_size) radius *= tilt_coeff;
236 if(map_tilt_opacity) opacity *= tilt_coeff;
237 if(map_tilt_flow) flow *= tilt_coeff;
238 if(map_tilt_softness) hardness *= tilt_coeff;
240 if(map_accel_size) radius *= accel_coeff;
241 if(map_accel_opacity) opacity *= accel_coeff;
242 if(map_accel_flow) flow *= accel_coeff;
243 if(map_accel_softness) hardness *= accel_coeff;
245 radius = fmaxf(radius, 0.5f);
249 if(
state->have_last_input_dab)
251 const float dx = lx -
state->last_input_dab.x;
252 const float dy = ly -
state->last_input_dab.y;
253 const float dir_len = hypotf(dx, dy);
256 dir_x = dx / dir_len;
257 dir_y = dy / dir_len;
272 .hardness = hardness,
281 if((map_pressure_size || map_pressure_opacity || map_pressure_flow || map_pressure_softness || map_tilt_size
282 || map_tilt_opacity || map_tilt_flow || map_tilt_softness || map_accel_size || map_accel_opacity
283 || map_accel_flow || map_accel_softness)
285 || ((
state->history && (
state->history->len & 15u) == 0u))))
288 "[drawlayer] map p=%.4f t=%.4f a=%.4f coeff[p=%.4f t=%.4f a=%.4f] "
289 "base[r=%.2f o=%.3f f=%.3f h=%.3f] out[r=%.2f o=%.3f f=%.3f h=%.3f] "
290 "flags[p=%d%d%d%d t=%d%d%d%d a=%d%d%d%d]\n",
291 pressure_norm, tilt_norm, accel_norm, pressure_coeff, tilt_coeff, accel_coeff, base_radius,
293 map_pressure_size ? 1 : 0, map_pressure_opacity ? 1 : 0, map_pressure_flow ? 1 : 0,
294 map_pressure_softness ? 1 : 0, map_tilt_size ? 1 : 0, map_tilt_opacity ? 1 : 0, map_tilt_flow ? 1 : 0,
295 map_tilt_softness ? 1 : 0, map_accel_size ? 1 : 0, map_accel_opacity ? 1 : 0, map_accel_flow ? 1 : 0,
296 map_accel_softness ? 1 : 0);
306 return (ctx && ctx->
self)
334 const int sample_count = (int)
g->stroke.stroke_sample_count;
377 const gint64 input_ts = input->
event_ts ? input->
event_ts : g_get_monotonic_time();
397 g->process.cache_dirty =
TRUE;
415 patch, 1.0f, stroke_mask, &step_damage, stroke))
418 if(step_damage.
valid && batch_damage)
421 return step_damage.
valid;
531#if defined(_OPENMP) && OUTER_LOOP
538#if defined(_OPENMP) && OUTER_LOOP
539static gboolean _dab_batch_supports_outer_loop(
const GArray *dabs,
const guint count)
542 for(guint
i = 0;
i < count;
i++)
552 const double elapsed_ms,
const gboolean outer_loop)
556 tag ? tag :
"unknown", processed_dabs, thread_count, outer_loop ? 1 : 0, elapsed_ms);
560 const gint64 interval_us)
580 || dab->
radius <= 0.0f || dab->
opacity <= 0.0f || scale <= 0.0f)
583 const float support_radius = dab->
radius;
585 bounds->
nw[0] =
MAX(0, (
int)floorf((dab->
x - support_radius) * scale) - patch->
x);
586 bounds->
nw[1] =
MAX(0, (
int)floorf((dab->
y - support_radius) * scale) - patch->
y);
587 bounds->
se[0] =
MIN(patch->
width, (
int)ceilf((dab->
x + support_radius) * scale) - patch->
x + 1);
588 bounds->
se[1] =
MIN(patch->
height, (
int)ceilf((dab->
y + support_radius) * scale) - patch->
y + 1);
589 return bounds->
se[0] > bounds->
nw[0] && bounds->
se[1] > bounds->
nw[1];
603 for(guint
i = 0;
i < max_dabs;
i++)
611 return batch_bounds->
valid;
620 const int width = batch_bounds->
se[0] - batch_bounds->
nw[0];
621 const int height = batch_bounds->
se[1] - batch_bounds->
nw[1];
635 float *
const grown_noise = g_realloc_n(rt->
heartbeat_noise, needed,
sizeof(*grown_noise));
655 const int src_x0 = dst->
x - src->
x;
656 const int src_y0 = dst->
y - src->
y;
657 if(src_x0 < 0 || src_y0 < 0 || src_x0 + dst->
width > src->
width || src_y0 + dst->
height > src->
height)
return;
659 for(
int y = 0; y < dst->
height; y++)
661 const float *src_row = src->
pixels + 4 * ((size_t)(src_y0 + y) * src->
width + src_x0);
662 float *dst_row = dst->
pixels + 4 * ((size_t)y * dst->
width);
663 memcpy(dst_row, src_row, (
size_t)dst->
width * 4 *
sizeof(
float));
678 const int src_x0 = dst->
x - src->
x;
679 const int src_y0 = dst->
y - src->
y;
686 for(
int y = 0; y < dst->
height; y++)
688 const float *src_row = src->
pixels + (size_t)(src_y0 + y) * src->
width + src_x0;
689 float *dst_row = dst->
pixels + (size_t)y * dst->
width;
690 memcpy(dst_row, src_row, (
size_t)dst->
width *
sizeof(
float));
702 *absolute_damage = *local_damage;
703 absolute_damage->
nw[0] += patch->
x;
704 absolute_damage->
nw[1] += patch->
y;
705 absolute_damage->
se[0] += patch->
x;
706 absolute_damage->
se[1] += patch->
y;
717 const int copy_w = local_damage->
se[0] - local_damage->
nw[0];
718 const int copy_h = local_damage->
se[1] - local_damage->
nw[1];
719 if(copy_w <= 0 || copy_h <= 0)
return;
721 const int dst_x0 = src->
x + local_damage->
nw[0] - dst->
x;
722 const int dst_y0 = src->
y + local_damage->
nw[1] - dst->
y;
723 if(dst_x0 < 0 || dst_y0 < 0 || dst_x0 + copy_w > dst->
width || dst_y0 + copy_h > dst->
height)
return;
725 for(
int y = 0; y < copy_h; y++)
727 const float *src_row = src->
pixels + 4 * ((size_t)(local_damage->
nw[1] + y) * src->
width + local_damage->
nw[0]);
728 float *dst_row = dst->
pixels + 4 * ((size_t)(dst_y0 + y) * dst->
width + dst_x0);
729 memcpy(dst_row, src_row, (
size_t)copy_w * 4 *
sizeof(
float));
740 const int copy_w = local_damage->
se[0] - local_damage->
nw[0];
741 const int copy_h = local_damage->
se[1] - local_damage->
nw[1];
742 if(copy_w <= 0 || copy_h <= 0)
return;
744 const int dst_x0 = src->
x + local_damage->
nw[0] - dst->
x;
745 const int dst_y0 = src->
y + local_damage->
nw[1] - dst->
y;
746 if(dst_x0 < 0 || dst_y0 < 0 || dst_x0 + copy_w > dst->
width || dst_y0 + copy_h > dst->
height)
return;
748 for(
int y = 0; y < copy_h; y++)
750 const float *src_row = src->
pixels + (size_t)(local_damage->
nw[1] + y) * src->
width + local_damage->
nw[0];
751 float *dst_row = dst->
pixels + (size_t)(dst_y0 + y) * dst->
width + dst_x0;
752 memcpy(dst_row, src_row, (
size_t)copy_w *
sizeof(
float));
762 const int clear_w = local_damage->
se[0] - local_damage->
nw[0];
763 const int clear_h = local_damage->
se[1] - local_damage->
nw[1];
764 if(clear_w <= 0 || clear_h <= 0)
return;
766 for(
int y = 0; y < clear_h; y++)
768 float *
row = patch->
pixels + 4 * ((size_t)(local_damage->
nw[1] + y) * patch->
width + local_damage->
nw[0]);
769 memset(
row, 0, (
size_t)clear_w * 4 *
sizeof(
float));
779 const int clear_w = local_damage->
se[0] - local_damage->
nw[0];
780 const int clear_h = local_damage->
se[1] - local_damage->
nw[1];
781 if(clear_w <= 0 || clear_h <= 0)
return;
783 for(
int y = 0; y < clear_h; y++)
785 float *
row = patch->
pixels + (size_t)(local_damage->
nw[1] + y) * patch->
width + local_damage->
nw[0];
786 memset(
row, 0, (
size_t)clear_w *
sizeof(
float));
790#if defined(_OPENMP) && OUTER_LOOP
806 if(!runtime || !*runtime)
return;
807 if((*runtime)->dab_window) g_array_free((*runtime)->dab_window,
TRUE);
808 (*runtime)->dab_window = NULL;
812static void _lock_batch_tiles(omp_lock_t *locks,
const int tile_cols,
const int tile_origin_x,
815 if(!locks || tile_cols <= 0 ||
IS_NULL_PTR(bounds) || !bounds->
valid)
return;
820 for(
int ty = ty0; ty <= ty1; ty++)
821 for(
int tx = tx0; tx <= tx1; tx++)
822 omp_set_lock(&locks[ty * tile_cols + tx]);
825static void _unlock_batch_tiles(omp_lock_t *locks,
const int tile_cols,
const int tile_origin_x,
828 if(!locks || tile_cols <= 0 ||
IS_NULL_PTR(bounds) || !bounds->
valid)
return;
833 for(
int ty = ty1; ty >= ty0; ty--)
834 for(
int tx = tx1; tx >= tx0; tx--)
835 omp_unset_lock(&locks[ty * tile_cols + tx]);
838static guint _rasterize_dab_batch_outer_loop(
const GArray *dabs,
const guint max_dabs,
const float distance_percent,
849 for(guint
i = 0;
i < max_dabs;
i++)
856 if(!batch_bounds.
valid)
return 0;
864 omp_lock_t *tile_locks = g_malloc0((
size_t)tile_cols * tile_rows *
sizeof(*tile_locks));
865 if(!thread_runtime ||
IS_NULL_PTR(thread_damage) || !tile_locks)
867 g_free(thread_runtime);
868 g_free(thread_damage);
873 for(guint
i = 0;
i < thread_count;
i++)
875 thread_runtime[
i] = _create_batch_runtime();
876 if(!thread_runtime[
i])
878 for(guint
k = 0;
k <
i;
k++) _destroy_batch_runtime(&thread_runtime[
k]);
879 g_free(thread_runtime);
880 g_free(thread_damage);
886 for(
int i = 0;
i < tile_cols * tile_rows;
i++)
887 omp_init_lock(&tile_locks[
i]);
890#pragma omp parallel for default(shared)
891 for(guint
i = 0;
i < max_dabs;
i++)
906 _lock_batch_tiles(tile_locks, tile_cols, tile_origin_x, tile_origin_y, &bounds);
908 &runtime_damage, runtime);
909 _unlock_batch_tiles(tile_locks, tile_cols, tile_origin_x, tile_origin_y, &bounds);
917 for(guint
i = 0;
i < thread_count;
i++)
920 _destroy_batch_runtime(&thread_runtime[
i]);
922 for(
int i = 0;
i < tile_cols * tile_rows;
i++)
923 omp_destroy_lock(&tile_locks[
i]);
925 g_free(thread_runtime);
926 g_free(thread_damage);
940 if(
IS_NULL_PTR(
g->process.base_patch.pixels) ||
g->process.base_patch.width <= 0 ||
g->process.base_patch.height <= 0)
945 const guint batch_dabs = (budget_us > 0)
948 if(batch_dabs == 0)
return 0;
953 g_array_remove_range(stroke->
pending_dabs, 0, batch_dabs);
961 guint processed_dabs = 0;
962 gboolean used_outer_loop =
FALSE;
978 batch_dabs, &uniform_batch)
985 used_outer_loop =
TRUE;
986 processed_dabs = batch_dabs;
997#if defined(_OPENMP) && OUTER_LOOP
998 if(processed_dabs == 0 && batch_dabs >= min_batch
999 && _dab_batch_supports_outer_loop(stroke->
pending_dabs, batch_dabs))
1001 used_outer_loop =
TRUE;
1002 processed_dabs = _rasterize_dab_batch_outer_loop(stroke->
pending_dabs, batch_dabs,
1004 &
g->process.base_patch, heartbeat_patch, 1.0f,
1005 heartbeat_mask, &batch_damage,
"heartbeat");
1008 while(processed_dabs < batch_dabs)
1012 _process_backend_dab(dab, ctx, &
g->process.base_patch, heartbeat_patch, heartbeat_mask, &batch_damage);
1017 const gint64 elapsed_us = (gint64)(1000000.0 * (
dt_get_wtime() - batch_t0));
1018 if(processed_dabs >= min_batch && elapsed_us >= budget_us)
break;
1024 if(processed_dabs == 0)
return 0;
1028 g_array_remove_range(stroke->
pending_dabs, 0, processed_dabs);
1030 if(batch_damage.
valid)
1042 g->process.base_patch.cache_entry, -1);
1051 g->process.cache_dirty =
TRUE;
1059 g->stroke.last_dab_valid =
TRUE;
1060 g->stroke.last_dab_x = last_dab->
x;
1061 g->stroke.last_dab_y = last_dab->
y;
1065 if(!used_outer_loop)
1067 return processed_dabs;
1150#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
1151 struct sched_param
param = { 0 };
1152 const int max_prio = sched_get_priority_max(SCHED_FIFO);
1155 param.sched_priority =
MIN(max_prio, 80);
1156 pthread_setschedparam(pthread_self(), SCHED_FIFO, &
param);
1189 gboolean active =
FALSE;
1200 gboolean active =
FALSE;
1235 return G_SOURCE_REMOVE;
1246 if(source_id) g_source_remove(source_id);
1263 guint processed_dabs = 0;
1268 if(should_stop)
break;
1272 if(processed_dabs == 0)
break;
1380 gboolean *painting, gboolean *finish_commit_pending,
1381 guint *stroke_sample_count, uint32_t *current_stroke_batch)
1386 rt = g_malloc0(
sizeof(*rt));
1430 "[drawlayer] BUG: dt_drawlayer_worker_wait_idle called on the worker thread — it consumes the "
1431 "ring it is about to wait on, and will deadlock with the GUI thread.\n");
1436 if(worker->
stop)
break;
1466 gboolean have_event =
FALSE;
1467 gboolean have_backlog =
FALSE;
1589 pthread_join(worker->
thread, NULL);
1650 gboolean ok =
FALSE;
1670 worker->
ring[last_index] = end_event;
1677 dt_control_alert(_(
"Drawing not saved"), _(
"drawing worker queue is full, stroke aborted"), NULL);
1691 if(input) end_input = *input;
1703 gboolean *painting, gboolean *finish_commit_pending,
1704 guint *stroke_sample_count, uint32_t *current_stroke_batch)
1706 _rt_init_state(self, worker, painting, finish_commit_pending, stroke_sample_count, current_stroke_batch);
1746 snapshot->backend_state = backend->
state;
1747 snapshot->backend_queue_count = backend->
ring_count;
@ DT_DRAWLAYER_BRUSH_MODE_SMUDGE
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_control_alert(const char *title, const char *message, const char *item)
Tell the user something that must not go unseen: what it says stays true after a toast would have fad...
void dt_control_log(const char *msg,...)
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
gboolean dt_drawlayer_widget_to_layer_coords(dt_iop_module_t *self, const double wx, const double wy, float *lx, float *ly)
void dt_dev_transient_params_set(dt_iop_module_t *module, const void *params, const size_t params_size, const void *blend_params, const size_t blend_size)
Out-of-history transient param channel.
void dt_drawlayer_touch_stroke_commit_hash(dt_iop_drawlayer_params_t *params, const int dab_count, const gboolean have_last_dab, const float last_dab_x, const float last_dab_y, const uint32_t publish_serial)
gboolean dt_drawlayer_commit_dabs(dt_iop_module_t *self, gboolean record_history)
int dt_pthread_create(pthread_t *thread, void *(*start_routine)(void *), void *arg, const gboolean realtime)
void dt_pthread_setname(const char *name)
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Initialise a mutex. With mutexattr NULL – which is how 54 of the 56 call sites in this tree spell it ...
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
static int dt_pthread_cond_wait(pthread_cond_t *cond, dt_pthread_mutex_t *mutex)
Wait on cond, releasing mutex for the duration.
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
int dt_gui_throttle_get_pipe_runtime_us(const dt_throttle_slot_t slot)
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
static float _clamp01(const float v)
Clamp scalar to [0,1].
gboolean dt_drawlayer_brush_batch_is_uniform(const dt_drawlayer_brush_dab_t *dabs, const guint count, dt_drawlayer_brush_batch_t *out)
Decide whether a run of dabs can take the batch path, and describe it if so.
gboolean dt_drawlayer_brush_rasterize_batch(const dt_drawlayer_brush_batch_t *batch, dt_drawlayer_cache_patch_t *patch, const float scale, dt_drawlayer_cache_patch_t *stroke_mask, float *const transmittance, float *const noise_scratch, dt_drawlayer_damaged_rect_t *batch_damage)
Rasterize a whole batch in two passes: accumulate transmittance, then composite once.
Patch/cache helpers for drawlayer process and preview buffers.
Private drawlayer module types and lightweight shared helpers.
static float _mapping_profile_value(const drawlayer_mapping_profile_t profile, const float x)
@ DRAWLAYER_INPUT_MAP_ACCEL_SIZE
@ DRAWLAYER_INPUT_MAP_ACCEL_FLOW
@ DRAWLAYER_INPUT_MAP_PRESSURE_SOFTNESS
@ DRAWLAYER_INPUT_MAP_TILT_SOFTNESS
@ DRAWLAYER_INPUT_MAP_TILT_OPACITY
@ DRAWLAYER_INPUT_MAP_PRESSURE_OPACITY
@ DRAWLAYER_INPUT_MAP_TILT_FLOW
@ DRAWLAYER_INPUT_MAP_ACCEL_OPACITY
@ DRAWLAYER_INPUT_MAP_TILT_SIZE
@ DRAWLAYER_INPUT_MAP_PRESSURE_FLOW
@ DRAWLAYER_INPUT_MAP_ACCEL_SOFTNESS
@ DRAWLAYER_INPUT_MAP_PRESSURE_SIZE
drawlayer_mapping_profile_t
@ DRAWLAYER_PROFILE_INV_QUADRATIC
@ DRAWLAYER_PROFILE_LINEAR
#define DRAWLAYER_WORKER_RING_CAPACITY
void dt_drawlayer_paint_path_state_reset(dt_drawlayer_paint_stroke_t *state)
Reset full stroke state including queued raw input events.
void dt_drawlayer_paint_runtime_state_destroy(dt_drawlayer_damaged_rect_t **state)
Destroy stroke-damage accumulator state and null pointer.
gboolean dt_drawlayer_paint_queue_raw_input(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_paint_raw_input_t *input)
Queue one raw input event (FIFO).
void dt_drawlayer_paint_runtime_private_destroy(dt_drawlayer_paint_stroke_t **state)
Destroy stroke runtime payload and null pointer.
void dt_drawlayer_paint_interpolate_path(dt_drawlayer_paint_stroke_t *state, const dt_drawlayer_paint_callbacks_t *callbacks, void *user_data)
Drain queued raw input events and append evenly spaced dabs to state->pending_dabs.
void dt_drawlayer_paint_runtime_set_stroke_seed(dt_drawlayer_paint_stroke_t *state, const uint64_t seed)
Set deterministic stroke seed for noise-derived effects.
void dt_drawlayer_paint_finalize_path(dt_drawlayer_paint_stroke_t *state)
Finalize stroke by force-emitting the pending first sample if needed.
void dt_drawlayer_paint_runtime_private_reset(dt_drawlayer_paint_stroke_t *state)
Reset transient stroke runtime payload between strokes.
gboolean dt_drawlayer_paint_runtime_get_stroke_damage(const dt_drawlayer_damaged_rect_t *state, dt_drawlayer_damaged_rect_t *out_rect)
Read accumulated stroke damage rectangle.
gboolean dt_drawlayer_paint_rasterize_segment_to_buffer(const dt_drawlayer_brush_dab_t *dab, const float distance_percent, const dt_drawlayer_cache_patch_t *sample_patch, dt_drawlayer_cache_patch_t *patch, const float scale, dt_drawlayer_cache_patch_t *stroke_mask, dt_drawlayer_damaged_rect_t *runtime_state, dt_drawlayer_paint_stroke_t *runtime_private)
Replay one emitted dab segment into a float buffer through brush API.
void dt_drawlayer_paint_runtime_state_reset(dt_drawlayer_damaged_rect_t *state)
Reset stroke-damage accumulator to empty/invalid.
void dt_drawlayer_paint_runtime_note_dab_damage(dt_drawlayer_damaged_rect_t *state, const dt_drawlayer_damaged_rect_t *dab_rect)
Merge one dab rectangle into an accumulator rectangle.
dt_drawlayer_damaged_rect_t * dt_drawlayer_paint_runtime_state_create(void)
Allocate zero-initialized stroke-damage accumulator state.
gboolean dt_drawlayer_paint_merge_runtime_stroke_damage(dt_drawlayer_damaged_rect_t *path_state, dt_drawlayer_damaged_rect_t *target_rect)
Merge path-state damage into target rectangle and clear path-state accumulator.
dt_drawlayer_paint_stroke_t * dt_drawlayer_paint_runtime_private_create(void)
Allocate stroke runtime payload object used by paint+brush internals.
@ DT_DRAWLAYER_PAINT_STROKE_MIDDLE
@ DT_DRAWLAYER_PAINT_STROKE_FIRST
@ DT_DRAWLAYER_PAINT_STROKE_END
static const dt_iop_drawlayer_params_t * params
int32_t dt_get_debug_flags(void)
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
#define omp_get_max_threads()
#define omp_get_thread_num()
gboolean dt_dev_pixelpipe_cache_flush_host_pinned_image(void *host_ptr, dt_pixel_cache_entry_t *entry_hint, int devid)
Drop cached pinned OpenCL images associated with a given host buffer.
static void dt_dev_pixelpipe_or_changed(dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_change_t flags)
@ DT_DEV_PIPE_TOP_CHANGED
Asking the interface to repaint itself.
Private runtime state/helpers shared by drawlayer module entrypoints.
void dt_drawlayer_cache_patch_wrunlock(const dt_drawlayer_cache_patch_t *patch)
Release write lock on shared patch cache entry.
void dt_drawlayer_cache_patch_wrlock(const dt_drawlayer_cache_patch_t *patch)
Acquire write lock on shared patch cache entry.
void dt_drawlayer_cache_patch_clear(dt_drawlayer_cache_patch_t *patch, const char *external_alloc_name)
Release patch storage and reset patch metadata.
gboolean dt_drawlayer_cache_ensure_process_patch_buffer(dt_drawlayer_cache_patch_t *process_patch, dt_drawlayer_cache_patch_t *process_stroke_mask, const int width, const int height, const char *patch_buffer_name, const char *mask_buffer_name)
Ensure process patch and process-stroke-mask backing buffers exist.
void dt_drawlayer_cache_patch_rdlock(const dt_drawlayer_cache_patch_t *patch)
Acquire read lock on shared patch cache entry.
void dt_drawlayer_cache_patch_rdunlock(const dt_drawlayer_cache_patch_t *patch)
Release read lock on shared patch cache entry.
Shared drawlayer runtime helpers used across module/runtime files.
const float uint32_t state[4]
const float const float param
unsigned __int64 uint64_t
dt_drawlayer_paint_stroke_t * stroke
dt_drawlayer_worker_t * worker
Per-worker callback vtable.
drawlayer_rt_sample_cb process_sample
drawlayer_rt_stroke_end_cb process_stroke_end
drawlayer_rt_idle_cb on_idle
dt_drawlayer_worker_t * rt
One worker thread runtime including event ring buffer.
dt_drawlayer_worker_state_t state
dt_drawlayer_paint_raw_input_t * ring
struct dt_dev_pixelpipe_t * pipe
A run of dabs that share everything the batch composite needs to be a single step.
Fully resolved input dab descriptor.
Generic float RGBA patch stored either in malloc memory or pixel cache.
Integer axis-aligned rectangle in buffer coordinates.
Callback bundle used by stroke processing entry points.
dt_drawlayer_paint_build_dab_cb build_dab
Mutable stroke runtime state owned by worker/backend code.
Drawlayer worker global state shared with drawlayer module.
gboolean * finish_commit_pending
dt_drawlayer_damaged_rect_t * backend_path
uint32_t * current_stroke_batch
dt_drawlayer_cache_patch_t heartbeat_patch
size_t heartbeat_transmittance_capacity
guint * stroke_sample_count
GArray * stroke_raw_inputs
dt_drawlayer_cache_patch_t heartbeat_stroke_mask
drawlayer_rt_worker_t workers[DRAWLAYER_RT_WORKER_COUNT]
pthread_cond_t worker_cond
float * heartbeat_transmittance
dt_drawlayer_damaged_rect_t live_publish_damage
dt_iop_drawlayer_params_t publish_params
uint32_t live_publish_serial
dt_pthread_mutex_t worker_mutex
dt_drawlayer_paint_stroke_t * stroke
unsigned int stroke_commit_hash
struct dt_develop_t * dev
static double dt_get_wtime(void)
Telling the user something happened.
static void _backend_worker_on_idle(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Backend-worker idle hook.
static gboolean _stroke_create(dt_drawlayer_worker_t *rt)
Create stroke runtime if missing.
#define DRAWLAYER_BATCH_TILE_SIZE
void(* drawlayer_rt_stroke_end_cb)(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Callback signature for stroke-end event processing.
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.
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.
static gboolean _workers_active_locked(const dt_drawlayer_worker_t *rt)
Check whether workers still have pending activity (lock must be held).
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.
static gboolean _worker_is_busy(const drawlayer_rt_worker_t *worker)
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.
static gboolean _rt_queue_full(const dt_drawlayer_worker_t *rt)
Test whether event queue is full.
void dt_drawlayer_worker_stop(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Stop realtime and full-resolution worker threads.
static guint _worker_batch_min_size(void)
static void _rt_destroy_state(dt_iop_module_t *self, dt_drawlayer_worker_t **rt_out)
Stop and free an existing worker state object.
void dt_drawlayer_worker_cleanup(dt_drawlayer_worker_t **worker)
Public worker cleanup entry point.
void dt_drawlayer_worker_reset_backend_path(dt_drawlayer_worker_t *worker)
Reset worker-owned backend damage accumulator.
static guint _rasterize_pending_dab_batch(drawlayer_paint_backend_ctx_t *ctx, gint64 budget_us)
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).
drawlayer_rt_worker_kind_t
Internal worker slot kinds (currently backend only).
@ DRAWLAYER_RT_WORKER_COUNT
@ DRAWLAYER_RT_WORKER_BACKEND
static gboolean _backend_pending_dabs_locked(const dt_drawlayer_worker_t *rt)
gboolean dt_drawlayer_worker_wait_idle(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Wait until worker queue is drained and not busy.
void dt_drawlayer_worker_pause(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Pause worker processing after current callback returns.
gboolean dt_drawlayer_worker_ensure_running(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Ensure realtime/backend worker threads are started.
static gboolean _live_publish_deadline_reached(const dt_drawlayer_worker_t *rt, const gint64 input_ts, const gint64 interval_us)
static gboolean _start_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Start backend worker thread if not running.
void dt_drawlayer_worker_snapshot_params(dt_drawlayer_worker_t *worker, const dt_iop_drawlayer_params_t *params)
Seed the worker's private params blob for the stroke about to start. GUI thread only.
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.
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.
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.
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.
static void _reset_backend_path(dt_drawlayer_worker_t *rt)
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.
static void _process_backend_input(dt_iop_module_t *self, const dt_drawlayer_paint_raw_input_t *input, dt_drawlayer_paint_stroke_t *stroke)
#define DRAWLAYER_HEARTBEAT_MASK_NAME
#define DRAWLAYER_HEARTBEAT_PATCH_NAME
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.
static drawlayer_rt_worker_t * _backend_worker(dt_drawlayer_worker_t *rt)
static gboolean _workers_any_active_locked(const dt_drawlayer_worker_t *rt)
Check whether any worker activity remains.
static void * _drawlayer_worker_main(void *user_data)
Worker main loop: FIFO dequeue, process, and idle scheduling.
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.
static void _paint_stroke_seed_cb(void *user_data, uint64_t stroke_seed)
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.
static gboolean _rt_workers_any_active(dt_drawlayer_worker_t *rt)
Thread-safe wrapper for any worker activity.
static void _rt_cleanup_state(dt_drawlayer_worker_t **rt_out)
Destroy worker state object and all owned resources.
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.
static void _cancel_pending_gui_commit(dt_drawlayer_worker_t *rt)
Drop a queued GUI-thread commit request. GUI thread only.
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.
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)
static void _reset_live_publish(dt_drawlayer_worker_t *rt)
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.
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.
static gboolean _worker_pause_requested(const drawlayer_rt_worker_t *worker)
static gboolean _rt_workers_active(dt_drawlayer_worker_t *rt)
Thread-safe wrapper for active-workers status.
gboolean dt_drawlayer_worker_active(const dt_drawlayer_worker_t *worker)
Public status query: TRUE when worker has pending activity.
static void _set_current_thread_realtime_best_effort(void)
Try elevating current thread scheduling policy for lower-latency input.
static void _stop_worker(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Stop worker thread and clear transient state.
static gboolean _workers_ready_for_commit_locked(const dt_drawlayer_worker_t *rt)
Check if workers are idle and commit can be safely scheduled.
void dt_drawlayer_worker_publish_backend_stroke_damage(dt_iop_module_t *self)
Publish accumulated backend stroke damage into drawlayer process/runtime state.
static void _rt_queue_clear_locked(dt_drawlayer_worker_t *rt)
Clear queued events (lock must be held).
void dt_drawlayer_worker_request_commit(dt_drawlayer_worker_t *worker)
Public commit request helper.
gboolean dt_drawlayer_worker_any_active(const dt_drawlayer_worker_t *worker)
Public status query: TRUE when any worker still has pending activity.
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.
void dt_drawlayer_worker_reset_live_publish(dt_drawlayer_worker_t *worker)
Reset worker-owned transient live-publish state.
GArray * dt_drawlayer_worker_raw_inputs(dt_drawlayer_worker_t *worker)
Read-only access to preserved raw input history.
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)
static void _publish_backend_progress(drawlayer_paint_backend_ctx_t *ctx, gboolean flush_pending)
static gboolean _rt_queue_empty(const dt_drawlayer_worker_t *rt)
Test whether event queue is empty.
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).
static gboolean _commit_dabs_on_gui_thread(gpointer user_data)
Run the stroke-end commit on the GUI thread.
static gint64 _live_publish_interval_us(void)
void dt_drawlayer_worker_seal_for_commit(dt_drawlayer_worker_t *worker)
Seal current stroke for synchronous commit.
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)
void dt_drawlayer_worker_cancel_async_commit(dt_drawlayer_worker_t *rt)
Cancel deferred commit request state if any.
static const drawlayer_rt_worker_t * _backend_worker_const(const dt_drawlayer_worker_t *rt)
static void _log_worker_batch_timing(const char *tag, guint processed_dabs, guint thread_count, double elapsed_ms, gboolean outer_loop)
static gboolean _worker_is_started(const drawlayer_rt_worker_t *worker)
void dt_drawlayer_worker_reset_stroke(dt_drawlayer_worker_t *worker)
Clear preserved stroke runtime/history after commit completed.
void dt_drawlayer_worker_resume(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Resume worker processing and wake sleeping thread.
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.
void(* drawlayer_rt_idle_cb)(dt_iop_module_t *self, dt_drawlayer_worker_t *rt)
Callback signature for idle transitions inside worker loop.
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.
static gboolean _stroke_begin(dt_drawlayer_worker_t *rt)
Start new stroke runtime and reset history/path state.
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)
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)
static void _stroke_destroy(dt_drawlayer_worker_t *rt)
Destroy stroke runtime and owned dab window.
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).
static const drawlayer_rt_callbacks_t _rt_callbacks[DRAWLAYER_RT_WORKER_COUNT]
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.
#define DRAWLAYER_OUTER_LIVE_BATCH_MULTIPLIER
static void _stroke_clear(dt_drawlayer_worker_t *rt)
Clear current stroke state while preserving allocations.
Background stroke worker API for drawlayer realtime painting.
dt_drawlayer_worker_state_t
@ DT_DRAWLAYER_WORKER_STATE_STOPPED
@ DT_DRAWLAYER_WORKER_STATE_IDLE
@ DT_DRAWLAYER_WORKER_STATE_BUSY
@ DT_DRAWLAYER_WORKER_STATE_PAUSED
@ DT_DRAWLAYER_WORKER_STATE_PAUSING