Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
coefficient_field.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// Coefficient-field colour-line transport + HF-refit stage (CPU + OpenCL). (implementation; see
20// coefficient_field.h for the public API.)
21
22#include "common/darktable.h"
23#include "develop/imageop.h"
25#include "iop/highlights/blur.h"
28#include "iop/highlights/knee.h"
29#include <math.h>
30#include <string.h>
31
32// Variance-adaptive steering tensor: a continuous blend between the isophote
33// tensor (transport along level lines, correct where a HARD EDGE crosses the zone: the content
34// beyond the edge follows another colour-line) and the gradient tensor (radial transport,
35// correct on a clean halo: the model lives on the rim and must travel inward). The blend weight
36// m is the TREND-CORRECTED windowed variance of the steering plane: raw windowed variance minus
37// the part the local linear ramp explains -- a smooth halo ramp has variance but no residual,
38// a hard edge has residual variance no ramp can explain. m = v_res / (v_res + (k * mean)^2),
39// scale-free (k = relative std threshold). D = [m + (1-m) c2] t t^T + [m c2 + (1-m)] g g^T,
40// both weights in (0, 1], D SPD, so the Weickert stencil stays nonnegative (maximum principle).
41//
42// MATHS BRIDGE -- article "The algorithm" step 3, the E_transport steering tensor. Builds the D of
43// E_transport = Sum_p integral grad(p)^T D grad(p) dOmega, whose Euler-Lagrange div(D grad p)=0 is the
44// anisotropic fill relaxed below. Article eq:
45// D = [ m + (1-m) c2 ] t t^T + [ m c2 + (1-m) ] g g^T , c2 = exp(-|grad L_mean| / (4 <|grad L_mean|>))
46// m = v / (v + (k Lbar_mean)^2) , v = max( var_w(L_mean) - (4/3)|grad L_mean|^2 , 0 ) , k = 0.15
47// g = unit gradient (uphill) of the steering plane L_mean, t = unit isophote (level-line), m in [0,1] the
48// edge probability. m->0 (clean halo ramp) => D -> g g^T + c2 t t^T, transport radial inward along the
49// ramp; m->1 (hard edge in the zone) => D -> t t^T + c2 g g^T, transport along the boundary, not across it.
51static void _cf_adaptive_tensor(const float *const restrict luminance, float *const restrict tensor_xx,
52 float *const restrict tensor_xy, float *const restrict tensor_yy,
53 float *const restrict scratch_lin, float *const restrict scratch_quad,
54 const int region_w, const int region_h, const float k)
55{
56 const size_t region_pixels = (size_t)region_w * region_h;
57
58 // two 3x3 box passes on L (into scratch_lin) and on L^2 (into scratch_quad)
59 for(int pass = 0; pass < 2; pass++)
60 {
61 const float *const src_lin = (pass == 0) ? luminance : scratch_lin;
62 const float *const src_quad = (pass == 0) ? luminance : scratch_quad; // pass 0 squares on the fly
63
64 HL_PFOR(collapse(2))
65 for(int y = 0; y < region_h; y++)
66 for(int x = 0; x < region_w; x++)
67 {
68 double sum_lin = 0.0;
69 double sum_quad = 0.0;
70 int count = 0;
71 for(int offset_y = -1; offset_y <= 1; offset_y++)
72 for(int offset_x = -1; offset_x <= 1; offset_x++)
73 {
74 const int neighbour_y = CLAMP(y + offset_y, 0, region_h - 1);
75 const int neighbour_x = CLAMP(x + offset_x, 0, region_w - 1);
76 const float value_lin = src_lin[(size_t)neighbour_y * region_w + neighbour_x];
77 sum_lin += value_lin;
78 sum_quad += (pass == 0) ? (double)value_lin * value_lin
79 : src_quad[(size_t)neighbour_y * region_w + neighbour_x];
80 count++;
81 }
82 tensor_xx[(size_t)y * region_w + x] = (float)(sum_lin / count);
83 tensor_yy[(size_t)y * region_w + x] = (float)(sum_quad / count);
84 }
85
86 HL_PFOR()
87 for(size_t i = 0; i < region_pixels; i++)
88 {
89 scratch_lin[i] = tensor_xx[i];
90 scratch_quad[i] = tensor_yy[i];
91 }
92 }
93
94 // gradients of the blurred L + mean magnitude
95 double grad_sum = 0.0;
96 HL_PFOR(collapse(2) reduction(+ : grad_sum))
97 for(int y = 0; y < region_h; y++)
98 for(int x = 0; x < region_w; x++)
99 {
100 const int x_lo = MAX(x - 1, 0), x_hi = MIN(x + 1, region_w - 1);
101 const int y_lo = MAX(y - 1, 0), y_hi = MIN(y + 1, region_h - 1);
102 const float grad_x
103 = 0.5f * (scratch_lin[(size_t)y * region_w + x_hi] - scratch_lin[(size_t)y * region_w + x_lo]);
104 const float grad_y
105 = 0.5f * (scratch_lin[(size_t)y_hi * region_w + x] - scratch_lin[(size_t)y_lo * region_w + x]);
106 tensor_xx[(size_t)y * region_w + x] = grad_x;
107 tensor_xy[(size_t)y * region_w + x] = grad_y;
108 grad_sum += dt_fast_hypotf(grad_x, grad_y);
109 }
110 const float grad_mean
111 = fmaxf((float)(grad_sum / (double)region_pixels),
112 1e-9f); // <|grad L_mean|>, the regional mean magnitude (exposure-independent normaliser)
113
114 HL_PFOR()
115 for(size_t i = 0; i < region_pixels; i++)
116 {
117 const float grad_x = tensor_xx[i];
118 const float grad_y = tensor_xy[i];
119 const float grad_mag = dt_fast_hypotf(grad_x, grad_y);
120 const float nonzero = (grad_mag > 1e-12f) ? 1.f : 0.f;
121 const float inv_mag = nonzero / (grad_mag + (1.f - nonzero));
122 const float grad_unit_x = grad_x * inv_mag + (1.f - nonzero); // g = unit gradient direction (uphill)
123 const float grad_unit_y = grad_y * inv_mag;
124 const float isophote_x = -grad_unit_y, isophote_y = grad_unit_x; // t = unit isophote = g rotated 90deg
125 const float cross_damp = expf(-grad_mag / (4.f * grad_mean)); // c2 = exp(-|grad L_mean| / (4 <|grad
126 // L_mean|>)), edge-crossing damping
127
128 // trend-corrected windowed variance: two 3x3 box passes have spatial variance 4/3 per axis
129 const float variance = fmaxf(scratch_quad[i] - scratch_lin[i] * scratch_lin[i],
130 0.f); // var_w(L_mean) = E[L^2]-E[L]^2 (centred by construction of the box passes)
131 const float residual_var
132 = fmaxf(variance - (4.f / 3.f) * (grad_x * grad_x + grad_y * grad_y),
133 0.f); // v = max(var_w - (4/3)|grad L_mean|^2, 0): subtract the variance the local ramp explains
134 const float k_term
135 = sqf(k * fmaxf(scratch_lin[i], 1e-9f)); // (k * Lbar_mean)^2, the scale-free contrast threshold
136 const float edge_prob
137 = residual_var / (residual_var + k_term + 1e-18f); // m = v / (v + (k Lbar_mean)^2) in [0,1]
138
139 const float diffuse_tangent = edge_prob + (1.f - edge_prob) * cross_damp; // coeff of t t^T = m + (1-m) c2
140 const float diffuse_gradient = edge_prob * cross_damp + (1.f - edge_prob); // coeff of g g^T = m c2 + (1-m)
141
142 // D = diffuse_tangent * t t^T + diffuse_gradient * g g^T, stored as its symmetric xx/xy/yy entries
143 tensor_xx[i] = diffuse_tangent * isophote_x * isophote_x + diffuse_gradient * grad_unit_x * grad_unit_x;
144 tensor_xy[i] = diffuse_tangent * isophote_x * isophote_y + diffuse_gradient * grad_unit_x * grad_unit_y;
145 tensor_yy[i] = diffuse_tangent * isophote_y * isophote_y + diffuse_gradient * grad_unit_y * grad_unit_y;
146 }
147}
148
149// Coarse-to-fine harmonic fill of up to DT_HL_FILL_MAXP coefficient planes SHARING ONE anchor
150// mask: hole pixels relax toward their 4-neighbour average (Jacobi) with anchors pinned, each
151// pyramid level seeding the next finer one. Unconditionally stable by the maximum principle
152// (values stay within the anchors' range), unlike a float CG on the near-singular pure-harmonic
153// system, which diverges stochastically when the hole reaches the region border. Coefficients
154// are smooth, so the solve runs on a base grid downsampled by `base_ds` and is bilinearly
155// upsampled into the hole pixels.
156// With `steer` non-NULL (the coefficient planes), the relaxation is tensor-weighted instead
157// of uniform: per level, the variance-adaptive tensor is built from the downsampled steering
158// plane (_cf_adaptive_tensor) and the update becomes an 8-neighbour average with the Weickert
159// nonnegativity weights (_aniso_edge_w) -- all weights >= 0, so the fill stays a convex
160// combination of anchors (maximum principle intact). NULL steer = plain isotropic fill (the
161// rim-chrominance ratios, and any plane with no guide structure to follow).
162
163// One level's Jacobi relaxation of NP planes sharing one anchor mask, macro-generated so NP
164// is a compile-time literal: the plane guards fold away and the per-plane accumulators stay in
165// registers. (An inline function with a runtime plane count does NOT specialize -- GCC outlines
166// the OpenMP region and the count arrives through the shared-args struct, so the su[] array
167// spilled to the stack on every fma and the fused sweep measured 2.5x SLOWER than the
168// single-plane one. Literal NP recovers it.)
169//
170// Jacobi relaxation of the holes (anchors pinned): a flat 100-sweep budget per level.
171// Convergence is guaranteed by the pyramid depth of the caller, NOT by the sweep count --
172// boosting sweeps at the coarsest level instead was measured pathological (thousands of
173// parallel sweeps of microsecond work = pure scheduling overhead, seconds per fill on small
174// regions). One parallel region for the whole relaxation: launching a fresh team per sweep
175// was pure scheduling overhead on these small grids (the sweep's work is microseconds; 100
176// sweeps x levels x fills x regions reached tens of thousands of launches per image). Threads
177// ping-pong between u and tmp (no per-sweep memcpy); the even sweep count lands the final
178// solution in u. The omp-for barrier at the end of each sweep keeps Jacobi ordering.
179// All NP planes advance inside the same sweep: the weights are read once per cell.
180//
181// MATHS BRIDGE -- article "The algorithm" step 3, one Jacobi sweep of the E_transport solver
182// div(D grad p)=0 on the coefficient planes p in {a, b, d, R^2}. Discrete update rules (anchors are
183// Dirichlet boundary data, pinned = copied through unchanged):
184// steered (D != I): dst(i) = Sum_k w_ik * src(neighbour_k) / Sum_k w_ik over the 8-neighbour
185// Weickert nonnegativity stencil weights w_ik = _aniso_edge_w(D) >= 0, so the
186// update is a convex combination of neighbours -> maximum principle holds.
187// isotropic (D = I): dst(i) = 1/4 (north + south + west + east), the plain harmonic (Laplace) fill.
188// NOTE (C preprocessor): every comment inside this macro body MUST be a /* ... */ closed on its own
189// physical line before the trailing backslash -- a // comment would splice with the next line and
190// swallow the rest of the macro. That is why the annotations below use block-comment form.
191#define DEFINE_CF_FILL_RELAX(NP) \
192 __DT_CLONE_TARGETS__ \
193 static void _cf_fill_relax_##NP( \
194 float *const restrict field, float *const restrict tmp, const uint8_t *const restrict level_anchor, \
195 const float *const restrict edge_weights, const float *const restrict edge_weight_sum, const int coarse_w, \
196 const int coarse_h, const size_t cell_count, const int steered) \
197 { \
198 const int n_sweeps = 100; \
199 __OMP_PARALLEL__() \
200 for(int sweep = 0; sweep < n_sweeps; sweep++) \
201 { \
202 const float *const source = (sweep & 1) ? tmp : field; \
203 float *const dest = (sweep & 1) ? field : tmp; \
204 const float *const src0 = source; \
205 const float *const src1 = source + ((NP) > 1 ? cell_count : 0); \
206 const float *const src2 = source + ((NP) > 2 ? 2 * cell_count : 0); \
207 const float *const src3 = source + ((NP) > 3 ? 3 * cell_count : 0); \
208 float *const dst0 = dest; \
209 float *const dst1 = dest + ((NP) > 1 ? cell_count : 0); \
210 float *const dst2 = dest + ((NP) > 2 ? 2 * cell_count : 0); \
211 float *const dst3 = dest + ((NP) > 3 ? 3 * cell_count : 0); \
212 \
213 __OMP_FOR__(collapse(2)) \
214 for(int cell_y = 0; cell_y < coarse_h; cell_y++) \
215 for(int cell_x = 0; cell_x < coarse_w; cell_x++) \
216 { \
217 const size_t i = (size_t)cell_y * coarse_w + cell_x; \
218 \
219 /* anchor cell = Dirichlet boundary datum p|anchors = p_fit: copy it through unchanged */ \
220 if(level_anchor[i]) \
221 { \
222 dst0[i] = src0[i]; \
223 if((NP) > 1) dst1[i] = src1[i]; \
224 if((NP) > 2) dst2[i] = src2[i]; \
225 if((NP) > 3) dst3[i] = src3[i]; \
226 continue; \
227 } \
228 \
229 const size_t idx_north = (size_t)MAX(cell_y - 1, 0) * coarse_w + cell_x; \
230 const size_t idx_south = (size_t)MIN(cell_y + 1, coarse_h - 1) * coarse_w + cell_x; \
231 const size_t idx_west = (size_t)cell_y * coarse_w + MAX(cell_x - 1, 0); \
232 const size_t idx_east = (size_t)cell_y * coarse_w + MIN(cell_x + 1, coarse_w - 1); \
233 \
234 if(steered) \
235 { \
236 /* 8-neighbour Jacobi with the precomputed Weickert nonnegativity weights: every */ \
237 /* weight >= 0, so the update is a convex combination and the maximum principle */ \
238 /* is preserved. */ \
239 static const int neighbour_dy[8] = { 0, 0, -1, 1, -1, 1, 1, -1 }; \
240 static const int neighbour_dx[8] = { -1, 1, 0, 0, -1, 1, -1, 1 }; \
241 float accum0 = 0.f, accum1 = 0.f, accum2 = 0.f, accum3 = 0.f; \
242 for(int k = 0; k < 8; k++) \
243 { \
244 const int neighbour_y = CLAMP(cell_y + neighbour_dy[k], 0, coarse_h - 1); \
245 const int neighbour_x = CLAMP(cell_x + neighbour_dx[k], 0, coarse_w - 1); \
246 const size_t j = (size_t)neighbour_y * coarse_w + neighbour_x; \
247 const float weight = edge_weights[i * 8 + k]; \
248 accum0 += weight * src0[j]; \
249 if((NP) > 1) accum1 += weight * src1[j]; \
250 if((NP) > 2) accum2 += weight * src2[j]; \
251 if((NP) > 3) accum3 += weight * src3[j]; \
252 } \
253 /* dst = Sum_k w_ik src(nb_k) / Sum_k w_ik : the steered div(D grad p)=0 Jacobi update */ \
254 const float weight_sum = edge_weight_sum[i]; \
255 const int valid = (weight_sum > 1e-9f); \
256 dst0[i] = valid ? accum0 / weight_sum : src0[i]; \
257 if((NP) > 1) dst1[i] = valid ? accum1 / weight_sum : src1[i]; \
258 if((NP) > 2) dst2[i] = valid ? accum2 / weight_sum : src2[i]; \
259 if((NP) > 3) dst3[i] = valid ? accum3 / weight_sum : src3[i]; \
260 } \
261 /* D = I: plain 4-neighbour average, the discrete harmonic (Laplace) fill div(grad p)=0 */ \
262 else \
263 { \
264 dst0[i] = 0.25f * (src0[idx_north] + src0[idx_south] + src0[idx_west] + src0[idx_east]); \
265 if((NP) > 1) dst1[i] = 0.25f * (src1[idx_north] + src1[idx_south] + src1[idx_west] + src1[idx_east]); \
266 if((NP) > 2) dst2[i] = 0.25f * (src2[idx_north] + src2[idx_south] + src2[idx_west] + src2[idx_east]); \
267 if((NP) > 3) dst3[i] = 0.25f * (src3[idx_north] + src3[idx_south] + src3[idx_west] + src3[idx_east]); \
268 } \
269 } \
270 } \
271 }
272
277
278// MATHS BRIDGE -- article "The algorithm" step 3, the E_transport solver: the anchored, coarse-to-fine
279// anisotropic transport of the coefficient planes. Minimizes E_transport = Sum_p int grad(p)^T D grad(p)
280// with p|anchors = p_fit by relaxing div(D grad p)=0 to its fixed point. `hole` marks the cells to fill
281// (holes); its complement are the anchors (the gated colour-line fits, R^2 > 0.25, bounded slopes).
282// `steer` non-NULL feeds _cf_adaptive_tensor to build D (steered fill); NULL => D = I (plain harmonic
283// fill). Coefficients are smooth, so the whole relaxation runs on a base grid at pitch ~sigma/4
284// (article "Cell"), and Jacobi convergence comes from the PYRAMID DEPTH, not the fixed 100-sweep budget:
285// the coarsest level starts from a flat anchor mean and each finer level is bilinearly seeded from the
286// coarser solution, then corrected. Final result is bilinearly upsampled into the full-res hole pixels.
288static void _cf_harmonic_fill_n(float *const restrict *vals, const int n_planes_in,
289 const uint8_t *const restrict hole, const int region_w, const int region_h,
290 const int base_ds, const float *const restrict steer,
291 const dt_dev_pixelpipe_t *pipe)
292{
293 const int n_planes = CLAMP(n_planes_in, 1, DT_HL_FILL_MAXP);
294 const int downsample = CLAMP(base_ds, 1, 8);
295 const int base_w = (region_w + downsample - 1) / downsample;
296 const int base_h = (region_h + downsample - 1) / downsample;
297 const size_t cell_count = (size_t)base_w * base_h;
298
299 float *const restrict base_vals = dt_pixelpipe_cache_alloc_align_float(cell_count * n_planes, pipe);
300 uint8_t *const restrict base_anchor
301 = (uint8_t *)dt_pixelpipe_cache_alloc_align(sizeof(uint8_t) * cell_count, pipe);
302 // field, tmp, f (n_planes planes each, plane-major) + shared L
303 float *const restrict level_buffers
304 = dt_pixelpipe_cache_alloc_align_float(cell_count * 3 * (size_t)n_planes, pipe);
305 uint8_t *const restrict level_anchor
306 = (uint8_t *)dt_pixelpipe_cache_alloc_align(sizeof(uint8_t) * cell_count, pipe);
307 // aniso: base-grid steering plane + per-level {level_steer, tensor_xx, tensor_xy, tensor_yy, scratch,
308 // var-scratch}
309 // + per-cell edge weights (8, interleaved) + their sum, precomputed once per level
310 float *const restrict aniso_aux = steer ? dt_pixelpipe_cache_alloc_align_float(cell_count * 16, pipe) : NULL;
311 const int steered = (steer && aniso_aux) ? 1 : 0;
312
313 if(!base_vals || !base_anchor || !level_buffers || !level_anchor || (steer && !aniso_aux))
314 {
315 // fallback: fill the holes with the global anchor mean (never leave garbage coefficients)
316 for(int plane = 0; plane < n_planes; plane++)
317 {
318 float *const restrict plane_vals = vals[plane];
319 double anchor_sum = 0.0;
320 size_t anchor_count = 0;
321 for(size_t i = 0; i < (size_t)region_w * region_h; i++)
322 if(!hole[i])
323 {
324 anchor_sum += plane_vals[i];
325 anchor_count++;
326 }
327
328 const float anchor_mean = anchor_count ? (float)(anchor_sum / (double)anchor_count) : 0.f;
329 HL_PFOR()
330 for(size_t i = 0; i < (size_t)region_w * region_h; i++)
331 if(hole[i]) plane_vals[i] = anchor_mean;
332 }
333
336 dt_pixelpipe_cache_free_align(level_buffers);
337 dt_pixelpipe_cache_free_align(level_anchor);
339 return;
340 }
341
342 // aniso: steering plane on the base grid (plain cell mean; the tensor smooths later)
343 float *const restrict base_steer = steered ? aniso_aux + 5 * cell_count : NULL; // plane 6 = adaptive scratch
344 if(steered)
345 {
346 HL_PFOR(collapse(2))
347 for(int base_y = 0; base_y < base_h; base_y++)
348 for(int base_x = 0; base_x < base_w; base_x++)
349 {
350 double accum = 0.0;
351 int n_total = 0;
352 for(int y = base_y * downsample; y < MIN((base_y + 1) * downsample, region_h); y++)
353 for(int x = base_x * downsample; x < MIN((base_x + 1) * downsample, region_w); x++)
354 {
355 accum += steer[(size_t)y * region_w + x];
356 n_total++;
357 }
358 base_steer[(size_t)base_y * base_w + base_x] = (float)(accum / n_total);
359 }
360 }
361
362 // base grid: anchor-weighted mean per cell and per plane, anchor = cell majority (shared)
363 HL_PFOR(collapse(2))
364 for(int base_y = 0; base_y < base_h; base_y++)
365 for(int base_x = 0; base_x < base_w; base_x++)
366 {
367 double accum[DT_HL_FILL_MAXP] = { 0.0 };
368 int n_anchor = 0;
369 int n_total = 0;
370
371 for(int y = base_y * downsample; y < MIN((base_y + 1) * downsample, region_h); y++)
372 for(int x = base_x * downsample; x < MIN((base_x + 1) * downsample, region_w); x++)
373 {
374 const size_t i = (size_t)y * region_w + x;
375 n_total++;
376
377 if(!hole[i])
378 {
379 for(int plane = 0; plane < n_planes; plane++) accum[plane] += vals[plane][i];
380 n_anchor++;
381 }
382 }
383
384 const size_t cell_index = (size_t)base_y * base_w + base_x;
385 for(int plane = 0; plane < n_planes; plane++)
386 base_vals[plane * cell_count + cell_index] = n_anchor ? (float)(accum[plane] / n_anchor) : 0.f;
387 base_anchor[cell_index] = (2 * n_anchor > n_total);
388 }
389
390 // Pyramid depth (article step 3, "Convergence comes from the pyramid's depth"): the slowest Jacobi
391 // error mode on a hole N cells wide decays in O(N^2) sweeps, so the coarsest grid must be small.
392 // Halve until the LONG side is <= 8 cells. The coarsest level is seeded
393 // with a flat anchor mean, and Jacobi needs ~O(N^2) sweeps to relax a flat seed on a hole
394 // N cells wide -- so the coarsest grid must be small enough that the fixed per-level sweep
395 // budget genuinely converges it; every finer level then only corrects local interpolation
396 // error. (The previous short-side floor of 16 left elongated coarsest grids under-converged
397 // on deep holes: pk1synth -22% RMSE, occluded -13% once actually converged.)
398 int n_levels = 1;
399 while((MAX(base_w, base_h) >> n_levels) > 8 && n_levels < 12) n_levels++;
400
401 float *const restrict field = level_buffers + 0 * cell_count; // n_planes planes, stride cell_count
402 float *const restrict tmp = level_buffers + (size_t)n_planes * cell_count; // n_planes planes, stride cell_count
403 float *const restrict level_vals
404 = level_buffers + 2 * (size_t)n_planes * cell_count; // n_planes planes, stride cell_count
405
406 int prev_level_w = 0;
407 int prev_level_h = 0;
408
409 for(int level = n_levels - 1; level >= 0; level--)
410 {
411 const int step = 1 << level;
412 const int level_w = (base_w + step - 1) / step;
413 const int level_h = (base_h + step - 1) / step;
414
415 // downsample the base grid to this level (anchor-weighted mean + majority), into f/level_anchor
416 HL_PFOR(collapse(2))
417 for(int level_y = 0; level_y < level_h; level_y++)
418 for(int level_x = 0; level_x < level_w; level_x++)
419 {
420 double accum[DT_HL_FILL_MAXP] = { 0.0 };
421 int n_anchor = 0;
422 int n_total = 0;
423
424 for(int y = level_y * step; y < MIN((level_y + 1) * step, base_h); y++)
425 for(int x = level_x * step; x < MIN((level_x + 1) * step, base_w); x++)
426 {
427 const size_t i = (size_t)y * base_w + x;
428 n_total++;
429
430 if(base_anchor[i])
431 {
432 for(int plane = 0; plane < n_planes; plane++) accum[plane] += base_vals[plane * cell_count + i];
433 n_anchor++;
434 }
435 }
436
437 const size_t cell_index = (size_t)level_y * level_w + level_x;
438 for(int plane = 0; plane < n_planes; plane++)
439 level_vals[plane * cell_count + cell_index] = n_anchor ? (float)(accum[plane] / n_anchor) : 0.f;
440 level_anchor[cell_index] = (2 * n_anchor > n_total);
441 }
442
443 // aniso: level steering plane -> structure tensor (Weickert-stencil weights)
444 float *const restrict level_steer = steered ? aniso_aux + 0 * cell_count : NULL;
445 float *const restrict tensor_xx = steered ? aniso_aux + 1 * cell_count : NULL;
446 float *const restrict tensor_xy = steered ? aniso_aux + 2 * cell_count : NULL;
447 float *const restrict tensor_yy = steered ? aniso_aux + 3 * cell_count : NULL;
448
449 if(steered)
450 {
451 HL_PFOR(collapse(2))
452 for(int level_y = 0; level_y < level_h; level_y++)
453 for(int level_x = 0; level_x < level_w; level_x++)
454 {
455 double steer_sum = 0.0;
456 int n_total = 0;
457 for(int y = level_y * step; y < MIN((level_y + 1) * step, base_h); y++)
458 for(int x = level_x * step; x < MIN((level_x + 1) * step, base_w); x++)
459 {
460 steer_sum += base_steer[(size_t)y * base_w + x];
461 n_total++;
462 }
463 level_steer[(size_t)level_y * level_w + level_x] = (float)(steer_sum / n_total);
464 }
465
466 // build the steering tensor D at this pyramid level from the downsampled L_mean plane
467 _cf_adaptive_tensor(level_steer, tensor_xx, tensor_xy, tensor_yy, aniso_aux + 4 * cell_count,
468 aniso_aux + 6 * cell_count, level_w, level_h, DT_HL_CF_K);
469
470 // The Weickert edge weights are constant across every sweep of this level (the tensor is
471 // fixed): precompute the 8 weights per cell (interleaved) plus their sum once, so the
472 // Jacobi inner loop is a pure multiply-accumulate. Same values, same accumulation order
473 // as the previous inline computation -- the relaxation result is bit-identical.
474 float *const restrict edge_weights = aniso_aux + 7 * cell_count;
475 float *const restrict edge_weight_sum = aniso_aux + 15 * cell_count;
476 HL_PFOR(collapse(2))
477 for(int level_y = 0; level_y < level_h; level_y++)
478 for(int level_x = 0; level_x < level_w; level_x++)
479 {
480 static const int neighbour_dy[8] = { 0, 0, -1, 1, -1, 1, 1, -1 };
481 static const int neighbour_dx[8] = { -1, 1, 0, 0, -1, 1, -1, 1 };
482 const size_t i = (size_t)level_y * level_w + level_x;
483 float weight_sum = 0.f;
484 for(int k = 0; k < 8; k++)
485 {
486 const int neighbour_y = CLAMP(level_y + neighbour_dy[k], 0, level_h - 1);
487 const int neighbour_x = CLAMP(level_x + neighbour_dx[k], 0, level_w - 1);
488 const size_t cell_index = (size_t)neighbour_y * level_w + neighbour_x;
489 // w_ik: Weickert nonnegativity stencil weight for direction k, derived from D (>= 0)
490 const float weight
491 = _aniso_edge_w(tensor_xx, tensor_xy, tensor_yy, i, cell_index, neighbour_dx[k], neighbour_dy[k]);
492 edge_weights[i * 8 + k] = weight;
493 weight_sum += weight;
494 }
495 edge_weight_sum[i] = weight_sum;
496 }
497 }
498
499 if(level == n_levels - 1)
500 {
501 // coarsest: seed the holes with the level's anchor mean, per plane (the flat starting state,
502 // farthest from the solution; the pyramid depth guarantees Jacobi relaxes it within budget)
503 double anchor_sum[DT_HL_FILL_MAXP] = { 0.0 };
504 size_t anchor_count = 0;
505 for(size_t i = 0; i < (size_t)level_w * level_h; i++)
506 if(level_anchor[i])
507 {
508 for(int plane = 0; plane < n_planes; plane++) anchor_sum[plane] += level_vals[plane * cell_count + i];
509 anchor_count++;
510 }
511
512 float anchor_mean[DT_HL_FILL_MAXP];
513 for(int plane = 0; plane < n_planes; plane++)
514 anchor_mean[plane] = anchor_count ? (float)(anchor_sum[plane] / (double)anchor_count) : 0.f;
515 HL_PFOR()
516 for(size_t i = 0; i < (size_t)level_w * level_h; i++)
517 for(int plane = 0; plane < n_planes; plane++)
518 tmp[plane * cell_count + i] = level_anchor[i] ? level_vals[plane * cell_count + i] : anchor_mean[plane];
519 }
520 else
521 {
522 // seed the holes from the coarser solution (bilinear), anchors from this level's means
523 HL_PFOR(collapse(2))
524 for(int level_y = 0; level_y < level_h; level_y++)
525 for(int level_x = 0; level_x < level_w; level_x++)
526 {
527 const size_t i = (size_t)level_y * level_w + level_x;
528
529 if(level_anchor[i])
530 {
531 for(int plane = 0; plane < n_planes; plane++)
532 tmp[plane * cell_count + i] = level_vals[plane * cell_count + i];
533 continue;
534 }
535
536 const float grid_x = ((float)level_x + 0.5f) * 0.5f - 0.5f;
537 const float grid_y = ((float)level_y + 0.5f) * 0.5f - 0.5f;
538 const int x_lo = CLAMP((int)floorf(grid_x), 0, prev_level_w - 1);
539 const int y_lo = CLAMP((int)floorf(grid_y), 0, prev_level_h - 1);
540 const int x_hi = MIN(x_lo + 1, prev_level_w - 1);
541 const int y_hi = MIN(y_lo + 1, prev_level_h - 1);
542 const float frac_x = CLAMP(grid_x - x_lo, 0.f, 1.f);
543 const float frac_y = CLAMP(grid_y - y_lo, 0.f, 1.f);
544 for(int plane = 0; plane < n_planes; plane++)
545 {
546 const float *const plane_field = field + plane * cell_count;
547 const float interp_top = plane_field[(size_t)y_lo * prev_level_w + x_lo] * (1.f - frac_x)
548 + plane_field[(size_t)y_lo * prev_level_w + x_hi] * frac_x;
549 const float interp_bottom = plane_field[(size_t)y_hi * prev_level_w + x_lo] * (1.f - frac_x)
550 + plane_field[(size_t)y_hi * prev_level_w + x_hi] * frac_x;
551 tmp[plane * cell_count + i] = interp_top * (1.f - frac_y) + interp_bottom * frac_y;
552 }
553 }
554 }
555
556 for(int plane = 0; plane < n_planes; plane++)
557 memcpy(field + plane * cell_count, tmp + plane * cell_count, (size_t)level_w * level_h * sizeof(float));
558
559 // relaxation: iterate the div(D grad p)=0 Jacobi update to its fixed point on this level
560 // (specialized on the plane count, see DEFINE_CF_FILL_RELAX)
561 {
562 const float *const restrict edge_weights = steered ? aniso_aux + 7 * cell_count : NULL;
563 const float *const restrict edge_weight_sum = steered ? aniso_aux + 15 * cell_count : NULL;
564 switch(n_planes)
565 {
566 case 1:
567 _cf_fill_relax_1(field, tmp, level_anchor, edge_weights, edge_weight_sum, level_w, level_h, cell_count,
568 steered);
569 break;
570 case 2:
571 _cf_fill_relax_2(field, tmp, level_anchor, edge_weights, edge_weight_sum, level_w, level_h, cell_count,
572 steered);
573 break;
574 case 3:
575 _cf_fill_relax_3(field, tmp, level_anchor, edge_weights, edge_weight_sum, level_w, level_h, cell_count,
576 steered);
577 break;
578 default:
579 _cf_fill_relax_4(field, tmp, level_anchor, edge_weights, edge_weight_sum, level_w, level_h, cell_count,
580 steered);
581 break;
582 }
583 }
584
585 prev_level_w = level_w;
586 prev_level_h = level_h;
587 }
588
589 // upsample the base-grid coefficient solution into the full-res hole pixels by bilinear interp
590 // (anchors keep their exact fitted values -- the Dirichlet data is never overwritten)
591 HL_PFOR(collapse(2))
592 for(int y = 0; y < region_h; y++)
593 for(int x = 0; x < region_w; x++)
594 {
595 const size_t i = (size_t)y * region_w + x;
596
597 if(!hole[i]) continue;
598
599 const float grid_x = ((float)x + 0.5f) / downsample - 0.5f;
600 const float grid_y = ((float)y + 0.5f) / downsample - 0.5f;
601 const int x_lo = CLAMP((int)floorf(grid_x), 0, base_w - 1);
602 const int y_lo = CLAMP((int)floorf(grid_y), 0, base_h - 1);
603 const int x_hi = MIN(x_lo + 1, base_w - 1);
604 const int y_hi = MIN(y_lo + 1, base_h - 1);
605 const float frac_x = CLAMP(grid_x - x_lo, 0.f, 1.f);
606 const float frac_y = CLAMP(grid_y - y_lo, 0.f, 1.f);
607 for(int plane = 0; plane < n_planes; plane++)
608 {
609 const float *const plane_field = field + plane * cell_count;
610 const float interp_top = plane_field[(size_t)y_lo * base_w + x_lo] * (1.f - frac_x)
611 + plane_field[(size_t)y_lo * base_w + x_hi] * frac_x;
612 const float interp_bottom = plane_field[(size_t)y_hi * base_w + x_lo] * (1.f - frac_x)
613 + plane_field[(size_t)y_hi * base_w + x_hi] * frac_x;
614 vals[plane][i] = interp_top * (1.f - frac_y) + interp_bottom * frac_y;
615 }
616 }
617
620 dt_pixelpipe_cache_free_align(level_buffers);
621 dt_pixelpipe_cache_free_align(level_anchor);
623}
624
625void _cf_harmonic_fill(float *const restrict val, const uint8_t *const restrict hole, const int region_w,
626 const int region_h, const int base_ds, const float *const restrict steer,
627 const dt_dev_pixelpipe_t *pipe)
628{
629 float *plane_ptrs[1] = { val };
630 _cf_harmonic_fill_n((float *const restrict *)plane_ptrs, 1, hole, region_w, region_h, base_ds, steer, pipe);
631}
632
635{
636 const _hl_region_t *const region = ctx->region;
637 const dt_dev_pixelpipe_t *const pipe = ctx->pipe;
638 const int region_w = ctx->region_w;
639 const int region_h = ctx->region_h;
640 const size_t region_pixels = ctx->region_pixels;
641 float *const restrict estimate = ctx->estimate;
642 float *const restrict prev_scale = ctx->prev_scale;
643 float *const restrict valid = ctx->valid;
644 float *const restrict blur_in = ctx->blur_in;
645 float *const restrict plane1 = ctx->plane1;
646 float *const restrict plane2 = ctx->plane2;
647 float *const restrict plane3 = ctx->plane3;
648 float *const restrict valid_variance = ctx->valid_variance;
649 float *const restrict guide_score = ctx->guide_score;
650 float *const restrict clip_depth = ctx->clip_depth;
651 float *const restrict clip0 = ctx->clip0;
652 uint8_t *const restrict hole = ctx->hole;
653 float *const restrict solver_field = ctx->solver_field;
654 float *const restrict fill_planes = ctx->fill_planes;
655 float *const restrict dome_lum = ctx->dome_lum;
656 float *const restrict lum_accum = ctx->lum_accum;
657 float *const restrict reaction_weight = ctx->reaction_weight;
658 float *const restrict flat_target = ctx->flat_target;
659
660 const float cf_sigma
661 = CLAMP(region->radius / 6.f, 8.f, 64.f); // sigma = clip(r/6, 8, 64): +/-3 sigma window reaches the deepest
662 // pixel; floor/cap bound samples/cost
663 const float cf_fmin = 0.05f;
664
665 // region luminance + the blown zone's plateau level, for the occlusion-aware fills
666 HL_PFOR()
667 for(size_t i = 0; i < region_pixels; i++)
668 lum_accum[i] = estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
669
670 double laccum = 0.0;
671 size_t lcnt = 0;
672 HL_PFOR(reduction(+ : laccum, lcnt))
673 for(size_t i = 0; i < region_pixels; i++)
674 if(valid[i * 4 + 0] < 0.5f || valid[i * 4 + 1] < 0.5f || valid[i * 4 + 2] < 0.5f)
675 {
676 laccum += lum_accum[i];
677 lcnt++;
678 }
679
680 const float cf_lref = lcnt ? (float)(laccum / (double)lcnt) : 0.f;
681
682 // Steering plane for the coefficient fills = the measured guide structure.
683 // Mean of the VALID channels where at least one survives (real data inside the
684 // partial-clip zone); the flat plateau mean elsewhere (all-clip core), where a flat
685 // steer degenerates the tensor to identity, i.e. back to the isotropic fill.
686 float *const restrict steer = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
687 if(steer)
688 {
689 HL_PFOR()
690 for(size_t i = 0; i < region_pixels; i++)
691 {
692 float accum = 0.f;
693 int n_valid = 0;
694 for(int c = 0; c < 3; c++)
695 if(valid[i * 4 + c] >= 0.5f)
696 {
697 accum += estimate[i * 4 + c];
698 n_valid++;
699 }
700 steer[i] = n_valid ? accum / n_valid : lum_accum[i] / 3.f;
701 }
702 }
703
704 // Per-channel means of the VALID values: the moment packs below are CENTERED on them.
705 // var = E[u^2] - E[u]^2 in float32 on a smooth plane cancels catastrophically (the mean
706 // squared dwarfs the variance, ~4 digits lost) and the fit's cov/var division amplifies
707 // the surviving noise -- measured as a device-dependent slope error growing with depth.
708 // Centering the packs makes the blurred moments carry the (co)variances directly ; the
709 // slopes and R^2 are shift-invariant, and the intercept is unshifted right after the fit.
710 double maccum[3] = { 0.0, 0.0, 0.0 };
711 size_t mcnt[3] = { 0, 0, 0 };
712 HL_PFOR(reduction(+ : maccum[:3], mcnt[:3]))
713 for(size_t i = 0; i < region_pixels; i++)
714 for(int c = 0; c < 3; c++)
715 if(valid[i * 4 + c] >= 0.5f)
716 {
717 maccum[c] += estimate[i * 4 + c];
718 mcnt[c]++;
719 }
720 const float channel_means[3]
721 = { mcnt[0] ? (float)(maccum[0] / mcnt[0]) : 0.f, mcnt[1] ? (float)(maccum[1] / mcnt[1]) : 0.f,
722 mcnt[2] ? (float)(maccum[2] / mcnt[2]) : 0.f };
723
724 // Soft luminance affinity for the FIT WINDOWS: pixels much darker than the blown zone's
725 // plateau contribute ~nothing to the windowed moments, so a window straddling a dark
726 // occluder and the sky fits the SKY's colour-line instead of a poisoned mixture. Content
727 // brighter than ~a third of the plateau keeps full weight, so unoccluded scenes are
728 // untouched by construction.
729 const float cf_binv = (cf_lref > 1e-9f) ? 1.f / (0.35f * cf_lref) : 0.f;
730
731 // broad-anchor mask for the model-quality plane (bounded even where the fit degenerates)
732 uint8_t *const restrict hole2
733 = (uint8_t *)dt_pixelpipe_cache_alloc_align(sizeof(uint8_t) * region_pixels, ctx->pipe);
734
735 // bsc collects the DIFFUSED fit quality (R^2) per channel: the weight of the
736 // high-frequency damping and of the depth-gated self-dome blend below. It is spatially
737 // smooth (windowed moments + harmonic fill), so neither conaccumer introduces a hand-off.
738 HL_PFOR()
739 for(size_t i = 0; i < region_pixels; i++)
740 for(int c = 0; c < 4; c++) guide_score[i * 4 + c] = 0.f;
741
742 // The TEN blurred moment planes of the fit (article step 3: "solved from ten blurred moment
743 // planes ... through the 2x2 normal equations"). Each _region_blur below IS the windowed weighted
744 // sum Sum_y w(y) G_sigma(x-y) (.) : packing the per-pixel product then Gaussian-blurring gives the
745 // windowed moment at x. w(y) = [all three channels valid] * lum_weight (the soft occlusion weight).
746 // Moment 1 of 3 (blur -> prev_scale): the mass count n and the 3 centred means.
747 // joint windowed moments, weight = all three channels valid at the pixel, packed as
748 // prev = [n, wR, wG, wB], s1 = [wRR, wGG, wBB, wRG], s3 = [wRB, wGB, 0, 0]
749 HL_PFOR()
750 for(size_t i = 0; i < region_pixels; i++)
751 {
752 const float lum_weight = (cf_binv > 0.f) ? sqf(fminf(lum_accum[i] * cf_binv, 1.f)) : 1.f;
753 const float weight
754 = (valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f) ? lum_weight : 0.f;
755 blur_in[i * 4 + 0] = weight; // Sum w -> n (trusted mass)
756 blur_in[i * 4 + 1] = weight * (estimate[i * 4 + 0] - channel_means[0]); // Sum w*(R-Rbar) -> centred mean of R
757 blur_in[i * 4 + 2] = weight * (estimate[i * 4 + 1] - channel_means[1]); // Sum w*(G-Gbar) -> centred mean of G
758 blur_in[i * 4 + 3] = weight * (estimate[i * 4 + 2] - channel_means[2]); // Sum w*(B-Bbar) -> centred mean of B
759 }
760
761 _region_blur(blur_in, prev_scale, region_w, region_h, cf_sigma);
762
763 HL_PFOR()
764 for(size_t i = 0; i < region_pixels; i++)
765 {
766 const float lum_weight = (cf_binv > 0.f) ? sqf(fminf(lum_accum[i] * cf_binv, 1.f)) : 1.f;
767 const float weight
768 = (valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f) ? lum_weight : 0.f;
769 // Moment 2 of 3 (blur -> plane1): four of the six centred second moments (products of x-xbar)
770 const float val_r
771 = estimate[i * 4 + 0] - channel_means[0]; // R - Rbar (centred, avoids the E[u^2]-E[u]^2 cancellation)
772 const float val_g = estimate[i * 4 + 1] - channel_means[1]; // G - Gbar
773 const float val_b = estimate[i * 4 + 2] - channel_means[2]; // B - Bbar
774 blur_in[i * 4 + 0] = weight * val_r * val_r; // -> E[(R-Rbar)^2] = Var(R)
775 blur_in[i * 4 + 1] = weight * val_g * val_g; // -> Var(G)
776 blur_in[i * 4 + 2] = weight * val_b * val_b; // -> Var(B)
777 blur_in[i * 4 + 3] = weight * val_r * val_g; // -> Cov(R,G)
778 }
779
780 _region_blur(blur_in, plane1, region_w, region_h, cf_sigma);
781
782 HL_PFOR()
783 for(size_t i = 0; i < region_pixels; i++)
784 {
785 const float lum_weight = (cf_binv > 0.f) ? sqf(fminf(lum_accum[i] * cf_binv, 1.f)) : 1.f;
786 const float weight
787 = (valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f) ? lum_weight : 0.f;
788 // Moment 3 of 3 (blur -> plane3): the last two centred second moments + the unweighted mass
789 blur_in[i * 4 + 0] = weight * (estimate[i * 4 + 0] - channel_means[0])
790 * (estimate[i * 4 + 2] - channel_means[2]); // -> Cov(R,B)
791 blur_in[i * 4 + 1] = weight * (estimate[i * 4 + 1] - channel_means[1])
792 * (estimate[i * 4 + 2] - channel_means[2]); // -> Cov(G,B)
793 blur_in[i * 4 + 2] = (valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f)
794 ? 1.f
795 : 0.f; // UNWEIGHTED valid mass: anchors must exist at the rim
796 blur_in[i * 4 + 3] = 0.f;
797 }
798
799 _region_blur(blur_in, plane3, region_w, region_h, cf_sigma);
800
801 // second-moment plane lookup: diag (c,c) -> s1 slot c; off-diag (a,b) -> slot 2+a+b,
802 // where slots 3 = RG (s1), 4 = RB (s3[0]), 5 = GB (s3[1])
803 // CF_M2(i, a, b) returns the (unnormalized) windowed sum Sum w*(u_a-ubar_a)(u_b-ubar_b) at pixel i,
804 // i.e. n*Cov(u_a,u_b) once divided by the mass n -- the raw material of the normal matrix Sigma.
805#define CF_M2(nb_index, coef_a, coef_b) \
806 (((coef_a) == (coef_b)) ? plane1[(nb_index) * 4 + (coef_a)] \
807 : ((2 + (coef_a) + (coef_b)) < 4 ? plane1[(nb_index) * 4 + 2 + (coef_a) + (coef_b)] \
808 : plane3[(nb_index) * 4 + (coef_a) + (coef_b) - 2]))
809
810 // The DEEP channel (most clipped pixels: its zone contains the multi-clip cores) is not
811 // evaluated here -- its diffused coefficients are STASHED and evaluated after the pair
812 // fallbacks have reconstructed the other clipped channels, so its joint model reads
813 // CONTINUOUS guides everywhere. Evaluating it against a guide that jumps from measured
814 // to clip-plateau at the guide's own clip contour printed that contour as an arc.
815 size_t nclip_c[3] = { 0, 0, 0 };
816 for(size_t i = 0; i < region_pixels; i++)
817 for(int c = 0; c < 3; c++)
818 if(valid[i * 4 + c] < 0.5f) nclip_c[c]++;
819
820 // cdeep = the channel with the most clipped pixels (its zone holds the multi-clip cores)
821 const int cdeep
822 = (nclip_c[0] >= nclip_c[1] && nclip_c[0] >= nclip_c[2]) ? 0 : ((nclip_c[1] >= nclip_c[2]) ? 1 : 2);
823 int deep_stashed = 0;
824
825 // ---- per channel: joint 2-guide coefficients, harmonic diffusion, evaluation ----
826 for(int c = 0; c < 3; c++)
827 {
828 // guide-pair selection: predict clipped channel v = c from its two OTHER channels u1=guide1, u2=guide2
829 const int guide1 = (c == 0) ? 1 : 0;
830 const int guide2 = (c == 2) ? 1 : 2;
831
832 size_t ntarget = 0;
833 HL_PFOR(reduction(+ : ntarget))
834 for(size_t i = 0; i < region_pixels; i++)
835 if(valid[i * 4 + c] < 0.5f && (valid[i * 4 + guide1] >= 0.5f || valid[i * 4 + guide2] >= 0.5f)) ntarget++;
836
837 if(ntarget == 0) continue;
838
839 // NOTE the ntarget gate accepts ONE surviving guide so the DEEP channel is fitted
840 // (and stashed) even when its zone has no strict two-guide pixel; the immediate
841 // evaluation below stays strict.
842
843 // coefficients (a, b, d) from the windowed moments at every pixel (garbage where the
844 // window held no trusted mass -- replaced by the diffusion); anchor = trusted window
845 // Solve the 2x2 normal equations of the weighted least squares at every pixel (article step 3):
846 // Sigma [a;b] = [Cov(u1,v); Cov(u2,v)], Sigma = [[Var u1, Cov(u1,u2)],[Cov(u1,u2), Var u2]]
847 // via Cramer's rule, with relative Tikhonov ridge lambda added to the diagonal.
848 HL_PFOR()
849 for(size_t i = 0; i < region_pixels; i++)
850 {
851 const float norm = fmaxf(prev_scale[i * 4 + 0], 1e-9f); // n = windowed trusted mass
852 const float inv_det = 1.f / norm; // 1/n, turns the summed moments into expectations
853 const float mean1 = prev_scale[i * 4 + 1 + guide1] * inv_det; // E[u1] (of the centred pack)
854 const float mean2 = prev_scale[i * 4 + 1 + guide2] * inv_det; // E[u2]
855 const float mean_target = prev_scale[i * 4 + 1 + c] * inv_det; // E[v]
856 const float var11
857 = fmaxf(CF_M2(i, guide1, guide1) * inv_det - mean1 * mean1, 0.f); // Var(u1) = E[u1^2]-E[u1]^2
858 const float var22 = fmaxf(CF_M2(i, guide2, guide2) * inv_det - mean2 * mean2, 0.f); // Var(u2)
859 const float var12 = CF_M2(i, guide1, guide2) * inv_det - mean1 * mean2; // Cov(u1,u2)
860 const float cov_tg1 = CF_M2(i, c, guide1) * inv_det - mean_target * mean1; // Cov(v,u1) = RHS_1
861 const float cov_tg2 = CF_M2(i, c, guide2) * inv_det - mean_target * mean2; // Cov(v,u2) = RHS_2
862
863 const float var_target
864 = fmaxf(CF_M2(i, c, c) * inv_det - mean_target * mean_target, 0.f); // Var(v), denom of R^2
865
866 // relative Tikhonov: scales with the signal, never eats a weak-but-real slope
867 const float lambda = 1e-3f * 0.5f * (var11 + var22) + 1e-12f; // ridge = 1e-3 * (Var u1 + Var u2)/2
868 const float determinant
869 = fmaxf((var11 + lambda) * (var22 + lambda) - var12 * var12, 1e-18f); // det Sigma (with ridge)
870 const float slope_a
871 = ((var22 + lambda) * cov_tg1 - var12 * cov_tg2) / determinant; // a = (Sigma^-1 RHS)_1 (Cramer)
872 const float slope_b
873 = ((var11 + lambda) * cov_tg2 - var12 * cov_tg1) / determinant; // b = (Sigma^-1 RHS)_2 (Cramer)
874 const float r_sq = CLAMP((slope_a * cov_tg1 + slope_b * cov_tg2) / (var_target + 1e-12f), 0.f,
875 1.f); // R^2 = (a Cov(v,u1)+b Cov(v,u2)) / Var(v) = explained/total
876
877 valid_variance[i * 4 + 0] = slope_a;
878 valid_variance[i * 4 + 1] = slope_b;
879 // intercept of the CENTERED fit, unshifted back to absolute values: d = E[v] - a E[u1] - b E[u2]
880 valid_variance[i * 4 + 2] = (mean_target + channel_means[c]) - slope_a * (mean1 + channel_means[guide1])
881 - slope_b * (mean2 + channel_means[guide2]);
882 valid_variance[i * 4 + 3] = r_sq;
883
884 // anchor = trusted window AND a sane fit: degenerate (near-zero-variance) windows
885 // produce exploding slopes that would poison the diffusion boundary
886 // anchors EXIST wherever enough valid pixels are in reach (continuity at the rim
887 // needs locally-exact fits there), and their weighted fits are bright-content-pure;
888 // windows that are MOSTLY dark (weighted mass a small fraction of the valid mass)
889 // describe unrelated content and must not anchor
890 const int mass_ok = (plane3[i * 4 + 2] > cf_fmin && prev_scale[i * 4 + 0] > 0.25f * plane3[i * 4 + 2]);
891 // anchor gate (article: R^2 > 0.25 with bounded slopes) -> the Dirichlet data for E_transport.
892 // hole = NOT an anchor (the cell to be filled by the transport); |a|,|b| < 64 rejects only
893 // degenerate near-zero-variance windows whose exploding slopes would poison the fill boundary.
894 hole[i] = !(mass_ok && valid[i * 4 + c] >= 0.5f && r_sq > 0.25f && fabsf(slope_a) < 64.f
895 && fabsf(slope_b) < 64.f);
896 if(hole2)
897 hole2[i] = !(mass_ok && valid[i * 4 + c] >= 0.5f); // broader (mass-only) anchor set for the R^2 plane
898 }
899
900 // harmonic diffusion of each coefficient field into the non-anchor area (stable
901 // coarse-to-fine Jacobi fill; base grid at ~sigma/4 since coefficients are smooth).
902 // a/b/d share the anchor mask, so they ride ONE fused fill (one mask pyramid, one
903 // tensor, one sweep pass); r2 may use its own broader mask and fills alone.
904 {
905 HL_PFOR()
906 for(size_t i = 0; i < region_pixels; i++)
907 {
908 fill_planes[i] = valid_variance[i * 4 + 0];
909 fill_planes[region_pixels + i] = valid_variance[i * 4 + 1];
910 fill_planes[2 * region_pixels + i] = valid_variance[i * 4 + 2];
911 solver_field[i] = valid_variance[i * 4 + 3];
912 }
913
914 // E_transport on p in {a, b, d}: anchored anisotropic fill, base grid pitch ~sigma/4 (article "Cell")
915 float *planes[3] = { fill_planes, fill_planes + region_pixels, fill_planes + 2 * region_pixels };
916 _cf_harmonic_fill_n((float *const restrict *)planes, 3, hole, region_w, region_h, (int)(cf_sigma / 4.f),
917 steer, pipe);
918 // the R^2 plane is diffused too (article: "R^2 is diffused alongside (a,b,d) as a fourth plane"),
919 // on the broader mass-only anchor set so it stays bounded even where the fit degenerates
920 _cf_harmonic_fill(solver_field, hole2 ? hole2 : hole, region_w, region_h, (int)(cf_sigma / 4.f), steer, pipe);
921
922 HL_PFOR()
923 for(size_t i = 0; i < region_pixels; i++)
924 {
925 valid_variance[i * 4 + 0] = fill_planes[i];
926 valid_variance[i * 4 + 1] = fill_planes[region_pixels + i];
927 valid_variance[i * 4 + 2] = fill_planes[2 * region_pixels + i];
928 valid_variance[i * 4 + 3] = solver_field[i];
929 }
930 }
931
932 // evaluate against the measured guides at every joint target pixel, keeping the
933 // diffused fit R^2 (in-sample R^2 is the honest quality signal: decorrelated content
934 // simply has no colour-line and scores 0.25..0.6 against ~0.9 for correlated content)
935 if(c == cdeep)
936 {
937 // stash the diffused fields (dbuf/tbuf/ldb/bsc slot 3 are free until the HF and
938 // dome stages); evaluated after the pair fallbacks below
939 HL_PFOR()
940 for(size_t i = 0; i < region_pixels; i++)
941 {
942 reaction_weight[i] = valid_variance[i * 4 + 0];
943 flat_target[i] = valid_variance[i * 4 + 1];
944 dome_lum[i] = valid_variance[i * 4 + 2];
945 guide_score[i * 4 + 3] = valid_variance[i * 4 + 3];
946 }
947 deep_stashed = 1;
948 continue;
949 }
950
951 // strict two-guide gate: extending this evaluation into the multi-clip band (with the
952 // clipped guide at its plateau) was tried and regressed the correlated synthetics --
953 // continuity there is the deep channel's deferred evaluation's job, and the non-deep
954 // channels' pair fits are locally anchored at their own fences anyway
955 HL_PFOR()
956 for(size_t i = 0; i < region_pixels; i++)
957 if(valid[i * 4 + c] < 0.5f && valid[i * 4 + guide1] >= 0.5f && valid[i * 4 + guide2] >= 0.5f)
958 {
959 // evaluation v_hat = a*u1 + b*u2 + d against the MEASURED guides (diffused a,b,d; true u1,u2)
960 estimate[i * 4 + c] = valid_variance[i * 4 + 0] * estimate[i * 4 + guide1]
961 + valid_variance[i * 4 + 1] * estimate[i * 4 + guide2] + valid_variance[i * 4 + 2];
962 guide_score[i * 4 + c]
963 = CLAMP(valid_variance[i * 4 + 3], 0.f, 1.f); // carry the diffused R^2 as the model quality
964 }
965 }
966
967#undef CF_M2
968
969 // ---- single-guide fallback for 2-clip pixels (target + one other channel clipped) ----
970 // Article step 3: "Pixels with a single surviving guide get the same treatment with a one-guide
971 // fit." Same fit+transport+evaluate, but the model collapses to v_hat = a*u + d (one guide u):
972 // a = Cov(u,v)/Var(u) (1x1 normal equation), R^2 = Cov(u,v)^2 / (Var(u) Var(v)) = squared correlation.
973 size_t n2clip = 0;
974 HL_PFOR(reduction(+ : n2clip))
975 for(size_t i = 0; i < region_pixels; i++)
976 {
977 const int n_valid = (valid[i * 4 + 0] >= 0.5f) + (valid[i * 4 + 1] >= 0.5f) + (valid[i * 4 + 2] >= 0.5f);
978 if(n_valid == 1) n2clip++;
979 }
980
981 if(n2clip > 0)
982 for(int chan_a = 0; chan_a < 3; chan_a++)
983 for(int chan_b = chan_a + 1; chan_b < 3; chan_b++)
984 {
985 // pair moments, weight = both channels of the pair valid, packed as
986 // s2 = [n, wa, wb, waa], s3 = [wbb, wab, unweighted n, 0]
987 HL_PFOR()
988 for(size_t i = 0; i < region_pixels; i++)
989 {
990 const float lum_weight = (cf_binv > 0.f) ? sqf(fminf(lum_accum[i] * cf_binv, 1.f)) : 1.f;
991 const float weight = (valid[i * 4 + chan_a] >= 0.5f && valid[i * 4 + chan_b] >= 0.5f) ? lum_weight : 0.f;
992 const float var_a = estimate[i * 4 + chan_a] - channel_means[chan_a];
993 const float var_b = estimate[i * 4 + chan_b] - channel_means[chan_b];
994 blur_in[i * 4 + 0] = weight;
995 blur_in[i * 4 + 1] = weight * var_a;
996 blur_in[i * 4 + 2] = weight * var_b;
997 blur_in[i * 4 + 3] = weight * var_a * var_a;
998 }
999
1000 _region_blur(blur_in, plane2, region_w, region_h, cf_sigma);
1001
1002 HL_PFOR()
1003 for(size_t i = 0; i < region_pixels; i++)
1004 {
1005 const float lum_weight = (cf_binv > 0.f) ? sqf(fminf(lum_accum[i] * cf_binv, 1.f)) : 1.f;
1006 const float weight = (valid[i * 4 + chan_a] >= 0.5f && valid[i * 4 + chan_b] >= 0.5f) ? lum_weight : 0.f;
1007 const float var_a = estimate[i * 4 + chan_a] - channel_means[chan_a];
1008 const float var_b = estimate[i * 4 + chan_b] - channel_means[chan_b];
1009 blur_in[i * 4 + 0] = weight * var_b * var_b;
1010 blur_in[i * 4 + 1] = weight * var_a * var_b;
1011 blur_in[i * 4 + 2] = (valid[i * 4 + chan_a] >= 0.5f && valid[i * 4 + chan_b] >= 0.5f) ? 1.f : 0.f;
1012 blur_in[i * 4 + 3] = 0.f;
1013 }
1014
1015 _region_blur(blur_in, plane3, region_w, region_h, cf_sigma);
1016
1017 // both orientations: predict a from b, then b from a
1018 for(int orient = 0; orient < 2; orient++)
1019 {
1020 const int target_chan = orient ? chan_b : chan_a; // target channel
1021 const int guide_chan = orient ? chan_a : chan_b; // guide channel
1022 const int other_chan = 3 - chan_a - chan_b; // the third channel, must be clipped at the target
1023
1024 size_t ntarget = 0;
1025 HL_PFOR(reduction(+ : ntarget))
1026 for(size_t i = 0; i < region_pixels; i++)
1027 if(valid[i * 4 + target_chan] < 0.5f && valid[i * 4 + guide_chan] >= 0.5f
1028 && valid[i * 4 + other_chan] < 0.5f)
1029 ntarget++;
1030
1031 if(ntarget == 0) continue;
1032
1033 HL_PFOR()
1034 for(size_t i = 0; i < region_pixels; i++)
1035 {
1036 const float norm = fmaxf(plane2[i * 4 + 0], 1e-9f);
1037 const float inv_det = 1.f / norm;
1038 const float pair_mean_target = plane2[i * 4 + (orient ? 2 : 1)] * inv_det;
1039 const float mean_guide = plane2[i * 4 + (orient ? 1 : 2)] * inv_det;
1040 const float var_guide
1041 = fmaxf((orient ? plane2[i * 4 + 3] : plane3[i * 4 + 0]) * inv_det - mean_guide * mean_guide,
1042 0.f); // Var(u) (guide)
1043 const float var_t = fmaxf((orient ? plane3[i * 4 + 0] : plane2[i * 4 + 3]) * inv_det
1044 - pair_mean_target * pair_mean_target,
1045 0.f); // Var(v) (target), denom of R^2
1046 const float covariance = plane3[i * 4 + 1] * inv_det - pair_mean_target * mean_guide; // Cov(u,v)
1047 const float slope_a
1048 = covariance / (var_guide * (1.f + 1e-3f) + 1e-12f); // a = Cov(u,v)/Var(u), 1e-3 relative ridge
1049 const float r_sq = CLAMP(covariance * covariance / (var_guide * var_t + 1e-18f), 0.f,
1050 1.f); // R^2 = Cov^2/(Var u Var v)
1051
1052 valid_variance[i * 4 + 0] = slope_a;
1053 // intercept of the CENTERED fit, unshifted back to absolute values: d = E[v] - a E[u]
1054 valid_variance[i * 4 + 1] = (pair_mean_target + channel_means[target_chan])
1055 - slope_a * (mean_guide + channel_means[guide_chan]);
1056 valid_variance[i * 4 + 2] = r_sq;
1057 const int mass_ok = (plane3[i * 4 + 2] > cf_fmin && plane2[i * 4 + 0] > 0.25f * plane3[i * 4 + 2]);
1058 hole[i] = !(mass_ok && valid[i * 4 + target_chan] >= 0.5f && r_sq > 0.25f && fabsf(slope_a) < 64.f);
1059 if(hole2) hole2[i] = !(mass_ok && valid[i * 4 + target_chan] >= 0.5f);
1060 }
1061
1062 // slope and intercept share the anchor mask -> one fused fill; r2 may use its
1063 // own broader mask and fills alone
1064 {
1065 HL_PFOR()
1066 for(size_t i = 0; i < region_pixels; i++)
1067 {
1068 fill_planes[i] = valid_variance[i * 4 + 0];
1069 fill_planes[region_pixels + i] = valid_variance[i * 4 + 1];
1070 solver_field[i] = valid_variance[i * 4 + 2];
1071 }
1072
1073 float *planes[2] = { fill_planes, fill_planes + region_pixels };
1074 _cf_harmonic_fill_n((float *const restrict *)planes, 2, hole, region_w, region_h,
1075 (int)(cf_sigma / 4.f), steer, pipe);
1076 _cf_harmonic_fill(solver_field, hole2 ? hole2 : hole, region_w, region_h, (int)(cf_sigma / 4.f), steer,
1077 pipe);
1078
1079 HL_PFOR()
1080 for(size_t i = 0; i < region_pixels; i++)
1081 {
1082 valid_variance[i * 4 + 0] = fill_planes[i];
1083 valid_variance[i * 4 + 1] = fill_planes[region_pixels + i];
1084 valid_variance[i * 4 + 2] = solver_field[i];
1085 }
1086 }
1087
1088 // FEATHERED hand-off: instead of switching hard to the pair model exactly where
1089 // the third channel clips (its contour prints the joint/pair disagreement as an
1090 // arc), blend by the blurred oc-clip mask -- ~0 far into the joint region, ~1
1091 // deep into the multi-clip band, ~0.5 at the contour where BOTH estimates are
1092 // continuous extrapolations. est currently holds the extended joint estimate.
1093 // (A sharper ramp was tried and regressed the outer-contour smoothness.)
1094 // hard write at the multi-clip pixels (the iter-3 semantics). For the deep
1095 // channel this is only the DEEP-CORE estimate: the deferred stashed-joint
1096 // evaluation below owns the fence and blends this back in by depth. For the
1097 // other channels the pair fit is locally anchored at their fence (both its
1098 // channels are measured in the adjacent band), so the hard write is already
1099 // continuous there. A feathered joint-ext blend over this write was tried and
1100 // regressed the correlated synthetics without helping the arc.
1101 HL_PFOR()
1102 for(size_t i = 0; i < region_pixels; i++)
1103 if(valid[i * 4 + target_chan] < 0.5f && valid[i * 4 + guide_chan] >= 0.5f
1104 && valid[i * 4 + other_chan] < 0.5f)
1105 {
1106 // evaluation v_hat = a*u + d against the measured guide (diffused a,d; true u)
1107 estimate[i * 4 + target_chan]
1108 = valid_variance[i * 4 + 0] * estimate[i * 4 + guide_chan] + valid_variance[i * 4 + 1];
1109 guide_score[i * 4 + target_chan] = CLAMP(valid_variance[i * 4 + 2], 0.f, 1.f);
1110 }
1111 }
1112 }
1113
1114 // ---- deep-channel evaluation from the stashed joint model ----
1115 // Runs after the pair fallbacks so a clipped guide reads as its RECONSTRUCTION (itself
1116 // continuous: the pair fit of a less-clipped channel is anchored in the adjacent band
1117 // where both its channels are measured). Smooth coefficient fields x continuous guides
1118 // = no estimator hand-off anywhere inside the deep channel's zone -- the arc the hard
1119 // joint <-> pair switch used to print at the second guide's clip contour cannot form.
1120 // DEPTH SPLIT: the chained evaluation is only NEEDED near the multi-clip fence; deep
1121 // inside the core the direct pair colour-line is the better estimator on correlated
1122 // content (one hop, no compounded reconstruction error). Blend pair over stashed-joint
1123 // by a smoothstep of the blurred multi-clip mask: ~0 at the fence (mask ~0.5 there),
1124 // ~1 deep inside. Smooth weight x smooth fields = still no printable level set.
1125 if(deep_stashed)
1126 {
1127 const int guide1 = (cdeep == 0) ? 1 : 0;
1128 const int guide2 = (cdeep == 2) ? 1 : 2;
1129
1130 HL_PFOR()
1131 for(size_t i = 0; i < region_pixels; i++)
1132 {
1133 blur_in[i * 4 + 0]
1134 = (valid[i * 4 + cdeep] < 0.5f && (valid[i * 4 + guide1] < 0.5f || valid[i * 4 + guide2] < 0.5f)) ? 1.f
1135 : 0.f;
1136 blur_in[i * 4 + 1] = blur_in[i * 4 + 2] = blur_in[i * 4 + 3] = 0.f;
1137 }
1138
1139 _region_blur(blur_in, plane2, region_w, region_h,
1140 cf_sigma); // s2 is free scratch here (pair moments are done)
1141
1142 HL_PFOR()
1143 for(size_t i = 0; i < region_pixels; i++)
1144 {
1145 const int anyvalid = (valid[i * 4 + 0] >= 0.5f) || (valid[i * 4 + 1] >= 0.5f) || (valid[i * 4 + 2] >= 0.5f);
1146 if(valid[i * 4 + cdeep] < 0.5f && anyvalid)
1147 {
1148 // deferred evaluation of the stashed deep-channel joint model: v_hat = a*u1 + b*u2 + d
1149 // (a=reaction_weight, b=flat_target, d=dome_lum are the stashed diffused coefficients)
1150 const float joint = reaction_weight[i] * estimate[i * 4 + guide1]
1151 + flat_target[i] * estimate[i * 4 + guide2] + dome_lum[i];
1152 // pair values exist only at multi-clip px (the pair loop's write gate)
1153 const int has_pair = (valid[i * 4 + guide1] < 0.5f || valid[i * 4 + guide2] < 0.5f);
1154 const float pair_conf = CLAMP(plane2[i * 4 + 0], 0.f, 1.f);
1155 const float smooth_t = CLAMP((pair_conf - 0.7f) / 0.25f, 0.f, 1.f);
1156 const float floor_width = has_pair ? smooth_t * smooth_t * (3.f - 2.f * smooth_t) : 0.f;
1157 estimate[i * 4 + cdeep] = floor_width * estimate[i * 4 + cdeep] + (1.f - floor_width) * joint;
1158 guide_score[i * 4 + cdeep] = floor_width * guide_score[i * 4 + cdeep]
1159 + (1.f - floor_width) * CLAMP(guide_score[i * 4 + 3], 0.f, 1.f);
1160 }
1161 }
1162 }
1163
1164 // MATHS BRIDGE -- Step 4 (HF refit), article §"Hybrid Laplacian-band guiding of the high
1165 // frequencies" / §"Rebuild the high frequencies": the estimate is split at sigma/4 into a low band
1166 // ubar (plane2 below) and a detail band u - ubar. The detail band gets its OWN windowed colour-line
1167 // with R^2-shrunk gains (on a zero-mean band shrinkage is the correct estimator: no magnitude to
1168 // lose, only noise to not print), and the HF is blended between this guided resynthesis
1169 // h_g = a(u_g1-ubar_g1)+b(u_g2-ubar_g2) and the R^2-damped transfer h_d = R^2 (u_c - ubar_c) by
1170 // quadratic min-energy odds w = e_d^2/(e_d^2 + e_g^2), e_{d,g} = blurred |HF_{d,g}| -- an edge
1171 // misfire spikes the guided HF energy e_g, so w -> 0 and the damped path wins exactly there (the
1172 // failure self-detects, no content discriminator needed). Note the band split blurs at sigma/4
1173 // (floored at 2 px) while the moments below blur at the fit's cf_sigma -- two deliberate scales.
1174 //
1175 // R^2-scaled HIGH-FREQUENCY damping: where the colour-line is weak, the guides' fine
1176 // texture is unrelated to the truth and must not be printed onto the reconstruction.
1177 // Continuous in the quality weight -- no estimator hand-off.
1178 memcpy(blur_in, estimate, region_pixels * 4 * sizeof(float));
1179 _region_blur(blur_in, plane2, region_w, region_h,
1180 fmaxf(cf_sigma / 4.f, 2.f)); // ubar = low band, Gaussian at sigma/4 (>= 2 px)
1181
1182 // ---- Laplacian-band guiding (see the DT_HL_HF_GUIDE macro comment) ----
1183 // detail-band moments, weight = all three channels valid; packed exactly like the
1184 // full-signal moments: prev = [n, hR, hG, hB], s1 = [hRR, hGG, hBB, hRG], s3 = [hRB, hGB]
1185 HL_PFOR()
1186 for(size_t i = 0; i < region_pixels; i++)
1187 {
1188 const float lum_weight = (cf_binv > 0.f) ? sqf(fminf(lum_accum[i] * cf_binv, 1.f)) : 1.f;
1189 const float weight
1190 = (valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f) ? lum_weight : 0.f;
1191 // detail band H = est - ubar (plane2), weighted; packed like the CF moments = [n, hR, hG, hB]
1192 blur_in[i * 4 + 0] = weight;
1193 blur_in[i * 4 + 1] = weight * (estimate[i * 4 + 0] - plane2[i * 4 + 0]);
1194 blur_in[i * 4 + 2] = weight * (estimate[i * 4 + 1] - plane2[i * 4 + 1]);
1195 blur_in[i * 4 + 3] = weight * (estimate[i * 4 + 2] - plane2[i * 4 + 2]);
1196 }
1197
1198 _region_blur(blur_in, prev_scale, region_w, region_h,
1199 cf_sigma); // windowed means of the detail band (blur at fit sigma)
1200
1201 HL_PFOR()
1202 for(size_t i = 0; i < region_pixels; i++)
1203 {
1204 const float weight = blur_in[i * 4 + 0];
1205 const float hf_r = estimate[i * 4 + 0] - plane2[i * 4 + 0];
1206 const float hf_g = estimate[i * 4 + 1] - plane2[i * 4 + 1];
1207 const float hf_b = estimate[i * 4 + 2] - plane2[i * 4 + 2];
1208 blur_in[i * 4 + 0] = weight * hf_r * hf_r;
1209 blur_in[i * 4 + 1] = weight * hf_g * hf_g;
1210 blur_in[i * 4 + 2] = weight * hf_b * hf_b;
1211 blur_in[i * 4 + 3] = weight * hf_r * hf_g;
1212 }
1213
1214 _region_blur(blur_in, plane1, region_w, region_h, cf_sigma);
1215
1216 HL_PFOR()
1217 for(size_t i = 0; i < region_pixels; i++)
1218 {
1219 const float lum_weight = (cf_binv > 0.f) ? sqf(fminf(lum_accum[i] * cf_binv, 1.f)) : 1.f;
1220 const float weight
1221 = (valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f) ? lum_weight : 0.f;
1222 blur_in[i * 4 + 0]
1223 = weight * (estimate[i * 4 + 0] - plane2[i * 4 + 0]) * (estimate[i * 4 + 2] - plane2[i * 4 + 2]);
1224 blur_in[i * 4 + 1]
1225 = weight * (estimate[i * 4 + 1] - plane2[i * 4 + 1]) * (estimate[i * 4 + 2] - plane2[i * 4 + 2]);
1226 blur_in[i * 4 + 2] = (valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f)
1227 ? 1.f
1228 : 0.f; // unweighted valid mass for the anchor gate
1229 blur_in[i * 4 + 3] = 0.f;
1230 }
1231
1232 _region_blur(blur_in, plane3, region_w, region_h, cf_sigma);
1233
1234 // HF_M2(i, a, b) returns the windowed sum Sum w * H_a * H_b at pixel i (H = detail band, already
1235 // zero-mean, so no centering needed unlike CF_M2), indexing the packed second-moment planes:
1236 // diag (a==b) in plane1[0..2], RG/RB in plane1[3]/plane3[0], GB in plane3[1] -- feeds Var/Cov below
1237#define HF_M2(nb_index, coef_a, coef_b) \
1238 (((coef_a) == (coef_b)) ? plane1[(nb_index) * 4 + (coef_a)] \
1239 : ((2 + (coef_a) + (coef_b)) < 4 ? plane1[(nb_index) * 4 + 2 + (coef_a) + (coef_b)] \
1240 : plane3[(nb_index) * 4 + (coef_a) + (coef_b) - 2]))
1241
1242 for(int c = 0; c < 3; c++)
1243 {
1244 const int guide1 = (c == 0) ? 1 : 0;
1245 const int guide2 = (c == 2) ? 1 : 2;
1246
1247 size_t ntarget = 0;
1248 HL_PFOR(reduction(+ : ntarget))
1249 for(size_t i = 0; i < region_pixels; i++)
1250 if(valid[i * 4 + c] < 0.5f && valid[i * 4 + guide1] >= 0.5f && valid[i * 4 + guide2] >= 0.5f) ntarget++;
1251
1252 if(ntarget == 0) continue;
1253
1254 // R^2-shrunk detail-band gains at every pixel; anchors = trusted mass + bounded slopes
1255 HL_PFOR()
1256 for(size_t i = 0; i < region_pixels; i++)
1257 {
1258 const float norm = fmaxf(prev_scale[i * 4 + 0], 1e-9f);
1259 const float inv_det = 1.f / norm;
1260 const float mean1 = prev_scale[i * 4 + 1 + guide1] * inv_det;
1261 const float mean2 = prev_scale[i * 4 + 1 + guide2] * inv_det;
1262 const float mean_target = prev_scale[i * 4 + 1 + c] * inv_det;
1263 // same 2x2 normal equations as the CF fit, but on the detail-band moments (article step 4):
1264 // solve Sigma [a;b] = [Cov(H_u1,H_c); Cov(H_u2,H_c)] by Cramer's rule. u1=guide1, u2=guide2, v=c.
1265 const float var11 = fmaxf(HF_M2(i, guide1, guide1) * inv_det - mean1 * mean1, 0.f); // Var(H_u1)
1266 const float var22 = fmaxf(HF_M2(i, guide2, guide2) * inv_det - mean2 * mean2, 0.f); // Var(H_u2)
1267 const float var12 = HF_M2(i, guide1, guide2) * inv_det - mean1 * mean2; // Cov(H_u1,H_u2)
1268 const float cov_tg1 = HF_M2(i, c, guide1) * inv_det - mean_target * mean1; // Cov(H_v,H_u1) = RHS_1
1269 const float cov_tg2 = HF_M2(i, c, guide2) * inv_det - mean_target * mean2; // Cov(H_v,H_u2) = RHS_2
1270 const float var_target
1271 = fmaxf(HF_M2(i, c, c) * inv_det - mean_target * mean_target, 0.f); // Var(H_v), denom of R^2
1272
1273 const float lambda = 1e-3f * 0.5f * (var11 + var22) + 1e-12f; // relative Tikhonov ridge
1274 const float determinant = fmaxf((var11 + lambda) * (var22 + lambda) - var12 * var12, 1e-18f); // det Sigma
1275 const float hf_a = ((var22 + lambda) * cov_tg1 - var12 * cov_tg2) / determinant; // a (Cramer)
1276 const float hf_b_slope = ((var11 + lambda) * cov_tg2 - var12 * cov_tg1) / determinant; // b (Cramer)
1277 const float hf_r2 = CLAMP((hf_a * cov_tg1 + hf_b_slope * cov_tg2) / (var_target + 1e-12f), 0.f, 1.f); // R^2
1278
1279 // R^2-shrunk gains g*R^2 (correct estimator on a zero-mean band): stashed for the diffusion below
1280 reaction_weight[i] = hf_a * hf_r2;
1281 flat_target[i] = hf_b_slope * hf_r2;
1282 hole[i] = !(plane3[i * 4 + 2] > cf_fmin && prev_scale[i * 4 + 0] > 0.25f * plane3[i * 4 + 2]
1283 && valid[i * 4 + c] >= 0.5f && fabsf(reaction_weight[i]) < 64.f && fabsf(flat_target[i]) < 64.f);
1284 }
1285
1286 {
1287 // the two HF gain planes share the anchor mask -> one fused fill
1288 float *planes[2] = { reaction_weight, flat_target };
1289 _cf_harmonic_fill_n((float *const restrict *)planes, 2, hole, region_w, region_h, (int)(cf_sigma / 4.f),
1290 steer, pipe);
1291 }
1292
1293 // both HF candidates + their local energies (blurred |.|), packed into varc via one blur
1294 HL_PFOR()
1295 for(size_t i = 0; i < region_pixels; i++)
1296 {
1297 // h_g = a(u_g1-ubar_g1) + b(u_g2-ubar_g2): guide-transferred detail (diffused shrunk gains)
1298 const float hf_guided = reaction_weight[i] * (estimate[i * 4 + guide1] - plane2[i * 4 + guide1])
1299 + flat_target[i] * (estimate[i * 4 + guide2] - plane2[i * 4 + guide2]);
1300 // h_d = R^2 (u_c - ubar_c): the channel's own detail damped by its fit quality
1301 const float hf_damped = CLAMP(guide_score[i * 4 + c], 0.f, 1.f) * (estimate[i * 4 + c] - plane2[i * 4 + c]);
1302 blur_in[i * 4 + 0] = fabsf(hf_guided); // |h_g| -> blurred to e_g
1303 blur_in[i * 4 + 1] = fabsf(hf_damped); // |h_d| -> blurred to e_d
1304 blur_in[i * 4 + 2] = 0.f;
1305 blur_in[i * 4 + 3] = 0.f;
1306 }
1307
1308 _region_blur(blur_in, valid_variance, region_w, region_h, fmaxf(cf_sigma / 4.f, 2.f));
1309
1310 // quadratic min-energy blend of the two HF sources, then resynthesize
1311 HL_PFOR()
1312 for(size_t i = 0; i < region_pixels; i++)
1313 if(valid[i * 4 + c] < 0.5f && valid[i * 4 + guide1] >= 0.5f && valid[i * 4 + guide2] >= 0.5f)
1314 {
1315 const float hf_guided = reaction_weight[i] * (estimate[i * 4 + guide1] - plane2[i * 4 + guide1])
1316 + flat_target[i] * (estimate[i * 4 + guide2] - plane2[i * 4 + guide2]);
1317 const float hf_damped
1318 = CLAMP(guide_score[i * 4 + c], 0.f, 1.f) * (estimate[i * 4 + c] - plane2[i * 4 + c]);
1319 const float energy_g = valid_variance[i * 4 + 0]; // e_g = blurred |h_g|
1320 const float energy_d = valid_variance[i * 4 + 1]; // e_d = blurred |h_d|
1321 // quadratic min-energy odds w = e_d^2/(e_d^2 + e_g^2): favours the LOWER-energy candidate,
1322 // so a guide misfire (spiked e_g) drives w -> 0 and the damped path wins there
1323 const float energy_weight = energy_d * energy_d / fmaxf(energy_d * energy_d + energy_g * energy_g, 1e-18f);
1324 // resynthesis: u_c = ubar_c + w*h_g + (1-w)*h_d
1325 estimate[i * 4 + c] = plane2[i * 4 + c] + energy_weight * hf_guided + (1.f - energy_weight) * hf_damped;
1326 }
1327 }
1328
1329#undef HF_M2
1330
1331 // pixels with a single surviving guide keep the damped treatment
1332 HL_PFOR()
1333 for(size_t i = 0; i < region_pixels; i++)
1334 {
1335 const int n_valid = (valid[i * 4 + 0] >= 0.5f) + (valid[i * 4 + 1] >= 0.5f) + (valid[i * 4 + 2] >= 0.5f);
1336 if(n_valid != 1) continue;
1337 for(int c = 0; c < 3; c++)
1338 if(valid[i * 4 + c] < 0.5f)
1339 {
1340 // no second guide -> no h_g: keep only the R^2-damped own detail u_c = ubar_c + R^2(u_c-ubar_c)
1341 const float hf_weight = CLAMP(guide_score[i * 4 + c], 0.f, 1.f);
1342 estimate[i * 4 + c] = plane2[i * 4 + c] + hf_weight * (estimate[i * 4 + c] - plane2[i * 4 + c]);
1343 }
1344 }
1345
1346 // Step 5 / Soft saturation floor (article §"The algorithm" step 5): a clipped channel is
1347 // physically at least its saturated reading c0, but the hard max(e, c0) prints the
1348 // floor-binding contour as an edge wherever a weak prediction oscillates around saturation;
1349 // round the transition over ~2% of c0 instead. out = 1/2 (e + c0 + sqrt((e-c0)^2 + (0.02 c0)^2)),
1350 // a smooth max: -> e for e >> c0, -> c0 for e << c0, softened over a width 0.02*c0.
1351 HL_PFOR()
1352 for(size_t i = 0; i < region_pixels; i++)
1353 for(int c = 0; c < 3; c++)
1354 if(valid[i * 4 + c] < 0.5f)
1355 {
1356 const float clip_floor_c = clip0[i * 4 + c]; // c0, the saturated reading
1357 const float delta = estimate[i * 4 + c] - clip_floor_c; // e - c0
1358 const float weight = 0.02f * fmaxf(clip_floor_c, 1e-6f); // transition width = 2% of c0
1359 // c0 + 1/2 ( (e-c0) + sqrt((e-c0)^2 + width^2) ): the rounded lower bound at c0
1360 estimate[i * 4 + c] = clip_floor_c + 0.5f * (delta + sqrtf(delta * delta + weight * weight));
1361 }
1362
1363 // Step 6 dome gate (article §"The algorithm" step 6): hand the dome-blend weight to the
1364 // self-dome block (it reads varc as Wc, uses We = Wc^2 as the keep weight). The two factors
1365 // answer two questions: dome fraction = (1 - S_{0.4}^{0.85}(R^2)) * exp(-(delta/1.5 sigma)^2)
1366 // R^2 (guide_score) -> "is the colour-line real here" via a smoothstep S (0 below 0.4,
1367 // 1 above 0.85): low R^2 = DOUBTFUL model -> lean on the dome.
1368 // delta (clip_depth) -> "is the dome trustworthy here" via a gaussian of depth/(1.5 sigma):
1369 // biharmonic extrapolation is excellent near the rim and degrades with distance, so the
1370 // hand-over decays over ~1.5 sigma of depth. Deep interiors always stay on the fit.
1371 // We store Wc = sqrt(keep) with keep = 1 - dome_fraction = 1 - (1 - S(R^2)) * gdep, so the
1372 // self-dome block's conf_weight = Wc^2 = keep is exactly the coefficient-field share.
1373 HL_PFOR()
1374 for(size_t i = 0; i < region_pixels; i++)
1375 for(int c = 0; c < 3; c++)
1376 {
1377 const float dome_t = CLAMP((guide_score[i * 4 + c] - 0.4f) / 0.45f, 0.f, 1.f); // ramp arg (0.4..0.85)
1378 const float we_r2 = dome_t * dome_t * (3.f - 2.f * dome_t); // S_{0.4}^{0.85}(R^2)
1379 const float smooth_t = clip_depth[i] / (1.5f * cf_sigma); // delta / (1.5 sigma)
1380 const float gdep = expf(-smooth_t * smooth_t); // exp(-(delta/1.5 sigma)^2)
1381 valid_variance[i * 4 + c] = sqrtf(CLAMP(1.f - (1.f - we_r2) * gdep, 0.f, 1.f)); // Wc = sqrt(keep)
1382 }
1383
1386}
1387
1388// ============================ OpenCL ============================
1389
1390#ifdef HAVE_OPENCL
1391// GPU counterpart of _cf_harmonic_fill_n: harmonic fill (repeatedly replace each hole pixel by
1392// the average of its four neighbours -- Jacobi iterations -- run coarse-to-fine on shrunken
1393// copies of the grid) of up to 3 planes SHARING ONE anchor mask, executed entirely on device
1394// buffers. The mask pyramid, the tensor and the edge weights depend only on (mask, steer,
1395// geometry), so the planes share one build and the fused Jacobi kernels advance all of them
1396// per launch, reading the weights once per cell.
1397// vals[p] (float, rw*rh) hold the planes to fill; despite its name, `hole` (uchar, rw*rh) must
1398// be the ANCHOR mask (1 = trusted pixel to keep, 0 = hole to fill) -- see the caller-contract
1399// note below. Fills the hole cells of every vals[p] in place. Mirrors _cf_harmonic_fill_n on
1400// the CPU: any change here must be mirrored there and re-validated with the HL_FILLCL_TEST
1401// self-test (_cf_harmonic_fill_cl_selftest).
1402//
1403// MATHS BRIDGE -- article "The algorithm" step 3, the E_transport solver on the GPU: the anchored,
1404// coarse-to-fine anisotropic transport that minimizes E_transport = Sum_p int grad(p)^T D grad(p),
1405// p|anchors = p_fit, by relaxing div(D grad p)=0 (steer != NULL builds D via the hl_cfa_* kernels;
1406// steer == NULL => D = I, the plain harmonic fill). Structure mirrors the CPU _cf_harmonic_fill_n:
1407// base grid at pitch ~sigma/4, pyramid halved until the long side <= 8 cells (convergence from depth,
1408// not sweep count), flat anchor-mean seed at the coarsest level, bilinear seed of each finer level,
1409// 100 Jacobi sweeps per level, then bilinear upsample into the full-res hole pixels.
1410static cl_int _cf_harmonic_fill_cl_n(const int devid, void *gd_void, cl_mem *vals, const int n_planes_in,
1411 cl_mem hole, const int region_w, const int region_h, const int base_ds,
1412 const int mask_is_hole, cl_mem steer)
1413{
1415 const int n_planes = CLAMP(n_planes_in, 1, DT_HL_FILL_CL_MAXP);
1416 const int downsample = CLAMP(base_ds, 1, 8);
1417 const int base_w = (region_w + downsample - 1) / downsample;
1418 const int base_h = (region_h + downsample - 1) / downsample;
1419 const size_t cell_count = (size_t)base_w * base_h;
1420 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
1421 const int steered = (steer != NULL);
1422 const float steer_k = DT_HL_CF_K;
1423
1424 cl_mem base_vals[DT_HL_FILL_CL_MAXP] = { NULL };
1425 cl_mem level_vals[DT_HL_FILL_CL_MAXP] = { NULL };
1426 cl_mem level_solution[DT_HL_FILL_CL_MAXP] = { NULL };
1427 cl_mem level_scratch[DT_HL_FILL_CL_MAXP] = { NULL };
1428 cl_mem prev_level_solution[DT_HL_FILL_CL_MAXP] = { NULL };
1429 int alloc_ok = 1;
1430 for(int plane = 0; plane < n_planes; plane++)
1431 {
1432 base_vals[plane] = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1433 level_vals[plane] = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1434 level_solution[plane] = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1435 level_scratch[plane] = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1436 prev_level_solution[plane] = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1437 alloc_ok &= (base_vals[plane] && level_vals[plane] && level_solution[plane] && level_scratch[plane]
1438 && prev_level_solution[plane]);
1439 }
1440 cl_mem base_anchor_mask = dt_opencl_alloc_device_buffer(devid, cell_count);
1441 cl_mem level_anchor_mask = dt_opencl_alloc_device_buffer(devid, cell_count);
1442 // aniso steering planes, only needed when a steering plane was passed in (all sized to the
1443 // base grid). Allocate them in one guarded block and fold their null checks into alloc_ok,
1444 // so the abort decision is taken exactly once below.
1445 cl_mem base_steer = NULL;
1446 cl_mem level_steer = NULL;
1447 cl_mem steer_blur_lin = NULL;
1448 cl_mem steer_blur_quad = NULL;
1449 cl_mem steer_grad_x = NULL;
1450 cl_mem steer_grad_y = NULL;
1451 cl_mem steer_tensor_xx = NULL;
1452 cl_mem steer_tensor_xy = NULL;
1453 cl_mem steer_tensor_yy = NULL;
1454 cl_mem grad_partial_sums = NULL;
1455 cl_mem grad_mean_norm = NULL;
1456 cl_mem neighbour_weights = NULL;
1457 cl_mem neighbour_weights_sum = NULL;
1458 if(steered)
1459 {
1460 base_steer = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1461 level_steer = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1462 steer_blur_lin = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1463 steer_blur_quad = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1464 steer_grad_x = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1465 steer_grad_y = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1466 steer_tensor_xx = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1467 steer_tensor_xy = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1468 steer_tensor_yy = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1469 grad_partial_sums = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 256);
1470 grad_mean_norm = dt_opencl_alloc_device_buffer(devid, sizeof(float));
1471 neighbour_weights = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count * 8);
1472 neighbour_weights_sum = dt_opencl_alloc_device_buffer(devid, sizeof(float) * cell_count);
1473 alloc_ok &= (base_steer && level_steer && steer_blur_lin && steer_blur_quad && steer_grad_x && steer_grad_y
1474 && steer_tensor_xx && steer_tensor_xy && steer_tensor_yy && grad_partial_sums && grad_mean_norm
1475 && neighbour_weights && neighbour_weights_sum)
1476 ? 1
1477 : 0;
1478 }
1479 if(!alloc_ok || !base_anchor_mask || !level_anchor_mask) goto out;
1480
1481 // base grid from full resolution, per plane (base_anchor_mask is identical every time). The caller's mask
1482 // may be in either convention (1 = trusted anchor, or 1 = hole with mask_is_hole set);
1483 // hl_fill_down normalizes iter, and every internal level mask below is in the ANCHOR
1484 // convention regardless.
1485 for(int plane = 0; plane < n_planes; plane++)
1486 {
1487 const int kernel = global_data->kernel_hl_fill_down;
1488 size_t size[3] = { ROUNDUPDWD(base_w, devid), ROUNDUPDHT(base_h, devid), 1 };
1489 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &vals[plane]);
1490 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &hole);
1491 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &base_vals[plane]);
1492 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &base_anchor_mask);
1493 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
1494 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
1495 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &base_w);
1496 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &base_h);
1497 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &downsample);
1498 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &mask_is_hole);
1499 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1500 if(cl_err != CL_SUCCESS) goto out;
1501 }
1502
1503 // aniso: steering plane on the base grid (plain block mean)
1504 if(steered)
1505 {
1506 const int kernel = global_data->kernel_hl_cfa_down;
1507 size_t size_level[3] = { ROUNDUPDWD(base_w, devid), ROUNDUPDHT(base_h, devid), 1 };
1508 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &steer);
1509 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &base_steer);
1510 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &region_w);
1511 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_h);
1512 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &base_w);
1513 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &base_h);
1514 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &downsample);
1515 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size_level);
1516 if(cl_err != CL_SUCCESS) goto out;
1517 }
1518
1519 // pyramid depth: halve until the LONG side is <= 8 cells (same rationale as the CPU fill:
1520 // the coarsest flat seed must be trivially relaxable within the fixed sweep budget)
1521 int nlev = 1;
1522 while((MAX(base_w, base_h) >> nlev) > 8 && nlev < 12) nlev++;
1523
1524 // coarse-to-fine sweep: solve on the coarsest grid first, then use each solved level to seed
1525 // the next finer one (prev_level_w/prev_level_h remember the previous level's dimensions for the seed upsample)
1526 int prev_level_w = 0;
1527 int prev_level_h = 0;
1528 for(int level = nlev - 1; level >= 0; level--)
1529 {
1530 const int step = 1 << level;
1531 const int level_w = (base_w + step - 1) / step;
1532 const int level_h = (base_h + step - 1) / step;
1533 size_t size[3] = { ROUNDUPDWD(level_w, devid), ROUNDUPDHT(level_h, devid), 1 };
1534
1535 // level grid from the base grid, per plane (level_anchor_mask identical every time)
1536 for(int plane = 0; plane < n_planes; plane++)
1537 {
1538 const int kernel_down = global_data->kernel_hl_fill_down;
1539 dt_opencl_set_kernel_arg(devid, kernel_down, 0, sizeof(cl_mem), &base_vals[plane]);
1540 dt_opencl_set_kernel_arg(devid, kernel_down, 1, sizeof(cl_mem), &base_anchor_mask);
1541 dt_opencl_set_kernel_arg(devid, kernel_down, 2, sizeof(cl_mem), &level_vals[plane]);
1542 dt_opencl_set_kernel_arg(devid, kernel_down, 3, sizeof(cl_mem), &level_anchor_mask);
1543 dt_opencl_set_kernel_arg(devid, kernel_down, 4, sizeof(int), &base_w);
1544 dt_opencl_set_kernel_arg(devid, kernel_down, 5, sizeof(int), &base_h);
1545 dt_opencl_set_kernel_arg(devid, kernel_down, 6, sizeof(int), &level_w);
1546 dt_opencl_set_kernel_arg(devid, kernel_down, 7, sizeof(int), &level_h);
1547 dt_opencl_set_kernel_arg(devid, kernel_down, 8, sizeof(int), &step);
1548 const int level_anchor = 0; // internal level masks are always in the anchor convention
1549 dt_opencl_set_kernel_arg(devid, kernel_down, 9, sizeof(int), &level_anchor);
1550 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel_down, size);
1551 if(cl_err != CL_SUCCESS) goto out;
1552 }
1553
1554 if(level == nlev - 1)
1555 {
1556 // coarsest level: seed every hole cell with the mean of the anchor cells (single workgroup)
1557 for(int plane = 0; plane < n_planes; plane++)
1558 {
1559 const int kernel_seed = global_data->kernel_hl_fill_seed;
1560 const int n_cells = level_w * level_h;
1561 const int local_size = 256;
1562 size_t size_level[3] = { local_size, 1, 1 };
1563 size_t local[3] = { local_size, 1, 1 };
1564 dt_opencl_set_kernel_arg(devid, kernel_seed, 0, sizeof(cl_mem), &level_solution[plane]);
1565 dt_opencl_set_kernel_arg(devid, kernel_seed, 1, sizeof(cl_mem), &level_vals[plane]);
1566 dt_opencl_set_kernel_arg(devid, kernel_seed, 2, sizeof(cl_mem), &level_anchor_mask);
1567 dt_opencl_set_kernel_arg(devid, kernel_seed, 3, sizeof(int), &n_cells);
1568 dt_opencl_set_kernel_arg(devid, kernel_seed, 4, sizeof(float) * local_size, NULL);
1569 dt_opencl_set_kernel_arg(devid, kernel_seed, 5, sizeof(int) * local_size, NULL);
1570 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel_seed, size_level, local);
1571 if(cl_err != CL_SUCCESS) goto out;
1572 }
1573 }
1574 else
1575 {
1576 // finer levels: seed hole cells by upsampling the previous (coarser) level's solution
1577 for(int plane = 0; plane < n_planes; plane++)
1578 {
1579 const int kernel_seed_up = global_data->kernel_hl_fill_seed_up;
1580 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 0, sizeof(cl_mem), &level_solution[plane]);
1581 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 1, sizeof(cl_mem), &level_vals[plane]);
1582 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 2, sizeof(cl_mem), &level_anchor_mask);
1583 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 3, sizeof(cl_mem), &prev_level_solution[plane]);
1584 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 4, sizeof(int), &level_w);
1585 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 5, sizeof(int), &level_h);
1586 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 6, sizeof(int), &prev_level_w);
1587 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 7, sizeof(int), &prev_level_h);
1588 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel_seed_up, size);
1589 if(cl_err != CL_SUCCESS) goto out;
1590 }
1591 }
1592
1593 // aniso: build the E_transport steering tensor D at this level (article step 3 D equation):
1594 // level steering plane -> blurred L/L^2 -> gradients (+ mean-magnitude reduction)
1595 // -> Weickert tensor (hl_cfa_tensor = _cf_adaptive_tensor) -> precomputed edge weights.
1596 // Mirrors the CPU per-level build exactly;
1597 // the gnorm reduction is finished on device so the queue never drains mid-fill. Shared by
1598 // all n_planes planes -- fusing them is what amortizes this whole chain.
1599 if(steered)
1600 {
1601 const int n_cells = level_w * level_h;
1602 {
1603 const int kernel = global_data->kernel_hl_cfa_down;
1604 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &base_steer);
1605 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &level_steer);
1606 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &base_w);
1607 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &base_h);
1608 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &level_w);
1609 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &level_h);
1610 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &step);
1611 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1612 if(cl_err != CL_SUCCESS) goto out;
1613 }
1614 for(int pass = 0; pass < 2; pass++)
1615 {
1616 const int kernel = global_data->kernel_hl_cfa_box;
1617 cl_mem blur_in_lin = pass ? steer_grad_x : level_steer;
1618 cl_mem blur_in_quad = pass ? steer_grad_y : level_steer;
1619 cl_mem outL = pass ? steer_blur_lin : steer_grad_x;
1620 cl_mem outQ = pass ? steer_blur_quad : steer_grad_y;
1621 const int square = (pass == 0);
1622 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &blur_in_lin);
1623 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &blur_in_quad);
1624 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &outL);
1625 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &outQ);
1626 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &level_w);
1627 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &level_h);
1628 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &square);
1629 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1630 if(cl_err != CL_SUCCESS) goto out;
1631 }
1632 {
1633 const int kernel = global_data->kernel_hl_cfa_grad;
1634 const int local_size = 64, n_groups = 256;
1635 size_t size_1d[3] = { (size_t)n_groups * local_size, 1, 1 };
1636 size_t local_size_1d[3] = { local_size, 1, 1 };
1637 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &steer_blur_lin);
1638 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &steer_grad_x);
1639 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &steer_grad_y);
1640 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &grad_partial_sums);
1641 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &level_w);
1642 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &level_h);
1643 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float) * local_size, NULL);
1644 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, size_1d, local_size_1d);
1645 if(cl_err != CL_SUCCESS) goto out;
1646 }
1647 {
1648 // finish the reduction on device (single work-item): no blocking readback
1649 const int kernel = global_data->kernel_hl_cfa_gnorm;
1650 const int ngroups = 256;
1651 size_t size_1d[3] = { 1, 1, 1 };
1652 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &grad_partial_sums);
1653 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &grad_mean_norm);
1654 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &ngroups);
1655 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &n_cells);
1656 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size_1d);
1657 if(cl_err != CL_SUCCESS) goto out;
1658 }
1659 {
1660 const int kernel = global_data->kernel_hl_cfa_tensor;
1661 size_t size_1d[3] = { ROUNDUPDWD(n_cells, devid), 1, 1 };
1662 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &steer_grad_x);
1663 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &steer_grad_y);
1664 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &steer_blur_lin);
1665 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &steer_blur_quad);
1666 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &steer_tensor_xx);
1667 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &steer_tensor_xy);
1668 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), &steer_tensor_yy);
1669 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(cl_mem), &grad_mean_norm);
1670 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(float), &steer_k);
1671 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &n_cells);
1672 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size_1d);
1673 if(cl_err != CL_SUCCESS) goto out;
1674 }
1675 // edge weights are constant across the level's sweeps: precompute once -- but only for
1676 // the small grids the block kernel serves (the large-grid launch loop reads the tensor
1677 // planes directly, see above)
1678 if(level_w * level_h <= 4096)
1679 {
1680 const int kernel = global_data->kernel_hl_cfa_weights;
1681 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &steer_tensor_xx);
1682 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &steer_tensor_xy);
1683 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &steer_tensor_yy);
1684 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &neighbour_weights);
1685 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &neighbour_weights_sum);
1686 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &level_w);
1687 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &level_h);
1688 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1689 if(cl_err != CL_SUCCESS) goto out;
1690 }
1691 }
1692
1693 // small grids: all 100 iterations inside ONE single-workgroup launch (bit-identical);
1694 // the 100-launch ping-pong was the region loop's dominant enqueue cost on small regions
1695 if(level_w * level_h <= 4096)
1696 {
1697 const int iters = 100; // flat budget; convergence comes from the pyramid depth
1698 size_t size_box[3] = { 256, 1, 1 };
1699 size_t local_box[3] = { 256, 1, 1 };
1700 if(steered)
1701 {
1702 // fused: all n_planes planes advance inside the single launch (dummy slots read plane 0)
1703 const int kernel_block = global_data->kernel_hl_cfa_jacobi_block;
1704 cl_mem solution1 = (n_planes > 1) ? level_solution[1] : level_solution[0];
1705 cl_mem solution2 = (n_planes > 2) ? level_solution[2] : level_solution[0];
1706 cl_mem scratch1 = (n_planes > 1) ? level_scratch[1] : level_scratch[0];
1707 cl_mem scratch2 = (n_planes > 2) ? level_scratch[2] : level_scratch[0];
1708 int arg_index = 0;
1709 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &level_solution[0]);
1710 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &solution1);
1711 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &solution2);
1712 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &level_scratch[0]);
1713 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &scratch1);
1714 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &scratch2);
1715 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &level_anchor_mask);
1716 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &neighbour_weights);
1717 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &neighbour_weights_sum);
1718 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(int), &level_w);
1719 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(int), &level_h);
1720 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(int), &iters);
1721 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(int), &n_planes);
1722 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel_block, size_box, local_box);
1723 if(cl_err != CL_SUCCESS) goto out;
1724 }
1725 else
1726 for(int plane = 0; plane < n_planes; plane++)
1727 {
1728 const int kernel_block = global_data->kernel_hl_fill_jacobi_block;
1729 int arg_index = 0;
1730 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &level_solution[plane]);
1731 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &level_scratch[plane]);
1732 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(cl_mem), &level_anchor_mask);
1733 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(int), &level_w);
1734 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(int), &level_h);
1735 dt_opencl_set_kernel_arg(devid, kernel_block, arg_index++, sizeof(int), &iters);
1736 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel_block, size_box, local_box);
1737 if(cl_err != CL_SUCCESS) goto out;
1738 }
1739 // 100 (even) internal swaps leave the solution in u, exactly like the launch loop:
1740 // rotate iter into prev for the next finer level's seed
1741 for(int plane = 0; plane < n_planes; plane++)
1742 {
1743 cl_mem swap_buf = prev_level_solution[plane];
1744 prev_level_solution[plane] = level_solution[plane];
1745 level_solution[plane] = swap_buf;
1746 }
1747 prev_level_w = level_w;
1748 prev_level_h = level_h;
1749 continue;
1750 }
1751
1752 // larger grids: Jacobi sweeps as separate launches, ping-ponging between the two buffers
1753 const int n_iter = 100; // flat budget; convergence comes from the pyramid depth
1754 cl_mem solution_planes[DT_HL_FILL_CL_MAXP], scratch_planes[DT_HL_FILL_CL_MAXP];
1755 for(int plane = 0; plane < n_planes; plane++)
1756 {
1757 solution_planes[plane] = level_solution[plane];
1758 scratch_planes[plane] = level_scratch[plane];
1759 }
1760 for(int iter = 0; iter < n_iter; iter++)
1761 {
1762 if(steered)
1763 {
1764 // fused sweep: one launch advances all n_planes planes (dummy slots read plane 0).
1765 // large grids keep the tensor form (see the kernel comment: cache reuse beats
1766 // precomputed weights there); only the small-grid block kernel uses neighbour_weights/neighbour_weights_sum
1767 const int kernel_jacobi = global_data->kernel_hl_cfa_jacobi;
1768 cl_mem solution1 = (n_planes > 1) ? solution_planes[1] : solution_planes[0];
1769 cl_mem solution2 = (n_planes > 2) ? solution_planes[2] : solution_planes[0];
1770 cl_mem scratch1 = (n_planes > 1) ? scratch_planes[1] : scratch_planes[0];
1771 cl_mem scratch2 = (n_planes > 2) ? scratch_planes[2] : scratch_planes[0];
1772 int arg_index = 0;
1773 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &solution_planes[0]);
1774 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &solution1);
1775 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &solution2);
1776 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &scratch_planes[0]);
1777 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &scratch1);
1778 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &scratch2);
1779 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &level_anchor_mask);
1780 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &steer_tensor_xx);
1781 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &steer_tensor_xy);
1782 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &steer_tensor_yy);
1783 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(int), &level_w);
1784 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(int), &level_h);
1785 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(int), &n_planes);
1786 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel_jacobi, size);
1787 if(cl_err != CL_SUCCESS) goto out;
1788 }
1789 else
1790 for(int plane = 0; plane < n_planes; plane++)
1791 {
1792 const int kernel_jacobi = global_data->kernel_hl_fill_jacobi;
1793 int arg_index = 0;
1794 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &solution_planes[plane]);
1795 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &scratch_planes[plane]);
1796 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(cl_mem), &level_anchor_mask);
1797 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(int), &level_w);
1798 dt_opencl_set_kernel_arg(devid, kernel_jacobi, arg_index++, sizeof(int), &level_h);
1799 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel_jacobi, size);
1800 if(cl_err != CL_SUCCESS) goto out;
1801 }
1802 for(int plane = 0; plane < n_planes; plane++)
1803 {
1804 cl_mem swap_buf = solution_planes[plane];
1805 solution_planes[plane] = scratch_planes[plane];
1806 scratch_planes[plane] = swap_buf;
1807 }
1808 }
1809 // solution of this level in `a`: stash into prev for the next finer seed, keeping u/v the
1810 // two scratches distinct from prev
1811 for(int plane = 0; plane < n_planes; plane++)
1812 {
1813 cl_mem swap_buf = prev_level_solution[plane];
1814 prev_level_solution[plane] = solution_planes[plane];
1815 solution_planes[plane] = swap_buf;
1816 level_solution[plane]
1817 = (prev_level_solution[plane] == level_solution[plane]) ? solution_planes[plane] : level_solution[plane];
1818 level_scratch[plane]
1819 = (prev_level_solution[plane] == level_scratch[plane]) ? solution_planes[plane] : level_scratch[plane];
1820 }
1821 prev_level_w = level_w;
1822 prev_level_h = level_h;
1823 }
1824
1825 // upsample prev (base-grid solution) into the full-res holes: kernel expects HOLE mask; our
1826 // `hole` buffer holds ANCHORS (caller contract), so hl_fill_up's test is inverted there.
1827 for(int plane = 0; plane < n_planes; plane++)
1828 {
1829 const int kernel_seed_up = global_data->kernel_hl_fill_up;
1830 size_t size_upsample[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
1831 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 0, sizeof(cl_mem), &vals[plane]);
1832 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 1, sizeof(cl_mem), &hole);
1833 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 2, sizeof(cl_mem), &prev_level_solution[plane]);
1834 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 3, sizeof(int), &region_w);
1835 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 4, sizeof(int), &region_h);
1836 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 5, sizeof(int), &base_w);
1837 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 6, sizeof(int), &base_h);
1838 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 7, sizeof(int), &downsample);
1839 dt_opencl_set_kernel_arg(devid, kernel_seed_up, 8, sizeof(int), &mask_is_hole);
1840 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel_seed_up, size_upsample);
1841 if(cl_err != CL_SUCCESS) goto out;
1842 }
1843
1844out:
1845 for(int plane = 0; plane < DT_HL_FILL_CL_MAXP; plane++)
1846 {
1847 dt_opencl_release_mem_object(base_vals[plane]);
1848 dt_opencl_release_mem_object(level_vals[plane]);
1849 dt_opencl_release_mem_object(level_solution[plane]);
1850 dt_opencl_release_mem_object(level_scratch[plane]);
1851 dt_opencl_release_mem_object(prev_level_solution[plane]);
1852 }
1853 dt_opencl_release_mem_object(base_anchor_mask);
1854 dt_opencl_release_mem_object(level_anchor_mask);
1855 dt_opencl_release_mem_object(base_steer);
1856 dt_opencl_release_mem_object(level_steer);
1857 dt_opencl_release_mem_object(steer_blur_lin);
1858 dt_opencl_release_mem_object(steer_blur_quad);
1859 dt_opencl_release_mem_object(steer_grad_x);
1860 dt_opencl_release_mem_object(steer_grad_y);
1861 dt_opencl_release_mem_object(steer_tensor_xx);
1862 dt_opencl_release_mem_object(steer_tensor_xy);
1863 dt_opencl_release_mem_object(steer_tensor_yy);
1864 dt_opencl_release_mem_object(grad_partial_sums);
1865 dt_opencl_release_mem_object(grad_mean_norm);
1866 dt_opencl_release_mem_object(neighbour_weights);
1867 dt_opencl_release_mem_object(neighbour_weights_sum);
1868 return cl_err;
1869}
1870
1871cl_int _cf_harmonic_fill_cl(const int devid, void *gd_void, cl_mem val, cl_mem hole, const int region_w,
1872 const int region_h, const int base_ds, const int mask_is_hole, cl_mem steer)
1873{
1874 cl_mem val_planes[1] = { val };
1875 return _cf_harmonic_fill_cl_n(devid, gd_void, val_planes, 1, hole, region_w, region_h, base_ds, mask_is_hole,
1876 steer);
1877}
1878#endif // HAVE_OPENCL
1879
1880#ifdef HAVE_OPENCL
1881
1882cl_int _cf_joint_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem model_quality,
1883 cl_mem mom0, cl_mem mom1, cl_mem mom2, cl_mem steer,
1884 const float *const restrict channel_means, const int region_w, const int region_h,
1885 const float cf_sigma, const float cf_fmin, const int c, const int guide1,
1886 const int guide2)
1887{
1889 const size_t region_pixels = (size_t)region_w * region_h;
1890 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
1891 size_t work_size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
1892
1893 cl_mem coeff_slope_a = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1894 cl_mem coeff_slope_b = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1895 cl_mem coeff_offset = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1896 cl_mem coeff_r2 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1897 cl_mem anchor = dt_opencl_alloc_device_buffer(devid, region_pixels);
1898 cl_mem broad = dt_opencl_alloc_device_buffer(devid, region_pixels);
1899 if(!coeff_slope_a || !coeff_slope_b || !coeff_offset || !coeff_r2 || !anchor || !broad) goto out;
1900
1901 // per-pixel colour-line fit from the blurred moments; also writes the anchor masks
1902 {
1903 const int kernel = global_data->kernel_hl_cf_fit_joint;
1904 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &mom0);
1905 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &mom1);
1906 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &mom2);
1907 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &valid);
1908 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
1909 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
1910 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &c);
1911 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &guide1);
1912 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &guide2);
1913 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(float), &cf_fmin);
1914 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(cl_mem), &coeff_slope_a);
1915 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(cl_mem), &coeff_slope_b);
1916 dt_opencl_set_kernel_arg(devid, kernel, 12, sizeof(cl_mem), &coeff_offset);
1917 dt_opencl_set_kernel_arg(devid, kernel, 13, sizeof(cl_mem), &coeff_r2);
1918 dt_opencl_set_kernel_arg(devid, kernel, 14, sizeof(cl_mem), &anchor);
1919 dt_opencl_set_kernel_arg(devid, kernel, 15, sizeof(cl_mem), &broad);
1920 dt_opencl_set_kernel_arg(devid, kernel, 16, sizeof(float), &channel_means[0]);
1921 dt_opencl_set_kernel_arg(devid, kernel, 17, sizeof(float), &channel_means[1]);
1922 dt_opencl_set_kernel_arg(devid, kernel, 18, sizeof(float), &channel_means[2]);
1923 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
1924 if(cl_err != CL_SUCCESS) goto out;
1925 }
1926
1927 // harmonic diffusion of the coefficient fields across the clipped zone (fit quality cr2 uses
1928 // the broader anchor mask)
1929 {
1930 const int base_ds = (int)(cf_sigma / 4.f);
1931 cl_mem coeff_planes[3]
1932 = { coeff_slope_a, coeff_slope_b, coeff_offset }; // shared anchor mask -> one fused fill
1933 cl_err
1934 = _cf_harmonic_fill_cl_n(devid, gd_void, coeff_planes, 3, anchor, region_w, region_h, base_ds, 0, steer);
1935 if(cl_err != CL_SUCCESS) goto out;
1936 cl_err = _cf_harmonic_fill_cl(devid, gd_void, coeff_r2, broad, region_w, region_h, base_ds, 0, steer);
1937 if(cl_err != CL_SUCCESS) goto out;
1938 }
1939
1940 // evaluate the diffused colour line against the measured guides; write est + fit score bsc
1941 {
1942 const int kernel = global_data->kernel_hl_cf_eval_joint;
1943 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &coeff_slope_a);
1944 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &coeff_slope_b);
1945 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &coeff_offset);
1946 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &coeff_r2);
1947 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &valid);
1948 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &estimate);
1949 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), &model_quality);
1950 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &region_w);
1951 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &region_h);
1952 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &c);
1953 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(int), &guide1);
1954 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(int), &guide2);
1955 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
1956 }
1957
1958out:
1959 dt_opencl_release_mem_object(coeff_slope_a);
1960 dt_opencl_release_mem_object(coeff_slope_b);
1961 dt_opencl_release_mem_object(coeff_offset);
1965 return cl_err;
1966}
1967
1968// Pair stage for one orientation: predict one clipped channel from a SINGLE guide channel
1969// (slope + intercept fitted from the windowed moments), diffuse the fitted coefficients across
1970// the clipped zone, then evaluate against the measured guide and write into est.
1971// `a`/`b` name the channel pair and `o` picks the orientation (which of the two is the target
1972// tc and which is the guide gc); oc is the remaining third channel.
1973// Runs unconditionally (no target-count guard: an empty target set writes nothing).
1974// Mirrors the pair coefficient-field stage inside _region_guided_filter (CPU): any change here
1975// must be mirrored there and re-validated with the HL_CFCL_TEST self-tests.
1976//
1977// MATHS BRIDGE -- article "The algorithm" step 3, the single-guide fallback: the model collapses to
1978// v_hat = a*u + d with a = Cov(u,v)/Var(u) and R^2 = Cov(u,v)^2/(Var(u) Var(v)) (hl_cf_fit_pair),
1979// transported by the E_transport fill and evaluated by hl_cf_eval_pair. Same fit/transport/evaluate
1980// skeleton as the joint stage, one guide instead of two.
1981static cl_int _cf_pair_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid,
1982 cl_mem model_quality, cl_mem moment_a, cl_mem moment_b, cl_mem steer,
1983 const float *const restrict channel_means, const int region_w, const int region_h,
1984 const float cf_sigma, const float cf_fmin, const int chan_a, const int chan_b,
1985 const int orientation)
1986{
1988 const size_t region_pixels = (size_t)region_w * region_h;
1989 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
1990 size_t work_size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
1991 const int target_chan = orientation ? chan_b : chan_a;
1992 const int guide_chan = orientation ? chan_a : chan_b;
1993 const int other_chan = 3 - chan_a - chan_b;
1994
1995 cl_mem coeff_slope = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1996 cl_mem coeff_intercept = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1997 cl_mem coeff_r2 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1998 cl_mem anchor = dt_opencl_alloc_device_buffer(devid, region_pixels);
1999 cl_mem broad = dt_opencl_alloc_device_buffer(devid, region_pixels);
2000 if(!coeff_slope || !coeff_intercept || !coeff_r2 || !anchor || !broad) goto out;
2001
2002 // per-pixel single-guide fit (slope cs, intercept ci, fit quality cr2) from the moments
2003 {
2004 const int kernel = global_data->kernel_hl_cf_fit_pair;
2005 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &moment_a);
2006 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &moment_b);
2007 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &valid);
2008 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_w);
2009 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_h);
2010 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &target_chan);
2011 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &orientation);
2012 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &cf_fmin);
2013 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(cl_mem), &coeff_slope);
2014 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(cl_mem), &coeff_intercept);
2015 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(cl_mem), &coeff_r2);
2016 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(cl_mem), &anchor);
2017 dt_opencl_set_kernel_arg(devid, kernel, 12, sizeof(cl_mem), &broad);
2018 dt_opencl_set_kernel_arg(devid, kernel, 13, sizeof(float), &channel_means[target_chan]);
2019 dt_opencl_set_kernel_arg(devid, kernel, 14, sizeof(float), &channel_means[guide_chan]);
2020 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
2021 if(cl_err != CL_SUCCESS) goto out;
2022 }
2023
2024 // harmonic diffusion of slope/intercept/fit-quality fields across the clipped zone
2025 {
2026 const int base_ds = (int)(cf_sigma / 4.f);
2027 cl_mem coeff_planes[2] = { coeff_slope, coeff_intercept }; // shared anchor mask -> one fused fill
2028 cl_err
2029 = _cf_harmonic_fill_cl_n(devid, gd_void, coeff_planes, 2, anchor, region_w, region_h, base_ds, 0, steer);
2030 if(cl_err != CL_SUCCESS) goto out;
2031 cl_err = _cf_harmonic_fill_cl(devid, gd_void, coeff_r2, broad, region_w, region_h, base_ds, 0, steer);
2032 if(cl_err != CL_SUCCESS) goto out;
2033 }
2034
2035 // evaluate the diffused fit against the measured guide; write est + fit score bsc
2036 {
2037 const int kernel = global_data->kernel_hl_cf_eval_pair;
2038 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &coeff_slope);
2039 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &coeff_intercept);
2040 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &coeff_r2);
2041 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &valid);
2042 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &estimate);
2043 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &model_quality);
2044 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &region_w);
2045 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &region_h);
2046 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &target_chan);
2047 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &guide_chan);
2048 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(int), &other_chan);
2049 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
2050 }
2051
2052out:
2053 dt_opencl_release_mem_object(coeff_slope);
2054 dt_opencl_release_mem_object(coeff_intercept);
2058 return cl_err;
2059}
2060
2061// Variant of the joint stage that fits and DIFFUSES the coefficient fields but defers the
2062// evaluation: the caller keeps the four returned buffers (slope a, slope b, offset d, fit
2063// quality r2) to evaluate later in the deep-channel cascade (deep-channel stash).
2064// Caller releases the returned cl_mem buffers. Mirrors the deferred joint fit inside
2065// _region_guided_filter (CPU): any change here must be mirrored there and re-validated with
2066// the HL_CFCL_TEST self-tests.
2067//
2068// MATHS BRIDGE -- article "The algorithm" step 3, the deep channel's ordering subtlety: fit (a,b,d)
2069// and transport them (E_transport) exactly as the joint stage, but DEFER v_hat = a*u1 + b*u2 + d so it
2070// is evaluated only after the other clipped channels are reconstructed -- then every guide it reads is
2071// a continuous surface (no clip-contour arc). Returns the four diffused planes (a, b, d, R^2) to stash.
2072static cl_int _cf_joint_fit_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem mom0,
2073 cl_mem mom1, cl_mem mom2, cl_mem steer, const float *const restrict channel_means,
2074 const int region_w, const int region_h, const float cf_sigma, const float cf_fmin,
2075 const int c, const int guide1, const int guide2, cl_mem *ca_out, cl_mem *cb_out,
2076 cl_mem *cd_out, cl_mem *cr2_out)
2077{
2079 const size_t region_pixels = (size_t)region_w * region_h;
2080 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
2081 size_t work_size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
2082
2083 cl_mem coeff_slope_a = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
2084 cl_mem coeff_slope_b = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
2085 cl_mem coeff_offset = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
2086 cl_mem coeff_r2 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
2087 cl_mem anchor = dt_opencl_alloc_device_buffer(devid, region_pixels);
2088 cl_mem broad = dt_opencl_alloc_device_buffer(devid, region_pixels);
2089 *ca_out = *cb_out = *cd_out = *cr2_out = NULL;
2090 if(!coeff_slope_a || !coeff_slope_b || !coeff_offset || !coeff_r2 || !anchor || !broad) goto out;
2091
2092 {
2093 const int kernel = global_data->kernel_hl_cf_fit_joint;
2094 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &mom0);
2095 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &mom1);
2096 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &mom2);
2097 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &valid);
2098 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
2099 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
2100 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &c);
2101 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &guide1);
2102 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &guide2);
2103 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(float), &cf_fmin);
2104 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(cl_mem), &coeff_slope_a);
2105 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(cl_mem), &coeff_slope_b);
2106 dt_opencl_set_kernel_arg(devid, kernel, 12, sizeof(cl_mem), &coeff_offset);
2107 dt_opencl_set_kernel_arg(devid, kernel, 13, sizeof(cl_mem), &coeff_r2);
2108 dt_opencl_set_kernel_arg(devid, kernel, 14, sizeof(cl_mem), &anchor);
2109 dt_opencl_set_kernel_arg(devid, kernel, 15, sizeof(cl_mem), &broad);
2110 dt_opencl_set_kernel_arg(devid, kernel, 16, sizeof(float), &channel_means[0]);
2111 dt_opencl_set_kernel_arg(devid, kernel, 17, sizeof(float), &channel_means[1]);
2112 dt_opencl_set_kernel_arg(devid, kernel, 18, sizeof(float), &channel_means[2]);
2113 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
2114 if(cl_err != CL_SUCCESS) goto out;
2115 }
2116
2117 {
2118 const int base_ds = (int)(cf_sigma / 4.f);
2119 cl_mem coeff_planes[3]
2120 = { coeff_slope_a, coeff_slope_b, coeff_offset }; // shared anchor mask -> one fused fill
2121 cl_err
2122 = _cf_harmonic_fill_cl_n(devid, gd_void, coeff_planes, 3, anchor, region_w, region_h, base_ds, 0, steer);
2123 if(cl_err != CL_SUCCESS) goto out;
2124 cl_err = _cf_harmonic_fill_cl(devid, gd_void, coeff_r2, broad, region_w, region_h, base_ds, 0, steer);
2125 if(cl_err != CL_SUCCESS) goto out;
2126 }
2127
2128 *ca_out = coeff_slope_a;
2129 *cb_out = coeff_slope_b;
2130 *cd_out = coeff_offset;
2131 *cr2_out = coeff_r2;
2132 coeff_slope_a = coeff_slope_b = coeff_offset = coeff_r2 = NULL;
2133 cl_err = CL_SUCCESS;
2134
2135out:
2136 dt_opencl_release_mem_object(coeff_slope_a);
2137 dt_opencl_release_mem_object(coeff_slope_b);
2138 dt_opencl_release_mem_object(coeff_offset);
2142 return cl_err;
2143}
2144
2145cl_int _cf_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem model_quality,
2146 cl_mem luminance, cl_mem steer, const float *const restrict channel_means,
2147 dt_gaussian_cl_t *gaussian, const int region_w, const int region_h, const float cf_sigma,
2148 const float cf_fmin, const float cf_binv, const int cdeep)
2149{
2150 cl_int cl_err = CL_SUCCESS;
2151 cl_mem deep_a = NULL, deep_b = NULL, deep_d = NULL, deep_r2 = NULL;
2153 size_t size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
2154
2155 // the windowed moments are shared: the joint weight (all-valid x frozen bw_) does not depend
2156 // on the target channel and the evals only write CLIPPED channels, so estimate never changes at chan_a
2157 // weighted pixel -- packed + blur ONCE and fit the three channels from the same fields (the
2158 // per-channel repack the CPU does is redundant on both sides; here the blurs dominate)
2159 cl_mem packed = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2160 cl_mem moment0 = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2161 cl_mem moment1 = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2162 cl_mem moment2 = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2163 cl_mem moments[3];
2164 moments[0] = moment0;
2165 moments[1] = moment1;
2166 moments[2] = moment2;
2167 if(!packed || !moment0 || !moment1 || !moment2)
2168 {
2169 cl_err = DT_OPENCL_DEFAULT_ERROR;
2170 goto out;
2171 }
2172 // three modes = the ten centred moment planes: mode 0 -> [n, wR, wG, wB], mode 1 -> [wRR, wGG, wBB, wRG],
2173 // mode 2 -> [wRB, wGB, unweighted-n, 0]; each packed product image is Gaussian-blurred to a windowed moment
2174 for(int mode = 0; mode < 3 && cl_err == CL_SUCCESS; mode++)
2175 {
2176 const int kernel = global_data->kernel_hl_cf_pack_joint;
2177 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
2178 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
2179 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &luminance);
2180 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &packed);
2181 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
2182 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
2183 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &cf_binv);
2184 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &mode);
2185 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(float), &channel_means[0]);
2186 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(float), &channel_means[1]);
2187 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(float), &channel_means[2]);
2188 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
2189 if(cl_err == CL_SUCCESS)
2190 cl_err = gaussian ? dt_gaussian_blur_cl(gaussian, packed, moments[mode])
2191 : _region_blur_cl(devid, packed, moments[mode], region_w, region_h, cf_sigma);
2192 }
2193 if(cl_err != CL_SUCCESS) goto out;
2194
2195 // joint fits: immediate strict eval for the non-deep channels, stash for the deep one
2196 for(int c = 0; c < 3; c++)
2197 {
2198 const int guide1 = (c == 0) ? 1 : 0;
2199 const int guide2 = (c == 2) ? 1 : 2;
2200 if(c == cdeep)
2201 {
2202 cl_err = _cf_joint_fit_cl(devid, gd_void, estimate, valid, moment0, moment1, moment2, steer, channel_means,
2203 region_w, region_h, cf_sigma, cf_fmin, c, guide1, guide2, &deep_a, &deep_b,
2204 &deep_d, &deep_r2);
2205 }
2206 else
2207 cl_err = _cf_joint_stage_cl(devid, gd_void, estimate, valid, model_quality, moment0, moment1, moment2, steer,
2208 channel_means, region_w, region_h, cf_sigma, cf_fmin, c, guide1, guide2);
2209 if(cl_err != CL_SUCCESS) goto out;
2210 }
2211
2212 // pair fallbacks: the pair weight (both-valid x frozen bw_) is orientation-independent and
2213 // estimate at weighted pixels never changes, so packed each pair's moments once for both orientations
2214 for(int chan_a = 0; chan_a < 3; chan_a++)
2215 for(int chan_b = chan_a + 1; chan_b < 3; chan_b++)
2216 {
2217 for(int mode = 0; mode < 2 && cl_err == CL_SUCCESS; mode++)
2218 {
2219 const int kernel = global_data->kernel_hl_cf_pack_pair;
2220 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
2221 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
2222 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &luminance);
2223 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &packed);
2224 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
2225 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
2226 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &cf_binv);
2227 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &chan_a);
2228 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &chan_b);
2229 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &mode);
2230 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(float), &channel_means[chan_a]);
2231 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(float), &channel_means[chan_b]);
2232 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
2233 if(cl_err == CL_SUCCESS)
2234 cl_err = gaussian
2235 ? dt_gaussian_blur_cl(gaussian, packed, mode ? moment1 : moment0)
2236 : _region_blur_cl(devid, packed, mode ? moment1 : moment0, region_w, region_h, cf_sigma);
2237 }
2238 for(int orientation = 0; orientation < 2 && cl_err == CL_SUCCESS; orientation++)
2239 cl_err
2240 = _cf_pair_stage_cl(devid, gd_void, estimate, valid, model_quality, moment0, moment1, steer,
2241 channel_means, region_w, region_h, cf_sigma, cf_fmin, chan_a, chan_b, orientation);
2242 if(cl_err != CL_SUCCESS) goto out;
2243 }
2244
2245 // deferred deep evaluation: blur the deep-channel validity masks (feathered depth split),
2246 // then evaluate the stashed coefficient fields now that the guide channels are reconstructed
2247 if(deep_a)
2248 {
2249 const int guide1 = (cdeep == 0) ? 1 : 0;
2250 const int guide2 = (cdeep == 2) ? 1 : 2;
2251 cl_mem deep_packed = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2252 cl_mem mask_blurred = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2253 if(!deep_packed || !mask_blurred)
2254 {
2255 dt_opencl_release_mem_object(deep_packed);
2256 dt_opencl_release_mem_object(mask_blurred);
2257 cl_err = DT_OPENCL_DEFAULT_ERROR;
2258 goto out;
2259 }
2260
2261 const int kernel_mask = global_data->kernel_hl_cf_pack_deepmask;
2262 dt_opencl_set_kernel_arg(devid, kernel_mask, 0, sizeof(cl_mem), &valid);
2263 dt_opencl_set_kernel_arg(devid, kernel_mask, 1, sizeof(cl_mem), &deep_packed);
2264 dt_opencl_set_kernel_arg(devid, kernel_mask, 2, sizeof(int), &region_w);
2265 dt_opencl_set_kernel_arg(devid, kernel_mask, 3, sizeof(int), &region_h);
2266 dt_opencl_set_kernel_arg(devid, kernel_mask, 4, sizeof(int), &cdeep);
2267 dt_opencl_set_kernel_arg(devid, kernel_mask, 5, sizeof(int), &guide1);
2268 dt_opencl_set_kernel_arg(devid, kernel_mask, 6, sizeof(int), &guide2);
2269 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel_mask, size);
2270 if(cl_err == CL_SUCCESS)
2271 cl_err = gaussian ? dt_gaussian_blur_cl(gaussian, deep_packed, mask_blurred)
2272 : _region_blur_cl(devid, deep_packed, mask_blurred, region_w, region_h, cf_sigma);
2273
2274 if(cl_err == CL_SUCCESS)
2275 {
2276 const int kernel_eval = global_data->kernel_hl_cf_eval_deep;
2277 dt_opencl_set_kernel_arg(devid, kernel_eval, 0, sizeof(cl_mem), &deep_a);
2278 dt_opencl_set_kernel_arg(devid, kernel_eval, 1, sizeof(cl_mem), &deep_b);
2279 dt_opencl_set_kernel_arg(devid, kernel_eval, 2, sizeof(cl_mem), &deep_d);
2280 dt_opencl_set_kernel_arg(devid, kernel_eval, 3, sizeof(cl_mem), &deep_r2);
2281 dt_opencl_set_kernel_arg(devid, kernel_eval, 4, sizeof(cl_mem), &mask_blurred);
2282 dt_opencl_set_kernel_arg(devid, kernel_eval, 5, sizeof(cl_mem), &valid);
2283 dt_opencl_set_kernel_arg(devid, kernel_eval, 6, sizeof(cl_mem), &estimate);
2284 dt_opencl_set_kernel_arg(devid, kernel_eval, 7, sizeof(cl_mem), &model_quality);
2285 dt_opencl_set_kernel_arg(devid, kernel_eval, 8, sizeof(int), &region_w);
2286 dt_opencl_set_kernel_arg(devid, kernel_eval, 9, sizeof(int), &region_h);
2287 dt_opencl_set_kernel_arg(devid, kernel_eval, 10, sizeof(int), &cdeep);
2288 dt_opencl_set_kernel_arg(devid, kernel_eval, 11, sizeof(int), &guide1);
2289 dt_opencl_set_kernel_arg(devid, kernel_eval, 12, sizeof(int), &guide2);
2290 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel_eval, size);
2291 }
2292 dt_opencl_release_mem_object(deep_packed);
2293 dt_opencl_release_mem_object(mask_blurred);
2294 }
2295
2296out:
2305 return cl_err;
2306}
2307
2308cl_int _hf_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem model_quality,
2309 cl_mem luminance, cl_mem steer, dt_gaussian_cl_t *gaussian, const int region_w,
2310 const int region_h, const float cf_sigma, const float cf_fmin, const float cf_binv)
2311{
2313 const size_t region_pixels = (size_t)region_w * region_h;
2314 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
2315 size_t size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
2316 const float blur_sigma = fmaxf(cf_sigma / 4.f, 2.f);
2317
2318 cl_mem packed = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2319 cl_mem lowpass = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2320 cl_mem moment0 = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2321 cl_mem moment1 = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2322 cl_mem moment2 = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2323 cl_mem energy = dt_opencl_alloc_device(devid, size[0], size[1], sizeof(float) * 4);
2324 cl_mem moments[3];
2325 cl_mem gain_a = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
2326 cl_mem gain_b = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
2327 cl_mem anchor = dt_opencl_alloc_device_buffer(devid, region_pixels);
2328 moments[0] = moment0;
2329 moments[1] = moment1;
2330 moments[2] = moment2;
2331 if(!packed || !lowpass || !moment0 || !moment1 || !moment2 || !energy || !gain_a || !gain_b || !anchor) goto out;
2332
2333 // lowpass of estimate (computed ONCE, shared by every channel and the damped path)
2334 {
2335 const int kernel = global_data->kernel_hl_buf_to_img;
2336 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
2337 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &packed);
2338 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &region_w);
2339 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_h);
2340 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
2341 if(cl_err != CL_SUCCESS) goto out;
2342 cl_err = _region_blur_cl(devid, packed, lowpass, region_w, region_h, blur_sigma);
2343 if(cl_err != CL_SUCCESS) goto out;
2344 }
2345
2346 // windowed moments of the detail band (estimate minus lowpass), packed then blurred
2347 for(int mode = 0; mode < 3; mode++)
2348 {
2349 const int kernel = global_data->kernel_hl_hf_pack;
2350 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
2351 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
2352 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &luminance);
2353 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &lowpass);
2354 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &packed);
2355 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_w);
2356 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &region_h);
2357 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &cf_binv);
2358 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &mode);
2359 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
2360 if(cl_err != CL_SUCCESS) goto out;
2361 cl_err = gaussian ? dt_gaussian_blur_cl(gaussian, packed, moments[mode])
2362 : _region_blur_cl(devid, packed, moments[mode], region_w, region_h, cf_sigma);
2363 if(cl_err != CL_SUCCESS) goto out;
2364 }
2365
2366 // per channel: fit detail-band gains, diffuse them, measure both candidates' energy, evaluate
2367 for(int c = 0; c < 3; c++)
2368 {
2369 const int guide1 = (c == 0) ? 1 : 0;
2370 const int guide2 = (c == 2) ? 1 : 2;
2371
2372 // fit the quality-shrunk detail-band gains (gain_a, gain_b) and the anchor mask
2373 {
2374 const int kernel = global_data->kernel_hl_hf_fit;
2375 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &moment0);
2376 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &moment1);
2377 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &moment2);
2378 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &valid);
2379 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
2380 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
2381 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &c);
2382 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &guide1);
2383 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &guide2);
2384 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(float), &cf_fmin);
2385 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(cl_mem), &gain_a);
2386 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(cl_mem), &gain_b);
2387 dt_opencl_set_kernel_arg(devid, kernel, 12, sizeof(cl_mem), &anchor);
2388 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
2389 if(cl_err != CL_SUCCESS) goto out;
2390 }
2391
2392 // harmonic diffusion of the two gain fields across the clipped zone
2393 const int base_ds = (int)(cf_sigma / 4.f);
2394 cl_mem gain_pair[2] = { gain_a, gain_b }; // shared anchor mask -> one fused fill
2395 cl_err = _cf_harmonic_fill_cl_n(devid, gd_void, gain_pair, 2, anchor, region_w, region_h, base_ds, 0, steer);
2396 if(cl_err != CL_SUCCESS) goto out;
2397
2398 // local energy of the guided vs damped detail candidates (blurred absolute values)
2399 {
2400 const int kernel = global_data->kernel_hl_hf_energy;
2401 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
2402 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
2403 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &model_quality);
2404 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &lowpass);
2405 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &gain_a);
2406 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &gain_b);
2407 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), &packed);
2408 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &region_w);
2409 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &region_h);
2410 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &c);
2411 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(int), &guide1);
2412 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(int), &guide2);
2413 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
2414 if(cl_err != CL_SUCCESS) goto out;
2415 cl_err = _region_blur_cl(devid, packed, energy, region_w, region_h, blur_sigma);
2416 if(cl_err != CL_SUCCESS) goto out;
2417 }
2418
2419 // minimum-energy blend of the two candidates at strict (both-guides-valid) targets
2420 {
2421 const int kernel = global_data->kernel_hl_hf_eval;
2422 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
2423 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
2424 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &model_quality);
2425 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &lowpass);
2426 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &energy);
2427 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &gain_a);
2428 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), &gain_b);
2429 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &region_w);
2430 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &region_h);
2431 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &c);
2432 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(int), &guide1);
2433 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(int), &guide2);
2434 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
2435 if(cl_err != CL_SUCCESS) goto out;
2436 }
2437 }
2438
2439 // single-guide pixels: only the quality-damped detail (no guided resynthesis possible)
2440 {
2441 const int kernel = global_data->kernel_hl_hf_damp;
2442 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
2443 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
2444 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &model_quality);
2445 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &lowpass);
2446 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
2447 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
2448 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
2449 }
2450
2451out:
2461 return cl_err;
2462}
2463
2464#endif // HAVE_OPENCL
cl_int _region_blur_cl(const int devid, cl_mem in, cl_mem out, const int region_w, const int region_h, const float sigma)
Definition blur.c:76
static void _region_blur(const float *const restrict in, float *const restrict out, const int region_w, const int region_h, const float sigma)
Definition blur.h:34
static float _aniso_edge_w(const float *const restrict tensor_xx, const float *const restrict tensor_xy, const float *const restrict tensor_yy, const size_t i, const size_t j, const int offset_x, const int offset_y)
Definition chroma.h:54
static cl_int _cf_harmonic_fill_cl_n(const int devid, void *gd_void, cl_mem *vals, const int n_planes_in, cl_mem hole, const int region_w, const int region_h, const int base_ds, const int mask_is_hole, cl_mem steer)
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)
static cl_int _cf_pair_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem model_quality, cl_mem moment_a, cl_mem moment_b, cl_mem steer, const float *const restrict channel_means, const int region_w, const int region_h, const float cf_sigma, const float cf_fmin, const int chan_a, const int chan_b, const int orientation)
cl_int _cf_harmonic_fill_cl(const int devid, void *gd_void, cl_mem val, cl_mem hole, const int region_w, const int region_h, const int base_ds, const int mask_is_hole, cl_mem steer)
#define HF_M2(nb_index, coef_a, coef_b)
static __DT_CLONE_TARGETS__ void _cf_fill_relax_3(float *const restrict field, float *const restrict tmp, const uint8_t *const restrict level_anchor, const float *const restrict edge_weights, const float *const restrict edge_weight_sum, const int coarse_w, const int coarse_h, const size_t cell_count, const int steered)
static __DT_CLONE_TARGETS__ void _cf_fill_relax_4(float *const restrict field, float *const restrict tmp, const uint8_t *const restrict level_anchor, const float *const restrict edge_weights, const float *const restrict edge_weight_sum, const int coarse_w, const int coarse_h, const size_t cell_count, const int steered)
__DT_CLONE_TARGETS__ void _cf_reconstruct(_hl_region_ctx_t *const ctx)
static __DT_CLONE_TARGETS__ void _cf_adaptive_tensor(const float *const restrict luminance, float *const restrict tensor_xx, float *const restrict tensor_xy, float *const restrict tensor_yy, float *const restrict scratch_lin, float *const restrict scratch_quad, const int region_w, const int region_h, const float k)
static __DT_CLONE_TARGETS__ void _cf_fill_relax_2(float *const restrict field, float *const restrict tmp, const uint8_t *const restrict level_anchor, const float *const restrict edge_weights, const float *const restrict edge_weight_sum, const int coarse_w, const int coarse_h, const size_t cell_count, const int steered)
static __DT_CLONE_TARGETS__ void _cf_harmonic_fill_n(float *const restrict *vals, const int n_planes_in, const uint8_t *const restrict hole, const int region_w, const int region_h, const int base_ds, const float *const restrict steer, const dt_dev_pixelpipe_t *pipe)
#define CF_M2(nb_index, coef_a, coef_b)
cl_int _cf_joint_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem model_quality, cl_mem mom0, cl_mem mom1, cl_mem mom2, cl_mem steer, const float *const restrict channel_means, const int region_w, const int region_h, const float cf_sigma, const float cf_fmin, const int c, const int guide1, const int guide2)
static __DT_CLONE_TARGETS__ void _cf_fill_relax_1(float *const restrict field, float *const restrict tmp, const uint8_t *const restrict level_anchor, const float *const restrict edge_weights, const float *const restrict edge_weight_sum, const int coarse_w, const int coarse_h, const size_t cell_count, const int steered)
static cl_int _cf_joint_fit_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem mom0, cl_mem mom1, cl_mem mom2, cl_mem steer, const float *const restrict channel_means, const int region_w, const int region_h, const float cf_sigma, const float cf_fmin, const int c, const int guide1, const int guide2, cl_mem *ca_out, cl_mem *cb_out, cl_mem *cd_out, cl_mem *cr2_out)
void _cf_harmonic_fill(float *const restrict val, const uint8_t *const restrict hole, const int region_w, const int region_h, const int base_ds, const float *const restrict steer, const dt_dev_pixelpipe_t *pipe)
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)
#define DEFINE_CF_FILL_RELAX(NP)
const dt_colormatrix_t dt_aligned_pixel_t out
const float delta
#define dt_pixelpipe_cache_alloc_align(size, pipe)
Definition darktable.h:449
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
#define dt_pixelpipe_cache_alloc_align_float(pixels, pipe)
Definition darktable.h:454
#define __DT_CLONE_TARGETS__
Definition darktable.h:379
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:30
static float gaussian(float x, float std)
Definition filmic.c:397
cl_int dt_gaussian_blur_cl(dt_gaussian_cl_t *g, cl_mem dev_in, cl_mem dev_out)
Definition gaussian.c:441
static float kernel(const float *x, const float *y)
static const float x
float *const restrict luminance
float *const restrict const size_t k
size_t size
Definition mipmap_cache.c:3
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2164
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:2580
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2504
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:2155
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:2170
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2415
#define DT_OPENCL_DEFAULT_ERROR
Definition opencl.h:57
#define ROUNDUPDHT(a, b)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:81
#define DT_HL_FILL_CL_MAXP
#define DT_HL_CF_K
#define DT_HL_FILL_MAXP
#define HL_PFOR(...)
const _hl_region_t * region
const dt_dev_pixelpipe_t * pipe
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29