Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
pde.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// Sparse-SPD PDE assembly/solve on the region grid (screened Poisson / diffusion), CPU + OpenCL. (implementation;
20// see pde.h for the public API.)
21
22#include "common/darktable.h"
23#include "develop/imageop.h"
25#include "iop/highlights/blur.h"
26#include "iop/highlights/pde.h"
27#include <math.h>
28#include <stdlib.h>
29#include <string.h>
30
31static inline void _lap5(const float *const restrict field, float *const restrict laplacian, const int region_w,
32 const int region_h)
33{
34 __OMP_PARALLEL_FOR__(collapse(2))
35 for(int y = 0; y < region_h; y++)
36 {
37 for(int x = 0; x < region_w; x++)
38 {
39 // clamped neighbour coordinates (replicate at the borders)
40 const int y_north = (y > 0) ? (y - 1) : y;
41 const int y_south = (y < region_h - 1) ? (y + 1) : y;
42 const int x_west = (x > 0) ? (x - 1) : x;
43 const int x_east = (x < region_w - 1) ? (x + 1) : x;
44
45 // centre, 4 edge-neighbours, 4 diagonal-neighbours
46 const float c = field[(size_t)y * region_w + x];
47 const float north = field[(size_t)y_north * region_w + x];
48 const float south = field[(size_t)y_south * region_w + x];
49 const float west = field[(size_t)y * region_w + x_west];
50 const float east = field[(size_t)y * region_w + x_east];
51 const float north_west = field[(size_t)y_north * region_w + x_west];
52 const float north_east = field[(size_t)y_north * region_w + x_east];
53 const float south_west = field[(size_t)y_south * region_w + x_west];
54 const float south_east = field[(size_t)y_south * region_w + x_east];
55
56 // isotropic Laplacian = (4 * edges + corners - 20 * centre) / 6
57 laplacian[(size_t)y * region_w + x]
58 = (4.f * (north + south + west + east) + (north_west + north_east + south_west + south_east) - 20.f * c)
59 / 6.f;
60 }
61 }
62}
63
64// Apply the diffusion operator (the matrix of the partial differential equation) to a
65// full-grid field: order 1 -> minus the Laplacian (harmonic smoothing), order 2 -> the
66// biharmonic operator (Laplacian applied twice: smooth in value AND slope). Both are
67// symmetric positive definite, which is what the Cholesky/conjugate-gradient solvers
68// require. `sc` is single-channel scratch.
69static inline void _apply_op(const float *const restrict field, float *const restrict output_field,
70 float *const restrict scratch, const int order, const int region_w,
71 const int region_h)
72{
73 const size_t region_pixels = (size_t)region_w * region_h;
74 if(order == 1)
75 {
76 _lap5(field, output_field, region_w, region_h);
77 for(size_t i = 0; i < region_pixels; i++) output_field[i] = -output_field[i];
78 }
79 else
80 {
81 _lap5(field, scratch, region_w, region_h);
82 _lap5(scratch, output_field, region_w, region_h);
83 }
84}
85
87
88// ---- operator rows for the sparse assembly --------------------------------------------------
89// row of the 9-point isotropic Laplacian at (y, x), replicate-clamped like _lap5; duplicates
90// (folded taps at the borders) are accumulated. Returns the target count (<= 9).
91static int _sp_row_l9(const int y, const int x, const int region_w, const int region_h,
92 int *const restrict targets, double *const restrict target_weights)
93{
94 static const int offset_y[9] = { 0, -1, 1, 0, 0, -1, -1, 1, 1 };
95 static const int offset_x[9] = { 0, 0, 0, -1, 1, -1, 1, -1, 1 };
96 static const double stencil_weight[9]
97 = { -20. / 6., 4. / 6., 4. / 6., 4. / 6., 4. / 6., 1. / 6., 1. / 6., 1. / 6., 1. / 6. };
98 int count = 0;
99 for(int k = 0; k < 9; k++)
100 {
101 const int neighbour_y = CLAMP(y + offset_y[k], 0, region_h - 1);
102 const int neighbour_x = CLAMP(x + offset_x[k], 0, region_w - 1);
103 const int target = neighbour_y * region_w + neighbour_x;
104 int slot = 0;
105 for(; slot < count; slot++)
106 if(targets[slot] == target)
107 {
108 target_weights[slot] += stencil_weight[k];
109 break;
110 }
111 if(slot == count)
112 {
113 targets[count] = target;
114 target_weights[count] = stencil_weight[k];
115 count++;
116 }
117 }
118 return count;
119}
120
121// Row of the diffusion operator at grid index o, for the sparse-matrix assembly: order 1 ->
122// minus the 9-point Laplacian, order 2 -> the biharmonic operator (the 9-point Laplacian
123// composed with itself), both exactly as _apply_op computes them, including the border
124// clamping. Targets cover the WHOLE grid; the caller filters hole/non-hole. Returns the
125// number of targets (<= 25).
126static int _sp_row_op(const int grid_index, const int order, const int region_w, const int region_h,
127 int *const restrict targets, double *const restrict target_weights)
128{
129 const int y = grid_index / region_w;
130 const int x = grid_index - y * region_w;
131 int lap_targets[9];
132 double lap_weights[9];
133 const int lap_count = _sp_row_l9(y, x, region_w, region_h, lap_targets, lap_weights);
134 if(order == 1)
135 {
136 for(int i = 0; i < lap_count; i++)
137 {
138 targets[i] = lap_targets[i];
139 target_weights[i] = -lap_weights[i];
140 }
141 return lap_count;
142 }
143 int count = 0;
144 int lap2_targets[9];
145 double lap2_weights[9];
146 for(int i = 0; i < lap_count; i++)
147 {
148 const int mid_y = lap_targets[i] / region_w;
149 const int mid_x = lap_targets[i] - mid_y * region_w;
150 const int lap2_count = _sp_row_l9(mid_y, mid_x, region_w, region_h, lap2_targets, lap2_weights);
151 for(int j = 0; j < lap2_count; j++)
152 {
153 const int target = lap2_targets[j];
154 const double value = lap_weights[i] * lap2_weights[j];
155 int slot = 0;
156 for(; slot < count; slot++)
157 if(targets[slot] == target)
158 {
159 target_weights[slot] += value;
160 break;
161 }
162 if(slot == count)
163 {
164 targets[count] = target;
165 target_weights[count] = value;
166 count++;
167 }
168 }
169 }
170 return count;
171}
172
173int _sp_pde_assemble(const uint8_t *const restrict hole, const float *const restrict diffusion,
174 const float diffusion_const, const int order, const float lambda, const int region_w,
175 const int region_h, int **matrix_col_ptr_out, int **matrix_row_index_out,
176 double **matrix_values_out, int **perm_grid_out, int *n_unknowns_out,
177 const dt_dev_pixelpipe_t *const pipe)
178{
179 const size_t region_pixels = (size_t)region_w * region_h;
180 int n_unknowns = 0;
181 for(size_t i = 0; i < region_pixels; i++)
182 if(hole[i]) n_unknowns++;
183 if(n_unknowns == 0 || n_unknowns > DT_HL_SPARSE_MAX) return 0;
184
185 int *grid_to_unknown = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * region_pixels, pipe);
186 int *unknown_to_grid = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * n_unknowns, pipe);
187 int *unknown_x = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * n_unknowns, pipe);
188 int *unknown_y = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * n_unknowns, pipe);
189 int *permutation = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * n_unknowns, pipe);
190 int success = 0;
191 int *matrix_col_ptr = NULL, *matrix_row_index = NULL, *inverse_perm = NULL, *perm_grid = NULL;
192 double *matrix_values = NULL;
193 if(!grid_to_unknown || !unknown_to_grid || !unknown_x || !unknown_y || !permutation) goto done;
194
195 int unknown_index = 0;
196 for(size_t i = 0; i < region_pixels; i++)
197 {
198 grid_to_unknown[i] = hole[i] ? unknown_index : -1;
199 if(hole[i])
200 {
201 unknown_to_grid[unknown_index] = (int)i;
202 unknown_y[unknown_index] = (int)(i / region_w);
203 unknown_x[unknown_index] = (int)(i - (size_t)unknown_y[unknown_index] * region_w);
204 unknown_index++;
205 }
206 }
207
208 for(int i = 0; i < n_unknowns; i++) permutation[i] = i;
209 const int reach = (order == 1) ? 1 : 2;
210 _sp_nd_order(permutation, n_unknowns, unknown_x, unknown_y, reach);
211
212 inverse_perm = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * n_unknowns, pipe);
213 if(!inverse_perm) goto done;
214 for(int perm_index = 0; perm_index < n_unknowns; perm_index++)
215 inverse_perm[permutation[perm_index]] = perm_index;
216
217 // assembly, two passes (count then fill), upper triangle in permuted indexing
218 matrix_col_ptr = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * (n_unknowns + 1), pipe);
219 if(!matrix_col_ptr) goto done;
220
221 int targets[25];
222 double target_weights[25];
223
224 for(int pass = 0; pass < 2; pass++)
225 {
226 if(pass == 1)
227 {
228 int total = 0;
229 for(int perm_index = 0; perm_index < n_unknowns; perm_index++)
230 {
231 const int col_count = matrix_col_ptr[perm_index];
232 matrix_col_ptr[perm_index] = total;
233 total += col_count;
234 }
235 matrix_col_ptr[n_unknowns] = total;
236 matrix_row_index = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * total, pipe);
237 matrix_values = (double *)dt_pixelpipe_cache_alloc_align(sizeof(double) * total, pipe);
238 if(!matrix_row_index || !matrix_values) goto done;
239 }
240
241 for(int perm_index = 0; perm_index < n_unknowns; perm_index++)
242 {
243 const int origin_grid = unknown_to_grid[permutation[perm_index]];
244 const int count = _sp_row_op(origin_grid, order, region_w, region_h, targets, target_weights);
245 int n_col_entries = 0;
246
247 for(int slot = 0; slot < count; slot++)
248 {
249 const int target_grid = targets[slot];
250 const int target_unknown = grid_to_unknown[target_grid];
251 if(target_unknown < 0) continue; // boundary: lives in the RHS
252 const int target_row = inverse_perm[target_unknown];
253 if(target_row > perm_index) continue; // upper triangle only
254
255 // replicate-clamping makes border rows nonsymmetric; like the dense solver (which reads
256 // only the lower triangle of the row-assembled matrix), keep the row value of the
257 // later-eliminated unknown and let the factorization mirror it -- measured better than
258 // (A + A^T)/2 on the border-touching test cases, and identical in the interior
259 double value = target_weights[slot];
260 value *= lambda; // lam * Op entry (the -Delta / biharmonic stencil weight scaled by lambda)
261 if(target_row == perm_index)
262 // diagonal += diag(d): the screening/reaction term (lambda_solid * I) of (lambda*I - Delta)
263 value += (diffusion ? (double)diffusion[origin_grid] : (double)diffusion_const);
264
265 if(pass == 1)
266 {
267 matrix_row_index[matrix_col_ptr[perm_index] + n_col_entries] = target_row;
268 matrix_values[matrix_col_ptr[perm_index] + n_col_entries] = value;
269 }
270 n_col_entries++;
271 }
272 if(pass == 0) matrix_col_ptr[perm_index] = n_col_entries;
273 }
274 }
275
276 // permuted-unknown -> grid mapping (composition of unknown_to_grid and the ND permutation)
277 perm_grid = (int *)dt_pixelpipe_cache_alloc_align(sizeof(int) * n_unknowns, pipe);
278 if(!perm_grid) goto done;
279 for(int perm_index = 0; perm_index < n_unknowns; perm_index++)
280 perm_grid[perm_index] = unknown_to_grid[permutation[perm_index]];
281 success = 1;
282
283done:
284 dt_pixelpipe_cache_free_align(grid_to_unknown);
287 dt_pixelpipe_cache_free_align(inverse_perm);
288 dt_pixelpipe_cache_free_align(unknown_to_grid);
290 if(success)
291 {
292 *matrix_col_ptr_out = matrix_col_ptr;
293 *matrix_row_index_out = matrix_row_index;
294 *matrix_values_out = matrix_values;
295 *perm_grid_out = perm_grid;
296 *n_unknowns_out = n_unknowns;
297 }
298 else
299 {
300 dt_pixelpipe_cache_free_align(matrix_col_ptr);
301 dt_pixelpipe_cache_free_align(matrix_row_index);
302 dt_pixelpipe_cache_free_align(matrix_values);
304 }
305 return success;
306}
307
308_sp_chol_t *_sp_pde_factor(const uint8_t *const restrict hole, const float *const restrict diffusion,
309 const int order, const float lambda, const int region_w, const int region_h,
310 int **perm_out, int *n_unknowns_out, const dt_dev_pixelpipe_t *pipe)
311{
312 int *matrix_col_ptr = NULL, *matrix_row_index = NULL, *perm_grid = NULL;
313 double *matrix_values = NULL;
314 int n_unknowns = 0;
315 if(!_sp_pde_assemble(hole, diffusion, 0.f, order, lambda, region_w, region_h, &matrix_col_ptr, &matrix_row_index,
316 &matrix_values, &perm_grid, &n_unknowns, pipe))
317 return NULL;
318
319 _sp_chol_t *factor = _sp_chol_factor(n_unknowns, matrix_col_ptr, matrix_row_index, matrix_values, pipe);
320 dt_pixelpipe_cache_free_align(matrix_col_ptr);
321 dt_pixelpipe_cache_free_align(matrix_row_index);
322 dt_pixelpipe_cache_free_align(matrix_values);
323 if(!factor)
324 {
326 return NULL;
327 }
328 *perm_out = perm_grid;
329 *n_unknowns_out = n_unknowns;
330 return factor;
331}
332
334void _sp_pde_solve(const _sp_chol_t *const factor, const int *const restrict perm_grid,
335 float *const restrict field, const uint8_t *const restrict hole,
336 const float *const restrict diffusion, const float *const restrict target,
337 const float *const restrict source, const int order, const float lambda, const int region_w,
338 const int region_h, double *const restrict rhs, float *const restrict embedded,
339 float *const restrict operator_out, float *const restrict scratch)
340{
341 const size_t region_pixels = (size_t)region_w * region_h;
342 // embed only the fixed boundary (rim) values, zero on the hole, then apply Op to them: this is
343 // the Dirichlet contribution Op(r_valid) that gets moved to the RHS
345 for(size_t i = 0; i < region_pixels; i++) embedded[i] = hole[i] ? 0.f : field[i];
346
347 _apply_op(embedded, operator_out, scratch, order, region_w, region_h);
348
349 const int n_unknowns = factor->dimension;
351 for(int unknown_index = 0; unknown_index < n_unknowns; unknown_index++)
352 {
353 const size_t i = (size_t)perm_grid[unknown_index];
354 // RHS = diag(d)*r_target + source - lambda*Op(boundary): the screening pull toward the flat
355 // target plus the eliminated Dirichlet rim term, matching A = diag(d) + lambda*Op
356 rhs[unknown_index] = (diffusion ? (double)diffusion[i] * target[i] : 0.0) + (source ? (double)source[i] : 0.0)
357 - (double)lambda * operator_out[i];
358 }
359
360 _sp_chol_solve(factor, rhs); // two triangular solves against the shared Cholesky factor of A
361
363 for(int unknown_index = 0; unknown_index < n_unknowns; unknown_index++)
364 field[perm_grid[unknown_index]] = (float)rhs[unknown_index];
365}
366
368void _region_pde_solve(float *const restrict field, const uint8_t *const restrict hole,
369 const float *const restrict diffusion, const float *const restrict target,
370 const float *const restrict source, const int order, const float lambda, const int region_w,
371 const int region_h, float *const restrict residual, float *const restrict search_dir,
372 float *const restrict operator_dir, float *const restrict embedded,
373 float *const restrict scratch, const int maxiter)
374{
375 const size_t region_pixels = (size_t)region_w * region_h;
376 // rhs_hole = diffusion*target + source - lambda*Op(boundary embedded, hole=0)
378 for(size_t i = 0; i < region_pixels; i++) embedded[i] = hole[i] ? 0.f : field[i];
379
380 _apply_op(embedded, scratch, operator_dir, order, region_w, region_h);
381
383 for(size_t i = 0; i < region_pixels; i++)
384 residual[i]
385 = hole[i]
386 ? ((diffusion ? diffusion[i] * target[i] : 0.f) + (source ? source[i] : 0.f) - lambda * scratch[i])
387 : 0.f;
388
389 // residual <- rhs - A*x (x = current field on hole); A x = diffusion*x + lambda*Op(x embedded)
391 for(size_t i = 0; i < region_pixels; i++) embedded[i] = hole[i] ? field[i] : 0.f;
392
393 _apply_op(embedded, scratch, operator_dir, order, region_w, region_h);
394
395 double residual_sq = 0.0;
396 __OMP_PARALLEL_FOR__(reduction(+ : residual_sq))
397 for(size_t i = 0; i < region_pixels; i++)
398 {
399 if(!hole[i])
400 {
401 search_dir[i] = 0.f;
402 continue;
403 }
404
405 residual[i] -= (diffusion ? diffusion[i] * field[i] : 0.f) + lambda * scratch[i];
406 search_dir[i] = residual[i];
407 residual_sq += (double)residual[i] * residual[i];
408 }
409
410 const double residual_sq0 = residual_sq;
411 if(residual_sq0 < 1e-20) return;
412 for(int iter = 0; iter < maxiter; iter++)
413 {
415 for(size_t i = 0; i < region_pixels; i++) embedded[i] = hole[i] ? search_dir[i] : 0.f;
416
417 _apply_op(embedded, scratch, operator_dir, order, region_w, region_h);
418
419 double dir_operator_dot = 0.0;
420 __OMP_PARALLEL_FOR__(reduction(+ : dir_operator_dot))
421 for(size_t i = 0; i < region_pixels; i++)
422 {
423 if(!hole[i])
424 {
425 operator_dir[i] = 0.f;
426 continue;
427 }
428
429 operator_dir[i] = (diffusion ? diffusion[i] * search_dir[i] : 0.f) + lambda * scratch[i];
430 dir_operator_dot += (double)search_dir[i] * operator_dir[i];
431 }
432
433 if(dir_operator_dot <= 1e-30) break;
434 const float alpha = (float)(residual_sq / dir_operator_dot);
435 double new_residual_sq = 0.0;
436
437 __OMP_PARALLEL_FOR__(reduction(+ : new_residual_sq))
438 for(size_t i = 0; i < region_pixels; i++)
439 if(hole[i])
440 {
441 field[i] += alpha * search_dir[i];
442 residual[i] -= alpha * operator_dir[i];
443 new_residual_sq += (double)residual[i] * residual[i];
444 }
445
446 if(new_residual_sq < 1e-4 * residual_sq0) break;
447 const float beta = (float)(new_residual_sq / residual_sq);
448
450 for(size_t i = 0; i < region_pixels; i++)
451 if(hole[i]) search_dir[i] = residual[i] + beta * search_dir[i];
452
453 residual_sq = new_residual_sq;
454 }
455}
456
457// ============================ OpenCL ============================
458
460#ifdef HAVE_OPENCL
461#endif // HAVE_OPENCL
462
463#if defined(HAVE_OPENCL) && DT_HL_SPARSE_SOLVE
464cl_int _region_blur1_cl(const int devid, cl_mem in, cl_mem out, const int region_w, const int region_h,
465 const float sigma)
466{
467 const float vmax[1] = { 1e9f };
468 const float vmin[1] = { -1e9f };
469 dt_gaussian_cl_t *gaussian = dt_gaussian_init_cl(devid, region_w, region_h, 1, vmax, vmin, sigma, 0);
471 const cl_int cl_err = dt_gaussian_blur_cl(gaussian, in, out);
473 return cl_err;
474}
475
476cl_int _region_pde_cg_cl(const int devid, void *gd_void, cl_mem solution, cl_mem hole, const int region_w,
477 const int region_h, const float dscalar, const float tscalar, const int maxiter)
478{
480 const size_t region_pixels = (size_t)region_w * region_h;
481 const int unknown_count = (int)region_pixels;
482 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
483 size_t work_size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
484 const int local_size = 64, n_groups = 256;
485 size_t work_size_1d[3] = { (size_t)n_groups * local_size, 1, 1 };
486 size_t local_size_1d[3] = { local_size, 1, 1 };
487
488 if(global_data->kernel_hl_cg_r1 < 0) return cl_err; // no fp64 device
489
490 cl_mem temp1 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
491 cl_mem temp2 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
492 cl_mem residual = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
493 cl_mem search_dir = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
494 cl_mem matvec = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
495 cl_mem partials = dt_opencl_alloc_device_buffer(devid, sizeof(double) * n_groups);
496 double partial_sums[256];
497 if(!temp1 || !temp2 || !residual || !search_dir || !matvec || !partials) goto out;
498
499#define CG_EMBED(src_, keep_) \
500 do \
501 { \
502 const int kernel = global_data->kernel_hl_cg_embed; \
503 const int keep_flag = (keep_); \
504 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &(src_)); \
505 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &hole); \
506 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &temp1); \
507 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_w); \
508 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_h); \
509 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &keep_flag); \
510 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size); \
511 if(cl_err != CL_SUCCESS) goto out; \
512 const int kernel_op = global_data->kernel_hl_cg_op; \
513 dt_opencl_set_kernel_arg(devid, kernel_op, 0, sizeof(cl_mem), &temp1); \
514 dt_opencl_set_kernel_arg(devid, kernel_op, 1, sizeof(cl_mem), &temp2); \
515 dt_opencl_set_kernel_arg(devid, kernel_op, 2, sizeof(int), &region_w); \
516 dt_opencl_set_kernel_arg(devid, kernel_op, 3, sizeof(int), &region_h); \
517 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel_op, work_size); \
518 if(cl_err != CL_SUCCESS) goto out; \
519 } while(0)
520
521 // b = d*target - Op(boundary-embedded u)
522 CG_EMBED(solution, 0);
523 {
524 const int kernel = global_data->kernel_hl_cg_r0;
525 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &residual);
526 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &temp2);
527 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &hole);
528 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_w);
529 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_h);
530 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), &dscalar);
531 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &tscalar);
532 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
533 if(cl_err != CL_SUCCESS) goto out;
534 }
535
536 // r <- b - A u; p = r; rr
537 CG_EMBED(solution, 1);
538 double residual_norm;
539 {
540 const int kernel = global_data->kernel_hl_cg_r1;
541 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &residual);
542 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &search_dir);
543 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &solution);
544 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &temp2);
545 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &hole);
546 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &partials);
547 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &unknown_count);
548 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &dscalar);
549 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(double) * local_size, NULL);
550 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, work_size_1d, local_size_1d);
551 if(cl_err != CL_SUCCESS) goto out;
552 cl_err
553 = dt_opencl_read_buffer_from_device(devid, partial_sums, partials, 0, sizeof(double) * n_groups, CL_TRUE);
554 if(cl_err != CL_SUCCESS) goto out;
555 residual_norm = 0.0;
556 for(int group_index = 0; group_index < n_groups; group_index++) residual_norm += partial_sums[group_index];
557 }
558
559 const double residual_norm_init = residual_norm;
560 if(residual_norm_init < 1e-20)
561 {
562 cl_err = CL_SUCCESS;
563 goto out;
564 }
565
566 for(int iteration = 0; iteration < maxiter; iteration++)
567 {
568 CG_EMBED(search_dir, 1);
569 double p_dot_matvec;
570 {
571 const int kernel = global_data->kernel_hl_cg_ap;
572 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &matvec);
573 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &search_dir);
574 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &temp2);
575 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &hole);
576 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &partials);
577 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &unknown_count);
578 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &dscalar);
579 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(double) * local_size, NULL);
580 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, work_size_1d, local_size_1d);
581 if(cl_err != CL_SUCCESS) goto out;
582 cl_err = dt_opencl_read_buffer_from_device(devid, partial_sums, partials, 0, sizeof(double) * n_groups,
583 CL_TRUE);
584 if(cl_err != CL_SUCCESS) goto out;
585 p_dot_matvec = 0.0;
586 for(int group_index = 0; group_index < n_groups; group_index++) p_dot_matvec += partial_sums[group_index];
587 }
588
589 if(p_dot_matvec <= 1e-30) break;
590 const float alpha = (float)(residual_norm / p_dot_matvec);
591 double residual_norm_new;
592 {
593 const int kernel = global_data->kernel_hl_cg_update;
594 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &solution);
595 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &residual);
596 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &search_dir);
597 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &matvec);
598 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &hole);
599 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &partials);
600 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &unknown_count);
601 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &alpha);
602 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(double) * local_size, NULL);
603 cl_err = dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, work_size_1d, local_size_1d);
604 if(cl_err != CL_SUCCESS) goto out;
605 cl_err = dt_opencl_read_buffer_from_device(devid, partial_sums, partials, 0, sizeof(double) * n_groups,
606 CL_TRUE);
607 if(cl_err != CL_SUCCESS) goto out;
608 residual_norm_new = 0.0;
609 for(int group_index = 0; group_index < n_groups; group_index++)
610 residual_norm_new += partial_sums[group_index];
611 }
612
613 if(residual_norm_new < 1e-4 * residual_norm_init) break;
614 const float beta = (float)(residual_norm_new / residual_norm);
615 {
616 const int kernel = global_data->kernel_hl_cg_beta;
617 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &search_dir);
618 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &residual);
619 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &hole);
620 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &region_w);
621 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_h);
622 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), &beta);
623 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
624 if(cl_err != CL_SUCCESS) goto out;
625 }
626 residual_norm = residual_norm_new;
627 }
628 cl_err = CL_SUCCESS;
629
630#undef CG_EMBED
631out:
638 return cl_err;
639}
640
641#endif // HAVE_OPENCL && DT_HL_SPARSE_SOLVE
const dt_colormatrix_t dt_aligned_pixel_t out
#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_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
static float gaussian(float x, float std)
Definition filmic.c:397
void dt_gaussian_free_cl(dt_gaussian_cl_t *g)
Definition gaussian.c:353
cl_int dt_gaussian_blur_cl(dt_gaussian_cl_t *g, cl_mem dev_in, cl_mem dev_out)
Definition gaussian.c:441
dt_gaussian_cl_t * dt_gaussian_init_cl(const int devid, const int width, const int height, const int channels, const float *max, const float *min, const float sigma, const int order)
Definition gaussian.c:364
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
int dt_opencl_enqueue_kernel_2d_with_local(const int dev, const int kernel, const size_t *sizes, const size_t *local)
Definition opencl.c:2170
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2415
#define DT_OPENCL_DEFAULT_ERROR
Definition opencl.h:57
#define ROUNDUPDHT(a, b)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:81
__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:368
static void _apply_op(const float *const restrict field, float *const restrict output_field, float *const restrict scratch, const int order, const int region_w, const int region_h)
Definition pde.c:69
_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:308
static int _sp_row_l9(const int y, const int x, const int region_w, const int region_h, int *const restrict targets, double *const restrict target_weights)
Definition pde.c:91
__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:334
static void _lap5(const float *const restrict field, float *const restrict laplacian, const int region_w, const int region_h)
Definition pde.c:31
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:173
static int _sp_row_op(const int grid_index, const int order, const int region_w, const int region_h, int *const restrict targets, double *const restrict target_weights)
Definition pde.c:126
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_solve(const _sp_chol_t *const factor, double *const restrict rhs)
const float sigma
#define DT_HL_SPARSE_MAX
typedef double((*spd)(unsigned long int wavelength, double TempK))