Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
region.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with darktable. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19// Per-region gather/composite + the region reconstruction driver (CPU + OpenCL). (implementation; see region.h for
20// the public API.)
21
22#include "system/macros.h"
23#include "system/mem_alloc.h"
24#include "system/simd.h"
27#include "pixel/gaussian.h"
28#include "iop/noise_generator.h"
29#include "iop/highlights/blur.h"
32#include "iop/highlights/core.h"
34#include <math.h>
35#include <string.h>
36
38static void _region_gather(_hl_region_ctx_t *const ctx)
39{
40 float *const restrict interp = ctx->interp;
41 const float *const restrict mask = ctx->mask;
42 const float *const restrict depth = ctx->depth;
43 const int width = ctx->width;
44 const _hl_region_t *const region = ctx->region;
45 const int region_w = ctx->region_w;
46 const int region_h = ctx->region_h;
47 const size_t region_pixels = ctx->region_pixels;
48 float *const restrict estimate = ctx->estimate;
49 float *const restrict valid = ctx->valid;
50 float *const restrict clip_depth = ctx->clip_depth;
51 float *const restrict clip0 = ctx->clip0;
52
53 // gather region into contiguous buffers
54 HL_PFOR(collapse(2))
55 for(int y = 0; y < region_h; y++)
56 for(int x = 0; x < region_w; x++)
57 {
58 const size_t pixel_index = (size_t)(region->ry0 + y) * width + (region->rx0 + x);
59 const size_t src_offset = pixel_index * 4;
60 const size_t dst_offset = ((size_t)y * region_w + x) * 4;
61 clip_depth[(size_t)y * region_w + x] = depth[pixel_index];
62 for(int k = 0; k < 4; k++)
63 {
64 estimate[dst_offset + k] = interp[src_offset + k];
65 clip0[dst_offset + k] = interp[src_offset + k]; // saturated value, physical floor for clipped ch.
66 valid[dst_offset + k] = fmaxf(1.f - mask[src_offset + k], 0.f); // per-channel validity
67 }
68 }
69
70 // A clipped channel saturated, so its true value is >= its clip level: floor the reconstruction at
71 // the saturated value so a low-guide fit cannot push it below saturation (the amber -> magenta
72 // collapse). Monotone (only raises), so no overshoot and no per-pixel switching. Applied before the
73 // joint core, so the all-clip dome and chroma diffusion are fed the corrected (brighter) rim.
74 HL_PFOR()
75 for(size_t i = 0; i < region_pixels; i++)
76 for(int c = 0; c < 3; c++)
77 if(valid[i * 4 + c] < 0.5f) estimate[i * 4 + c] = fmaxf(estimate[i * 4 + c], clip0[i * 4 + c]);
78}
79
80// Stage 9 -- optional Poissonian grain on the reconstructed channels, then scatter the
81// padded-window estimate back into the full-res interp buffer at the region's offset.
83static void _region_composite(_hl_region_ctx_t *const ctx)
84{
85 float *const restrict interp = ctx->interp;
86 const float *const restrict mask = ctx->mask;
87 const int width = ctx->width;
88 const _hl_region_t *const region = ctx->region;
89 const int region_w = ctx->region_w;
90 const int region_h = ctx->region_h;
91 const float noise_level = ctx->noise_level;
92 float *const restrict estimate = ctx->estimate;
93 float *const restrict valid = ctx->valid;
94
95 // Optional grain: reconstructed highlights are very smooth, so break them up with Poissonian noise
96 // whose amplitude scales with the local value (the "noise level" user parameter). Only clipped
97 // channels get it; valid channels keep their real data. Matches the legacy last-scale noise.
98 if(noise_level > 0.f)
99 {
100 HL_PFOR(collapse(2))
101 for(int y = 0; y < region_h; y++)
102 {
103 for(int x = 0; x < region_w; x++)
104 {
105 const size_t i = ((size_t)y * region_w + x) * 4;
106
107 // per-pixel RNG, deterministic in region coordinates so the render is reproducible
108 uint32_t DT_ALIGNED_ARRAY state[4]
109 = { splitmix32(x + 1), splitmix32((y + 1) * (x + 3)), splitmix32(1337), splitmix32(666) };
114
115 // per-channel noise standard deviation = value * noise_level
116 dt_aligned_pixel_t current = { estimate[i], estimate[i + 1], estimate[i + 2], estimate[i + 3] };
117 dt_aligned_pixel_t nsigma = { current[0] * noise_level, current[1] * noise_level, current[2] * noise_level,
118 current[3] * noise_level };
119 const int DT_ALIGNED_ARRAY flip[4] = { TRUE, FALSE, TRUE, FALSE };
120 dt_aligned_pixel_t noise = { 0.f };
122
123 // one-sided (brightening) grain, only on the reconstructed (clipped) channels
124 for(int c = 0; c < 3; c++)
125 if(valid[i + c] < 0.5f) estimate[i + c] = fmaxf(current[c] + fabsf(noise[c] - current[c]), 0.f);
126 }
127 }
128 }
129
130 // FLOW: final per-region composite (article §"The algorithm", the flowchart's remosaic-feeding step).
131 // Scatter the reconstructed clipped channels from the padded window back into the full-res interp
132 // buffer at the region's absolute offset (region->rx0/ry0). Only the channels that were ACTUALLY
133 // clipped (mask > 0.5) are overwritten -- valid channels keep their measured values untouched -- and
134 // the write is floored at 0 (no negative radiance). Unclipped pixels outside every region are never
135 // visited, so the reconstruction only ever edits the holes.
136 HL_PFOR(collapse(2))
137 for(int y = 0; y < region_h; y++)
138 {
139 for(int x = 0; x < region_w; x++)
140 {
141 const size_t src_offset = ((size_t)y * region_w + x) * 4;
142 const size_t dst_offset = ((size_t)(region->ry0 + y) * width + (region->rx0 + x)) * 4;
143
144 // only overwrite the channels that were actually clipped
145 for(int c = 0; c < 3; c++)
146 if(mask[dst_offset + c] > 0.5f) interp[dst_offset + c] = fmaxf(estimate[src_offset + c], 0.f);
147 }
148 }
149}
150
151void _region_guided_filter(float *const restrict interp, const float *const restrict mask,
152 const float *const restrict depth, const int width, const _hl_region_t *const region,
153 const dt_dev_pixelpipe_t *pipe, const float solid_color, const int max_iter,
154 const float noise_level, const float floor_gate, const float module_scale)
155{
156 const int region_w = region->rx1 - region->rx0 + 1;
157 const int region_h = region->ry1 - region->ry0 + 1;
158 if(region_w < 2 || region_h < 2) return;
159 const size_t region_pixels = (size_t)region_w * region_h;
160 // Sanity guard only (the pipe-cache arena handles memory): skip a pathologically huge region.
161 // Normal clipped regions in a full raw stay well under this; keep it high so nothing is missed.
162 if(region_pixels > (size_t)64 * 1024 * 1024) return;
163
164 float *const restrict estimate
165 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe); // running estimate (RGB+norm)
166 float *const restrict prev_scale
167 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe); // snapshot at scale start
168 float *const restrict valid
169 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe); // per-channel validity (0..1)
170 float *const restrict blur_in
171 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe); // blur scratch (in)
172 float *const restrict plane1
173 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe); // per-channel fit accumulator
174 float *const restrict plane2
175 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe); // blur scratch (out)
176 float *const restrict plane3
177 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe); // blur scratch (out)
178 float *const restrict valid_variance
179 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe); // per-channel valid variance
180 float *const restrict guide_score
181 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe); // per-channel best guide score
182 float *const restrict clip_depth
183 = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe); // per-pixel clip-to-valid depth
184 float *const restrict clip0
185 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe); // saturated (clipped) value per channel
186 if(!estimate || !prev_scale || !valid || !blur_in || !plane1 || !plane2 || !plane3 || !valid_variance
187 || !guide_score || !clip_depth || !clip0)
188 {
196 dt_pixelpipe_cache_free_align(valid_variance);
200 return;
201 }
202
203 const int extent = MAX(region->x1 - region->x0, region->y1 - region->y0) + 1;
204 const float epsilon = 1e-6f;
205 const int max_cg_iter = CLAMP(2 * extent, 200, 2000);
206 // The prototype solves the seam regulariser with a direct sparse solve (exact). C has no sparse
207 // direct solver, so run the FULL CG budget instead of capping at the user "iterations" param:
208 // an under-converged biharmonic CG stops each channel at a different point -> per-channel
209 // inconsistency -> chroma drift. maxit (not max_iter) is the honest best-effort here.
210 (void)max_iter;
211
212 uint8_t *const restrict hole = (uint8_t *)dt_pixelpipe_cache_alloc_align(sizeof(uint8_t) * region_pixels, pipe);
213 float *const restrict solver_field
214 = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe); // solver working field
215 float *const restrict fill_planes
216 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 3, pipe); // fused-fill planes
217 float *const restrict dome_lum = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe); // luminance dome
218 float *const restrict lum_accum
219 = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe); // luminance accum (chroma denom)
220 float *const restrict reaction_weight
221 = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe); // chroma reaction weight
222 float *const restrict flat_target
223 = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe); // chroma flat target
224 float *const restrict cg_residual = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe); // CG scratch
225 float *const restrict cg_dir = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
226 float *const restrict cg_operator = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
227 float *const restrict cg_tmp1 = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
228 float *const restrict cg_tmp2 = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
229
230 _hl_region_ctx_t ctx = {
231 .interp = interp,
232 .mask = mask,
233 .depth = depth,
234 .width = width,
235 .region = region,
236 .pipe = pipe,
237 .region_w = region_w,
238 .region_h = region_h,
239 .region_pixels = region_pixels,
240 .extent = extent,
241 .scale = module_scale,
242 .epsilon = epsilon,
243 .max_cg_iter = max_cg_iter,
244 .solid_color = solid_color,
245 .noise_level = noise_level,
246 .floor_gate = floor_gate,
247 .estimate = estimate,
248 .prev_scale = prev_scale,
249 .valid = valid,
250 .blur_in = blur_in,
251 .plane1 = plane1,
252 .plane2 = plane2,
253 .plane3 = plane3,
254 .valid_variance = valid_variance,
255 .guide_score = guide_score,
256 .clip_depth = clip_depth,
257 .clip0 = clip0,
258 .hole = hole,
259 .solver_field = solver_field,
260 .fill_planes = fill_planes,
261 .dome_lum = dome_lum,
262 .lum_accum = lum_accum,
263 .reaction_weight = reaction_weight,
264 .flat_target = flat_target,
265 .cg_residual = cg_residual,
266 .cg_dir = cg_dir,
267 .cg_operator = cg_operator,
268 .cg_tmp1 = cg_tmp1,
269 .cg_tmp2 = cg_tmp2,
270 };
271
272 _region_gather(&ctx);
273
274 if(hole && solver_field && fill_planes && dome_lum && lum_accum && reaction_weight && flat_target && cg_residual
275 && cg_dir && cg_operator && cg_tmp1 && cg_tmp2)
276 {
277 _cf_reconstruct(&ctx);
278 _selfdome(&ctx);
279 _joint_core(&ctx);
280 _aniso_chroma(&ctx);
282 // Per-region timing breakdown. Only for regions big enough to matter (small ones are noise) and
283 }
285 dt_pixelpipe_cache_free_align(solver_field);
289 dt_pixelpipe_cache_free_align(reaction_weight);
296
297 _region_composite(&ctx);
298
306 dt_pixelpipe_cache_free_align(valid_variance);
310}
311
312// ---------------------------------------------------------------------------------------------
313// R9 sensor-rolloff (knee) estimation + inversion. See the DT_HL_KNEE macro comment for the why.
314// All values are handled in CLIP-NORMALIZED units: x = value / (clip level), so the detection
315// threshold sits at DT_HL_KNEE_DET (the clips[] passed around equal 0.995 * clip level) and the
316// band under estimation is [DT_HL_KNEE_LO, DT_HL_KNEE_DET).
317// ---------------------------------------------------------------------------------------------
318
319// ============================ OpenCL ============================
320
321#if defined(HAVE_OPENCL) && DT_HL_COEFF_FIELD && DT_HL_SPARSE_SOLVE && (DT_HL_ANISO_SOLVER == 2)
322// Device counterpart (per-region GPU orchestrator) of _region_guided_filter: gathers the
323// padded region window, derives the stage parameters from one on-device reduction
324// (union-hole plateau brightness -> cf_binv, per-channel clip counts -> deep channel, union
325// count -> shared dome grid), then chains the proven stages -- coefficient field
326// (_cf_stage_cl), high-frequency detail hybrid (_hf_stage_cl), floors + gated self-dome
327// (_selfdome_stage_cl), all-clip joint core (_joint_core_stage_cl), divergence-form
328// anisotropic chroma (_aniso_stage_cl) -- and scatters the clipped channels back. Everything
329// stays on the device except the reduction partials. Caller must handle noise_level > 0 on
330// the CPU (the grain epilogue is not ported).
331// Any change here must be mirrored in _region_guided_filter (CPU) and re-validated with the
332// HL_REGCL_TEST self-test (_region_guided_filter_cl_selftest).
333// Regions below this pixel count are reconstructed on the CPU even when the pipe runs on the
334// GPU: a device region pays ~1000 kernel launches (iterative stages, per-level sparse solves)
335
336cl_int _region_cpu_offload_cl(const int devid, void *gd_void, cl_mem interp, cl_mem mask, cl_mem depth,
337 const int width, const _hl_region_t *const region, const dt_dev_pixelpipe_t *pipe,
338 const float solid_color, const int max_iter, const float noise_level,
339 const float floor_gate, const float module_scale)
340{
342 const int region_w = region->rx1 - region->rx0 + 1;
343 const int region_h = region->ry1 - region->ry0 + 1;
344 if(region_w < 2 || region_h < 2) return CL_SUCCESS;
345 const size_t region_pixels = (size_t)region_w * region_h;
346
347 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
348 size_t work_size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
349
350 cl_mem staging = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 9);
351 float *host = dt_pixelpipe_cache_alloc_align_float(region_pixels * 9, pipe);
352 if(!staging || IS_NULL_PTR(host)) goto out;
353
354 {
355 const int kernel = global_data->kernel_hl_window_pack;
356 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &interp);
357 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &mask);
358 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &depth);
359 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &staging);
360 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &width);
361 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region->rx0);
362 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &region->ry0);
363 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &region_w);
364 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &region_h);
365 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
366 if(cl_err != CL_SUCCESS) goto out;
367 }
368
369 cl_err = dt_opencl_read_buffer_from_device(devid, host, staging, 0, sizeof(float) * region_pixels * 9, CL_TRUE);
370 if(cl_err != CL_SUCCESS) goto out;
371
372 {
373 float *const hw_interp = host;
374 const float *const hw_mask = host + region_pixels * 4;
375 const float *const hw_depth = host + region_pixels * 8;
376
377 _hl_region_t translated_region = *region;
378 translated_region.x0 -= region->rx0;
379 translated_region.x1 -= region->rx0;
380 translated_region.y0 -= region->ry0;
381 translated_region.y1 -= region->ry0;
382 translated_region.rx1 -= region->rx0;
383 translated_region.ry1 -= region->ry0;
384 translated_region.rx0 = 0;
385 translated_region.ry0 = 0;
386
387 _region_guided_filter(hw_interp, hw_mask, hw_depth, region_w, &translated_region, pipe, solid_color, max_iter,
388 noise_level, floor_gate, module_scale);
389 }
390
391 cl_err = dt_opencl_write_buffer_to_device(devid, host, staging, 0, sizeof(float) * region_pixels * 4, CL_TRUE);
392 if(cl_err != CL_SUCCESS) goto out;
393
394 {
395 const int kernel = global_data->kernel_hl_window_unpack;
396 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &staging);
397 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &interp);
398 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &width);
399 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region->rx0);
400 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region->ry0);
401 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_w);
402 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &region_h);
403 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
404 }
405
406out:
409 return cl_err;
410}
411
412cl_int _region_guided_filter_cl(const int devid, void *gd_void, cl_mem interp, cl_mem mask, cl_mem depth,
413 const int width, const _hl_region_t *const region, const dt_dev_pixelpipe_t *pipe,
414 const float solid_color, const float floor_gate, const float module_scale)
415{
417 const int region_w = region->rx1 - region->rx0 + 1;
418 const int region_h = region->ry1 - region->ry0 + 1;
419 if(region_w < 2 || region_h < 2) return CL_SUCCESS;
420 const size_t region_pixels = (size_t)region_w * region_h;
421 if(region_pixels > (size_t)64 * 1024 * 1024) return CL_SUCCESS;
422
423 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
424 size_t work_size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
425 dt_gaussian_cl_t *cf_gaussian = NULL;
426
427 // The stage-2 reduction finalizers this needs are compiled only where the fp64 extension
428 // is (data/kernels/highlights_harmonic.cl). Without them the caller falls back to the CPU
429 // twin, the same way the sparse solver and the PDE/aniso stages already do.
430 if(global_data->kernel_hl_region_worth_finalize < 0) return cl_err; // no fp64 device
431
432 cl_mem estimate = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
433 cl_mem valid = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
434 cl_mem clip0 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
435 cl_mem model_quality = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
436 cl_mem region_worth_dev = NULL; // one floor verdict per region, device-resident (never read back)
437 cl_mem clip_depth = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
438 cl_mem lsb0 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels); // pre-ladder luminance
439 cl_mem partials = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 8 * 256);
440 cl_mem steer = NULL; // coefficient-fill steering plane (guide structure)
441 if(!estimate || !valid || !clip0 || !model_quality || !clip_depth || !lsb0 || !partials) goto out;
442
443 // gather the padded region window into contiguous device buffers (est/clip0/vld/dep/lsb0)
444 {
445 const int kernel = global_data->kernel_hl_region_gather;
446 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &interp);
447 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &mask);
448 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &depth);
449 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &estimate);
450 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &clip0);
451 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &valid);
452 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), &clip_depth);
453 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(cl_mem), &model_quality);
454 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(cl_mem), &lsb0);
455 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &width);
456 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(int), &region->rx0);
457 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(int), &region->ry0);
458 dt_opencl_set_kernel_arg(devid, kernel, 12, sizeof(int), &region_w);
459 dt_opencl_set_kernel_arg(devid, kernel, 13, sizeof(int), &region_h);
460 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
461 if(cl_err != CL_SUCCESS) goto out;
462 }
463
464 // ladder parameters from the pre-ladder statistics
465 const float cf_sigma = CLAMP(region->radius / 6.f, 8.f, 64.f);
466 const float cf_fmin = 0.05f;
467 float cf_binv;
468 float channel_means[3] = { 0.f, 0.f, 0.f }; // per-channel valid means (moment-pack centering)
469 int cdeep, ds_shared;
470 {
471 const int local_size = 64, n_groups = 256;
472 const int pixel_count = (int)region_pixels;
473 const int kernel = global_data->kernel_hl_region_stats;
474 size_t sizes[3] = { (size_t)n_groups * local_size, 1, 1 };
475 size_t local[3] = { local_size, 1, 1 };
476 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
477 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
478 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &partials);
479 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &pixel_count);
480 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float) * 8 * local_size, NULL);
481 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, sizes, local);
482 if(cl_err != CL_SUCCESS) goto out;
483
484 float partials_host[8 * 256];
485 cl_err = dt_opencl_read_buffer_from_device(devid, partials_host, partials, 0, sizeof(float) * 8 * n_groups,
486 CL_TRUE);
487 if(cl_err != CL_SUCCESS) goto out;
488 double lsum = 0.0, lcnt = 0.0, clip_count_r = 0.0, clip_count_g = 0.0, clip_count_b = 0.0;
489 double msum[3] = { 0.0, 0.0, 0.0 };
490 for(int group = 0; group < n_groups; group++)
491 {
492 lsum += (double)partials_host[8 * group + 0];
493 lcnt += (double)partials_host[8 * group + 1];
494 clip_count_r += (double)partials_host[8 * group + 2];
495 clip_count_g += (double)partials_host[8 * group + 3];
496 clip_count_b += (double)partials_host[8 * group + 4];
497 msum[0] += (double)partials_host[8 * group + 5];
498 msum[1] += (double)partials_host[8 * group + 6];
499 msum[2] += (double)partials_host[8 * group + 7];
500 }
501 if(lcnt <= 0.0)
502 {
503 cl_err = CL_SUCCESS; // no clipped pixel in this window: nothing to do
504 goto out;
505 }
506 const float cf_lref = (float)(lsum / lcnt);
507 cf_binv = (cf_lref > 1e-9f) ? 1.f / (0.35f * cf_lref) : 0.f;
508 cdeep = (clip_count_r >= clip_count_g && clip_count_r >= clip_count_b)
509 ? 0
510 : ((clip_count_g >= clip_count_b) ? 1 : 2);
511 ds_shared = MAX(1, (int)ceilf(sqrtf((float)lcnt / (float)DT_HL_DOME_NMAX_SPARSE)));
512 // per-channel means of the VALID values: the moment packs are centered on them (see the
513 // CPU counterpart for the cancellation rationale)
514 const double valid_count_r = (double)region_pixels - clip_count_r,
515 valid_count_g = (double)region_pixels - clip_count_g,
516 valid_count_b = (double)region_pixels - clip_count_b;
517 channel_means[0] = valid_count_r > 0.5 ? (float)(msum[0] / valid_count_r) : 0.f;
518 channel_means[1] = valid_count_g > 0.5 ? (float)(msum[1] / valid_count_g) : 0.f;
519 channel_means[2] = valid_count_b > 0.5 ? (float)(msum[2] / valid_count_b) : 0.f;
520 }
521
522 // one gaussian handle serves every cf_sigma blur of the region (each init allocates two
523 // region-sized temp buffers -- 13+ per-blur re-allocations were pure churn)
524 cf_gaussian = _region_blur_handle(devid, region_w, region_h, cf_sigma);
525
526 // Steering plane for the coefficient fills = the measured guide structure, built ONCE here
527 // (same est state as the CPU: after the saturation floor) and shared by the coefficient-field
528 // and HF stages, exactly like the CPU path.
529 {
530 steer = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
531 if(steer)
532 {
533 const int kernel = global_data->kernel_hl_cfa_steer;
534 const int pixel_count = (int)region_pixels;
535 size_t work_size_1d[3] = { ROUNDUPDWD(pixel_count, devid), 1, 1 };
536 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
537 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
538 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &steer);
539 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &pixel_count);
540 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size_1d);
541 if(cl_err != CL_SUCCESS) goto out;
542 }
543 }
544
545 cl_err = _cf_stage_cl(devid, gd_void, estimate, valid, model_quality, lsb0, steer, channel_means, cf_gaussian,
546 region_w, region_h, cf_sigma, cf_fmin, cf_binv, cdeep);
547 if(cl_err == CL_SUCCESS) dt_opencl_finish(devid);
548 if(cl_err != CL_SUCCESS) goto out;
549 cl_err = _hf_stage_cl(devid, gd_void, estimate, valid, model_quality, lsb0, steer, cf_gaussian, region_w,
550 region_h, cf_sigma, cf_fmin, cf_binv);
551 if(cl_err == CL_SUCCESS) dt_opencl_finish(devid);
552 if(cl_err != CL_SUCCESS) goto out;
553
554 // ONE floor decision per region, on the fitted estimates (mirror of _cf_reconstruct pass 1):
555 // reduce both floor candidates' aggregate chromaticity on the DEVICE into a scalar every floor
556 // kernel reads -- the two floors cannot be mixed spatially, so they share the same verdict.
557 region_worth_dev = dt_opencl_alloc_device_buffer(devid, sizeof(float));
558 if(!region_worth_dev)
559 {
561 goto out;
562 }
563 {
564 const int local_size = 64, n_groups = 256;
565 const int pixel_count = (int)region_pixels;
566 cl_mem bpart = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 6 * n_groups);
567 if(!bpart)
568 {
570 goto out;
571 }
572 size_t sizes[3] = { (size_t)n_groups * local_size, 1, 1 };
573 size_t local[3] = { local_size, 1, 1 };
574 int kernel = global_data->kernel_hl_region_benefit;
575 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
576 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
577 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &clip0);
578 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &bpart);
579 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &pixel_count);
580 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float) * 6 * local_size, NULL);
581 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, sizes, local);
582 if(cl_err == CL_SUCCESS)
583 {
585 const float lo = CF_REGION_LO, hi = CF_REGION_HI;
586 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &bpart);
587 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &region_worth_dev);
588 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &n_groups);
589 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(float), &lo);
590 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), &hi);
591 size_t one[3] = { 1, 1, 1 };
592 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, one);
593 }
595 if(cl_err != CL_SUCCESS) goto out;
596 }
597
598 // gated self-dome: the soft floor is unconditional (production applies it right after the
599 // HF hybrid); the dome + blend + hard floor only run where a clipped channel with a
600 // surviving guide sits on a weak colour-line
601 int need_self = 0;
602 {
603 const int local_size = 64, n_groups = 256;
604 const int pixel_count = (int)region_pixels;
605 const int kernel = global_data->kernel_hl_need_self;
606 size_t sizes[3] = { (size_t)n_groups * local_size, 1, 1 };
607 size_t local[3] = { local_size, 1, 1 };
608 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &valid);
609 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &model_quality);
610 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &clip_depth);
611 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &partials);
612 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &pixel_count);
613 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), &cf_sigma);
614 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float) * local_size, NULL);
615 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, sizes, local);
616 if(cl_err != CL_SUCCESS) goto out;
617 float partials_host[256];
618 cl_err
619 = dt_opencl_read_buffer_from_device(devid, partials_host, partials, 0, sizeof(float) * n_groups, CL_TRUE);
620 if(cl_err != CL_SUCCESS) goto out;
621 for(int group = 0; group < n_groups; group++)
622 if(partials_host[group] > 0.f) need_self = 1;
623 }
624
625 if(need_self)
626 {
627 cl_err = _selfdome_stage_cl(devid, gd_void, estimate, valid, model_quality, clip0, clip_depth,
628 region_worth_dev, region_w, region_h, cf_sigma, region->radius, ds_shared,
629 floor_gate, pipe);
630 if(cl_err != CL_SUCCESS) goto out;
631 }
632 else
633 {
634 const int kernel = global_data->kernel_hl_soft_floor;
635 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
636 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
637 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &clip0);
638 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &region_worth_dev);
639 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
640 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
641 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &floor_gate);
642 const float joint_tau = CF_JOINT_TAU;
643 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &joint_tau);
644 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
645 if(cl_err != CL_SUCCESS) goto out;
646 }
647
648 {
649 // Step 7: all-clip joint core (shared biharmonic dome x screened-Poisson diffused chroma)
650 const int extent = MAX(region->x1 - region->x0, region->y1 - region->y0) + 1;
651 cl_err = _joint_core_stage_cl(devid, gd_void, estimate, valid, clip0, region_w, region_h, solid_color,
652 region->radius, extent, floor_gate, pipe);
653 if(cl_err == CL_SUCCESS) dt_opencl_finish(devid);
654 }
655 if(cl_err != CL_SUCCESS) goto out;
656 // Step 8: structure-steered chrominance coherence (div(D grad r)=0 under the obstacle r >= c0/L)
657 cl_err = _aniso_stage_cl(devid, gd_void, estimate, valid, clip0, region_w, region_h, region->radius,
658 floor_gate, solid_color, pipe);
659 if(cl_err == CL_SUCCESS) dt_opencl_finish(devid);
660 if(cl_err != CL_SUCCESS) goto out;
661
662 // Step 9: gradient-extending chroma (chromaticity-gradient continuation, article addendum)
663 cl_err = _chromaticity_gradient_stage_cl(devid, gd_void, estimate, valid, clip0, clip_depth, region_w,
664 region_h, region->radius, floor_gate, module_scale, pipe);
665 if(cl_err == CL_SUCCESS) dt_opencl_finish(devid);
666 if(cl_err != CL_SUCCESS) goto out;
667
668 // scatter the reconstructed clipped channels back into the full-res buffer
669 {
670 const int kernel = global_data->kernel_hl_region_scatter;
671 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &interp);
672 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &mask);
673 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &estimate);
674 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &width);
675 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region->rx0);
676 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region->ry0);
677 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &region_w);
678 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &region_h);
679 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
680 }
681
682out:
683 dt_gaussian_free_cl(cf_gaussian);
687 dt_opencl_release_mem_object(model_quality);
689 dt_opencl_release_mem_object(region_worth_dev);
693 return cl_err;
694}
695
696#endif // HAVE_OPENCL && DT_HL_COEFF_FIELD && DT_HL_SPARSE_SOLVE && ANISO_SOLVER 2
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
dt_gaussian_cl_t * _region_blur_handle(const int devid, const int region_w, const int region_h, const float sigma)
Definition blur.c:67
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
__DT_CLONE_TARGETS__ void _aniso_chroma(_hl_region_ctx_t *const ctx)
Definition chroma.c:336
cl_int _cf_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem model_quality, cl_mem luminance, cl_mem steer, const float *const restrict channel_means, dt_gaussian_cl_t *gaussian, const int region_w, const int region_h, const float cf_sigma, const float cf_fmin, const float cf_binv, const int cdeep)
__DT_CLONE_TARGETS__ void _cf_reconstruct(_hl_region_ctx_t *const ctx)
cl_int _hf_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem model_quality, cl_mem luminance, cl_mem steer, dt_gaussian_cl_t *gaussian, const int region_w, const int region_h, const float cf_sigma, const float cf_fmin, const float cf_binv)
static const float x
const dt_colormatrix_t dt_aligned_pixel_t out
void _chromaticity_gradient(_hl_region_ctx_t *const ctx)
Definition core.c:509
__DT_CLONE_TARGETS__ void _joint_core(_hl_region_ctx_t *const ctx)
Definition core.c:267
__DT_CLONE_TARGETS__ void _selfdome(_hl_region_ctx_t *const ctx)
Definition core.c:36
static float4 dt_noise_generator_simd(const dt_noise_distribution_t distribution, const float4 mu, const float4 param, uint state[4])
static unsigned int splitmix32(const unsigned long seed)
static float xoshiro128plus(uint state[4])
void dt_gaussian_free_cl(dt_gaussian_cl_t *g)
Definition gaussian.c:365
static float kernel(const float *x, const float *y)
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define DT_ALIGNED_ARRAY
Align an object on a cacheline boundary, so AVX2 can load it whole.
Definition mem_alloc.h:80
uint32_t width
Definition mipmap_cache.c:0
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2554
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:2970
int dt_opencl_write_buffer_to_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2738
int dt_opencl_read_buffer_from_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2727
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2545
gboolean dt_opencl_finish(const int devid)
Definition opencl.c:1671
int dt_opencl_enqueue_kernel_2d_with_local(const int dev, const int kernel, const size_t *sizes, const size_t *local)
Definition opencl.c:2560
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
#define DT_OPENCL_DEFAULT_ERROR
Definition opencl.h:61
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define dt_pixelpipe_cache_alloc_align(size, pipe)
#define dt_pixelpipe_cache_free_align(mem)
#define dt_pixelpipe_cache_alloc_align_float(pixels, pipe)
static __DT_CLONE_TARGETS__ void _region_composite(_hl_region_ctx_t *const ctx)
Definition region.c:83
static __DT_CLONE_TARGETS__ void _region_gather(_hl_region_ctx_t *const ctx)
Definition region.c:38
void _region_guided_filter(float *const restrict interp, const float *const restrict mask, const float *const restrict depth, const int width, const _hl_region_t *const region, const dt_dev_pixelpipe_t *pipe, const float solid_color, const int max_iter, const float noise_level, const float floor_gate, const float module_scale)
Definition region.c:151
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#define CF_REGION_LO
#define DT_HL_DOME_NMAX_SPARSE
#define CF_JOINT_TAU
#define HL_PFOR(...)
#define CF_REGION_HI
const float uint32_t state[4]
const float const int flip
const float noise
const _hl_region_t * region
#define __DT_CLONE_TARGETS__
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MAX(a, b)
Definition thinplate.c:29