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