Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
dome.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// Biharmonic luminance dome solve (CPU + OpenCL). (implementation; see dome.h for the public API.)
20
21#include "common/darktable.h"
23#include "control/control.h"
24#include "develop/imageop.h"
26#include "iop/highlights/blur.h"
27#include "iop/highlights/dome.h"
28#include "iop/highlights/pde.h"
29#include <math.h>
30#include <string.h>
31
33void _biharmonic_dome(float *const restrict field, const uint8_t *const restrict hole, const int region_w,
34 const int region_h, const int forced_downsample, const dt_dev_pixelpipe_t *pipe)
35{
36 const size_t region_pixels = (size_t)region_w * region_h;
37 size_t n_hole_fine = 0;
38 for(size_t i = 0; i < region_pixels; i++)
39 if(hole[i]) n_hole_fine++;
40 if(n_hole_fine == 0) return;
41
42 // pick a downsampling factor so the coarse hole has at most ~DT_HL_DOME_NMAX unknowns (the dense
43 // Cholesky is O(N^3)). Raise DT_HL_DOME_NMAX to make the dome grid finer / exact (downsample -> 1)
44 // at more cost -- a quick way to test whether the coarse approximation matters for a given image.
45 const int max_unknowns = DT_HL_DOME_NMAX_SPARSE;
46 // The caller may force the factor (forced_downsample > 0) so several per-channel domes share ONE
47 // grid resolution. With a per-channel factor (each channel picking its own from its own hole size)
48 // the three domes are approximated at different scales, their ratio drifts, and a saturated colour
49 // collapses off-hue. forced_downsample == 0 keeps the standalone behaviour (auto from this hole).
50 int downsample = (forced_downsample > 0) ? forced_downsample
51 : MAX(1, (int)ceilf(sqrtf((float)n_hole_fine / (float)max_unknowns)));
52 int coarse_w = (region_w + downsample - 1) / downsample;
53 int coarse_h = (region_h + downsample - 1) / downsample;
54 const size_t coarse_pixels = (size_t)coarse_w * coarse_h;
55
56 float *const restrict coarse_field = dt_pixelpipe_cache_alloc_align_float(coarse_pixels, pipe);
57 uint8_t *const restrict coarse_hole
58 = (uint8_t *)dt_pixelpipe_cache_alloc_align(sizeof(uint8_t) * coarse_pixels, pipe);
59 int *const restrict coarse_index = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * coarse_pixels, pipe);
60 if(!coarse_field || !coarse_hole || !coarse_index)
61 {
65 return;
66 }
67
68 // box-downsample: coarse value = mean of the block's VALID (non-hole) fine pixels; a coarse cell
69 // is a hole if the majority of its block is hole (so boundary cells keep real rim data)
70 __OMP_PARALLEL_FOR__(collapse(2))
71 for(int coarse_y = 0; coarse_y < coarse_h; coarse_y++)
72 for(int coarse_x = 0; coarse_x < coarse_w; coarse_x++)
73 {
74 double accum = 0.0;
75 int n_valid = 0, n_hole_block = 0, n_total = 0;
76 for(int fine_y = coarse_y * downsample; fine_y < MIN((coarse_y + 1) * downsample, region_h); fine_y++)
77 for(int fine_x = coarse_x * downsample; fine_x < MIN((coarse_x + 1) * downsample, region_w); fine_x++)
78 {
79 const size_t fine_index = (size_t)fine_y * region_w + fine_x;
80 n_total++;
81 if(hole[fine_index])
82 {
83 n_hole_block++;
84 }
85 else
86 {
87 accum += field[fine_index];
88 n_valid++;
89 }
90 }
91 const size_t coarse_i = (size_t)coarse_y * coarse_w + coarse_x;
92 coarse_hole[coarse_i] = (2 * n_hole_block > n_total) ? 1 : 0;
93 coarse_field[coarse_i] = (n_valid > 0) ? (float)(accum / n_valid) : 0.f;
94 }
95
96 // enumerate coarse hole unknowns
97 int n_unknowns = 0;
98 for(size_t coarse_i = 0; coarse_i < coarse_pixels; coarse_i++)
99 coarse_index[coarse_i] = coarse_hole[coarse_i] ? n_unknowns++ : -1;
100
101 if(n_unknowns > 0)
102 {
103 // 13-point Delta^2 stencil (Laplacian of the 5-point Laplacian): the discrete biharmonic
104 // operator Delta^2 u = Delta(Delta u), reaching TWO rings out (hence the +-2 taps and the
105 // 2-ring Dirichlet). Weights {20,-8,-8,-8,-8, 2,2,2,2, 1,1,1,1} = the standard 5-point
106 // Laplacian convolved with itself (center 20, edge -8, diagonal 2, far-axis 1).
107 const int stencil_dy[13] = { 0, -1, 1, 0, 0, -1, -1, 1, 1, -2, 2, 0, 0 };
108 const int stencil_dx[13] = { 0, 0, 0, -1, 1, -1, 1, -1, 1, 0, 0, -2, 2 };
109 const float stencil_weight[13] = { 20.f, -8.f, -8.f, -8.f, -8.f, 2.f, 2.f, 2.f, 2.f, 1.f, 1.f, 1.f, 1.f };
110 int solved = 0;
111
112 // ---- sparse direct solve (the DT_HL_DOME_NMAX_SPARSE-sized grid) ----
113 {
114 int *unknown_x = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * n_unknowns, pipe);
115 int *unknown_y = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * n_unknowns, pipe);
116 int *permutation = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * n_unknowns, pipe);
117 int *inverse_perm = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * n_unknowns, pipe);
118 int *matrix_col_ptr = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * (n_unknowns + 1), pipe);
119 double *right_hand_side = (double *)dt_pixelpipe_cache_alloc_align(sizeof(double) * n_unknowns, pipe);
120 int *matrix_row_index = NULL;
121 double *matrix_values = NULL;
122
123 if(unknown_x && unknown_y && permutation && inverse_perm && matrix_col_ptr && right_hand_side)
124 {
125 for(size_t coarse_i = 0; coarse_i < coarse_pixels; coarse_i++)
126 if(coarse_hole[coarse_i])
127 {
128 unknown_x[coarse_index[coarse_i]] = (int)(coarse_i % coarse_w);
129 unknown_y[coarse_index[coarse_i]] = (int)(coarse_i / coarse_w);
130 }
131
132 for(int i = 0; i < n_unknowns; i++) permutation[i] = i;
133 _sp_nd_order(permutation, n_unknowns, unknown_x, unknown_y, 2);
134 for(int perm_index = 0; perm_index < n_unknowns; perm_index++)
135 inverse_perm[permutation[perm_index]] = perm_index;
136
137 // assembly (count pass, then fill), upper triangle, permuted indexing; border-clamped
138 // rows keep the later-eliminated unknown's row value, matching the dense solver's
139 // lower-triangle convention (see the same note in _sp_pde_assemble)
140 int success = 1;
141 int targets[13];
142 double target_weights[13];
143
144 for(int pass = 0; pass < 2 && success; pass++)
145 {
146 if(pass == 1)
147 {
148 int total = 0;
149 for(int perm_index = 0; perm_index < n_unknowns; perm_index++)
150 {
151 const int col_count = matrix_col_ptr[perm_index];
152 matrix_col_ptr[perm_index] = total;
153 total += col_count;
154 }
155 matrix_col_ptr[n_unknowns] = total;
156 matrix_row_index = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * total, pipe);
157 matrix_values = (double *)dt_pixelpipe_cache_alloc_align(sizeof(double) * total, pipe);
158 if(!matrix_row_index || !matrix_values) success = 0;
159 }
160
161 for(int perm_index = 0; perm_index < n_unknowns && success; perm_index++)
162 {
163 const int coarse_y = unknown_y[permutation[perm_index]];
164 const int coarse_x = unknown_x[permutation[perm_index]];
165
166 // row of the 13-point stencil at (coarse_y, coarse_x), clamped, duplicates summed:
167 // one row of Delta^2 u = 0 restricted to the hole unknowns
168 int count = 0;
169 double boundary_sum = 0.0;
170 for(int k = 0; k < 13; k++)
171 {
172 const int neighbour_y = CLAMP(coarse_y + stencil_dy[k], 0, coarse_h - 1);
173 const int neighbour_x = CLAMP(coarse_x + stencil_dx[k], 0, coarse_w - 1);
174 const size_t neighbour_i = (size_t)neighbour_y * coarse_w + neighbour_x;
175 if(!coarse_hole[neighbour_i])
176 {
177 // Dirichlet boundary term: a non-hole neighbour is fixed data (u|dOmega = u_valid),
178 // so its stencil contribution moves to the RHS as -weight * u_valid
179 boundary_sum -= (double)stencil_weight[k] * coarse_field[neighbour_i];
180 continue;
181 }
182 const int target = neighbour_y * coarse_w + neighbour_x;
183 int slot = 0;
184 for(; slot < count; slot++)
185 if(targets[slot] == target)
186 {
187 target_weights[slot] += stencil_weight[k];
188 break;
189 }
190 if(slot == count)
191 {
192 targets[count] = target;
193 target_weights[count] = stencil_weight[k];
194 count++;
195 }
196 }
197 if(pass == 1) right_hand_side[perm_index] = boundary_sum;
198
199 int n_col_entries = 0;
200 for(int slot = 0; slot < count; slot++)
201 {
202 const int target_row = inverse_perm[coarse_index[targets[slot]]];
203 if(target_row > perm_index) continue;
204 // border rows: keep the row value (the dense solver's lower-triangle convention)
205 const double value = target_weights[slot];
206 if(pass == 1)
207 {
208 matrix_row_index[matrix_col_ptr[perm_index] + n_col_entries] = target_row;
209 matrix_values[matrix_col_ptr[perm_index] + n_col_entries] = value;
210 }
211 n_col_entries++;
212 }
213 if(pass == 0) matrix_col_ptr[perm_index] = n_col_entries;
214 }
215 }
216
217 if(success)
218 {
219 // solve the restricted biharmonic system A u = b (A = Delta^2 over the hole unknowns,
220 // b = boundary_sum). A is symmetric positive-definite, so the sparse Cholesky applies
221 // (SPD factorization annotated in common/solvers/sparse_cholesky.h); a DIRECT solve is
222 // exact regardless of conditioning, unlike CG which stalls in float at kappa ~ L^4.
223 _sp_chol_t *factor = _sp_chol_factor(n_unknowns, matrix_col_ptr, matrix_row_index, matrix_values, pipe);
224 if(factor)
225 {
226 _sp_chol_solve(factor, right_hand_side);
227 for(size_t coarse_i = 0; coarse_i < coarse_pixels; coarse_i++)
228 if(coarse_hole[coarse_i])
229 coarse_field[coarse_i] = (float)right_hand_side[(size_t)inverse_perm[coarse_index[coarse_i]]];
230 solved = 1;
232 }
233 }
234 }
235
239 dt_pixelpipe_cache_free_align(inverse_perm);
240 dt_pixelpipe_cache_free_align(matrix_col_ptr);
241 dt_pixelpipe_cache_free_align(matrix_row_index);
242 dt_pixelpipe_cache_free_align(matrix_values);
243 dt_pixelpipe_cache_free_align(right_hand_side);
244 }
245
246 if(!solved && n_unknowns <= DT_HL_DOME_NMAX)
247 {
248 // dense fallback (previous solver), only affordable on the small dense-era grids
249 float *const restrict matrix = dt_pixelpipe_cache_alloc_align_float((size_t)n_unknowns * n_unknowns, pipe);
250 float *const restrict right_hand_side = dt_pixelpipe_cache_alloc_align_float((size_t)n_unknowns, pipe);
251 if(matrix && right_hand_side)
252 {
253 memset(matrix, 0, (size_t)n_unknowns * n_unknowns * sizeof(float));
255 for(int coarse_y = 0; coarse_y < coarse_h; coarse_y++)
256 for(int coarse_x = 0; coarse_x < coarse_w; coarse_x++)
257 {
258 const size_t coarse_i = (size_t)coarse_y * coarse_w + coarse_x;
259 if(!coarse_hole[coarse_i]) continue;
260 const int unknown_index = coarse_index[coarse_i];
261 float boundary_sum = 0.f;
262 for(int k = 0; k < 13; k++)
263 {
264 const int neighbour_y = CLAMP(coarse_y + stencil_dy[k], 0, coarse_h - 1);
265 const int neighbour_x = CLAMP(coarse_x + stencil_dx[k], 0, coarse_w - 1);
266 const size_t neighbour_i = (size_t)neighbour_y * coarse_w + neighbour_x;
267 if(coarse_hole[neighbour_i])
268 matrix[(size_t)unknown_index * n_unknowns + coarse_index[neighbour_i]] += stencil_weight[k];
269 else
270 boundary_sum -= stencil_weight[k] * coarse_field[neighbour_i];
271 }
272 right_hand_side[unknown_index] = boundary_sum;
273 }
274
275 // direct SPD solve (dense Cholesky) of the same restricted Delta^2 u = 0 system, only for
276 // the small dense-era grids. right_hand_side holds the solution on return.
277 if(solve_hermitian(matrix, right_hand_side, (size_t)n_unknowns, TRUE) == 0)
278 {
279 for(size_t coarse_i = 0; coarse_i < coarse_pixels; coarse_i++)
280 if(coarse_hole[coarse_i]) coarse_field[coarse_i] = right_hand_side[coarse_index[coarse_i]];
281 solved = 1;
282 }
283 }
285 dt_pixelpipe_cache_free_align(right_hand_side);
286 }
287
288 if(!solved)
289 {
290 // last resort (OOM): fill the coarse hole with the anchor mean -- never leave the zeroed
291 // hole cells to be upsampled as a black dome
292 double anchor_sum = 0.0;
293 size_t anchor_count = 0;
294 for(size_t coarse_i = 0; coarse_i < coarse_pixels; coarse_i++)
295 if(!coarse_hole[coarse_i])
296 {
297 anchor_sum += coarse_field[coarse_i];
298 anchor_count++;
299 }
300 const float anchor_mean = anchor_count ? (float)(anchor_sum / (double)anchor_count) : 0.f;
301 for(size_t coarse_i = 0; coarse_i < coarse_pixels; coarse_i++)
302 if(coarse_hole[coarse_i]) coarse_field[coarse_i] = anchor_mean;
303 }
304 }
305
306 // bilinear-upsample the coarse dome into the fine hole
307 __OMP_PARALLEL_FOR__(collapse(2))
308 for(int y = 0; y < region_h; y++)
309 for(int x = 0; x < region_w; x++)
310 {
311 const size_t fine_index = (size_t)y * region_w + x;
312 if(!hole[fine_index]) continue;
313 const float grid_x = ((float)x + 0.5f) / downsample - 0.5f;
314 const float grid_y = ((float)y + 0.5f) / downsample - 0.5f;
315 const int x_lo = CLAMP((int)floorf(grid_x), 0, coarse_w - 1);
316 const int y_lo = CLAMP((int)floorf(grid_y), 0, coarse_h - 1);
317 const int x_hi = MIN(x_lo + 1, coarse_w - 1);
318 const int y_hi = MIN(y_lo + 1, coarse_h - 1);
319 const float frac_x = CLAMP(grid_x - x_lo, 0.f, 1.f);
320 const float frac_y = CLAMP(grid_y - y_lo, 0.f, 1.f);
321 const float interp_top = coarse_field[(size_t)y_lo * coarse_w + x_lo] * (1.f - frac_x)
322 + coarse_field[(size_t)y_lo * coarse_w + x_hi] * frac_x;
323 const float interp_bottom = coarse_field[(size_t)y_hi * coarse_w + x_lo] * (1.f - frac_x)
324 + coarse_field[(size_t)y_hi * coarse_w + x_hi] * frac_x;
325 field[fine_index] = interp_top * (1.f - frac_y) + interp_bottom * frac_y;
326 }
327
328 dt_pixelpipe_cache_free_align(coarse_field);
330 dt_pixelpipe_cache_free_align(coarse_index);
331}
332
333// ===== anisotropic chroma diffusion (structure-steered, coarse-to-fine) ======================
334// The guided ladder recovers MAGNITUDE well but its chroma carries guide-flip seams and scale
335// hand-off patches. Chromaticity (est_c / L) is a BOUNDED quantity, so interpolation is the right
336// tool for it -- provided it flows ALONG image structure, never across it, or unrelated colours
337// (warm horizon glow vs cool upper sky) mix into magenta. This implements the diffuse.c model on
338// the region buffer: per-pixel diffusion tensor D = t x t + exp(-|grad L|/k) * g x g, where g is
339// the unit gradient of the RECOVERED luminance (content!) and t its orthogonal (the isophote).
340// Explicit iterations only travel ~sqrt(iters) pixels, so a COARSE-TO-FINE pyramid seeds the whole
341// hole at the coarsest level first (the "unreached interior stays magenta" fix), like diffuse.c's
342// multiscale scheme.
343//
344// MATHS BRIDGE -- Step 8 / E_chrominance anisotropic (article §"The optimization problem" term 3,
345// §"Chrominance coherence", §"The saturation floors, as obstacles"): the whole block minimizes
346// int_Omega grad(r_c)^T D grad(r_c) dOmega subject to the obstacle r_c >= c0/L_sum, whose
347// Euler-Lagrange (unconstrained) is the divergence-form steered fill div(D grad r) = 0. D here is
348// the structure-steered tensor built from the recovered luminance: gradient-dominant on a clean
349// halo ramp (transport radially inward), isophote-dominant where a hard edge crosses (transport
350// along level lines, never across a boundary). r = RGB/L_sum, recombined RGB = L_sum * r.
351
352// ============================ OpenCL ============================
353
354#if defined(HAVE_OPENCL) && DT_HL_SPARSE_SOLVE
355cl_int _biharmonic_dome_cl(const int devid, void *gd_void, cl_mem field, cl_mem hole, const int region_w,
356 const int region_h, const int downsample, const dt_dev_pixelpipe_t *pipe)
357{
359 const int coarse_w = (region_w + downsample - 1) / downsample;
360 const int coarse_h = (region_h + downsample - 1) / downsample;
361 const size_t coarse_pixels = (size_t)coarse_w * coarse_h;
362 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
363
364 cl_mem dval = dt_opencl_alloc_device_buffer(devid, sizeof(float) * coarse_pixels);
365 cl_mem dhole = dt_opencl_alloc_device_buffer(devid, coarse_pixels);
366 float *cf = dt_pixelpipe_cache_alloc_align_float(coarse_pixels, pipe);
367 uint8_t *coarse_hole = (uint8_t *)dt_pixelpipe_cache_alloc_align(coarse_pixels, pipe);
368 int *idx = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * coarse_pixels, pipe);
369 _sp_chol_cl_t *factor = NULL;
370 double *rhs = NULL;
371 int *matrix_col_ptr = NULL, *matrix_row_index = NULL;
372 double *matrix_values = NULL;
373 cl_mem solution_device = NULL;
374 if(!dval || !dhole || !cf || !coarse_hole || !idx) goto out;
375
376 // coarse-grid reduction on device: average the full-res field/hole into the ds-downsampled grid
377 {
378 const int kernel = global_data->kernel_hl_dome_down;
379 size_t work_size[3] = { ROUNDUPDWD(coarse_w, devid), ROUNDUPDHT(coarse_h, devid), 1 };
380 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &field);
381 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &hole);
382 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &dval);
383 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &dhole);
384 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
385 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
386 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &coarse_w);
387 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &coarse_h);
388 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &downsample);
389 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
390 if(cl_err != CL_SUCCESS) goto out;
391 }
392
393 // coarse metadata to host: assembly + symbolic analysis (integer work)
394 cl_err = dt_opencl_read_buffer_from_device(devid, cf, dval, 0, sizeof(float) * coarse_pixels, CL_TRUE);
395 if(cl_err != CL_SUCCESS) goto out;
396 cl_err = dt_opencl_read_buffer_from_device(devid, coarse_hole, dhole, 0, coarse_pixels, CL_TRUE);
397 if(cl_err != CL_SUCCESS) goto out;
398
399 // number the coarse hole cells: these are the unknowns of the linear system
400 int unknown_count = 0;
401 for(size_t coarse_index = 0; coarse_index < coarse_pixels; coarse_index++)
402 idx[coarse_index] = coarse_hole[coarse_index] ? unknown_count++ : -1;
403 // Nh == 0 (no coarse cell reached hole majority -- thin streaks, speckle holes): skip the
404 // solve but STILL upsample the coarse block means into the fine holes, exactly like the CPU
405 // dome, whose bilinear upsample runs unconditionally. Early-exiting here left the field
406 // untouched and diverged from the CPU on thin-hole topologies.
407 if(unknown_count > 0)
408 {
409
410 {
411 // assemble the 13-point biharmonic operator Delta^2 = Delta(Delta) (the 5-point Laplacian
412 // convolved with itself: center 20, edge -8, diagonal 2, far-axis 1; reaches two rings out),
413 // with the unknowns permuted by geometric nested dissection (the CPU dome's exact system)
414 static const int stencil_off_y[13] = { 0, -1, 1, 0, 0, -1, -1, 1, 1, -2, 2, 0, 0 };
415 static const int stencil_off_x[13] = { 0, 0, 0, -1, 1, -1, 1, -1, 1, 0, 0, -2, 2 };
416 static const double stencil_coef[13] = { 20., -8., -8., -8., -8., 2., 2., 2., 2., 1., 1., 1., 1. };
417
418 int *unknown_x = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * unknown_count, pipe);
419 int *unknown_y = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * unknown_count, pipe);
420 int *perm = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * unknown_count, pipe);
421 int *inv_perm = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * unknown_count, pipe);
422 matrix_col_ptr = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * (unknown_count + 1), pipe);
423 matrix_row_index = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * (size_t)unknown_count * 13, pipe);
424 matrix_values = (double *)dt_pixelpipe_cache_alloc_align(sizeof(double) * (size_t)unknown_count * 13, pipe);
425 rhs = (double *)dt_pixelpipe_cache_alloc_align(sizeof(double) * unknown_count, pipe);
426 int alloc_ok = (unknown_x && unknown_y && perm && inv_perm && matrix_col_ptr && matrix_row_index
427 && matrix_values && rhs);
428 if(alloc_ok)
429 {
430 for(size_t coarse_index = 0; coarse_index < coarse_pixels; coarse_index++)
431 if(coarse_hole[coarse_index])
432 {
433 unknown_x[idx[coarse_index]] = (int)(coarse_index % coarse_w);
434 unknown_y[idx[coarse_index]] = (int)(coarse_index / coarse_w);
435 }
436 for(int i = 0; i < unknown_count; i++) perm[i] = i;
437 _sp_nd_order(perm, unknown_count, unknown_x, unknown_y, 2);
438 for(int perm_index = 0; perm_index < unknown_count; perm_index++) inv_perm[perm[perm_index]] = perm_index;
439
440 int n_nonzero = 0;
441 for(int perm_index = 0; perm_index < unknown_count; perm_index++)
442 {
443 const int cell_y = unknown_y[perm[perm_index]], cell_x = unknown_x[perm[perm_index]];
444 matrix_col_ptr[perm_index] = n_nonzero;
445 double rhs_accum = 0.0;
446 for(int stencil = 0; stencil < 13; stencil++)
447 {
448 const int neighbor_y = CLAMP(cell_y + stencil_off_y[stencil], 0, coarse_h - 1);
449 const int neighbor_x = CLAMP(cell_x + stencil_off_x[stencil], 0, coarse_w - 1);
450 const size_t neighbor_index = (size_t)neighbor_y * coarse_w + neighbor_x;
451 if(!coarse_hole[neighbor_index])
452 {
453 // Dirichlet boundary: a non-hole neighbour is fixed data (u|dOmega = u_valid), so its
454 // stencil term moves to the RHS as -coef * u_valid
455 rhs_accum -= stencil_coef[stencil] * cf[neighbor_index];
456 continue;
457 }
458 const int row_index = inv_perm[idx[neighbor_index]];
459 if(row_index > perm_index) continue;
460 int fill_index = matrix_col_ptr[perm_index];
461 for(; fill_index < n_nonzero; fill_index++)
462 if(matrix_row_index[fill_index] == row_index)
463 {
464 matrix_values[fill_index] += stencil_coef[stencil];
465 break;
466 }
467 if(fill_index == n_nonzero)
468 {
469 matrix_row_index[n_nonzero] = row_index;
470 matrix_values[n_nonzero] = stencil_coef[stencil];
471 n_nonzero++;
472 }
473 }
474 rhs[perm_index] = rhs_accum;
475 }
476 matrix_col_ptr[unknown_count] = n_nonzero;
477
478 // factor + solve A u = b, A = the restricted Delta^2 (SPD), b = the boundary_sum RHS:
479 // the exact biharmonic dome on the coarse hole (GPU sparse Cholesky)
480 factor = _sp_chol_factor_cl(devid, _hl_sp_chol_kernels(gd_void), unknown_count, matrix_col_ptr,
481 matrix_row_index, matrix_values);
482 int solved = 0;
483 if(factor)
484 {
485 cl_mem rhs_device = _sp_cl_upload(devid, rhs, sizeof(double) * unknown_count);
486 if(rhs_device && !_sp_chol_solve_cl(factor, _hl_sp_chol_kernels(gd_void), rhs_device)
487 && dt_opencl_read_buffer_from_device(devid, rhs, rhs_device, 0, sizeof(double) * unknown_count,
488 CL_TRUE)
489 == CL_SUCCESS)
490 {
491 // the GPU factorization does not abort on a non-positive pivot the way the CPU
492 // up-looking factor does -- it silently produces NaN/inf. Validate the solution
493 // like the CPU validates the factor, and take the same fallback chain when the
494 // clamped-border row-assembly breaks SPD on an unlucky hole topology.
495 solved = 1;
496 for(int k = 0; k < unknown_count && solved; k++)
497 if(!isfinite(rhs[k])) solved = 0;
498 if(solved)
499 for(size_t coarse_index = 0; coarse_index < coarse_pixels; coarse_index++)
500 if(coarse_hole[coarse_index]) cf[coarse_index] = (float)rhs[(size_t)inv_perm[idx[coarse_index]]];
501 }
503 }
504
505 if(!solved && unknown_count <= DT_HL_DOME_NMAX)
506 {
507 // dense fallback, exactly the CPU dome's second stage
508 float *const restrict dense_matrix
509 = dt_pixelpipe_cache_alloc_align_float((size_t)unknown_count * unknown_count, pipe);
510 float *const restrict dense_rhs = dt_pixelpipe_cache_alloc_align_float((size_t)unknown_count, pipe);
511 if(dense_matrix && dense_rhs)
512 {
513 memset(dense_matrix, 0, (size_t)unknown_count * unknown_count * sizeof(float));
514 for(int cell_y = 0; cell_y < coarse_h; cell_y++)
515 for(int cell_x = 0; cell_x < coarse_w; cell_x++)
516 {
517 const size_t coarse_index = (size_t)cell_y * coarse_w + cell_x;
518 if(!coarse_hole[coarse_index]) continue;
519 const int k = idx[coarse_index];
520 float rhs_accum = 0.f;
521 for(int stencil = 0; stencil < 13; stencil++)
522 {
523 const int neighbor_y = CLAMP(cell_y + stencil_off_y[stencil], 0, coarse_h - 1);
524 const int neighbor_x = CLAMP(cell_x + stencil_off_x[stencil], 0, coarse_w - 1);
525 const size_t neighbor_index = (size_t)neighbor_y * coarse_w + neighbor_x;
526 if(coarse_hole[neighbor_index])
527 dense_matrix[(size_t)k * unknown_count + idx[neighbor_index]] += stencil_coef[stencil];
528 else
529 rhs_accum -= stencil_coef[stencil] * cf[neighbor_index];
530 }
531 dense_rhs[k] = rhs_accum;
532 }
533 if(solve_hermitian(dense_matrix, dense_rhs, (size_t)unknown_count, TRUE) == 0)
534 {
535 for(size_t coarse_index = 0; coarse_index < coarse_pixels; coarse_index++)
536 if(coarse_hole[coarse_index]) cf[coarse_index] = dense_rhs[idx[coarse_index]];
537 solved = 1;
538 }
539 }
540 dt_pixelpipe_cache_free_align(dense_matrix);
542 }
543
544 if(!solved)
545 {
546 // last resort, exactly the CPU dome's: anchor-mean fill (never upsample a black dome)
547 double asum = 0.0;
548 size_t acnt = 0;
549 for(size_t coarse_index = 0; coarse_index < coarse_pixels; coarse_index++)
550 if(!coarse_hole[coarse_index])
551 {
552 asum += cf[coarse_index];
553 acnt++;
554 }
555 const float amean = acnt ? (float)(asum / (double)acnt) : 0.f;
556 for(size_t coarse_index = 0; coarse_index < coarse_pixels; coarse_index++)
557 if(coarse_hole[coarse_index]) cf[coarse_index] = amean;
558 }
559 cl_err = CL_SUCCESS;
560 }
561 else
567 if(cl_err != CL_SUCCESS) goto out;
568 }
569 }
570
571 // upload the coarse solution and upsample into the full-res holes (hl_fill_up wants the
572 // ANCHOR mask; our `hole` buffer holds holes, so pass an inverted... hl_fill_up tests
573 // anc[i] -> skip: we need "write where hole": pass hole through a dedicated path -- reuse
574 // hl_fill_up by noting its test `if(anc[i]) return` writes where the mask is ZERO: our hole
575 // mask is 1 on holes -> invert on upload? Simplest: hl_fill_up writes where mask==0, so
576 // pass the INVERTED hole mask... we don't have it on device. Use hl_dome_up = hl_fill_up
577 // with the hole convention: kernel reuse trick -- write a tiny inverter is more code than
578 // benefit; instead upload solution and run hl_fill_up with `anc` = a mask we build by one
579 // extra kernel... For now: build the inverted mask on host (we HAVE ch/full-res? no, full
580 // -res hole only on device). Add: reuse hl_fill_jacobi convention... -> dedicated kernel
581 // exists: hl_fill_up(anc) -- we need anc = !hole full-res. One-line kernel would be
582 // cleaner; reuse hl_lsb_hole? No. We add hl_not_mask below in basic.cl? To avoid another
583 // kernel this call allocates an inverted mask via clEnqueue... keep it simple:
584 //
585 // PLAIN-WORDS SUMMARY of the design notes above: upload the coarse solution and upsample
586 // it into the full-res holes. Mask-convention mismatch: hl_fill_up writes only where its
587 // `anc` (anchor) mask is ZERO, i.e. it expects 1 = trusted / 0 = hole, while this function
588 // receives `hole` with 1 = hole. The full-res inverted mask exists nowhere (host or
589 // device), so invert `hole` once on device with the tiny hl_not_mask kernel and feed that
590 // to hl_fill_up.
591 {
592 // inverted mask via a tiny kernel would be ideal; as the region planes also need the
593 // anchor mask elsewhere, callers of _biharmonic_dome_cl pass `hole`; invert here once.
594 solution_device = _sp_cl_upload(devid, cf, sizeof(float) * coarse_pixels);
595 if(!solution_device)
596 {
598 goto out;
599 }
600 {
601 // upsample the coarse dome into the full-resolution hole pixels; the mask is in the
602 // hole convention (1 = fill), which hl_fill_up handles directly via mask_is_hole
603 const int kernel = global_data->kernel_hl_fill_up;
604 const int mask_is_hole = 1;
605 size_t work_size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
606 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &field);
607 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &hole);
608 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &solution_device);
609 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_w);
610 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_h);
611 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &coarse_w);
612 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &coarse_h);
613 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &downsample);
614 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &mask_is_hole);
615 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
616 }
617 }
618
619out:
622 dt_opencl_release_mem_object(solution_device);
626 dt_pixelpipe_cache_free_align(matrix_col_ptr);
627 dt_pixelpipe_cache_free_align(matrix_row_index);
628 dt_pixelpipe_cache_free_align(matrix_values);
631 return cl_err;
632}
633
634#endif // HAVE_OPENCL && DT_HL_SPARSE_SOLVE
#define TRUE
Definition ashift_lsd.c:162
static int solve_hermitian(const float *const restrict A, float *const restrict y, const size_t n, const int checks)
Definition choleski.h:264
const dt_colormatrix_t dt_aligned_pixel_t out
const dt_colormatrix_t matrix
#define dt_pixelpipe_cache_alloc_align(size, pipe)
Definition darktable.h:449
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
#define dt_pixelpipe_cache_alloc_align_float(pixels, pipe)
Definition darktable.h:454
#define __DT_CLONE_TARGETS__
Definition darktable.h:379
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
__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:33
static int perm[512]
Definition grain.c:174
static int permutation[]
Definition grain.c:160
static float kernel(const float *x, const float *y)
static const float x
float *const restrict const size_t k
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2164
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:2580
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:2337
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2155
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2415
#define DT_OPENCL_DEFAULT_ERROR
Definition opencl.h:57
#define ROUNDUPDHT(a, b)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:81
static _sp_chol_cl_kernels_t _hl_sp_chol_kernels(void *gd_void)
Definition pde.h:107
const float factor
Definition pdf.h:90
static void _sp_nd_order(int *const restrict unknown_ids, const int count, const int *const restrict unknown_x, const int *const restrict unknown_y, const int reach)
static _sp_chol_t * _sp_chol_factor(const int dimension, const int *const restrict matrix_col_ptr, const int *const restrict matrix_row_index, const double *const restrict matrix_values, const dt_dev_pixelpipe_t *pipe)
static void _sp_chol_free(_sp_chol_t *factor)
static void _sp_chol_solve(const _sp_chol_t *const factor, double *const restrict rhs)
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)
#define DT_HL_DOME_NMAX_SPARSE
#define DT_HL_DOME_NMAX
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29