Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
core.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// Self-dome fallback and all-clip joint core stages (CPU + OpenCL). (implementation; see core.h for the public
20// API.)
21
22#include "system/openmp.h"
23#include "system/simd.h"
27#include "iop/highlights/core.h"
28#include "iop/highlights/dome.h"
29#include "iop/highlights/knee.h"
30#include "iop/highlights/pde.h"
31#include <glib/gstdio.h>
32#include <math.h>
33#include <string.h>
34
37{
38 const _hl_region_t *const region = ctx->region;
39 const dt_dev_pixelpipe_t *const pipe = ctx->pipe;
40 const int region_w = ctx->region_w;
41 const int region_h = ctx->region_h;
42 const size_t region_pixels = ctx->region_pixels;
43 const float epsilon = ctx->epsilon;
44 float *const restrict estimate = ctx->estimate;
45 float *const restrict valid = ctx->valid;
46 float *const restrict plane1 = ctx->plane1;
47 float *const restrict valid_variance = ctx->valid_variance;
48 float *const restrict clip0 = ctx->clip0;
49 uint8_t *const restrict hole = ctx->hole;
50 float *const restrict solver_field = ctx->solver_field;
51 float *const restrict dome_lum = ctx->dome_lum;
52 float *const restrict lum_accum = ctx->lum_accum;
53 float *const restrict flat_target = ctx->flat_target;
54
55 // --- decide whether the per-channel self-dome fallback is worth solving ---
56 // It only matters where a channel is clipped, a guide survives, yet the colour-line is
57 // weak (We = Wc^2 well below 1): decorrelated content. Correlated content stays on the
58 // guide (We ~ 1), so skip the three biharmonic solves entirely -- the common case.
59 int need_self = 0;
60 for(size_t i = 0; i < region_pixels; i++)
61 {
62 const int anyvalid = (valid[i * 4 + 0] >= 0.5f) || (valid[i * 4 + 1] >= 0.5f) || (valid[i * 4 + 2] >= 0.5f);
63 if(!anyvalid) continue;
64 for(int c = 0; c < 3; c++)
65 if(valid[i * 4 + c] < 0.5f && valid_variance[i * 4 + c] * valid_variance[i * 4 + c] < 0.9f) need_self = 1;
66 if(need_self) break;
67 }
68
69 // --- self-dome fallback, only if needed ---
70 if(need_self)
71 {
72 // One SHARED downsampling factor sized from the UNION (any-clip) hole -- the largest, so
73 // the coarse grid stays within DT_HL_DOME_NMAX and every channel is approximated at the
74 // same resolution.
75 size_t nh_union = 0;
76 for(size_t i = 0; i < region_pixels; i++)
77 if(valid[i * 4 + 0] < 0.5f || valid[i * 4 + 1] < 0.5f || valid[i * 4 + 2] < 0.5f) nh_union++;
78
79 const int ds_shared = MAX(1, (int)ceilf(sqrtf((float)nh_union / (float)DT_HL_DOME_NMAX_SPARSE)));
80
81 // HUE-COUPLED dome: three independently-domed channels can drift apart exactly where the
82 // fallback engages (a low-R^2 zone), splitting the hue toward green/magenta -- the original
83 // failure this fallback used to be disabled for. Instead dome ONE shared quantity per kind:
84 // the LUMINANCE (biharmonic, gradient-extending) and a SMOOTH chromaticity (harmonic fill
85 // of the ratios from the rim). dome_c = L_dome * chroma_c: every channel shares the same
86 // shape, so the fallback cannot drift the hue by construction.
87 HL_PFOR()
88 for(size_t i = 0; i < region_pixels; i++)
89 {
90 hole[i] = (valid[i * 4 + 0] < 0.5f || valid[i * 4 + 1] < 0.5f || valid[i * 4 + 2] < 0.5f);
91 lum_accum[i] = estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2]; // L_sum = R+G+B
92 solver_field[i] = lum_accum[i];
93 }
94
95 // one shared biharmonic BRIGHTNESS dome over the union hole: Delta^2 L_sum = 0 with the
96 // valid rim as Dirichlet data (term 2 of E_bihar, hue-coupled form). Doming L_sum once and
97 // reusing it for all channels is what prevents three per-channel domes drifting the hue.
98 _biharmonic_dome(solver_field, hole, region_w, region_h, ds_shared, pipe);
99 memcpy(dome_lum, solver_field, region_pixels * sizeof(float));
100
101 // smooth chromaticity over the union hole (ratio planes stored in s1's 4-ch layout): each
102 // channel's ratio r_c = est_c / L_sum is a BOUNDED quantity, so a plain harmonic fill (flat
103 // rim-matched inpaint, no biharmonic doming) is the right tool -- brightness gets the dome,
104 // colour gets the harmonic fill, and recombining as dome_c = L_dome * r_c couples the hue.
105 const int cf_base = (int)(CLAMP(region->radius / 6.f, 8.f, 64.f) / 4.f);
106 const float floor_gate = ctx->floor_gate; // clip-asymmetry gate, see _hl_floor_gate (common.h)
107
108 // Mean valid chromaticity (gate > 0 only): the flat target the hole-interior dome chroma is
109 // pulled toward, lifting it off the biased rim-harmonic value toward the true surround. The
110 // pull strength scales with the gate (0 at unit-WB clips = approved behavior).
111 dt_aligned_pixel_t cmean = { 0.f, 0.f, 0.f, 0.f };
112 float cmean_beta = 0.f;
113 float refine_gate = 0.f; // floor_gate x trusted-ring vote (see below)
114 if(floor_gate > 1e-6f)
115 {
116 // BRIGHT valid pixels only (>= 0.35 x blown-zone plateau): the whole-window mean is
117 // contaminated by dark, unrelated content (the cgrad anchors learned the same lesson) --
118 // measured on MAC/sunrise: the ring vote against the all-valid mean stays closed on
119 // exactly the scenes the refinements are for.
120 double plateau_sum = 0.0, plateau_count = 0.0;
121 for(size_t i = 0; i < region_pixels; i++)
122 if(hole[i])
123 {
124 plateau_sum += (double)lum_accum[i];
125 plateau_count += 1.0;
126 }
127 const float lum_min = (plateau_count > 0.0) ? 0.35f * (float)(plateau_sum / plateau_count) : 0.f;
128 double cmean_accum[3] = { 0.0, 0.0, 0.0 };
129 double cmean_count = 0.0;
130 for(size_t i = 0; i < region_pixels; i++)
131 if(!hole[i] && lum_accum[i] >= lum_min)
132 {
133 const float inv_lum = 1.f / fmaxf(lum_accum[i], epsilon);
134 cmean_accum[0] += (double)(estimate[i * 4 + 0] * inv_lum);
135 cmean_accum[1] += (double)(estimate[i * 4 + 1] * inv_lum);
136 cmean_accum[2] += (double)(estimate[i * 4 + 2] * inv_lum);
137 cmean_count += 1.0;
138 }
139 if(cmean_count > 0.0)
140 {
141 for(int c = 0; c < 3; c++) cmean[c] = (float)(cmean_accum[c] / cmean_count);
142 // Trusted-ring vote on the flat-mean prior: the surround-importing refinements (this
143 // pull + the decoupled recombine below) only engage where the 1-clip ring confirms the
144 // region's mean colour describes the blown core (white lamps, uniform skies); they stand
145 // down on self-coloured emitters and gradient skies, leaving the joint floors.
146 refine_gate = floor_gate * _hl_ring_flat_mean_vote(estimate, valid, cmean, region_pixels);
147 cmean_beta = 0.5f * refine_gate;
148 }
149 }
150
151 for(int c = 0; c < 3; c++)
152 {
153 HL_PFOR()
154 for(size_t i = 0; i < region_pixels; i++)
155 flat_target[i] = estimate[i * 4 + c] / fmaxf(lum_accum[i], epsilon); // ratio r_c = est_c / L_sum
156
157 _cf_harmonic_fill(flat_target, hole, region_w, region_h, cf_base, NULL, pipe); // harmonic (Delta r = 0)
158
159 HL_PFOR()
160 for(size_t i = 0; i < region_pixels; i++)
161 {
162 float ratio = fmaxf(flat_target[i], 0.f);
163 if(hole[i] && cmean_beta > 0.f) ratio = (1.f - cmean_beta) * ratio + cmean_beta * cmean[c];
164 plane1[i * 4 + c] = ratio;
165 }
166 }
167
168 // recombine dome_c = L_dome * (r_c / sum r) and blend it into the estimate by the depth-gated
169 // KEEP weight conf_weight = Wc^2 (= 1 - dome_fraction of step 6): est = keep*est + (1-keep)*dome.
170 // A pixel with no surviving guide takes the dome outright (the all-clip core rebuilds it just after).
171 //
172 // CHROMA-DECOUPLED variant (blended by the clip-asymmetry gate): the per-channel blend lets the
173 // colour-line fit's biased chromaticity survive on multi-clip pixels; decoupling keeps the fit's
174 // per-channel LUMINANCE (keep = Wc^2) but reprojects the clipped SUBSET onto the dome's
175 // chromaticity. At gate 0 the per-channel blend runs verbatim (approved behavior).
176 HL_PFOR()
177 for(size_t i = 0; i < region_pixels; i++)
178 {
179 if(!hole[i]) continue;
180
181 const float caccum = fmaxf(plane1[i * 4 + 0] + plane1[i * 4 + 1] + plane1[i * 4 + 2], epsilon); // sum r
182 const int anyvalid = (valid[i * 4 + 0] >= 0.5f) || (valid[i * 4 + 1] >= 0.5f) || (valid[i * 4 + 2] >= 0.5f);
183
184 float blended_sub = 0.f, dome_sub = 0.f;
185 // the approved per-channel depth-gated blend (keep*est + (1-keep)*dome), kept verbatim as
186 // the gate-0 path and as one leg of the gated chroma-decoupled blend below;
187 // only clipped channels are written AND read; the init quiets -Wmaybe-uninitialized
188 float per_channel_blend[3] = { 0.f, 0.f, 0.f };
189 for(int c = 0; c < 3; c++)
190 if(valid[i * 4 + c] < 0.5f)
191 {
192 const float dome = dome_lum[i] * (plane1[i * 4 + c] / caccum); // dome_c = L_dome * chroma share
193 const float conf_weight = valid_variance[i * 4 + c] * valid_variance[i * 4 + c]; // keep = Wc^2
194 per_channel_blend[c] = anyvalid ? (conf_weight * estimate[i * 4 + c] + (1.f - conf_weight) * dome) : dome;
195 blended_sub += per_channel_blend[c];
196 dome_sub += dome;
197 }
198 for(int c = 0; c < 3; c++)
199 if(valid[i * 4 + c] < 0.5f)
200 {
201 if(refine_gate <= 1e-6f || !anyvalid || dome_sub <= epsilon)
202 {
203 estimate[i * 4 + c] = per_channel_blend[c]; // bit-exact approved path
204 continue;
205 }
206 const float decoupled = blended_sub * (dome_lum[i] * (plane1[i * 4 + c] / caccum) / dome_sub);
207 estimate[i * 4 + c] = refine_gate * decoupled + (1.f - refine_gate) * per_channel_blend[c];
208 }
209 }
210
211 // Re-assert the saturation floor AFTER the self dome (the prototype floors here): the dome only
212 // continues the valid rim, it does not know about saturation, so it can undershoot a clipped
213 // channel below its clip level. JOINT form blended by the clip-asymmetry gate (one scalar lift
214 // of the clipped subset preserves the reconstruction's chromaticity; per-channel at gate 0).
216 for(size_t i = 0; i < region_pixels; i++)
217 {
218 float lift = 1.f;
219 if(floor_gate > 1e-6f)
220 for(int c = 0; c < 3; c++)
221 if(valid[i * 4 + c] < 0.5f)
222 {
223 const float e = fmaxf(estimate[i * 4 + c], 1e-6f);
224 lift = fmaxf(lift, fminf(fmaxf(e, clip0[i * 4 + c]) / e, 8.f));
225 }
226 // Both candidates for every channel, so the decision below compares them as COLOURS. Same
227 // criterion as the coefficient field's floor (see _cf_reconstruct): the joint form keeps the
228 // fit and its noise, the per-channel floor clamps both -- so pay the noise only in proportion
229 // to the chromaticity actually rescued. Measured on this site: it is the DOMINANT contributor
230 // on a large blown sky (PK1_3540's G went 11.89 -> 14.39 here with the gate open, 10.79 ->
231 // 10.75 with it shut), while MAC25640's magenta rescue needs it in full.
232 float per_chan_v[3], joint_v[3];
233 for(int c = 0; c < 3; c++)
234 {
235 per_chan_v[c] = joint_v[c] = estimate[i * 4 + c];
236 if(valid[i * 4 + c] >= 0.5f) continue;
237 per_chan_v[c] = fmaxf(estimate[i * 4 + c], clip0[i * 4 + c]);
238 joint_v[c] = fmaxf(fmaxf(estimate[i * 4 + c], 1e-6f) * lift, clip0[i * 4 + c]);
239 }
240 if(floor_gate <= 1e-6f)
241 {
242 for(int c = 0; c < 3; c++)
243 if(valid[i * 4 + c] < 0.5f) estimate[i * 4 + c] = per_chan_v[c]; // bit-exact approved path
244 }
245 else
246 {
247 float sum_p = 0.f, sum_j = 0.f;
248 for(int c = 0; c < 3; c++)
249 {
250 sum_p += per_chan_v[c];
251 sum_j += joint_v[c];
252 }
253 sum_p = fmaxf(sum_p, 1e-9f);
254 sum_j = fmaxf(sum_j, 1e-9f);
255 float chroma_gain = 0.f;
256 for(int c = 0; c < 3; c++) chroma_gain += fabsf(joint_v[c] / sum_j - per_chan_v[c] / sum_p);
257 const float w = floor_gate * ctx->region_worth * _hl_floor_worth(chroma_gain);
258 for(int c = 0; c < 3; c++)
259 if(valid[i * 4 + c] < 0.5f)
260 estimate[i * 4 + c] = per_chan_v[c] + w * (joint_v[c] - per_chan_v[c]);
261 }
262 }
263 }
264}
265
268{
269 const _hl_region_t *const region = ctx->region;
270 const dt_dev_pixelpipe_t *const pipe = ctx->pipe;
271 const int region_w = ctx->region_w;
272 const int region_h = ctx->region_h;
273 const size_t region_pixels = ctx->region_pixels;
274 const float epsilon = ctx->epsilon;
275 const int max_cg_iter = ctx->max_cg_iter;
276 const float solid_color = ctx->solid_color;
277 float *const restrict estimate = ctx->estimate;
278 float *const restrict valid = ctx->valid;
279 float *const restrict plane1 = ctx->plane1;
280 float *const restrict clip0 = ctx->clip0;
281 uint8_t *const restrict hole = ctx->hole;
282 float *const restrict solver_field = ctx->solver_field;
283 float *const restrict dome_lum = ctx->dome_lum;
284 float *const restrict lum_accum = ctx->lum_accum;
285 float *const restrict reaction_weight = ctx->reaction_weight;
286 float *const restrict flat_target = ctx->flat_target;
287 float *const restrict cg_residual = ctx->cg_residual;
288 float *const restrict cg_dir = ctx->cg_dir;
289 float *const restrict cg_operator = ctx->cg_operator;
290 float *const restrict cg_tmp1 = ctx->cg_tmp1;
291 float *const restrict cg_tmp2 = ctx->cg_tmp2;
292
293 // --- all-clipped core: shared biharmonic luminance dome x diffused chromaticity ---
294 // Only pixels with NO surviving channel. Extending this to 2-clip pixels was tried and reverted:
295 // the bright sky is itself 2-clip (R,G clipped, B not), so it got swept into the coupled core
296 // and filled with diffused magenta chroma that bled into the sky. 2-clip pixels keep their
297 // (two-or-one-guide) guided/self-dome estimate; only the truly guide-less core is rebuilt here.
298 //
299 // MATHS BRIDGE -- Step 7 all-clip core (article §"Filling holes with no survivor", §"The
300 // algorithm" step 7). Magnitude and chrominance are split and reconstructed by different
301 // operators: ONE shared biharmonic luminance dome L_dome (Delta^2 L_sum = 0, E_bihar) for the
302 // magnitude common to all three channels, and the screened-Poisson rim-diffused chrominance
303 // r = RGB/L_sum ((lambda*I-Delta) r = lambda_solid*r_target, E_chrominance) carried inward from
304 // the reconstructed annulus. Recombination core_c = L_dome * (r_c / sum_j r_j), then a feathered
305 // blurred hand-over into the surrounding coefficient-field reconstruction (no hard core rim).
306 int has_allc = 0;
307 __OMP_PARALLEL_FOR__(reduction(| : has_allc))
308 for(size_t i = 0; i < region_pixels; i++)
309 {
310 hole[i] = (valid[i * 4 + 0] < 0.5f && valid[i * 4 + 1] < 0.5f && valid[i * 4 + 2] < 0.5f);
311 if(hole[i]) has_allc = 1;
312 }
313
314 if(has_allc)
315 {
316 // one shared luminance dome (biharmonic) from the reconstructed annulus rim
317 // L_sum = R + G + B (the summed luminance, the magnitude shared by all three channels)
319 for(size_t i = 0; i < region_pixels; i++)
320 {
321 lum_accum[i] = estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
322 solver_field[i] = lum_accum[i];
323 }
324
325 // Delta^2 L_sum = 0 on the core, L_sum|dOmega = L_valid on the reconstructed annulus rim:
326 // E_bihar magnitude dome (one scalar solve, not three, so no channel collapses off-hue)
327 _biharmonic_dome(solver_field, hole, region_w, region_h, 0,
328 pipe); // shared biharmonic luminance dome (auto ds)
329 memcpy(dome_lum, solver_field, region_pixels * sizeof(float));
330
331 // The all-clip core has EVERY channel saturated, so its luminance is at least the accum of the
332 // clip levels -- the brightest, not something to extrapolate downward. The biharmonic dome can
333 // dip below that (the floored rim has no upward gradient to continue), which darkens the centre
334 // below the annulus. Floor the dome at the saturated accum so the core is never darker than "all
335 // channels at clip". Above-clip doming is kept where the dome exceeds it.
337 for(size_t i = 0; i < region_pixels; i++)
338 if(hole[i])
339 {
340 // saturation floor on the dome: L_dome >= sum_c clip0_c ("all three channels at clip",
341 // the brightest the core can be); monotone, so it never dims a valid rim or shifts hue
342 const float lsat = clip0[i * 4 + 0] + clip0[i * 4 + 1] + clip0[i * 4 + 2];
343 dome_lum[i] = fmaxf(dome_lum[i], lsat);
344 }
345
346 // mean valid chromaticity -> flat target for the "inpaint a flat color" slider
347 // r_target = <RGB/L_sum> over fully-valid pixels: the screened-Poisson reaction pulls the
348 // core chroma toward this flat colour (article's bar-c_c, the mean valid chromaticity)
349 // accumulate in DOUBLE: a float running accum of ~1e5 terms carries an ULP of ~4e-3 per
350 // add near its final magnitude, which biased the mean by ~1e-4 relative (enough to show
351 // as a 4e-4 CPU-vs-GPU divergence on the reaction target)
352 dt_aligned_pixel_t cmean = { 0.f, 0.f, 0.f, 0.f };
353 double cacc[3] = { 0.0, 0.0, 0.0 };
354 double count = 0.0;
355 for(size_t i = 0; i < region_pixels; i++)
356 {
357 if(!(valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f)) continue;
358 const float invL = 1.f / fmaxf(lum_accum[i], epsilon);
359 cacc[0] += (double)(estimate[i * 4 + 0] * invL);
360 cacc[1] += (double)(estimate[i * 4 + 1] * invL);
361 cacc[2] += (double)(estimate[i * 4 + 2] * invL);
362 count += 1.0;
363 }
364 if(count > 0.0)
365 for(int c = 0; c < 3; c++) cmean[c] = (float)(cacc[c] / count);
366
367 // Re-hue the all-clip core's saturation floor toward the mean valid chromaticity, blended by
368 // the clip-asymmetry gate (see _hl_floor_gate). With WB'd clips, clip0's own chromaticity is
369 // the inverse-WB magenta -- it is a magnitude floor, not a colour -- yet the downstream aniso
370 // stage uses it as its ratio-space obstacle and reassembly floor, pinning the core to neutral
371 // raw. Redistributing clip0 to cmean preserves the magnitude sum_c clip0_c (cmean sums to 1 by
372 // construction) while the obstacle/floor now enforces the surround chromaticity. At gate 0
373 // clip0 is untouched (approved behavior on equal clips).
374 // BRIGHT surround mean for the rehue + its vote (separate from `cmean`, which feeds the
375 // APPROVED screened-Poisson seed and must stay the all-valid mean at any gate): dark
376 // foreground contaminates the all-valid mean and closes the vote on exactly the scenes the
377 // rehue is for (measured on MAC/sunrise).
378 dt_aligned_pixel_t cmean_bright = { 0.f, 0.f, 0.f, 0.f };
379 double bright_count = 0.0;
380 if(ctx->floor_gate > 1e-6f)
381 {
382 double plateau_sum = 0.0, plateau_count = 0.0;
383 for(size_t i = 0; i < region_pixels; i++)
384 if(valid[i * 4 + 0] < 0.5f || valid[i * 4 + 1] < 0.5f || valid[i * 4 + 2] < 0.5f)
385 {
386 plateau_sum += (double)lum_accum[i];
387 plateau_count += 1.0;
388 }
389 const float lum_min = (plateau_count > 0.0) ? 0.35f * (float)(plateau_sum / plateau_count) : 0.f;
390 double bright_accum[3] = { 0.0, 0.0, 0.0 };
391 for(size_t i = 0; i < region_pixels; i++)
392 {
393 if(!(valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f)) continue;
394 if(lum_accum[i] < lum_min) continue;
395 const float invL = 1.f / fmaxf(lum_accum[i], epsilon);
396 bright_accum[0] += (double)(estimate[i * 4 + 0] * invL);
397 bright_accum[1] += (double)(estimate[i * 4 + 1] * invL);
398 bright_accum[2] += (double)(estimate[i * 4 + 2] * invL);
399 bright_count += 1.0;
400 }
401 if(bright_count > 0.0)
402 for(int c = 0; c < 3; c++) cmean_bright[c] = (float)(bright_accum[c] / bright_count);
403 }
404 const float rehue_gate
405 = (bright_count > 0.0 && ctx->floor_gate > 1e-6f)
406 ? ctx->floor_gate * _hl_ring_flat_mean_vote(estimate, valid, cmean_bright, region_pixels)
407 : 0.f; // trusted-ring vote: rehue only where the bright-surround prior holds
408 if(rehue_gate > 1e-6f)
409 {
411 for(size_t i = 0; i < region_pixels; i++)
412 if(hole[i])
413 {
414 const float lsat = clip0[i * 4 + 0] + clip0[i * 4 + 1] + clip0[i * 4 + 2];
415 for(int c = 0; c < 3; c++)
416 clip0[i * 4 + c] = rehue_gate * (lsat * cmean_bright[c]) + (1.f - rehue_gate) * clip0[i * 4 + c];
417 }
418 }
419
420 // chromaticity: harmonic diffusion from the rim, with a screened-Poisson reaction
421 // pulling the core hue toward the flat mean by solid_color ("inpaint a flat color").
422 // react = lambda_solid = solid_color^2 * 4: the screening strength; 0 -> pure harmonic
423 // (Delta r = 0), larger -> a flatter, more uniform "solid colour" fill
424 const float react = solid_color * solid_color * 4.f;
426 for(size_t i = 0; i < region_pixels; i++) reaction_weight[i] = react;
427
428 // factor A = lambda_solid*I - Delta (order 1) ONCE; it serves the three channels (same matrix,
429 // three right-hand sides) -- the direct solve is EXACT where the float CG stopped at a tolerance
430 int *sp_pgrid = NULL;
431 int sp_nh = 0;
432 _sp_chol_t *sp_S = _sp_pde_factor(hole, (react > 0.f) ? reaction_weight : NULL, 1, 1.f, region_w, region_h,
433 &sp_pgrid, &sp_nh, pipe);
434 double *sp_b = sp_S ? (double *)dt_pixelpipe_cache_alloc_align(sizeof(double) * sp_nh, ctx->pipe) : NULL;
435 if(sp_S && !sp_b)
436 {
437 _sp_chol_free(sp_S);
438 sp_S = NULL;
439 }
440
441 for(int c = 0; c < 3; c++)
442 {
444 for(size_t i = 0; i < region_pixels; i++)
445 {
446 // boundary (Dirichlet) = the real rim chroma r_valid = est_c/L_sum; hole initial guess =
447 // the mean valid (amber) chroma r_target, so an under-converged core centre biases to
448 // amber, never to the guided magenta
449 solver_field[i] = hole[i] ? cmean[c] : (estimate[i * 4 + c] / fmaxf(lum_accum[i], epsilon));
450 flat_target[i] = cmean[c]; // r_target plane for the screening reaction term
451 }
452
453 // solve (lambda_solid*I - Delta) r_c = lambda_solid*r_target on the hole, r_c|dOmega = r_valid
454 if(sp_S)
455 _sp_pde_solve(sp_S, sp_pgrid, solver_field, hole, (react > 0.f) ? reaction_weight : NULL,
456 (react > 0.f) ? flat_target : NULL, NULL, 1, 1.f, region_w, region_h, sp_b, cg_tmp1, cg_tmp2,
457 cg_residual);
458 else
459 _region_pde_solve(solver_field, hole, (react > 0.f) ? reaction_weight : NULL,
460 (react > 0.f) ? flat_target : NULL, NULL, 1, 1.f, region_w, region_h, cg_residual,
461 cg_dir, cg_operator, cg_tmp1, cg_tmp2, max_cg_iter);
462
464 for(size_t i = 0; i < region_pixels; i++) plane1[i * 4 + c] = fmaxf(solver_field[i], 0.f);
465 }
466
467 _sp_chol_free(sp_S);
470
471 // FEATHERED composite: a hard all-clip mask makes the core <-> annulus hand-off a seam by
472 // construction. The dome (ldb ~ lsb outside the hole) and the diffused chroma (s1 = real
473 // ratios outside) are both valid past the hole boundary, so blending them in over a
474 // blurred mask is continuous in space at no cost to the core rebuild itself.
475 // core mask -> 1 inside, 0 outside; blurred into a smooth feather weight (the one smooth
476 // weight in the method: it blends two RECONSTRUCTIONS, never reclassifies measurements)
478 for(size_t i = 0; i < region_pixels; i++) solver_field[i] = hole[i] ? 1.f : 0.f;
479
480 _knee_blur(solver_field, reaction_weight, region_w, region_h,
481 fmaxf(4.f, CLAMP(region->radius / 6.f, 8.f, 64.f) / 4.f));
482
484 for(size_t i = 0; i < region_pixels; i++)
485 {
486 const float fit_weight = CLAMP(reaction_weight[i], 0.f, 1.f); // feather alpha (blurred core mask)
487 const float caccum = fmaxf(plane1[i * 4 + 0] + plane1[i * 4 + 1] + plane1[i * 4 + 2], epsilon); // sum_j r_j
488
489 if(hole[i])
490 {
491 // interior: core rebuild, full strength: core_c = L_dome * (r_c / sum_j r_j) (RGB = L*r)
492 for(int c = 0; c < 3; c++) estimate[i * 4 + c] = dome_lum[i] * (plane1[i * 4 + c] / caccum);
493 }
494 else if(fit_weight > 1e-4f)
495 {
496 // feather ring outside the core: alpha*core_c + (1-alpha)*est, on CLIPPED channels of
497 // the surrounding reconstruction only -- valid data is never touched
498 for(int c = 0; c < 3; c++)
499 if(valid[i * 4 + c] < 0.5f)
500 estimate[i * 4 + c] = fit_weight * dome_lum[i] * (plane1[i * 4 + c] / caccum)
501 + (1.f - fit_weight) * estimate[i * 4 + c];
502 }
503 }
504 }
505}
506
507// Chromaticity-gradient continuation (see core.h). Runs LAST in the region chain (after
508// _aniso_chroma), so it reprojects the final reconstructed values; the group-B scratch (solver_field, hole) is reused freely.
510{
511 const dt_dev_pixelpipe_t *const pipe = ctx->pipe;
512 const int region_w = ctx->region_w;
513 const int region_h = ctx->region_h;
514 const size_t region_pixels = ctx->region_pixels;
515 const float epsilon = ctx->epsilon;
516 float *const restrict estimate = ctx->estimate;
517 float *const restrict valid = ctx->valid;
518 float *const restrict clip0 = ctx->clip0;
519 float *const restrict plane2 = ctx->plane2; // extended chroma-share planes (4-ch layout)
520 float *const restrict solver_field = ctx->solver_field; // per-channel dome scratch
521 uint8_t *const restrict hole = ctx->hole; // rewritten: the field's extension domain
522 float *const restrict gate_src = ctx->cg_tmp1; // group-B scratch: agreement-weight source
523 float *const restrict gate_msk = ctx->cg_tmp2; // group-B scratch: agreement-weight mass
524 float *const restrict gate_wgt = ctx->cg_residual; // group-B scratch: diffused agreement weight
525 float *const restrict gate_nrm = ctx->cg_dir; // group-B scratch: diffused mass
526
527 // --- 1. anchors: fully-valid AND bright (>= 35% of the blown zone's plateau luminance) ---
528 // The extension must be anchored on the sky/emitter material only: dark valid content (occluder
529 // silhouettes, foreground) carries near-neutral noise chroma, and the fence band right at the clip
530 // contour is unrepresentative -- the dome's gradient continuation from the BRIGHT surround sails
531 // over both. Plateau proxy = mean current luminance over the any-clip pixels.
532 double plateau_accum = 0.0;
533 size_t plateau_count = 0;
534 for(size_t i = 0; i < region_pixels; i++)
535 if(valid[i * 4 + 0] < 0.5f || valid[i * 4 + 1] < 0.5f || valid[i * 4 + 2] < 0.5f)
536 {
537 plateau_accum += (double)(estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2]);
538 plateau_count++;
539 }
540 if(plateau_count == 0) return; // nothing blown in this region
541 const float lum_anchor_min = 0.35f * (float)(plateau_accum / (double)plateau_count);
542
543 // Guard band: the last valid band hugging the clip contour is itself unrepresentative (sensor
544 // rolloff, flare/scatter whitening -- measured bluer than the wider sky), so anchors must stand
545 // clear of it. Blur the any-clip mask into a proximity field and require anchors to be far enough
546 // that the blurred mask has decayed (~2-3 sigma from any clipped pixel).
547 float *const restrict guard_src = ctx->flat_target; // group-B scratch, free at this point
548 float *const restrict guard_blur = ctx->reaction_weight; // group-B scratch, free at this point
549 HL_PFOR()
550 for(size_t i = 0; i < region_pixels; i++)
551 guard_src[i]
552 = (valid[i * 4 + 0] < 0.5f || valid[i * 4 + 1] < 0.5f || valid[i * 4 + 2] < 0.5f) ? 1.f : 0.f;
553 // thin ring only: a wide moat exiles the anchors to unrepresentative far content (measured: the
554 // field then inherited the wrong quadrant's hue) -- the fence is a few pixels of rolloff, not tens
555 const float guard_sigma = 4.f;
556 _knee_blur(guard_src, guard_blur, region_w, region_h, guard_sigma);
557
558 size_t n_anchor = 0;
559 HL_PFOR(reduction(+ : n_anchor))
560 for(size_t i = 0; i < region_pixels; i++)
561 {
562 const int fully_valid
563 = (valid[i * 4 + 0] >= 0.5f) && (valid[i * 4 + 1] >= 0.5f) && (valid[i * 4 + 2] >= 0.5f);
564 const float lum = estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
565 const int anchor = fully_valid && (lum >= lum_anchor_min) && (guard_blur[i] < 0.05f);
566 hole[i] = !anchor;
567 n_anchor += anchor;
568 }
569 // no usable bright surround (e.g. an emitter in darkness): the fence chromaticity is all there is
570 if(n_anchor < 64 || n_anchor < region_pixels / 256) return;
571
572 // --- 2. per channel: extend the anchor chroma share biharmonically over everything else ---
573 for(int c = 0; c < 3; c++)
574 {
575 HL_PFOR()
576 for(size_t i = 0; i < region_pixels; i++)
577 {
578 const float lum = estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
579 solver_field[i] = estimate[i * 4 + c] / fmaxf(lum, epsilon); // share everywhere (hole = init guess)
580 }
581 _biharmonic_dome(solver_field, hole, region_w, region_h, 0, pipe); // gradient-extending (auto ds)
582 HL_PFOR()
583 for(size_t i = 0; i < region_pixels; i++)
584 plane2[i * 4 + c] = CLAMP(solver_field[i], 0.f, 1.f); // shares are bounded; clamp dome overshoot
585 }
586
587 // --- 3. CONTENT GATE: validate the extended field against the 1-clip annulus ---
588 // The chromaticity-continuation prior is a scene assumption: true for gradient skies (the
589 // measured sunrise win), false for self-coloured emitters whose core carries its OWN
590 // chromaticity (a magenta sun, coloured lamps -- the bench regressions). The method's own
591 // trusted zone arbitrates: 1-clip pixels are reconstructed from TWO measured guides
592 // (measured chromaticity-correct on every test image), and they ring every deeper zone. Where
593 // the extended field agrees with the 1-clip ring, the surround genuinely continues inward ->
594 // apply the reprojection; where it disagrees, the blown object is self-coloured -> keep the
595 // solver. The per-pixel agreement weight is diffused inward from the ring by normalized
596 // convolution, the same trick as every other hand-off in this module (no printable level set).
597 const float gate_tau = 0.10f; // L1 share-difference tolerance (~one just-noticeable hue step)
598 HL_PFOR()
599 for(size_t i = 0; i < region_pixels; i++)
600 {
601 const int n_clip = (valid[i * 4 + 0] < 0.5f) + (valid[i * 4 + 1] < 0.5f) + (valid[i * 4 + 2] < 0.5f);
602 float weight_src = 0.f, mask_src = 0.f;
603 // The gate validates the field against the pixel's MEASURED channels, never against the
604 // solver. The old form compared field shares to the 1-clip SOLVER's shares -- but a
605 // fence-contaminated solver and a self-coloured emitter produce the SAME disagreement, so that
606 // vote cannot tell who is wrong (measured on PK1_3540: vote 0.195, the correct field voted
607 // down by the very solver it exists to correct). At a 1-clip pixel TWO channels are measured,
608 // and the ratio between them is data: a self-coloured emitter's skirt carries the emitter's
609 // measured ratio and disagrees with the surround-extended field, a genuine sky agrees. With
610 // the solver out of the jury the floor-authored exclusion is unnecessary -- a floored pixel's
611 // measured channels are untainted, so it may vote. Measured: PK1 vote 0.195 -> 0.335 (ring
612 // reprojection engages), magentasun 0.3316 -> 0.3294 RMSE with SSIM 0.929 -> 0.941 (the
613 // protected case improves), occluded pays +3.9% -- the accepted trade of this change.
614 if(n_clip == 1)
615 {
616 const int cc = (valid[i * 4 + 0] < 0.5f) ? 0 : ((valid[i * 4 + 1] < 0.5f) ? 1 : 2);
617 const int m1 = (cc == 0) ? 1 : 0, m2 = (cc == 2) ? 1 : 2;
618 const float msum = estimate[i * 4 + m1] + estimate[i * 4 + m2];
619 const float meas = estimate[i * 4 + m1] / fmaxf(msum, epsilon);
620 const float fld = plane2[i * 4 + m1] / fmaxf(plane2[i * 4 + m1] + plane2[i * 4 + m2], epsilon);
621 const float t = (meas - fld) / (0.5f * gate_tau); // 2-channel share: half the 3-channel scale
622 weight_src = expf(-t * t);
623 mask_src = 1.f;
624 }
625 gate_src[i] = weight_src;
626 gate_msk[i] = mask_src;
627 }
628 const float gate_sigma = CLAMP(ctx->region->radius / 4.f, 8.f, 96.f);
629 _knee_blur(gate_src, gate_wgt, region_w, region_h, gate_sigma);
630 _knee_blur(gate_msk, gate_nrm, region_w, region_h, gate_sigma);
631
632 // region-level ring vote: deep interiors of large blown zones sit beyond the blur's reach of the
633 // thin 1-clip ring; there the ring's GLOBAL agreement decides (a gradient sky's ring votes yes
634 // everywhere, a self-coloured emitter's ring votes no). The per-pixel weight shrinks toward the
635 // vote as local evidence mass vanishes: w = (blur_w + lambda*vote) / (blur_m + lambda).
636 double vote_wsum = 0.0, vote_msum = 0.0;
637 for(size_t i = 0; i < region_pixels; i++)
638 {
639 vote_wsum += (double)gate_src[i];
640 vote_msum += (double)gate_msk[i];
641 }
642 const float gate_vote = (vote_msum > 0.0) ? (float)(vote_wsum / vote_msum) : 0.f;
643
644 // --- 4. reproject the multi-clip subsets onto the extended field, blended by the gate ---
645 HL_PFOR()
646 for(size_t i = 0; i < region_pixels; i++)
647 {
648 const int clip_r = valid[i * 4 + 0] < 0.5f;
649 const int clip_g = valid[i * 4 + 1] < 0.5f;
650 const int clip_b = valid[i * 4 + 2] < 0.5f;
651 const int n_clip = clip_r + clip_g + clip_b;
652 // 1-clip pixels are trusted where the fit SPOKE -- but where it did not, the model predicted
653 // below the pixel's own saturation level and fmaxf(pred, clip0) pinned the channel AT the floor,
654 // which is the minimum compatible value and prints the floor's own chroma. Measured on
655 // DSC_1267.NEF along a ridge: 96.5% of the 1-clip-G pixels 4-6 px from the rock, and 90.4% at
656 // 8-12 px, come out of the whole chain still at their floor (median lift 1.000) against 27.9%
657 // far from it -- a magenta rim exactly where the sky dims toward the ridge and drags the guides,
658 // and with them the prediction, below clip0. Those pixels have the same information as a partial
659 // multi-clip pixel -- two measured channels and a surround chromaticity -- so they get the same
660 // survivor-anchored reprojection, which is what the floor-authored band needed all along. The
661 // gate still arbitrates it, and the joint floor still vetoes any prediction that would sit below
662 // saturation (which is how this stays inert where the surround is the wrong reference).
663 // How much the fit failed to speak, as a RAMP and not a test: 1 where the channel sits exactly at
664 // its floor, fading to 0 by the time the fit has lifted it CF_AUTHORED_RAMP above saturation. A
665 // hard threshold here prints its own contour -- the boundary between mostly-floored pixels near
666 // the ridge and mostly-lifted ones further out becomes a visible edge, which is the same seam a3
667 // exists to avoid.
668 float authored_w = 1.f;
669 if(n_clip == 1)
670 {
671 // WB'd clips only. At unit WB every channel saturates at the same level, so a floored channel
672 // carries a NEUTRAL chroma that is already ~ the truth -- reprojecting it there replaces a
673 // correct value with a surround guess and is a pure loss (measured: the unit-WB bench cases
674 // regress up to +140% RMSE ungated). Same clip-asymmetry gate the floor sites themselves use.
675 if(ctx->floor_gate <= 1e-6f) continue;
676 const int cc = clip_r ? 0 : (clip_g ? 1 : 2);
677 const float lift = estimate[i * 4 + cc] / fmaxf(clip0[i * 4 + cc], 1e-9f);
678 const float over = (lift - 1.f) / (CF_AUTHORED_RAMP - 1.f);
679 authored_w = 1.f - CLAMP(over * over * (3.f - 2.f * over), 0.f, 1.f); // smoothstep, inverted
680 authored_w *= ctx->floor_gate; // blend in with the same asymmetry ramp as every floor site
681 if(authored_w <= 1e-4f) continue; // the fit spoke here: leave the pixel to it
682 }
683
684 // diffused agreement weight, shrunk toward the region-level ring vote as local evidence thins
685 const float gate_lambda = 0.05f;
686 const float gate_w
687 = CLAMP((gate_wgt[i] + gate_lambda * gate_vote) / (gate_nrm[i] + gate_lambda), 0.f, 1.f);
688 if(gate_w <= 1e-4f) continue;
689
690 const float share_sum = fmaxf(plane2[i * 4 + 0] + plane2[i * 4 + 1] + plane2[i * 4 + 2], epsilon);
691 const int anyvalid = !(clip_r && clip_g && clip_b);
692
693 if(anyvalid)
694 {
695 // partial multi-clip: the SURVIVING channels anchor the brightness against the field
696 // (scale = sum valid est / sum valid shares), the clipped channels take the field's shares
697 // outright -- the pixel lands exactly on the extended hue, and the measured data is honored.
698 // (Magnitude-preserving redistribution was tried first and cannot fix the hue here: the
699 // clipped subset's total IS the under-prediction the fence-hue fits produced.)
700 float sv_est = 0.f, sv_share = 0.f;
701 for(int c = 0; c < 3; c++)
702 if(valid[i * 4 + c] >= 0.5f)
703 {
704 sv_est += estimate[i * 4 + c];
705 sv_share += plane2[i * 4 + c] / share_sum;
706 }
707 if(sv_share <= epsilon || sv_est <= epsilon) continue;
708 // survivor-anchored scale, bounded: a tiny surviving share amplifies measurement noise into
709 // arbitrarily bright reprojections; cap the implied pixel magnitude at 4x its current one
710 const float scale
711 = fminf(sv_est / sv_share, 4.f * (estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2]));
712 const float reproject_w = gate_w * authored_w; // 1 for multi-clip; ramped for 1-clip
713 for(int c = 0; c < 3; c++)
714 if(valid[i * 4 + c] < 0.5f)
715 estimate[i * 4 + c] = reproject_w * (scale * (plane2[i * 4 + c] / share_sum))
716 + (1.f - reproject_w) * estimate[i * 4 + c];
717 }
718 else
719 {
720 // all-clip pixels are NOT reprojected: with no measured anchor the only magnitude authority is
721 // the joint core's dome, and redistributing ITS total by the field's shares is unstable when the
722 // core estimate is poor (measured on the gradsky bench case: single blown channels far above the
723 // others turn a hue reprojection into a large radiance error, down to negative pixels). The core
724 // keeps the joint-core/aniso result; the continuation prior only refines pixels that still hold
725 // at least one measurement.
726 continue;
727 }
728
729 // joint saturation floor re-assert (scalar-subset lift + per-channel safety, hue preserved)
730 float lift = 1.f;
731 for(int c = 0; c < 3; c++)
732 if(valid[i * 4 + c] < 0.5f)
733 {
734 const float e = fmaxf(estimate[i * 4 + c], 1e-6f);
735 lift = fmaxf(lift, fminf(fmaxf(e, clip0[i * 4 + c]) / e, 8.f));
736 }
737 for(int c = 0; c < 3; c++)
738 if(valid[i * 4 + c] < 0.5f)
739 {
740 if(lift > 1.f) estimate[i * 4 + c] = fmaxf(estimate[i * 4 + c], 1e-6f) * lift;
741 estimate[i * 4 + c] = fmaxf(estimate[i * 4 + c], clip0[i * 4 + c]);
742 }
743 }
744
745 // --- 5. PASS 2 = a3, VALUE continuation of the floor-authored 1-clip band (WB'd clips only).
746 // The share-based reprojections cannot help here and the floor rightly vetoes them (measured
747 // twice: the sunrise band, and the P1000388 blue LED array where the artifact is a B-value
748 // DISCONTINUITY across the G-clip contour -- B lifted above clip inside the 2-clip core, B
749 // pinned AT clip in the floor-authored collar, the jagged contour printed as a seam). The
750 // clipped channel's own VALUE is instead extended biharmonically over the authored band,
751 // anchored on BOTH sides -- the multi-clip reconstruction inside (lifted) and the measured
752 // data outside (just under clip) -- and floored at saturation. Writes only the clipped
753 // channel; approaches clip0 at the outer contour by construction (no seam); the same
754 // operator the luminance dome already trusts.
755 if(ctx->floor_gate > 1e-6f)
756 {
757 // width of that collar, in pixels of the buffer being rendered
758 const float a3_collar = DT_HL_A3_COLLAR_PX / fmaxf(ctx->scale, 1e-6f);
759 for(int c = 0; c < 3; c++)
760 {
761 size_t n_hole_c = 0;
762 float *const restrict authored = gate_src; // free after the vote; 1 where this pass may write
763 HL_PFOR(reduction(+ : n_hole_c))
764 for(size_t i = 0; i < region_pixels; i++)
765 {
766 const int clip_r = valid[i * 4 + 0] < 0.5f;
767 const int clip_g = valid[i * 4 + 1] < 0.5f;
768 const int clip_b = valid[i * 4 + 2] < 0.5f;
769 const int cc = clip_r ? 0 : (clip_g ? 1 : 2);
770 // A COLLAR, not a plateau. This pass exists to erase the SEAM the floor prints at the clip
771 // contour, and it is only sound where its stated anchoring -- measured data just outside,
772 // multi-clip reconstruction just inside -- actually exists within reach. Measured on
773 // DSC_1267.NEF (Nikon: G's WB multiplier is 1.0, so G saturates first and 99.5% of the blown
774 // zone is 1-clip-G): the authored band was 566969 px, median 72 px from any valid pixel and
775 // 232 px from any multi-clip pixel, with none at all within 500 px of the worst area. The
776 // dome then interpolated G across hundreds of pixels from unrelated distant anchors and
777 // landed at ~2x the neutral G/B, i.e. a saturated green bloom over a grey sky. Requiring the
778 // pixel to sit within a collar of the measured contour keeps the seam fix and drops the
779 // extrapolation. Scale-relative so a downscaled preview matches the full-resolution render.
780 const int is_hole = (clip_r + clip_g + clip_b == 1) && (cc == c)
781 && (ctx->clip_depth[i] <= a3_collar)
782 && (estimate[i * 4 + c] <= 1.03f * fmaxf(clip0[i * 4 + c], 1e-9f));
783 authored[i] = is_hole ? 1.f : 0.f;
784 // DARK valid content must not anchor this dome. The share anchors above already exclude it
785 // (dark unrelated material carries near-neutral noise chroma); the value pass needs it even
786 // more badly, because a dark intrusion INSIDE a bright blown zone -- birds against a blown
787 // sky, on DSC_1267.NEF -- pins the dome at a near-zero value and the extension around it
788 // swings wildly. Such pixels join the solve's domain so the dome interpolates ACROSS them,
789 // but they are never written back: only `authored` pixels are, so their measured value
790 // survives untouched.
791 const float lum = estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
792 const int too_dark_to_anchor = !is_hole && (lum < lum_anchor_min);
793 hole[i] = is_hole || too_dark_to_anchor;
794 solver_field[i] = estimate[i * 4 + c];
795 n_hole_c += is_hole;
796 }
797 if(n_hole_c == 0) continue;
798 _biharmonic_dome(solver_field, hole, region_w, region_h, 0, pipe);
799 HL_PFOR()
800 for(size_t i = 0; i < region_pixels; i++)
801 if(authored[i] > 0.5f) estimate[i * 4 + c] = fmaxf(solver_field[i], clip0[i * 4 + c]);
802 }
803 }
804
805}
806
807// ============================ OpenCL ============================
808
809#if defined(HAVE_OPENCL) && DT_HL_SPARSE_SOLVE
810cl_int _selfdome_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem model_quality,
811 cl_mem clip0, cl_mem depth, cl_mem region_worth, const int region_w, const int region_h,
812 const float cf_sigma, const float reg_radius, const int ds_shared,
813 const float floor_gate, const dt_dev_pixelpipe_t *pipe)
814{
816 const size_t region_pixels = (size_t)region_w * region_h;
817 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
818 size_t size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
819 const float epsilon = 1e-6f;
820
821 // The stage-2 reduction finalizers this needs are compiled only where the fp64 extension
822 // is (data/kernels/highlights_harmonic.cl). Without them the caller falls back to the CPU
823 // twin, the same way the sparse solver and the PDE/aniso stages already do.
824 if(global_data->kernel_hl_reduce_finalize < 0 || global_data->kernel_hl_cmean_finalize < 0
825 || global_data->kernel_hl_ring_vote_finalize < 0)
826 return cl_err; // no fp64 device
827
828 cl_mem luminance = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
829 cl_mem hole = dt_opencl_alloc_device_buffer(devid, region_pixels);
830 cl_mem dome_lum = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
831 cl_mem ratio0 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
832 cl_mem ratio1 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
833 cl_mem ratio2 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
834 cl_mem ratios[3];
835 ratios[0] = ratio0;
836 ratios[1] = ratio1;
837 ratios[2] = ratio2;
838 cl_mem partial_sums = NULL; // mean-chromaticity partial sums (gate > 0 only)
839 cl_mem lum_min_dev = NULL; // bright-valid luminance gate, device-resident
840 cl_mem cmean_dev = NULL; // {cmean.rgb, count}, device-resident (never read back)
841 cl_mem refine_dev = NULL; // {refine_gate, ratio_beta}, device-resident
842 if(!luminance || !hole || !dome_lum || !ratio0 || !ratio1 || !ratio2) goto out;
843
844 // soft floor first (production order: floor -> dome gate -> self dome)
845 {
846 const int kernel = global_data->kernel_hl_soft_floor;
847 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
848 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
849 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &clip0);
850 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &region_worth);
851 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
852 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
853 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &floor_gate);
854 const float joint_tau = CF_JOINT_TAU;
855 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &joint_tau);
856 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
857 if(cl_err != CL_SUCCESS) goto out;
858 }
859
860 // brightness plane (sum of the three channels) + union hole mask (any clipped channel)
861 {
862 const int kernel = global_data->kernel_hl_lsb_hole;
863 const int allmode = 0; // union hole: ANY clipped channel
864 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
865 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
866 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &luminance);
867 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &hole);
868 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
869 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
870 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &allmode);
871 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
872 if(cl_err != CL_SUCCESS) goto out;
873 }
874
875 // Mean valid chromaticity (gate > 0 only): the flat target the hole-interior dome chroma is
876 // pulled toward, mirroring the CPU _selfdome cmean pull (fully-valid == !hole here, so the
877 // joint-core reduction kernel serves unchanged). ratio_beta stays 0 when no valid pixel exists.
878 // cmean, refine_gate and ratio_beta all live in device buffers (cmean_dev / refine_dev): the
879 // chain is computed by hl_cmean_finalize + hl_ring_vote_finalize and consumed by the kernels
880 // below without ever reaching the host.
881 if(floor_gate > 1e-6f)
882 {
883 const int local_size = 64, n_groups = 256;
884 const int n_pixels = (int)region_pixels;
885 partial_sums = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 8 * n_groups);
886 lum_min_dev = dt_opencl_alloc_device_buffer(devid, sizeof(float));
887 cmean_dev = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4);
888 refine_dev = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 2);
889 if(!partial_sums || !lum_min_dev || !cmean_dev || !refine_dev)
890 {
892 goto out;
893 }
894 size_t sizes[3] = { (size_t)n_groups * local_size, 1, 1 };
895 size_t local[3] = { local_size, 1, 1 };
896
897 // blown-zone plateau -> bright-valid gate for the refinements' surround mean: the
898 // whole-window mean is contaminated by dark, unrelated content (the cgrad anchors learned
899 // the same lesson) -- measured on MAC/sunrise, the ring vote against the all-valid mean
900 // stays closed on exactly the scenes the refinements are for.
901 {
902 const int plateau_kernel = global_data->kernel_hl_cgrad_plateau;
903 dt_opencl_set_kernel_arg(devid, plateau_kernel, 0, sizeof(cl_mem), &estimate);
904 dt_opencl_set_kernel_arg(devid, plateau_kernel, 1, sizeof(cl_mem), &valid);
905 dt_opencl_set_kernel_arg(devid, plateau_kernel, 2, sizeof(cl_mem), &partial_sums);
906 dt_opencl_set_kernel_arg(devid, plateau_kernel, 3, sizeof(int), &n_pixels);
907 dt_opencl_set_kernel_arg(devid, plateau_kernel, 4, sizeof(float) * 2 * local_size, NULL);
908 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, plateau_kernel, sizes, local);
909 if(cl_err != CL_SUCCESS) goto out;
910 const int fin = global_data->kernel_hl_reduce_finalize;
911 const int fin_stride = 2, fin_mode = 1;
912 const float fin_scale = 0.35f;
913 dt_opencl_set_kernel_arg(devid, fin, 0, sizeof(cl_mem), &partial_sums);
914 dt_opencl_set_kernel_arg(devid, fin, 1, sizeof(cl_mem), &lum_min_dev);
915 dt_opencl_set_kernel_arg(devid, fin, 2, sizeof(int), &n_groups);
916 dt_opencl_set_kernel_arg(devid, fin, 3, sizeof(int), &fin_stride);
917 dt_opencl_set_kernel_arg(devid, fin, 4, sizeof(int), &fin_mode);
918 dt_opencl_set_kernel_arg(devid, fin, 5, sizeof(float), &fin_scale);
919 size_t fin_size[3] = { 1, 1, 1 };
920 cl_err = dt_opencl_enqueue_kernel_2d(devid, fin, fin_size);
921 if(cl_err != CL_SUCCESS) goto out;
922 }
923
924 const int kernel = global_data->kernel_hl_cmean_reduce;
925 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
926 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
927 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &luminance);
928 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &partial_sums);
929 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &n_pixels);
930 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), &epsilon);
931 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), &lum_min_dev);
932 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float) * 4 * local_size, NULL);
933 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, sizes, local);
934 if(cl_err != CL_SUCCESS) goto out;
935
936 // Fold the chromaticity mean on the DEVICE (hl_cmean_finalize), run the trusted-ring vote,
937 // and fold that on the device too (hl_ring_vote_finalize -> {refine_gate, ratio_beta}). Both
938 // consumers below read those buffers, so the whole cmean -> vote -> refine chain stays on the
939 // GPU: no scalar crosses the bus and no host branch decides whether a kernel runs.
940 {
941 const int fin = global_data->kernel_hl_cmean_finalize;
942 dt_opencl_set_kernel_arg(devid, fin, 0, sizeof(cl_mem), &partial_sums);
943 dt_opencl_set_kernel_arg(devid, fin, 1, sizeof(cl_mem), &cmean_dev);
944 dt_opencl_set_kernel_arg(devid, fin, 2, sizeof(int), &n_groups);
945 size_t one[3] = { 1, 1, 1 };
946 cl_err = dt_opencl_enqueue_kernel_2d(devid, fin, one);
947 if(cl_err != CL_SUCCESS) goto out;
948
949 const int vote_kernel = global_data->kernel_hl_ring_vote;
950 dt_opencl_set_kernel_arg(devid, vote_kernel, 0, sizeof(cl_mem), &estimate);
951 dt_opencl_set_kernel_arg(devid, vote_kernel, 1, sizeof(cl_mem), &valid);
952 dt_opencl_set_kernel_arg(devid, vote_kernel, 2, sizeof(cl_mem), &partial_sums);
953 dt_opencl_set_kernel_arg(devid, vote_kernel, 3, sizeof(int), &n_pixels);
954 dt_opencl_set_kernel_arg(devid, vote_kernel, 4, sizeof(float) * 8 * local_size, NULL);
955 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, vote_kernel, sizes, local);
956 if(cl_err != CL_SUCCESS) goto out;
957
958 const int vfin = global_data->kernel_hl_ring_vote_finalize;
959 dt_opencl_set_kernel_arg(devid, vfin, 0, sizeof(cl_mem), &partial_sums);
960 dt_opencl_set_kernel_arg(devid, vfin, 1, sizeof(cl_mem), &cmean_dev);
961 dt_opencl_set_kernel_arg(devid, vfin, 2, sizeof(cl_mem), &refine_dev);
962 dt_opencl_set_kernel_arg(devid, vfin, 3, sizeof(int), &n_groups);
963 dt_opencl_set_kernel_arg(devid, vfin, 4, sizeof(float), &floor_gate);
964 cl_err = dt_opencl_enqueue_kernel_2d(devid, vfin, one);
965 if(cl_err != CL_SUCCESS) goto out;
966 }
967 }
968
969 // debug dump (HL_REG_DUMP=<file path>): save this region's brightness plane + hole mask
970 // to the given file for offline replay through the HL_DOMECL_TEST self-test (the path is
971 // taken from the variable itself: no fixed world-writable location)
972 const char *reg_dump_path = getenv("HL_REG_DUMP");
973 if(reg_dump_path && reg_dump_path[0])
974 {
975 float *dump_data = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
976 uint8_t *dump_hole = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
977 if(dump_data && dump_hole
978 && dt_opencl_read_buffer_from_device(devid, dump_data, luminance, 0, sizeof(float) * region_pixels, CL_TRUE)
979 == CL_SUCCESS
980 && dt_opencl_read_buffer_from_device(devid, dump_hole, hole, 0, region_pixels, CL_TRUE) == CL_SUCCESS)
981 {
982 FILE *dump_file = g_fopen(reg_dump_path, "wb");
983 if(dump_file)
984 {
985 fwrite(&region_w, sizeof(int), 1, dump_file);
986 fwrite(&region_h, sizeof(int), 1, dump_file);
987 const int downsample_val = ds_shared;
988 fwrite(&downsample_val, sizeof(int), 1, dump_file);
989 fwrite(dump_data, sizeof(float), region_pixels, dump_file);
990 fwrite(dump_hole, 1, region_pixels, dump_file);
991 fclose(dump_file);
992 }
993 }
996 }
997 // shared biharmonic brightness dome over the union hole (GPU sparse Cholesky inside)
998 cl_err
999 = dt_opencl_enqueue_copy_buffer_to_buffer(devid, luminance, dome_lum, 0, 0, sizeof(float) * region_pixels);
1000 if(cl_err != CL_SUCCESS) goto out;
1001 cl_err = _biharmonic_dome_cl(devid, gd_void, dome_lum, hole, region_w, region_h, ds_shared, pipe);
1002 if(cl_err != CL_SUCCESS) goto out;
1003
1004 // harmonically filled chromaticity ratios over the union hole
1005 {
1006 const int cf_base = (int)(CLAMP(reg_radius / 6.f, 8.f, 64.f) / 4.f);
1007 for(int c = 0; c < 3 && cl_err == CL_SUCCESS; c++)
1008 {
1009 const int kernel = global_data->kernel_hl_ratio_plane;
1010 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1011 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &luminance);
1012 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &ratios[c]);
1013 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_w);
1014 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_h);
1015 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &c);
1016 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &epsilon);
1017 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1018 if(cl_err == CL_SUCCESS)
1019 cl_err = _cf_harmonic_fill_cl(devid, gd_void, ratios[c], hole, region_w, region_h, cf_base, 1, NULL);
1020 if(cl_err == CL_SUCCESS && floor_gate > 1e-6f)
1021 {
1022 // pull the filled ratio toward the mean valid chromaticity (CPU cmean pull mirror). The
1023 // launch is unconditional: the kernel reads ratio_beta from refine_dev and no-ops at 0,
1024 // so no host round-trip decides whether it runs.
1025 const int blend_kernel = global_data->kernel_hl_ratio_cmean_blend;
1026 dt_opencl_set_kernel_arg(devid, blend_kernel, 0, sizeof(cl_mem), &ratios[c]);
1027 dt_opencl_set_kernel_arg(devid, blend_kernel, 1, sizeof(cl_mem), &hole);
1028 dt_opencl_set_kernel_arg(devid, blend_kernel, 2, sizeof(cl_mem), &cmean_dev);
1029 dt_opencl_set_kernel_arg(devid, blend_kernel, 3, sizeof(cl_mem), &refine_dev);
1030 dt_opencl_set_kernel_arg(devid, blend_kernel, 4, sizeof(int), &region_w);
1031 dt_opencl_set_kernel_arg(devid, blend_kernel, 5, sizeof(int), &region_h);
1032 dt_opencl_set_kernel_arg(devid, blend_kernel, 6, sizeof(int), &c);
1033 cl_err = dt_opencl_enqueue_kernel_2d(devid, blend_kernel, size);
1034 }
1035 }
1036 if(cl_err != CL_SUCCESS) goto out;
1037 }
1038
1039 // depth-gated blend: dome value x filled ratios replaces the estimate where the fit is
1040 // doubtful and the pixel is shallow enough for the dome to be trustworthy
1041 {
1042 const int kernel = global_data->kernel_hl_dome_blend;
1043 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1044 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
1045 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &model_quality);
1046 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &depth);
1047 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &dome_lum);
1048 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &ratio0);
1049 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), &ratio1);
1050 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(cl_mem), &ratio2);
1051 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(cl_mem), &hole);
1052 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &region_w);
1053 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(int), &region_h);
1054 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(float), &cf_sigma);
1055 dt_opencl_set_kernel_arg(devid, kernel, 12, sizeof(float), &epsilon);
1056 dt_opencl_set_kernel_arg(devid, kernel, 13, sizeof(cl_mem), &refine_dev);
1057 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1058 if(cl_err != CL_SUCCESS) goto out;
1059 }
1060
1061 // hard floor re-assert: a clipped channel saturated, so its true value is >= its clip level
1062 {
1063 const int kernel = global_data->kernel_hl_hard_floor;
1064 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1065 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
1066 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &clip0);
1067 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &region_worth);
1068 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
1069 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
1070 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &floor_gate);
1071 const float joint_tau = CF_JOINT_TAU;
1072 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &joint_tau);
1073 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1074 }
1075
1076out:
1083 dt_opencl_release_mem_object(partial_sums);
1084 dt_opencl_release_mem_object(lum_min_dev);
1086 dt_opencl_release_mem_object(refine_dev);
1087 return cl_err;
1088}
1089
1090#endif // HAVE_OPENCL && DT_HL_SPARSE_SOLVE
1091
1092#if defined(HAVE_OPENCL) && DT_HL_SPARSE_SOLVE
1093
1094#endif // HAVE_OPENCL && DT_HL_SPARSE_SOLVE
1095
1096#if defined(HAVE_OPENCL) && DT_HL_SPARSE_SOLVE
1097cl_int _joint_core_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem clip0,
1098 const int region_w, const int region_h, const float solid_color,
1099 const float reg_radius, const int extent, const float floor_gate,
1100 const dt_dev_pixelpipe_t *pipe)
1101{
1103 const size_t region_pixels = (size_t)region_w * region_h;
1104 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
1105 size_t size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
1106 const float epsilon = 1e-6f;
1107 const float react
1108 = solid_color * solid_color * 4.f; // lambda_solid: the screened-Poisson reaction (flat-colour pull)
1109
1110 if(global_data->kernel_hl_pde_rhs < 0 || global_data->kernel_hl_pde_scatter < 0) return cl_err; // no fp64 device
1111
1112 cl_mem luminance = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1113 cl_mem hole = dt_opencl_alloc_device_buffer(devid, region_pixels);
1114 cl_mem dome_lum = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1115 cl_mem embedded = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1116 cl_mem ratio0 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1117 cl_mem ratio1 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1118 cl_mem ratio2 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1119 cl_mem cg_field = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1120 cl_mem ratios[3];
1121 ratios[0] = ratio0;
1122 ratios[1] = ratio1;
1123 ratios[2] = ratio2;
1124 cl_mem partial_sums = NULL, perm_grid_dev = NULL, rhs_dev = NULL, mask_img = NULL, mask_blur = NULL;
1125 cl_mem lum_min_dev2 = NULL, zero_dev = NULL; // device-resident scalars (never read back)
1126 cl_mem bright_dev = NULL, rehue_dev = NULL; // bright cmean + {rehue_gate, beta}, device-resident
1127 uint8_t *hole_mask = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
1128 int *matrix_col_ptr = NULL, *matrix_row_index = NULL, *perm_grid = NULL;
1129 double *matrix_values = NULL;
1130 _sp_chol_cl_t *factor = NULL;
1131 dt_aligned_pixel_t chroma_mean = { 0.f, 0.f, 0.f, 0.f };
1132 if(!luminance || !hole || !dome_lum || !embedded || !ratio0 || !ratio1 || !ratio2 || !cg_field || !hole_mask)
1133 goto out;
1134
1135 // luminance + ALL-clip hole (no surviving channel)
1136 {
1137 const int kernel = global_data->kernel_hl_lsb_hole;
1138 const int all_clip_mode = 1;
1139 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1140 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
1141 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &luminance);
1142 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &hole);
1143 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
1144 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
1145 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &all_clip_mode);
1146 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1147 if(cl_err != CL_SUCCESS) goto out;
1148 }
1149
1150 // the sparse symbolic analysis needs the mask on the host anyway; it also gives the
1151 // all-clip count for the early exit and the CPU's auto grid factor for the dome
1152 cl_err = dt_opencl_read_buffer_from_device(devid, hole_mask, hole, 0, region_pixels, CL_TRUE);
1153 if(cl_err != CL_SUCCESS) goto out;
1154
1155 size_t n_hole_fine = 0;
1156 for(size_t i = 0; i < region_pixels; i++)
1157 if(hole_mask[i]) n_hole_fine++;
1158 if(n_hole_fine == 0)
1159 {
1160 cl_err = CL_SUCCESS;
1161 goto out;
1162 }
1163 const int downsample = MAX(1, (int)ceilf(sqrtf((float)n_hole_fine / (float)DT_HL_DOME_NMAX_SPARSE)));
1164
1165 // shared biharmonic luminance dome, floored at "all channels at clip"
1166 cl_err
1167 = dt_opencl_enqueue_copy_buffer_to_buffer(devid, luminance, dome_lum, 0, 0, sizeof(float) * region_pixels);
1168 if(cl_err != CL_SUCCESS) goto out;
1169 cl_err = _biharmonic_dome_cl(devid, gd_void, dome_lum, hole, region_w, region_h, downsample, pipe);
1170 if(cl_err != CL_SUCCESS) goto out;
1171 {
1172 const int kernel = global_data->kernel_hl_core_floor;
1173 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &dome_lum);
1174 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &hole);
1175 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &clip0);
1176 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_w);
1177 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_h);
1178 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1179 if(cl_err != CL_SUCCESS) goto out;
1180 }
1181
1182 // mean valid chromaticity: device partial sums, host finish
1183 {
1184 const int local_size = 64, n_groups = 256;
1185 const int n_pixels = (int)region_pixels;
1186 partial_sums = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 8 * n_groups);
1187 lum_min_dev2 = dt_opencl_alloc_device_buffer(devid, sizeof(float));
1188 zero_dev = dt_opencl_alloc_device_buffer(devid, sizeof(float));
1189 bright_dev = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 4);
1190 rehue_dev = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 2);
1191 if(!partial_sums || !lum_min_dev2 || !zero_dev || !bright_dev || !rehue_dev)
1192 {
1193 cl_err = DT_OPENCL_DEFAULT_ERROR;
1194 goto out;
1195 }
1196 {
1197 // zero_dev is written by a kernel, not memset from the host: the value rides in the
1198 // command packet as a kernel argument, so no buffer crosses the bus.
1199 const int setk = global_data->kernel_hl_set_scalar;
1200 const float zero = 0.f;
1201 dt_opencl_set_kernel_arg(devid, setk, 0, sizeof(cl_mem), &zero_dev);
1202 dt_opencl_set_kernel_arg(devid, setk, 1, sizeof(float), &zero);
1203 size_t one[3] = { 1, 1, 1 };
1204 cl_err = dt_opencl_enqueue_kernel_2d(devid, setk, one);
1205 if(cl_err != CL_SUCCESS) goto out;
1206 }
1207 const int kernel = global_data->kernel_hl_cmean_reduce;
1208 size_t sizes[3] = { (size_t)n_groups * local_size, 1, 1 };
1209 size_t local[3] = { local_size, 1, 1 };
1210 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1211 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
1212 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &luminance);
1213 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &partial_sums);
1214 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &n_pixels);
1215 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), &epsilon);
1216 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), &zero_dev);
1217 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float) * 4 * local_size, NULL);
1218 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, sizes, local);
1219 if(cl_err != CL_SUCCESS) goto out;
1220
1221 float partial_host[8 * 256];
1222 cl_err = dt_opencl_read_buffer_from_device(devid, partial_host, partial_sums, 0, sizeof(float) * 4 * n_groups,
1223 CL_TRUE);
1224 if(cl_err != CL_SUCCESS) goto out;
1225 double accum[4] = { 0.0, 0.0, 0.0, 0.0 };
1226 for(int group = 0; group < n_groups; group++)
1227 for(int k = 0; k < 4; k++) accum[k] += (double)partial_host[group * 4 + k];
1228 if(accum[3] > 0.0)
1229 for(int c = 0; c < 3; c++) chroma_mean[c] = (float)(accum[c] / accum[3]);
1230
1231 // Re-hue the all-clip core's saturation floor toward the mean valid chromaticity, blended by
1232 // the clip-asymmetry gate x the trusted-ring vote (CPU _joint_core rehue mirror): clip0 is a
1233 // magnitude floor, not a colour -- redistribute it to cmean so the aniso obstacle/floor
1234 // enforces the surround chromaticity instead of the inverse-WB magenta. The vote confines
1235 // this to cores the region's flat mean actually describes (off on self-coloured emitters and
1236 // gradient skies). Untouched at gate 0 (approved behavior).
1237 if(accum[3] > 0.0 && floor_gate > 1e-6f)
1238 {
1239 // BRIGHT surround mean for the rehue + its vote (chroma_mean above stays the approved
1240 // all-valid solver target; see the CPU counterpart for the rationale)
1241 {
1242 const int plateau_kernel = global_data->kernel_hl_cgrad_plateau;
1243 dt_opencl_set_kernel_arg(devid, plateau_kernel, 0, sizeof(cl_mem), &estimate);
1244 dt_opencl_set_kernel_arg(devid, plateau_kernel, 1, sizeof(cl_mem), &valid);
1245 dt_opencl_set_kernel_arg(devid, plateau_kernel, 2, sizeof(cl_mem), &partial_sums);
1246 dt_opencl_set_kernel_arg(devid, plateau_kernel, 3, sizeof(int), &n_pixels);
1247 dt_opencl_set_kernel_arg(devid, plateau_kernel, 4, sizeof(float) * 2 * local_size, NULL);
1248 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, plateau_kernel, sizes, local);
1249 if(cl_err != CL_SUCCESS) goto out;
1250 const int fin = global_data->kernel_hl_reduce_finalize;
1251 const int fin_stride = 2, fin_mode = 1;
1252 const float fin_scale = 0.35f;
1253 dt_opencl_set_kernel_arg(devid, fin, 0, sizeof(cl_mem), &partial_sums);
1254 dt_opencl_set_kernel_arg(devid, fin, 1, sizeof(cl_mem), &lum_min_dev2);
1255 dt_opencl_set_kernel_arg(devid, fin, 2, sizeof(int), &n_groups);
1256 dt_opencl_set_kernel_arg(devid, fin, 3, sizeof(int), &fin_stride);
1257 dt_opencl_set_kernel_arg(devid, fin, 4, sizeof(int), &fin_mode);
1258 dt_opencl_set_kernel_arg(devid, fin, 5, sizeof(float), &fin_scale);
1259 size_t fin_size[3] = { 1, 1, 1 };
1260 cl_err = dt_opencl_enqueue_kernel_2d(devid, fin, fin_size);
1261 if(cl_err != CL_SUCCESS) goto out;
1262 }
1263 // BRIGHT surround mean for the rehue + its vote, all device-resident: the plateau gate
1264 // (hl_cgrad_plateau -> hl_reduce_finalize), the bright chromaticity mean
1265 // (hl_cmean_reduce -> hl_cmean_finalize) and the trusted-ring vote (hl_ring_vote ->
1266 // hl_ring_vote_finalize). chroma_mean above stays the approved all-valid solver target;
1267 // see the CPU counterpart for why the rehue needs the BRIGHT mean instead.
1268 {
1269 const int plateau_kernel = global_data->kernel_hl_cgrad_plateau;
1270 dt_opencl_set_kernel_arg(devid, plateau_kernel, 0, sizeof(cl_mem), &estimate);
1271 dt_opencl_set_kernel_arg(devid, plateau_kernel, 1, sizeof(cl_mem), &valid);
1272 dt_opencl_set_kernel_arg(devid, plateau_kernel, 2, sizeof(cl_mem), &partial_sums);
1273 dt_opencl_set_kernel_arg(devid, plateau_kernel, 3, sizeof(int), &n_pixels);
1274 dt_opencl_set_kernel_arg(devid, plateau_kernel, 4, sizeof(float) * 2 * local_size, NULL);
1275 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, plateau_kernel, sizes, local);
1276 if(cl_err != CL_SUCCESS) goto out;
1277
1278 const int fin = global_data->kernel_hl_reduce_finalize;
1279 const int fin_stride = 2, fin_mode = 1;
1280 const float fin_scale = 0.35f;
1281 size_t one[3] = { 1, 1, 1 };
1282 dt_opencl_set_kernel_arg(devid, fin, 0, sizeof(cl_mem), &partial_sums);
1283 dt_opencl_set_kernel_arg(devid, fin, 1, sizeof(cl_mem), &lum_min_dev2);
1284 dt_opencl_set_kernel_arg(devid, fin, 2, sizeof(int), &n_groups);
1285 dt_opencl_set_kernel_arg(devid, fin, 3, sizeof(int), &fin_stride);
1286 dt_opencl_set_kernel_arg(devid, fin, 4, sizeof(int), &fin_mode);
1287 dt_opencl_set_kernel_arg(devid, fin, 5, sizeof(float), &fin_scale);
1288 cl_err = dt_opencl_enqueue_kernel_2d(devid, fin, one);
1289 if(cl_err != CL_SUCCESS) goto out;
1290
1291 const int cmean_kernel = global_data->kernel_hl_cmean_reduce;
1292 dt_opencl_set_kernel_arg(devid, cmean_kernel, 0, sizeof(cl_mem), &estimate);
1293 dt_opencl_set_kernel_arg(devid, cmean_kernel, 1, sizeof(cl_mem), &valid);
1294 dt_opencl_set_kernel_arg(devid, cmean_kernel, 2, sizeof(cl_mem), &luminance);
1295 dt_opencl_set_kernel_arg(devid, cmean_kernel, 3, sizeof(cl_mem), &partial_sums);
1296 dt_opencl_set_kernel_arg(devid, cmean_kernel, 4, sizeof(int), &n_pixels);
1297 dt_opencl_set_kernel_arg(devid, cmean_kernel, 5, sizeof(float), &epsilon);
1298 dt_opencl_set_kernel_arg(devid, cmean_kernel, 6, sizeof(cl_mem), &lum_min_dev2);
1299 dt_opencl_set_kernel_arg(devid, cmean_kernel, 7, sizeof(float) * 4 * local_size, NULL);
1300 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, cmean_kernel, sizes, local);
1301 if(cl_err != CL_SUCCESS) goto out;
1302
1303 const int cfin = global_data->kernel_hl_cmean_finalize;
1304 dt_opencl_set_kernel_arg(devid, cfin, 0, sizeof(cl_mem), &partial_sums);
1305 dt_opencl_set_kernel_arg(devid, cfin, 1, sizeof(cl_mem), &bright_dev);
1306 dt_opencl_set_kernel_arg(devid, cfin, 2, sizeof(int), &n_groups);
1307 cl_err = dt_opencl_enqueue_kernel_2d(devid, cfin, one);
1308 if(cl_err != CL_SUCCESS) goto out;
1309
1310 const int vote_kernel = global_data->kernel_hl_ring_vote;
1311 dt_opencl_set_kernel_arg(devid, vote_kernel, 0, sizeof(cl_mem), &estimate);
1312 dt_opencl_set_kernel_arg(devid, vote_kernel, 1, sizeof(cl_mem), &valid);
1313 dt_opencl_set_kernel_arg(devid, vote_kernel, 2, sizeof(cl_mem), &partial_sums);
1314 dt_opencl_set_kernel_arg(devid, vote_kernel, 3, sizeof(int), &n_pixels);
1315 dt_opencl_set_kernel_arg(devid, vote_kernel, 4, sizeof(float) * 8 * local_size, NULL);
1316 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, vote_kernel, sizes, local);
1317 if(cl_err != CL_SUCCESS) goto out;
1318
1319 const int vfin = global_data->kernel_hl_ring_vote_finalize;
1320 dt_opencl_set_kernel_arg(devid, vfin, 0, sizeof(cl_mem), &partial_sums);
1321 dt_opencl_set_kernel_arg(devid, vfin, 1, sizeof(cl_mem), &bright_dev);
1322 dt_opencl_set_kernel_arg(devid, vfin, 2, sizeof(cl_mem), &rehue_dev);
1323 dt_opencl_set_kernel_arg(devid, vfin, 3, sizeof(int), &n_groups);
1324 dt_opencl_set_kernel_arg(devid, vfin, 4, sizeof(float), &floor_gate);
1325 cl_err = dt_opencl_enqueue_kernel_2d(devid, vfin, one);
1326 if(cl_err != CL_SUCCESS) goto out;
1327 }
1328
1329 {
1330 // unconditional launch: hl_clip0_rehue reads {rehue_gate, beta} from rehue_dev and no-ops
1331 // at 0, so no host round-trip decides whether the rehue runs
1332 const int rehue_kernel = global_data->kernel_hl_clip0_rehue;
1333 dt_opencl_set_kernel_arg(devid, rehue_kernel, 0, sizeof(cl_mem), &clip0);
1334 dt_opencl_set_kernel_arg(devid, rehue_kernel, 1, sizeof(cl_mem), &hole);
1335 dt_opencl_set_kernel_arg(devid, rehue_kernel, 2, sizeof(cl_mem), &bright_dev);
1336 dt_opencl_set_kernel_arg(devid, rehue_kernel, 3, sizeof(cl_mem), &rehue_dev);
1337 dt_opencl_set_kernel_arg(devid, rehue_kernel, 4, sizeof(int), &region_w);
1338 dt_opencl_set_kernel_arg(devid, rehue_kernel, 5, sizeof(int), &region_h);
1339 cl_err = dt_opencl_enqueue_kernel_2d(devid, rehue_kernel, size);
1340 if(cl_err != CL_SUCCESS) goto out;
1341 }
1342 }
1343 }
1344
1345 // ONE symbolic analysis + GPU numeric factorization for the three channels; when the core
1346 // exceeds DT_HL_SPARSE_MAX (or the factorization fails) take the same road as the CPU:
1347 // the matrix-free CG, here fully on the device
1348 // assemble A = lambda_solid*I - Delta (order 1) over the all-clip hole; use_cg when the core
1349 // is too large for the direct factorization (mirrors the CPU _sp_pde_factor / CG choice)
1350 int n_unknowns = 0;
1351 int use_cg
1352 = !_sp_pde_assemble(hole_mask, NULL, (react > 0.f) ? react : 0.f, 1, 1.f, region_w, region_h,
1353 &matrix_col_ptr, &matrix_row_index, &matrix_values, &perm_grid, &n_unknowns, pipe);
1354 if(!use_cg)
1355 {
1356 factor = _sp_chol_factor_cl(devid, _hl_sp_chol_kernels(gd_void), n_unknowns, matrix_col_ptr, matrix_row_index,
1357 matrix_values);
1358 perm_grid_dev = factor ? _sp_cl_upload(devid, perm_grid, sizeof(int) * n_unknowns) : NULL;
1359 rhs_dev = factor ? dt_opencl_alloc_device_buffer(devid, sizeof(double) * n_unknowns) : NULL;
1360 if(!factor)
1361 use_cg = 1;
1362 else if(!perm_grid_dev || !rhs_dev)
1363 {
1364 cl_err = DT_OPENCL_DEFAULT_ERROR;
1365 goto out;
1366 }
1367 }
1368 const int max_iter = CLAMP(2 * extent, 200, 2000);
1369
1370 // per channel: build the chromaticity ratio plane, solve its diffusion system, store into ratios[c]
1371 for(int c = 0; c < 3; c++)
1372 {
1373 // init: ratio plane on valid pixels, flat-colour seed on the hole (cg_field = solver unknown)
1374 {
1375 const int kernel = global_data->kernel_hl_pde_init;
1376 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1377 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &luminance);
1378 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &hole);
1379 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &embedded);
1380 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &ratios[c]);
1381 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &cg_field);
1382 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &region_w);
1383 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &region_h);
1384 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &c);
1385 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(float), &chroma_mean[c]);
1386 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(float), &epsilon);
1387 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1388 if(cl_err != CL_SUCCESS) goto out;
1389 }
1390 // direct path: assemble this channel's right-hand side on the device...
1391 if(!use_cg)
1392 {
1393 const int kernel = global_data->kernel_hl_pde_rhs;
1394 size_t size_1d[3] = { ROUNDUP(n_unknowns, 64), 1, 1 };
1395 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &embedded);
1396 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &perm_grid_dev);
1397 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &rhs_dev);
1398 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &n_unknowns);
1399 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
1400 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
1401 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &react);
1402 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &chroma_mean[c]);
1403 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size_1d);
1404 if(cl_err != CL_SUCCESS) goto out;
1405 }
1406 // ...solve with the shared Cholesky factor...
1407 if(!use_cg)
1408 {
1409 if(_sp_chol_solve_cl(factor, _hl_sp_chol_kernels(gd_void), rhs_dev))
1410 {
1411 cl_err = DT_OPENCL_DEFAULT_ERROR;
1412 goto out;
1413 }
1414 // validate before scattering: the device factor kernel takes sqrt() of the pivots
1415 // without checking their sign, so a system whose replicate-clamped border rows are not
1416 // positive definite yields quiet NaN -- the CPU factor REJECTS such systems and falls
1417 // back to conjugate gradient, and the device path must degrade the same way instead of
1418 // blending NaN into the output. n_unknowns <= 16384 doubles = at most 128 KB on the bus.
1419 double *solution_check = (double *)dt_pixelpipe_cache_alloc_align(sizeof(double) * n_unknowns, pipe);
1420 int finite = (solution_check != NULL);
1421 if(solution_check)
1422 {
1423 finite = (dt_opencl_read_buffer_from_device(devid, solution_check, rhs_dev, 0, sizeof(double) * n_unknowns,
1424 CL_TRUE)
1425 == CL_SUCCESS);
1426 for(int check_index = 0; finite && check_index < n_unknowns; check_index++)
1427 if(!isfinite(solution_check[check_index])) finite = 0;
1428 dt_pixelpipe_cache_free_align(solution_check);
1429 }
1430 if(!finite)
1431 {
1433 factor = NULL;
1434 use_cg = 1; // this channel and the remaining ones take the iterative road
1435 }
1436 else
1437 {
1438 // ...and scatter the solution back into the ratio plane
1439 const int kernel = global_data->kernel_hl_pde_scatter;
1440 size_t size_1d[3] = { ROUNDUP(n_unknowns, 64), 1, 1 };
1441 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &rhs_dev);
1442 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &perm_grid_dev);
1443 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &ratios[c]);
1444 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &n_unknowns);
1445 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size_1d);
1446 if(cl_err != CL_SUCCESS) goto out;
1447 continue;
1448 }
1449 }
1450 // iterative road: on-device conjugate gradient on the seeded unknown, then clamp
1451 // the ratios non-negative (also the recovery path when the direct solve was rejected)
1452 {
1453 cl_err = _region_pde_cg_cl(devid, gd_void, cg_field, hole, region_w, region_h, (react > 0.f) ? react : 0.f,
1454 (react > 0.f) ? chroma_mean[c] : 0.f, max_iter);
1455 if(cl_err != CL_SUCCESS) goto out;
1456 const int kernel = global_data->kernel_hl_relu;
1457 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &cg_field);
1458 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &ratios[c]);
1459 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &region_w);
1460 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_h);
1461 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1462 if(cl_err != CL_SUCCESS) goto out;
1463 }
1464 }
1465
1466 // feathered composite: blur the core mask and blend dome x ratios into estimate through it
1467 // (no hard hand-off at the core rim)
1468 mask_img = dt_opencl_alloc_device(devid, region_w, region_h, sizeof(float));
1469 mask_blur = dt_opencl_alloc_device(devid, region_w, region_h, sizeof(float));
1470 if(!mask_img || !mask_blur)
1471 {
1472 cl_err = DT_OPENCL_DEFAULT_ERROR;
1473 goto out;
1474 }
1475 {
1476 const int kernel = global_data->kernel_hl_mask_to_img1;
1477 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &hole);
1478 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &mask_img);
1479 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &region_w);
1480 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_h);
1481 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1482 if(cl_err != CL_SUCCESS) goto out;
1483 }
1484 cl_err = _region_blur1_cl(devid, mask_img, mask_blur, region_w, region_h,
1485 fmaxf(4.f, CLAMP(reg_radius / 6.f, 8.f, 64.f) / 4.f));
1486 if(cl_err != CL_SUCCESS) goto out;
1487 {
1488 const int kernel = global_data->kernel_hl_core_blend;
1489 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1490 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
1491 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &hole);
1492 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &dome_lum);
1493 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &ratio0);
1494 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &ratio1);
1495 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), &ratio2);
1496 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(cl_mem), &mask_blur);
1497 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &region_w);
1498 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &region_h);
1499 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(float), &epsilon);
1500 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1501 }
1502
1503out:
1512 dt_opencl_release_mem_object(partial_sums);
1513 dt_opencl_release_mem_object(lum_min_dev2);
1514 dt_opencl_release_mem_object(bright_dev);
1517 dt_opencl_release_mem_object(perm_grid_dev);
1522 dt_pixelpipe_cache_free_align(matrix_col_ptr);
1523 dt_pixelpipe_cache_free_align(matrix_row_index);
1524 dt_pixelpipe_cache_free_align(matrix_values);
1527 return cl_err;
1528}
1529
1530// Chromaticity-gradient continuation on the device (see _chromaticity_gradient / core.h for the rationale).
1531// Mirrors the CPU stage step by step: plateau reduction -> guard blur -> anchor mask (host-read for
1532// the bail decision and the dome's downsample factor, same auto formula as the CPU dome) -> three
1533// biharmonic share extensions -> reprojection + joint floor. Re-validate with HL_CGRADCL_TEST.
1534cl_int _chromaticity_gradient_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid,
1535 cl_mem clip0, cl_mem clip_depth, const int region_w, const int region_h,
1536 const float reg_radius, const float floor_gate,
1537 const float module_scale, const dt_dev_pixelpipe_t *pipe)
1538{
1540 const size_t region_pixels = (size_t)region_w * region_h;
1541 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
1542 size_t size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
1543 const float epsilon = 1e-6f;
1544
1545 // The stage-2 reduction finalizers this needs are compiled only where the fp64 extension
1546 // is (data/kernels/highlights_harmonic.cl). Without them the caller falls back to the CPU
1547 // twin, the same way the sparse solver and the PDE/aniso stages already do.
1548 if(global_data->kernel_hl_reduce_finalize < 0 || global_data->kernel_hl_cmean_finalize < 0
1549 || global_data->kernel_hl_ring_vote_finalize < 0)
1550 return cl_err; // no fp64 device
1551
1552 cl_mem guard_src = dt_opencl_alloc_device(devid, region_w, region_h, sizeof(float)); // image (gaussian)
1553 cl_mem guard_blur = dt_opencl_alloc_device(devid, region_w, region_h, sizeof(float)); // image (gaussian)
1554 cl_mem gate_src = dt_opencl_alloc_device(devid, region_w, region_h, sizeof(float)); // image (gaussian)
1555 cl_mem gate_msk = dt_opencl_alloc_device(devid, region_w, region_h, sizeof(float)); // image (gaussian)
1556 cl_mem gate_wgt = dt_opencl_alloc_device(devid, region_w, region_h, sizeof(float)); // image (gaussian)
1557 cl_mem gate_nrm = dt_opencl_alloc_device(devid, region_w, region_h, sizeof(float)); // image (gaussian)
1558 cl_mem hole = dt_opencl_alloc_device_buffer(devid, region_pixels);
1559 // pass 2 solves over `hole` (authored + dark intrusions) but writes only `authored`
1560 cl_mem authored = dt_opencl_alloc_device_buffer(devid, region_pixels);
1561 cl_mem field = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1562 cl_mem shares = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1563 cl_mem partial_sums = NULL, gate_vote_dev = NULL; // ring vote, device-resident
1564 uint8_t *hole_host = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
1565 if(!guard_src || !guard_blur || !gate_src || !gate_msk || !gate_wgt || !gate_nrm || !hole || !authored
1566 || !field || !shares || !hole_host)
1567 goto out;
1568
1569 // --- 1. plateau luminance over the any-clip pixels (device partials, host finish) ---
1570 float lum_anchor_min = 0.f;
1571 {
1572 const int local_size = 64, n_groups = 256;
1573 const int n_pixels = (int)region_pixels;
1574 partial_sums = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 2 * n_groups);
1575 if(!partial_sums) goto out;
1576 const int kernel = global_data->kernel_hl_cgrad_plateau;
1577 size_t sizes[3] = { (size_t)n_groups * local_size, 1, 1 };
1578 size_t local[3] = { local_size, 1, 1 };
1579 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1580 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
1581 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &partial_sums);
1582 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &n_pixels);
1583 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float) * 2 * local_size, NULL);
1584 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, sizes, local);
1585 if(cl_err != CL_SUCCESS) goto out;
1586 float partial_host[2 * 256];
1587 cl_err = dt_opencl_read_buffer_from_device(devid, partial_host, partial_sums, 0,
1588 sizeof(float) * 2 * n_groups, CL_TRUE);
1589 if(cl_err != CL_SUCCESS) goto out;
1590 double lum_accum = 0.0, count = 0.0;
1591 for(int group = 0; group < n_groups; group++)
1592 {
1593 lum_accum += (double)partial_host[group * 2 + 0];
1594 count += (double)partial_host[group * 2 + 1];
1595 }
1596 if(count == 0.0) // nothing blown in this region
1597 {
1598 cl_err = CL_SUCCESS;
1599 goto out;
1600 }
1601 lum_anchor_min = 0.35f * (float)(lum_accum / count);
1602 }
1603
1604 // --- 2. guard proximity: blur the any-clip mask (thin ring, sigma 4 -- see the CPU stage) ---
1605 {
1606 const int kernel = global_data->kernel_hl_cgrad_guard;
1607 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &valid);
1608 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &guard_src);
1609 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &region_w);
1610 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_h);
1611 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1612 if(cl_err != CL_SUCCESS) goto out;
1613 cl_err = _region_blur1_cl(devid, guard_src, guard_blur, region_w, region_h, 4.f);
1614 if(cl_err != CL_SUCCESS) goto out;
1615 }
1616
1617 // --- 3. anchor mask; host-read for the bail decision + the dome's auto downsample factor ---
1618 {
1619 const int kernel = global_data->kernel_hl_cgrad_anchor;
1620 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1621 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
1622 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &guard_blur);
1623 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &hole);
1624 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
1625 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
1626 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &lum_anchor_min);
1627 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1628 if(cl_err != CL_SUCCESS) goto out;
1629 }
1630 cl_err = dt_opencl_read_buffer_from_device(devid, hole_host, hole, 0, region_pixels, CL_TRUE);
1631 if(cl_err != CL_SUCCESS) goto out;
1632 size_t n_hole = 0;
1633 for(size_t i = 0; i < region_pixels; i++) n_hole += (hole_host[i] != 0);
1634 const size_t n_anchor = region_pixels - n_hole;
1635 if(n_anchor < 64 || n_anchor < region_pixels / 256) // no usable bright surround: keep the fence chromaticity
1636 {
1637 cl_err = CL_SUCCESS;
1638 goto out;
1639 }
1640 const int downsample = MAX(1, (int)ceilf(sqrtf((float)n_hole / (float)DT_HL_DOME_NMAX_SPARSE)));
1641
1642 // --- 4. per channel: biharmonic (gradient-extending) share extension ---
1643 for(int c = 0; c < 3; c++)
1644 {
1645 {
1646 const int kernel = global_data->kernel_hl_cgrad_share;
1647 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1648 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &field);
1649 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &region_w);
1650 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_h);
1651 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &c);
1652 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), &epsilon);
1653 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1654 if(cl_err != CL_SUCCESS) goto out;
1655 }
1656 cl_err = _biharmonic_dome_cl(devid, gd_void, field, hole, region_w, region_h, downsample, pipe);
1657 if(cl_err != CL_SUCCESS) goto out;
1658 {
1659 const int kernel = global_data->kernel_hl_cgrad_store;
1660 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &field);
1661 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &shares);
1662 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &region_w);
1663 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_h);
1664 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &c);
1665 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1666 if(cl_err != CL_SUCCESS) goto out;
1667 }
1668 }
1669
1670 // --- 5. content gate: agreement with the 1-clip annulus, diffused by normalized convolution
1671 // (mirrors the CPU gate; see _chromaticity_gradient) ---
1672 {
1673 const float gate_tau = 0.10f;
1674 const int kernel = global_data->kernel_hl_cgrad_gate;
1675 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1676 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
1677 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &shares);
1678 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &gate_src);
1679 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &gate_msk);
1680 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_w);
1681 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &region_h);
1682 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &epsilon);
1683 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(float), &gate_tau);
1684 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1685 if(cl_err != CL_SUCCESS) goto out;
1686 const float gate_sigma = CLAMP(reg_radius / 4.f, 8.f, 96.f);
1687 cl_err = _region_blur1_cl(devid, gate_src, gate_wgt, region_w, region_h, gate_sigma);
1688 if(cl_err != CL_SUCCESS) goto out;
1689 cl_err = _region_blur1_cl(devid, gate_msk, gate_nrm, region_w, region_h, gate_sigma);
1690 if(cl_err != CL_SUCCESS) goto out;
1691 }
1692
1693 // region-level ring vote (see the CPU stage), entirely on the device: a two-stage reduction over
1694 // the two gate planes publishes the scalar into gate_vote_dev, which the reprojection kernel reads
1695 // directly. Reading both full planes back just to divide two sums is what this replaces.
1696 {
1697 const int vote_local = 64, vote_groups = 256;
1698 cl_mem vote_partial = dt_opencl_alloc_device_buffer(devid, sizeof(float) * 2 * vote_groups);
1699 gate_vote_dev = dt_opencl_alloc_device_buffer(devid, sizeof(float));
1700 if(!vote_partial || !gate_vote_dev)
1701 {
1702 dt_opencl_release_mem_object(vote_partial);
1703 cl_err = DT_OPENCL_DEFAULT_ERROR;
1704 goto out;
1705 }
1706 const int vk = global_data->kernel_hl_vote_reduce;
1707 dt_opencl_set_kernel_arg(devid, vk, 0, sizeof(cl_mem), &gate_src);
1708 dt_opencl_set_kernel_arg(devid, vk, 1, sizeof(cl_mem), &gate_msk);
1709 dt_opencl_set_kernel_arg(devid, vk, 2, sizeof(cl_mem), &vote_partial);
1710 dt_opencl_set_kernel_arg(devid, vk, 3, sizeof(int), &region_w);
1711 dt_opencl_set_kernel_arg(devid, vk, 4, sizeof(int), &region_h);
1712 dt_opencl_set_kernel_arg(devid, vk, 5, sizeof(float) * 2 * vote_local, NULL);
1713 size_t vs[3] = { (size_t)vote_groups * vote_local, 1, 1 };
1714 size_t vl[3] = { vote_local, 1, 1 };
1715 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, vk, vs, vl);
1716 if(cl_err == CL_SUCCESS)
1717 {
1718 const int fin = global_data->kernel_hl_reduce_finalize;
1719 const int stride = 2, mode = 1;
1720 const float scale = 1.f;
1721 dt_opencl_set_kernel_arg(devid, fin, 0, sizeof(cl_mem), &vote_partial);
1722 dt_opencl_set_kernel_arg(devid, fin, 1, sizeof(cl_mem), &gate_vote_dev);
1723 dt_opencl_set_kernel_arg(devid, fin, 2, sizeof(int), &vote_groups);
1724 dt_opencl_set_kernel_arg(devid, fin, 3, sizeof(int), &stride);
1725 dt_opencl_set_kernel_arg(devid, fin, 4, sizeof(int), &mode);
1726 dt_opencl_set_kernel_arg(devid, fin, 5, sizeof(float), &scale);
1727 size_t one[3] = { 1, 1, 1 };
1728 cl_err = dt_opencl_enqueue_kernel_2d(devid, fin, one);
1729 }
1730 dt_opencl_release_mem_object(vote_partial);
1731 if(cl_err != CL_SUCCESS) goto out;
1732 }
1733
1734 // --- 6. reproject the multi-clip subsets onto the field (gate-blended) + joint saturation floor ---
1735 {
1736 const int kernel = global_data->kernel_hl_cgrad_reproject;
1737 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1738 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
1739 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &clip0);
1740 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &shares);
1741 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &gate_wgt);
1742 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &gate_nrm);
1743 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &region_w);
1744 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &region_h);
1745 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(float), &epsilon);
1746 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(cl_mem), &gate_vote_dev);
1747 const float authored_ramp = CF_AUTHORED_RAMP;
1748 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(float), &authored_ramp);
1749 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(float), &floor_gate);
1750 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1751 }
1752
1753 // --- 7. PASS 2 = a3, VALUE continuation of the floor-authored 1-clip band (WB'd clips only;
1754 // see the CPU stage's pass 2): per clipped channel, extend the channel's own VALUE
1755 // biharmonically over the authored collar, anchored on both sides, floored at saturation.
1756 if(floor_gate > 1e-6f)
1757 {
1758 // same collar as the CPU twin: full-resolution pixels converted to this buffer's pixels
1759 const float a3_collar = DT_HL_A3_COLLAR_PX / fmaxf(module_scale, 1e-6f);
1760 for(int c = 0; c < 3; c++)
1761 {
1762 {
1763 const int kernel = global_data->kernel_hl_cgrad_hole1c;
1764 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1765 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &valid);
1766 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &clip0);
1767 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &clip_depth);
1768 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &hole);
1769 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &authored);
1770 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_mem), &field);
1771 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &region_w);
1772 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &region_h);
1773 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &c);
1774 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(float), &a3_collar);
1775 dt_opencl_set_kernel_arg(devid, kernel, 11, sizeof(float), &lum_anchor_min);
1776 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1777 if(cl_err != CL_SUCCESS) goto out;
1778 }
1779 // Nothing authored for this channel -> nothing to fill (the dark pixels that merely join the
1780 // solve are not this pass's business).
1781 cl_err = dt_opencl_read_buffer_from_device(devid, hole_host, authored, 0, region_pixels, CL_TRUE);
1782 if(cl_err != CL_SUCCESS) goto out;
1783 size_t n_authored_c = 0;
1784 for(size_t i = 0; i < region_pixels; i++) n_authored_c += (hole_host[i] != 0);
1785 if(n_authored_c == 0) continue;
1786 // The dome's auto downsample must be sized on the SOLVE DOMAIN, which is the union -- that is
1787 // what the CPU twin gets when it passes forced_downsample = 0 and the dome counts the mask it
1788 // was handed. Sizing it on the authored count alone would pick a finer grid than the CPU and
1789 // silently break CPU/GPU parity on any region with dark intrusions.
1790 cl_err = dt_opencl_read_buffer_from_device(devid, hole_host, hole, 0, region_pixels, CL_TRUE);
1791 if(cl_err != CL_SUCCESS) goto out;
1792 size_t n_hole_c = 0;
1793 for(size_t i = 0; i < region_pixels; i++) n_hole_c += (hole_host[i] != 0);
1794 const int downsample_c = MAX(1, (int)ceilf(sqrtf((float)n_hole_c / (float)DT_HL_DOME_NMAX_SPARSE)));
1795 cl_err = _biharmonic_dome_cl(devid, gd_void, field, hole, region_w, region_h, downsample_c, pipe);
1796 if(cl_err != CL_SUCCESS) goto out;
1797 {
1798 const int kernel = global_data->kernel_hl_cgrad_write1c;
1799 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &estimate);
1800 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &authored);
1801 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &field);
1802 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &clip0);
1803 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
1804 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
1805 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &c);
1806 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, size);
1807 if(cl_err != CL_SUCCESS) goto out;
1808 }
1809 }
1810 }
1811
1812out:
1814 dt_opencl_release_mem_object(guard_blur);
1823 dt_opencl_release_mem_object(partial_sums);
1824 dt_opencl_release_mem_object(gate_vote_dev);
1826 return cl_err;
1827}
1828
1829#endif // HAVE_OPENCL && DT_HL_SPARSE_SOLVE
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)
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)
const int t
const dt_colormatrix_t dt_aligned_pixel_t out
void _chromaticity_gradient(_hl_region_ctx_t *const ctx)
Definition core.c:509
__DT_CLONE_TARGETS__ void _joint_core(_hl_region_ctx_t *const ctx)
Definition core.c:267
__DT_CLONE_TARGETS__ void _selfdome(_hl_region_ctx_t *const ctx)
Definition core.c:36
__DT_CLONE_TARGETS__ void _biharmonic_dome(float *const restrict field, const uint8_t *const restrict hole, const int region_w, const int region_h, const int forced_downsample, const dt_dev_pixelpipe_t *pipe)
Definition dome.c:113
static float kernel(const float *x, const float *y)
static void _knee_blur(const float *const restrict in, float *const restrict out, const int width, const int height, const float sigma)
Definition knee.h:31
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:2554
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:2970
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2894
int dt_opencl_read_buffer_from_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2727
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2545
int dt_opencl_enqueue_kernel_2d_with_local(const int dev, const int kernel, const size_t *sizes, const size_t *local)
Definition opencl.c:2560
int dt_opencl_enqueue_copy_buffer_to_buffer(const int devid, cl_mem src_buffer, cl_mem dst_buffer, size_t srcoffset, size_t dstoffset, size_t size)
Definition opencl.c:2714
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
#define DT_OPENCL_DEFAULT_ERROR
Definition opencl.h:61
#define ROUNDUP(a, n)
Definition opencl.h:82
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
__DT_CLONE_TARGETS__ void _region_pde_solve(float *const restrict field, const uint8_t *const restrict hole, const float *const restrict diffusion, const float *const restrict target, const float *const restrict source, const int order, const float lambda, const int region_w, const int region_h, float *const restrict residual, float *const restrict search_dir, float *const restrict operator_dir, float *const restrict embedded, float *const restrict scratch, const int maxiter)
Definition pde.c:366
_sp_chol_t * _sp_pde_factor(const uint8_t *const restrict hole, const float *const restrict diffusion, const int order, const float lambda, const int region_w, const int region_h, int **perm_out, int *n_unknowns_out, const dt_dev_pixelpipe_t *pipe)
Definition pde.c:306
__DT_CLONE_TARGETS__ void _sp_pde_solve(const _sp_chol_t *const factor, const int *const restrict perm_grid, float *const restrict field, const uint8_t *const restrict hole, const float *const restrict diffusion, const float *const restrict target, const float *const restrict source, const int order, const float lambda, const int region_w, const int region_h, double *const restrict rhs, float *const restrict embedded, float *const restrict operator_out, float *const restrict scratch)
Definition pde.c:332
int _sp_pde_assemble(const uint8_t *const restrict hole, const float *const restrict diffusion, const float diffusion_const, const int order, const float lambda, const int region_w, const int region_h, int **matrix_col_ptr_out, int **matrix_row_index_out, double **matrix_values_out, int **perm_grid_out, int *n_unknowns_out, const dt_dev_pixelpipe_t *const pipe)
Definition pde.c:171
static _sp_chol_cl_kernels_t _hl_sp_chol_kernels(void *gd_void)
Definition pde.h:107
const float factor
Definition pdf.h:91
#define dt_pixelpipe_cache_alloc_align(size, pipe)
#define dt_pixelpipe_cache_free_align(mem)
#define dt_pixelpipe_cache_alloc_align_float(pixels, pipe)
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
static void _sp_chol_free(_sp_chol_t *factor)
static void _sp_chol_cl_free(_sp_chol_cl_t *factor)
static _sp_chol_cl_t * _sp_chol_factor_cl(const int devid, const _sp_chol_cl_kernels_t kernels, const int dimension, const int *const restrict matrix_col_ptr, const int *const restrict matrix_row_index, const double *const restrict matrix_values)
static int _sp_chol_solve_cl(const _sp_chol_cl_t *const factor, const _sp_chol_cl_kernels_t kernels, cl_mem rhs)
static cl_mem _sp_cl_upload(const int devid, const void *data, const size_t bytes)
static float _hl_ring_flat_mean_vote(const float *const restrict estimate, const float *const restrict valid, const dt_aligned_pixel_t cmean, const size_t region_pixels)
#define DT_HL_DOME_NMAX_SPARSE
#define CF_JOINT_TAU
static float _hl_floor_worth(const float chroma_gain)
#define DT_HL_A3_COLLAR_PX
#define HL_PFOR(...)
#define CF_AUTHORED_RAMP
const _hl_region_t * region
const dt_dev_pixelpipe_t * pipe
#define __DT_CLONE_TARGETS__
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MAX(a, b)
Definition thinplate.c:29