Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
selftests.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// CPU/GPU parity self-tests of the harmonic OpenCL port (each vs a CPU replica). (implementation; see selftests.h
20// for the public API.)
21
22#include "common/darktable.h"
23#include "common/gaussian.h"
25#include "iop/highlights/blur.h"
28#include "iop/highlights/core.h"
29#include "iop/highlights/dome.h"
30#include "iop/highlights/knee.h"
31#include "iop/highlights/pde.h"
34#include <math.h>
35#include <string.h>
36
37#ifdef HAVE_OPENCL
38
39void _sp_chol_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
40{
41 static int done = 0;
42 if(done || !getenv("HL_SPCL_TEST")) return;
43 done = 1;
44
45 const int grid = 96;
46 uint8_t *hole = calloc((size_t)grid * grid, 1);
47 int *grid_index = malloc(sizeof(int) * grid * grid);
48 int dimension = 0;
49 for(int y = 0; y < grid; y++)
50 for(int x = 0; x < grid; x++)
51 {
52 const int delta_x = x - grid / 2;
53 const int delta_y = y - grid / 2;
54 hole[y * grid + x] = (delta_x * delta_x + delta_y * delta_y < 34 * 34);
55 grid_index[y * grid + x] = hole[y * grid + x] ? dimension++ : -1;
56 }
57
58 static const int stencil_dy[13] = { 0, -1, 1, 0, 0, -1, -1, 1, 1, -2, 2, 0, 0 };
59 static const int stencil_dx[13] = { 0, 0, 0, -1, 1, -1, 1, -1, 1, 0, 0, -2, 2 };
60 static const double stencil_coeff[13] = { 20., -8., -8., -8., -8., 2., 2., 2., 2., 1., 1., 1., 1. };
61
62 int *matrix_col_ptr = calloc(dimension + 1, sizeof(int));
63 int *matrix_row_index = malloc(sizeof(int) * (size_t)dimension * 13);
64 double *matrix_values = malloc(sizeof(double) * (size_t)dimension * 13);
65 double *rhs = malloc(sizeof(double) * dimension);
66 double *solution_cpu = malloc(sizeof(double) * dimension);
67 int n_nonzero = 0;
68 for(int y = 0; y < grid; y++)
69 for(int x = 0; x < grid; x++)
70 {
71 const int j = grid_index[y * grid + x];
72 if(j < 0) continue;
73 rhs[j] = 0.5 + 0.001 * (double)((x * 131 + y * 17) % 97);
74 matrix_col_ptr[j] = n_nonzero;
75 for(int tap_index = 0; tap_index < 13; tap_index++)
76 {
77 const int neighbour_y = CLAMP(y + stencil_dy[tap_index], 0, grid - 1);
78 const int neighbour_x = CLAMP(x + stencil_dx[tap_index], 0, grid - 1);
79 const int neighbour_index = grid_index[neighbour_y * grid + neighbour_x];
80 if(neighbour_index < 0 || neighbour_index > j) continue;
81 int scan = matrix_col_ptr[j];
82 for(; scan < n_nonzero; scan++)
83 if(matrix_row_index[scan] == neighbour_index)
84 {
85 matrix_values[scan] += stencil_coeff[tap_index];
86 break;
87 }
88 if(scan == n_nonzero)
89 {
90 matrix_row_index[n_nonzero] = neighbour_index;
91 matrix_values[n_nonzero] = stencil_coeff[tap_index];
92 n_nonzero++;
93 }
94 }
95 }
96 matrix_col_ptr[dimension] = n_nonzero;
97
98 // CPU reference
99 memcpy(solution_cpu, rhs, sizeof(double) * dimension);
100 _sp_chol_t *factor_cpu = _sp_chol_factor(dimension, matrix_col_ptr, matrix_row_index, matrix_values, pipe);
101 if(factor_cpu) _sp_chol_solve(factor_cpu, solution_cpu);
102
103 // GPU
104 _sp_chol_cl_t *factor_gpu = _sp_chol_factor_cl(devid, _hl_sp_chol_kernels(gd_void), dimension, matrix_col_ptr,
105 matrix_row_index, matrix_values);
106 double max_rel_diff = -1.0;
107 if(factor_cpu && factor_gpu)
108 {
109 cl_mem rhs_device = _sp_cl_upload(devid, rhs, sizeof(double) * dimension);
110 if(rhs_device && !_sp_chol_solve_cl(factor_gpu, _hl_sp_chol_kernels(gd_void), rhs_device))
111 {
112 double *solution_gpu = malloc(sizeof(double) * dimension);
113 if(dt_opencl_read_buffer_from_device(devid, solution_gpu, rhs_device, 0, sizeof(double) * dimension, CL_TRUE)
114 == CL_SUCCESS)
115 {
116 max_rel_diff = 0.0;
117 for(int i = 0; i < dimension; i++)
118 {
119 const double rel_diff = fabs(solution_gpu[i] - solution_cpu[i]) / fmax(fabs(solution_cpu[i]), 1e-12);
120 if(rel_diff > max_rel_diff) max_rel_diff = rel_diff;
121 }
122 }
123 free(solution_gpu);
124 }
126 }
127 fprintf(stderr, "[hl sparse-cl selftest] n=%d nnz=%d levels=%d/%d cpu=%s gpu=%s max rel diff=%.3e\n", dimension,
128 factor_cpu ? factor_cpu->col_ptr[dimension] : -1, factor_gpu ? factor_gpu->nlev : -1,
129 factor_gpu ? factor_gpu->nlev_bwd : -1, factor_cpu ? "ok" : "FAIL", factor_gpu ? "ok" : "FAIL",
130 max_rel_diff);
131
132 _sp_chol_free(factor_cpu);
133 _sp_chol_cl_free(factor_gpu);
134 free(hole);
135 free(grid_index);
136 free(matrix_col_ptr);
137 free(matrix_row_index);
138 free(matrix_values);
139 free(rhs);
140 free(solution_cpu);
141}
142
143void _region_blur_cl_selftest(const int devid, const dt_dev_pixelpipe_t *pipe)
144{
145 static int done = 0;
146 if(done || !getenv("HL_BLURCL_TEST") || devid < 0) return;
147 done = 1;
148
149 const int region_w = 731;
150 const int region_h = 517;
151 const size_t region_pixels = (size_t)region_w * region_h;
152 float *input = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
153 float *output_cpu = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
154 float *output_gpu = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
155 if(IS_NULL_PTR(input) || IS_NULL_PTR(output_cpu) || IS_NULL_PTR(output_gpu)) goto done_;
156
157 for(size_t i = 0; i < region_pixels; i++)
158 for(int c = 0; c < 4; c++)
159 input[i * 4 + c] = 0.5f + 0.5f * sinf(0.013f * (float)(i % region_w) + 0.007f * (float)(i / region_w) + c);
160
161 static const float test_sigmas[3] = { 4.f, 16.f, 64.f };
162 for(int sigma_index = 0; sigma_index < 3; sigma_index++)
163 {
164 const float sigma = test_sigmas[sigma_index];
165 _region_blur(input, output_cpu, region_w, region_h, sigma);
166
167 cl_mem in_device = dt_opencl_alloc_device(devid, region_w, region_h, sizeof(float) * 4);
168 cl_mem out_device = dt_opencl_alloc_device(devid, region_w, region_h, sizeof(float) * 4);
169 float max_diff = -1.f;
170 if(in_device && out_device
171 && dt_opencl_write_host_to_device(devid, input, in_device, region_w, region_h, sizeof(float) * 4)
172 == CL_SUCCESS
173 && _region_blur_cl(devid, in_device, out_device, region_w, region_h, sigma) == CL_SUCCESS
174 && dt_opencl_copy_device_to_host(devid, output_gpu, out_device, region_w, region_h, sizeof(float) * 4)
175 == CL_SUCCESS)
176 {
177 max_diff = 0.f;
178 for(size_t i = 0; i < region_pixels * 4; i++)
179 max_diff = fmaxf(max_diff, fabsf(output_gpu[i] - output_cpu[i]));
180 }
183 fprintf(stderr, "[hl blur-cl selftest] %dx%d sigma=%.0f max|gpu-cpu|=%.3e\n", region_w, region_h, sigma,
184 max_diff);
185 }
186
187done_:
191}
192
193void _cf_harmonic_fill_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
194{
195 static int done = 0;
196 if(done || !getenv("HL_FILLCL_TEST") || devid < 0) return;
197 done = 1;
198
199 const int region_w = 613;
200 const int region_h = 419;
201 const size_t region_pixels = (size_t)region_w * region_h;
202 float *val_cpu = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
203 float *val_gpu = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
204 uint8_t *hole = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe); // 1 = to fill
205 uint8_t *anchor
206 = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe); // 1 = trusted (GPU kernel convention)
207 if(IS_NULL_PTR(val_cpu) || IS_NULL_PTR(val_gpu) || IS_NULL_PTR(hole) || IS_NULL_PTR(anchor)) goto done_;
208
209 for(int y = 0; y < region_h; y++)
210 for(int x = 0; x < region_w; x++)
211 {
212 const size_t i = (size_t)y * region_w + x;
213 const int delta_x = x - region_w / 2;
214 const int delta_y = y - (region_h - 30);
215 hole[i] = (delta_x * delta_x + delta_y * delta_y < 120 * 120);
216 anchor[i] = !hole[i];
217 val_cpu[i] = hole[i] ? 0.f : 0.3f + 0.4f * sinf(0.02f * x) * cosf(0.017f * y);
218 }
219 memcpy(val_gpu, val_cpu, sizeof(float) * region_pixels);
220
221 _cf_harmonic_fill(val_cpu, hole, region_w, region_h, 2, NULL, pipe);
222
223 cl_mem dval = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
224 cl_mem danc = dt_opencl_alloc_device_buffer(devid, region_pixels);
225 float max_diff = -1.f;
226 if(dval && danc
227 && dt_opencl_write_buffer_to_device(devid, val_gpu, dval, 0, sizeof(float) * region_pixels, CL_TRUE)
228 == CL_SUCCESS
229 && dt_opencl_write_buffer_to_device(devid, anchor, danc, 0, region_pixels, CL_TRUE) == CL_SUCCESS
230 && _cf_harmonic_fill_cl(devid, gd_void, dval, danc, region_w, region_h, 2, 0, NULL) == CL_SUCCESS
231 && dt_opencl_read_buffer_from_device(devid, val_gpu, dval, 0, sizeof(float) * region_pixels, CL_TRUE)
232 == CL_SUCCESS)
233 {
234 max_diff = 0.f;
235 for(size_t i = 0; i < region_pixels; i++)
236 if(hole[i]) max_diff = fmaxf(max_diff, fabsf(val_gpu[i] - val_cpu[i]));
237 }
240 fprintf(stderr, "[hl fill-cl selftest] %dx%d hole=disc r120 max|gpu-cpu|=%.3e\n", region_w, region_h, max_diff);
241
242 // aniso leg: same fill with the variance-adaptive tensor (mode 3) steered by a synthetic
243 // plane carrying both a smooth ramp and a hard dark bar through the hole (occluder-like).
244 {
245 float *steer = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
246 if(steer)
247 {
248 for(int y = 0; y < region_h; y++)
249 for(int x = 0; x < region_w; x++)
250 {
251 const size_t i = (size_t)y * region_w + x;
252 const int dark_bar = (x > region_w / 2 - 12 && x < region_w / 2 + 12);
253 steer[i] = dark_bar ? 0.05f : (0.4f + 0.5f * (float)y / region_h);
254 val_cpu[i] = hole[i] ? 0.f : 0.3f + 0.4f * sinf(0.02f * x) * cosf(0.017f * y);
255 }
256 memcpy(val_gpu, val_cpu, sizeof(float) * region_pixels);
257
258 _cf_harmonic_fill(val_cpu, hole, region_w, region_h, 2, steer, pipe);
259
260 cl_mem aniso_val_device = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
261 cl_mem aniso_anchor_device = dt_opencl_alloc_device_buffer(devid, region_pixels);
262 cl_mem aniso_steer_device = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
263 float max_diff_aniso = -1.f;
264 if(aniso_val_device && aniso_anchor_device && aniso_steer_device
265 && dt_opencl_write_buffer_to_device(devid, val_gpu, aniso_val_device, 0, sizeof(float) * region_pixels,
266 CL_TRUE)
267 == CL_SUCCESS
268 && dt_opencl_write_buffer_to_device(devid, anchor, aniso_anchor_device, 0, region_pixels, CL_TRUE)
269 == CL_SUCCESS
270 && dt_opencl_write_buffer_to_device(devid, steer, aniso_steer_device, 0, sizeof(float) * region_pixels,
271 CL_TRUE)
272 == CL_SUCCESS
273 && _cf_harmonic_fill_cl(devid, gd_void, aniso_val_device, aniso_anchor_device, region_w, region_h, 2, 0,
274 aniso_steer_device)
275 == CL_SUCCESS
276 && dt_opencl_read_buffer_from_device(devid, val_gpu, aniso_val_device, 0, sizeof(float) * region_pixels,
277 CL_TRUE)
278 == CL_SUCCESS)
279 {
280 max_diff_aniso = 0.f;
281 for(size_t i = 0; i < region_pixels; i++)
282 if(hole[i]) max_diff_aniso = fmaxf(max_diff_aniso, fabsf(val_gpu[i] - val_cpu[i]));
283 }
284 dt_opencl_release_mem_object(aniso_val_device);
285 dt_opencl_release_mem_object(aniso_anchor_device);
286 dt_opencl_release_mem_object(aniso_steer_device);
287 fprintf(stderr, "[hl fill-cl ANISO selftest] %dx%d adaptive-tensor max|gpu-cpu|=%.3e\n", region_w, region_h,
288 max_diff_aniso);
289 }
291 }
292
293done_:
298}
299
300void _cf_joint_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
301{
302 static int done = 0;
303 if(done || !getenv("HL_CFCL_TEST") || devid < 0) return;
304 done = 1;
305
306 const int region_w = 509;
307 const int region_h = 371;
308 const size_t region_pixels = (size_t)region_w * region_h;
309 const float cf_sigma = 24.f;
310 const float cf_fmin = 0.05f;
311 const int c = 1;
312 const int guide1 = 0;
313 const int guide2 = 2;
314
315 float *estimate = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
316 float *valid = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
317 float *model_quality = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
318 float *estimate_gpu = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
319 float *input = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
320 float *moment1 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
321 float *moment2 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
322 float *moment3 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
323 float *coeff_field = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
324 float *plane = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
325 uint8_t *anchor = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
326 uint8_t *border = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
327 if(IS_NULL_PTR(estimate) || IS_NULL_PTR(valid) || IS_NULL_PTR(model_quality) || IS_NULL_PTR(estimate_gpu)
328 || IS_NULL_PTR(input) || IS_NULL_PTR(moment1) || IS_NULL_PTR(moment2) || IS_NULL_PTR(moment3)
329 || IS_NULL_PTR(coeff_field) || IS_NULL_PTR(plane) || IS_NULL_PTR(anchor) || IS_NULL_PTR(border))
330 goto done_;
331
332 for(int y = 0; y < region_h; y++)
333 for(int x = 0; x < region_w; x++)
334 {
335 const size_t i = (size_t)y * region_w + x;
336 const float base = 0.4f + 0.3f * sinf(0.011f * x) * cosf(0.014f * y);
337 estimate[i * 4 + 0] = 0.9f * base + 0.05f;
338 estimate[i * 4 + 1] = 1.2f * base + 0.02f;
339 estimate[i * 4 + 2] = 0.7f * base + 0.08f;
340 estimate[i * 4 + 3] = 0.f;
341 const int delta_x = x - region_w / 2;
342 const int delta_y = y - region_h / 2;
343 const int clip = (delta_x * delta_x + delta_y * delta_y < 90 * 90);
344 valid[i * 4 + 0] = 1.f;
345 valid[i * 4 + 1] = clip ? 0.f : 1.f;
346 valid[i * 4 + 2] = 1.f;
347 valid[i * 4 + 3] = clip ? 0.f : 1.f;
348 for(int k = 0; k < 4; k++) model_quality[i * 4 + k] = 0.f;
349 if(clip) estimate[i * 4 + 1] = 0.55f;
350 }
351 memcpy(estimate_gpu, estimate, region_pixels * 4 * sizeof(float));
352
353 double lum_accum = 0.0;
354 size_t lum_count = 0;
355 for(size_t i = 0; i < region_pixels; i++)
356 if(valid[i * 4 + 1] < 0.5f)
357 {
358 lum_accum += estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
359 lum_count++;
360 }
361 const float cf_lref = lum_count ? (float)(lum_accum / (double)lum_count) : 0.f;
362 const float cf_binv = (cf_lref > 1e-9f) ? 1.f / (0.35f * cf_lref) : 0.f;
363
364 // ---- CPU replica ----
365 for(int mode = 0; mode < 3; mode++)
366 {
367 for(size_t i = 0; i < region_pixels; i++)
368 {
369 const float val_r = estimate[i * 4 + 0];
370 const float val_g = estimate[i * 4 + 1];
371 const float val_b = estimate[i * 4 + 2];
372 const float rgb_sum = val_r + val_g + val_b;
373 const float bright_weight = (cf_binv > 0.f) ? sqf(fminf(rgb_sum * cf_binv, 1.f)) : 1.f;
374 const int all_valid = (valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f);
375 const float weight = all_valid ? bright_weight : 0.f;
376 if(mode == 0)
377 {
378 input[i * 4 + 0] = weight;
379 input[i * 4 + 1] = weight * val_r;
380 input[i * 4 + 2] = weight * val_g;
381 input[i * 4 + 3] = weight * val_b;
382 }
383 else if(mode == 1)
384 {
385 input[i * 4 + 0] = weight * val_r * val_r;
386 input[i * 4 + 1] = weight * val_g * val_g;
387 input[i * 4 + 2] = weight * val_b * val_b;
388 input[i * 4 + 3] = weight * val_r * val_g;
389 }
390 else
391 {
392 input[i * 4 + 0] = weight * val_r * val_b;
393 input[i * 4 + 1] = weight * val_g * val_b;
394 input[i * 4 + 2] = all_valid ? 1.f : 0.f;
395 input[i * 4 + 3] = 0.f;
396 }
397 }
398 _region_blur(input, (mode == 0) ? moment1 : (mode == 1) ? moment2 : moment3, region_w, region_h, cf_sigma);
399 }
400
401 for(size_t i = 0; i < region_pixels; i++)
402 {
403 const float norm = fmaxf(moment1[i * 4 + 0], 1e-9f);
404 const float inv_det = 1.f / norm;
405 const float mean[3]
406 = { moment1[i * 4 + 1] * inv_det, moment1[i * 4 + 2] * inv_det, moment1[i * 4 + 3] * inv_det };
407 const float second_moment[3]
408 = { moment2[i * 4 + 0] * inv_det, moment2[i * 4 + 1] * inv_det, moment2[i * 4 + 2] * inv_det };
409 const float cross_rg = moment2[i * 4 + 3] * inv_det;
410 const float cross_rb = moment3[i * 4 + 0] * inv_det;
411 const float cross_gb = moment3[i * 4 + 1] * inv_det;
412#define OFF2(chan_a, chan_b) \
413 (((chan_a) + (chan_b)) == 1 ? cross_rg : (((chan_a) + (chan_b)) == 2 ? cross_rb : cross_gb))
414 const float mean1 = mean[guide1];
415 const float mean2 = mean[guide2];
416 const float mean_target = mean[c];
417 const float var11 = fmaxf(second_moment[guide1] - mean1 * mean1, 0.f);
418 const float var22 = fmaxf(second_moment[guide2] - mean2 * mean2, 0.f);
419 const float var12 = OFF2(guide1, guide2) - mean1 * mean2;
420 const float cov_tg1 = OFF2(c, guide1) - mean_target * mean1;
421 const float cov_tg2 = OFF2(c, guide2) - mean_target * mean2;
422 const float var_target = fmaxf(second_moment[c] - mean_target * mean_target, 0.f);
423#undef OFF2
424 const float lambda = 1e-3f * 0.5f * (var11 + var22) + 1e-12f;
425 const float determinant = fmaxf((var11 + lambda) * (var22 + lambda) - var12 * var12, 1e-18f);
426 const float slope_a = ((var22 + lambda) * cov_tg1 - var12 * cov_tg2) / determinant;
427 const float slope_b = ((var11 + lambda) * cov_tg2 - var12 * cov_tg1) / determinant;
428 const float r_sq = CLAMP((slope_a * cov_tg1 + slope_b * cov_tg2) / (var_target + 1e-12f), 0.f, 1.f);
429 coeff_field[i * 4 + 0] = slope_a;
430 coeff_field[i * 4 + 1] = slope_b;
431 coeff_field[i * 4 + 2] = mean_target - slope_a * mean1 - slope_b * mean2;
432 coeff_field[i * 4 + 3] = r_sq;
433 const int mass_ok = (moment3[i * 4 + 2] > cf_fmin && moment1[i * 4 + 0] > 0.25f * moment3[i * 4 + 2]);
434 const int valid_ok = (valid[i * 4 + c] >= 0.5f);
435 anchor[i] = (mass_ok && valid_ok && r_sq > 0.25f && fabsf(slope_a) < 64.f && fabsf(slope_b) < 64.f);
436 border[i] = (mass_ok && valid_ok);
437 }
438 {
439 uint8_t *hole = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
440 uint8_t *hole2 = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
441 if(hole && hole2)
442 {
443 for(size_t i = 0; i < region_pixels; i++)
444 {
445 hole[i] = !anchor[i];
446 hole2[i] = !border[i];
447 }
448 const int base_downsample = (int)(cf_sigma / 4.f);
449 for(int k = 0; k < 4; k++)
450 {
451 for(size_t i = 0; i < region_pixels; i++) plane[i] = coeff_field[i * 4 + k];
452 _cf_harmonic_fill(plane, (k == 3) ? hole2 : hole, region_w, region_h, base_downsample, NULL, pipe);
453 for(size_t i = 0; i < region_pixels; i++) coeff_field[i * 4 + k] = plane[i];
454 }
455 }
458 }
459 for(size_t i = 0; i < region_pixels; i++)
460 if(valid[i * 4 + c] < 0.5f && valid[i * 4 + guide1] >= 0.5f && valid[i * 4 + guide2] >= 0.5f)
461 estimate[i * 4 + c] = coeff_field[i * 4 + 0] * estimate[i * 4 + guide1]
462 + coeff_field[i * 4 + 1] * estimate[i * 4 + guide2] + coeff_field[i * 4 + 2];
463
464 // ---- GPU stage ----
465 {
466 cl_mem dest = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
467 cl_mem dvld = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
468 cl_mem dbsc = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
469 cl_mem dlsb = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
470 float *luminance = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
471 float max_diff = -1.f;
472 if(luminance)
473 for(size_t i = 0; i < region_pixels; i++)
474 luminance[i] = estimate_gpu[i * 4 + 0] + estimate_gpu[i * 4 + 1] + estimate_gpu[i * 4 + 2];
475 size_t work_size[3] = { ROUNDUPDWD(region_w, devid), ROUNDUPDHT(region_h, devid), 1 };
476 cl_mem packed_device = dt_opencl_alloc_device(devid, work_size[0], work_size[1], sizeof(float) * 4);
477 cl_mem moment0_device = dt_opencl_alloc_device(devid, work_size[0], work_size[1], sizeof(float) * 4);
478 cl_mem moment1_device = dt_opencl_alloc_device(devid, work_size[0], work_size[1], sizeof(float) * 4);
479 cl_mem moment2_device = dt_opencl_alloc_device(devid, work_size[0], work_size[1], sizeof(float) * 4);
480 cl_mem moments_device[3];
481 moments_device[0] = moment0_device;
482 moments_device[1] = moment1_device;
483 moments_device[2] = moment2_device;
484 int moms_ok = (packed_device && moment0_device && moment1_device && moment2_device);
485 if(moms_ok && dest && dvld && dbsc && dlsb && luminance
486 && dt_opencl_write_buffer_to_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
487 == CL_SUCCESS
488 && dt_opencl_write_buffer_to_device(devid, valid, dvld, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
489 == CL_SUCCESS
490 && dt_opencl_write_buffer_to_device(devid, luminance, dlsb, 0, sizeof(float) * region_pixels, CL_TRUE)
491 == CL_SUCCESS)
492 {
494 cl_int test_cl_err = CL_SUCCESS;
495 for(int mode = 0; mode < 3 && test_cl_err == CL_SUCCESS; mode++)
496 {
497 const int kernel = test_global_data->kernel_hl_cf_pack_joint;
498 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &dest);
499 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &dvld);
500 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &dlsb);
501 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &packed_device);
502 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &region_w);
503 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &region_h);
504 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), &cf_binv);
505 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &mode);
506 const float zero_shift = 0.f; // uncentered, matching the inline CPU replica
507 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(float), &zero_shift);
508 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(float), &zero_shift);
509 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(float), &zero_shift);
510 test_cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, work_size);
511 if(test_cl_err == CL_SUCCESS)
512 test_cl_err = _region_blur_cl(devid, packed_device, moments_device[mode], region_w, region_h, cf_sigma);
513 }
514 moms_ok = (test_cl_err == CL_SUCCESS);
515 }
516 else
517 moms_ok = 0;
518 const float zero_means[3] = { 0.f, 0.f, 0.f }; // isolated stage test runs uncentered
519 if(moms_ok
520 && dt_opencl_write_buffer_to_device(devid, model_quality, dbsc, 0, sizeof(float) * region_pixels * 4,
521 CL_TRUE)
522 == CL_SUCCESS
523 && _cf_joint_stage_cl(devid, gd_void, dest, dvld, dbsc, moment0_device, moment1_device, moment2_device,
524 NULL, zero_means, region_w, region_h, cf_sigma, cf_fmin, c, guide1, guide2)
525 == CL_SUCCESS
526 && dt_opencl_read_buffer_from_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4,
527 CL_TRUE)
528 == CL_SUCCESS)
529 {
530 max_diff = 0.f;
531 for(size_t i = 0; i < region_pixels; i++)
532 if(valid[i * 4 + c] < 0.5f && valid[i * 4 + guide1] >= 0.5f && valid[i * 4 + guide2] >= 0.5f)
533 max_diff = fmaxf(max_diff, fabsf(estimate_gpu[i * 4 + c] - estimate[i * 4 + c]));
534 }
539 dt_opencl_release_mem_object(packed_device);
540 dt_opencl_release_mem_object(moment0_device);
541 dt_opencl_release_mem_object(moment1_device);
542 dt_opencl_release_mem_object(moment2_device);
544 fprintf(stderr, "[hl cf-joint-cl selftest] %dx%d G-disc r90 max|gpu-cpu|=%.3e\n", region_w, region_h, max_diff);
545 }
546
547done_:
550 dt_pixelpipe_cache_free_align(model_quality);
551 dt_pixelpipe_cache_free_align(estimate_gpu);
560}
561
562void _cf_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
563{
564 static int done = 0;
565 if(done || !getenv("HL_CFCL_TEST") || devid < 0) return;
566 done = 1;
567
568 const int region_w = 509;
569 const int region_h = 371;
570 const size_t region_pixels = (size_t)region_w * region_h;
571 const float cf_sigma = 24.f;
572 const float cf_fmin = 0.05f;
573 const int cdeep = 1; // G carries the most clipped pixels by construction
574
575 float *estimate = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
576 float *valid = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
577 float *model_quality = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
578 float *estimate_gpu = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
579 float *input = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
580 float *moment1 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
581 float *moment2 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
582 float *moment3 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
583 float *coeff_field_green = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
584 float *plane = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
585 uint8_t *anchor = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
586 uint8_t *border = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
587 uint8_t *hole = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
588 uint8_t *hole_border = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
589 if(IS_NULL_PTR(estimate) || IS_NULL_PTR(valid) || IS_NULL_PTR(model_quality) || IS_NULL_PTR(estimate_gpu)
590 || IS_NULL_PTR(input) || IS_NULL_PTR(moment1) || IS_NULL_PTR(moment2) || IS_NULL_PTR(moment3)
591 || IS_NULL_PTR(coeff_field_green) || IS_NULL_PTR(plane) || IS_NULL_PTR(anchor) || IS_NULL_PTR(border)
592 || IS_NULL_PTR(hole) || IS_NULL_PTR(hole_border))
593 goto done_;
594
595 for(int y = 0; y < region_h; y++)
596 for(int x = 0; x < region_w; x++)
597 {
598 const size_t i = (size_t)y * region_w + x;
599 const float base = 0.4f + 0.3f * sinf(0.011f * x) * cosf(0.014f * y);
600 estimate[i * 4 + 0] = 0.9f * base + 0.05f;
601 estimate[i * 4 + 1] = 1.2f * base + 0.02f;
602 estimate[i * 4 + 2] = 0.7f * base + 0.08f;
603 estimate[i * 4 + 3] = 0.f;
604 const int delta_x = x - region_w / 2;
605 const int delta_y = y - region_h / 2;
606 const int gclip = (delta_x * delta_x + delta_y * delta_y < 100 * 100);
607 const int rclip = (delta_x * delta_x + delta_y * delta_y < 45 * 45);
608 valid[i * 4 + 0] = rclip ? 0.f : 1.f;
609 valid[i * 4 + 1] = gclip ? 0.f : 1.f;
610 valid[i * 4 + 2] = 1.f;
611 valid[i * 4 + 3] = gclip ? 0.f : 1.f;
612 for(int k = 0; k < 4; k++) model_quality[i * 4 + k] = 0.f;
613 if(gclip) estimate[i * 4 + 1] = 0.58f;
614 if(rclip) estimate[i * 4 + 0] = 0.47f;
615 }
616 memcpy(estimate_gpu, estimate, region_pixels * 4 * sizeof(float));
617
618 double lum_accum = 0.0;
619 size_t lum_count = 0;
620 for(size_t i = 0; i < region_pixels; i++)
621 if(valid[i * 4 + 0] < 0.5f || valid[i * 4 + 1] < 0.5f || valid[i * 4 + 2] < 0.5f)
622 {
623 lum_accum += estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
624 lum_count++;
625 }
626 const float cf_lref = lum_count ? (float)(lum_accum / (double)lum_count) : 0.f;
627 const float cf_binv = (cf_lref > 1e-9f) ? 1.f / (0.35f * cf_lref) : 0.f;
628 const int base_downsample = (int)(cf_sigma / 4.f);
629
630 // pre-ladder luminance: production freezes lsb before the first fit
631 float *luminance = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
632 if(IS_NULL_PTR(luminance)) goto done_;
633 for(size_t i = 0; i < region_pixels; i++)
634 luminance[i] = estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
635
636 // ---- CPU replica ----
637 // joint fit for G (deep): coefficients diffused, evaluation deferred
638 {
639 const int c = 1;
640 const int guide1 = 0;
641 const int guide2 = 2;
642 for(int mode = 0; mode < 3; mode++)
643 {
644 for(size_t i = 0; i < region_pixels; i++)
645 {
646 const float val_r = estimate[i * 4 + 0];
647 const float val_g = estimate[i * 4 + 1];
648 const float val_b = estimate[i * 4 + 2];
649 const float rgb_sum = luminance[i];
650 const float bright_weight = (cf_binv > 0.f) ? sqf(fminf(rgb_sum * cf_binv, 1.f)) : 1.f;
651 const int all_valid = (valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f);
652 const float weight = all_valid ? bright_weight : 0.f;
653 if(mode == 0)
654 {
655 input[i * 4 + 0] = weight;
656 input[i * 4 + 1] = weight * val_r;
657 input[i * 4 + 2] = weight * val_g;
658 input[i * 4 + 3] = weight * val_b;
659 }
660 else if(mode == 1)
661 {
662 input[i * 4 + 0] = weight * val_r * val_r;
663 input[i * 4 + 1] = weight * val_g * val_g;
664 input[i * 4 + 2] = weight * val_b * val_b;
665 input[i * 4 + 3] = weight * val_r * val_g;
666 }
667 else
668 {
669 input[i * 4 + 0] = weight * val_r * val_b;
670 input[i * 4 + 1] = weight * val_g * val_b;
671 input[i * 4 + 2] = all_valid ? 1.f : 0.f;
672 input[i * 4 + 3] = 0.f;
673 }
674 }
675 _region_blur(input, (mode == 0) ? moment1 : (mode == 1) ? moment2 : moment3, region_w, region_h, cf_sigma);
676 }
677 for(size_t i = 0; i < region_pixels; i++)
678 {
679 const float norm = fmaxf(moment1[i * 4 + 0], 1e-9f);
680 const float inv_det = 1.f / norm;
681 const float mean[3]
682 = { moment1[i * 4 + 1] * inv_det, moment1[i * 4 + 2] * inv_det, moment1[i * 4 + 3] * inv_det };
683 const float second_moment[3]
684 = { moment2[i * 4 + 0] * inv_det, moment2[i * 4 + 1] * inv_det, moment2[i * 4 + 2] * inv_det };
685 const float cross_rg = moment2[i * 4 + 3] * inv_det;
686 const float cross_rb = moment3[i * 4 + 0] * inv_det;
687 const float cross_gb = moment3[i * 4 + 1] * inv_det;
688 const float mean1 = mean[guide1];
689 const float mean2 = mean[guide2];
690 const float mean_target = mean[c];
691 const float var11 = fmaxf(second_moment[guide1] - mean1 * mean1, 0.f);
692 const float var22 = fmaxf(second_moment[guide2] - mean2 * mean2, 0.f);
693 const float var12 = cross_rb - mean1 * mean2; // (g1,g2) = (0,2)
694 const float cov_tg1 = cross_rg - mean_target * mean1; // (c,g1) = (1,0)
695 const float cov_tg2 = cross_gb - mean_target * mean2; // (c,g2) = (1,2)
696 const float var_target = fmaxf(second_moment[c] - mean_target * mean_target, 0.f);
697 const float lambda = 1e-3f * 0.5f * (var11 + var22) + 1e-12f;
698 const float determinant = fmaxf((var11 + lambda) * (var22 + lambda) - var12 * var12, 1e-18f);
699 const float slope_a = ((var22 + lambda) * cov_tg1 - var12 * cov_tg2) / determinant;
700 const float slope_b = ((var11 + lambda) * cov_tg2 - var12 * cov_tg1) / determinant;
701 const float r_sq = CLAMP((slope_a * cov_tg1 + slope_b * cov_tg2) / (var_target + 1e-12f), 0.f, 1.f);
702 coeff_field_green[i * 4 + 0] = slope_a;
703 coeff_field_green[i * 4 + 1] = slope_b;
704 coeff_field_green[i * 4 + 2] = mean_target - slope_a * mean1 - slope_b * mean2;
705 coeff_field_green[i * 4 + 3] = r_sq;
706 const int mass_ok = (moment3[i * 4 + 2] > cf_fmin && moment1[i * 4 + 0] > 0.25f * moment3[i * 4 + 2]);
707 const int valid_ok = (valid[i * 4 + c] >= 0.5f);
708 anchor[i] = (mass_ok && valid_ok && r_sq > 0.25f && fabsf(slope_a) < 64.f && fabsf(slope_b) < 64.f);
709 border[i] = (mass_ok && valid_ok);
710 }
711 for(size_t i = 0; i < region_pixels; i++)
712 {
713 hole[i] = !anchor[i];
714 hole_border[i] = !border[i];
715 }
716 for(int k = 0; k < 4; k++)
717 {
718 for(size_t i = 0; i < region_pixels; i++) plane[i] = coeff_field_green[i * 4 + k];
719 _cf_harmonic_fill(plane, (k == 3) ? hole_border : hole, region_w, region_h, base_downsample, NULL, pipe);
720 for(size_t i = 0; i < region_pixels; i++) coeff_field_green[i * 4 + k] = plane[i];
721 }
722 }
723
724 // pair fits that fire: {R,B} tc=R (o=0), then {G,B} tc=G (o=0); same math as production
725 const int pair_a[2] = { 0, 1 };
726 const int pair_b[2] = { 2, 2 };
727 for(int pair = 0; pair < 2; pair++)
728 {
729 const int chan_a = pair_a[pair];
730 const int chan_b = pair_b[pair];
731 const int target_chan = chan_a;
732 const int guide_chan = chan_b;
733 const int other_chan = 3 - chan_a - chan_b;
734 for(int mode = 0; mode < 2; mode++)
735 {
736 for(size_t i = 0; i < region_pixels; i++)
737 {
738 const float value_a = estimate[i * 4 + chan_a];
739 const float value_b = estimate[i * 4 + chan_b];
740 const float rgb_sum = luminance[i];
741 const float bright_weight = (cf_binv > 0.f) ? sqf(fminf(rgb_sum * cf_binv, 1.f)) : 1.f;
742 const int pair_valid = (valid[i * 4 + chan_a] >= 0.5f && valid[i * 4 + chan_b] >= 0.5f);
743 const float weight = pair_valid ? bright_weight : 0.f;
744 if(mode == 0)
745 {
746 input[i * 4 + 0] = weight;
747 input[i * 4 + 1] = weight * value_a;
748 input[i * 4 + 2] = weight * value_b;
749 input[i * 4 + 3] = weight * value_a * value_a;
750 }
751 else
752 {
753 input[i * 4 + 0] = weight * value_b * value_b;
754 input[i * 4 + 1] = weight * value_a * value_b;
755 input[i * 4 + 2] = pair_valid ? 1.f : 0.f;
756 input[i * 4 + 3] = 0.f;
757 }
758 }
759 _region_blur(input, mode ? moment2 : moment1, region_w, region_h, cf_sigma);
760 }
761 for(size_t i = 0; i < region_pixels; i++)
762 {
763 const float norm = fmaxf(moment1[i * 4 + 0], 1e-9f);
764 const float inv_det = 1.f / norm;
765 const float mean_target = moment1[i * 4 + 1] * inv_det; // o = 0: target = a
766 const float mean_guide = moment1[i * 4 + 2] * inv_det;
767 const float var_guide = fmaxf(moment2[i * 4 + 0] * inv_det - mean_guide * mean_guide, 0.f);
768 const float var_t = fmaxf(moment1[i * 4 + 3] * inv_det - mean_target * mean_target, 0.f);
769 const float covariance = moment2[i * 4 + 1] * inv_det - mean_target * mean_guide;
770 const float slope_a = covariance / (var_guide * (1.f + 1e-3f) + 1e-12f);
771 const float r_sq = CLAMP(covariance * covariance / (var_guide * var_t + 1e-18f), 0.f, 1.f);
772 moment3[i * 4 + 0] = slope_a;
773 moment3[i * 4 + 1] = mean_target - slope_a * mean_guide;
774 moment3[i * 4 + 2] = r_sq;
775 const int mass_ok = (moment2[i * 4 + 2] > cf_fmin && moment1[i * 4 + 0] > 0.25f * moment2[i * 4 + 2]);
776 const int valid_ok = (valid[i * 4 + target_chan] >= 0.5f);
777 anchor[i] = (mass_ok && valid_ok && r_sq > 0.25f && fabsf(slope_a) < 64.f);
778 border[i] = (mass_ok && valid_ok);
779 }
780 for(size_t i = 0; i < region_pixels; i++)
781 {
782 hole[i] = !anchor[i];
783 hole_border[i] = !border[i];
784 }
785 for(int k = 0; k < 3; k++)
786 {
787 for(size_t i = 0; i < region_pixels; i++) plane[i] = moment3[i * 4 + k];
788 _cf_harmonic_fill(plane, (k == 2) ? hole_border : hole, region_w, region_h, base_downsample, NULL, pipe);
789 for(size_t i = 0; i < region_pixels; i++) moment3[i * 4 + k] = plane[i];
790 }
791 for(size_t i = 0; i < region_pixels; i++)
792 if(valid[i * 4 + target_chan] < 0.5f && valid[i * 4 + guide_chan] >= 0.5f && valid[i * 4 + other_chan] < 0.5f)
793 {
794 estimate[i * 4 + target_chan] = moment3[i * 4 + 0] * estimate[i * 4 + guide_chan] + moment3[i * 4 + 1];
795 model_quality[i * 4 + target_chan] = CLAMP(moment3[i * 4 + 2], 0.f, 1.f);
796 }
797 }
798
799 // deferred deep evaluation for G with the depth-split blend
800 {
801 const int guide1 = 0;
802 const int guide2 = 2;
803 for(size_t i = 0; i < region_pixels; i++)
804 {
805 const int multi_clip
806 = (valid[i * 4 + cdeep] < 0.5f && (valid[i * 4 + guide1] < 0.5f || valid[i * 4 + guide2] < 0.5f));
807 input[i * 4 + 0] = multi_clip ? 1.f : 0.f;
808 input[i * 4 + 1] = input[i * 4 + 2] = input[i * 4 + 3] = 0.f;
809 }
810 _region_blur(input, moment1, region_w, region_h, cf_sigma);
811 for(size_t i = 0; i < region_pixels; i++)
812 {
813 const int anyvalid = (valid[i * 4 + 0] >= 0.5f) || (valid[i * 4 + 1] >= 0.5f) || (valid[i * 4 + 2] >= 0.5f);
814 if(valid[i * 4 + cdeep] >= 0.5f || !anyvalid) continue;
815 const float joint = coeff_field_green[i * 4 + 0] * estimate[i * 4 + guide1]
816 + coeff_field_green[i * 4 + 1] * estimate[i * 4 + guide2] + coeff_field_green[i * 4 + 2];
817 const int has_pair = (valid[i * 4 + guide1] < 0.5f || valid[i * 4 + guide2] < 0.5f);
818 const float mass = CLAMP(moment1[i * 4 + 0], 0.f, 1.f);
819 const float smooth_t = CLAMP((mass - 0.7f) / 0.25f, 0.f, 1.f);
820 const float weight_depth = has_pair ? smooth_t * smooth_t * (3.f - 2.f * smooth_t) : 0.f;
821 estimate[i * 4 + cdeep] = weight_depth * estimate[i * 4 + cdeep] + (1.f - weight_depth) * joint;
822 model_quality[i * 4 + cdeep] = weight_depth * model_quality[i * 4 + cdeep]
823 + (1.f - weight_depth) * CLAMP(coeff_field_green[i * 4 + 3], 0.f, 1.f);
824 }
825 }
826
827 // ---- GPU: the complete CF stage ----
828 {
829 cl_mem dest = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
830 cl_mem dvld = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
831 cl_mem dbsc = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
832 float max_diff = -1.f;
833 float max_bsc_diff = -1.f;
834 if(dest && dvld && dbsc
835 && dt_opencl_write_buffer_to_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
836 == CL_SUCCESS
837 && dt_opencl_write_buffer_to_device(devid, valid, dvld, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
838 == CL_SUCCESS
839 && dt_opencl_read_buffer_from_device(devid, estimate_gpu, dest, 0, sizeof(float), CL_TRUE) == CL_SUCCESS)
840 {
841 float *zero = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
842 if(zero)
843 {
844 memset(zero, 0, sizeof(float) * region_pixels * 4);
845 dt_opencl_write_buffer_to_device(devid, zero, dbsc, 0, sizeof(float) * region_pixels * 4, CL_TRUE);
847 }
848 cl_mem dlsb = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
849 const float zero_means[3] = { 0.f, 0.f, 0.f }; // isolated stage test runs uncentered
850 if(dlsb
851 && dt_opencl_write_buffer_to_device(devid, luminance, dlsb, 0, sizeof(float) * region_pixels, CL_TRUE)
852 == CL_SUCCESS
853 && _cf_stage_cl(devid, gd_void, dest, dvld, dbsc, dlsb, NULL, zero_means, NULL, region_w, region_h,
854 cf_sigma, cf_fmin, cf_binv, cdeep)
855 == CL_SUCCESS
856 && dt_opencl_read_buffer_from_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4,
857 CL_TRUE)
858 == CL_SUCCESS)
859 {
860 max_diff = 0.f;
861 for(size_t i = 0; i < region_pixels; i++)
862 for(int c = 0; c < 3; c++)
863 if(valid[i * 4 + c] < 0.5f)
864 max_diff = fmaxf(max_diff, fabsf(estimate_gpu[i * 4 + c] - estimate[i * 4 + c]));
865
866 // the fit-quality plane steers the HF damping and the dome blend downstream: compare it too
867 if(dt_opencl_read_buffer_from_device(devid, estimate_gpu, dbsc, 0, sizeof(float) * region_pixels * 4,
868 CL_TRUE)
869 == CL_SUCCESS)
870 {
871 max_bsc_diff = 0.f;
872 size_t arg_index = 0;
873 int arg_chan = 0;
874 for(size_t i = 0; i < region_pixels; i++)
875 for(int c = 0; c < 3; c++)
876 {
877 const float diff = fabsf(estimate_gpu[i * 4 + c] - model_quality[i * 4 + c]);
878 if(diff > max_bsc_diff)
879 {
880 max_bsc_diff = diff;
881 arg_index = i;
882 arg_chan = c;
883 }
884 }
885 if(getenv("HL_CFCL_VERBOSE"))
886 fprintf(stderr, "[hl cf-full bsc argmax] px=(%zu,%zu) c=%d gpu=%f cpu=%f\n", arg_index % region_w,
887 arg_index / region_w, arg_chan, estimate_gpu[arg_index * 4 + arg_chan],
888 model_quality[arg_index * 4 + arg_chan]);
889 }
890 }
892 }
896 fprintf(stderr, "[hl cf-full-cl selftest] %dx%d G-disc r100 + R-core r45 max|gpu-cpu|=%.3e bsc=%.3e\n",
897 region_w, region_h, max_diff, max_bsc_diff);
898 }
899
900done_:
903 dt_pixelpipe_cache_free_align(model_quality);
904 dt_pixelpipe_cache_free_align(estimate_gpu);
909 dt_pixelpipe_cache_free_align(coeff_field_green);
916}
917
918void _hf_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
919{
920 static int done = 0;
921 if(done || !getenv("HL_HFCL_TEST") || devid < 0) return;
922 done = 1;
923
924 const int region_w = 509;
925 const int region_h = 371;
926 const size_t region_pixels = (size_t)region_w * region_h;
927 const float cf_sigma = 24.f;
928 const float cf_fmin = 0.05f;
929 const float blur_sigma = fmaxf(cf_sigma / 4.f, 2.f);
930 const int base_downsample = (int)(cf_sigma / 4.f);
931
932 float *estimate = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
933 float *valid = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
934 float *model_quality = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
935 float *estimate_gpu = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
936 float *input = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
937 float *lowpass = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
938 float *moment1 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
939 float *moment2 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
940 float *moment3 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
941 float *gain_ab = dt_pixelpipe_cache_alloc_align_float(region_pixels * 2, pipe);
942 float *energy = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
943 float *plane = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
944 uint8_t *anchor = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
945 uint8_t *hole = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
946 if(IS_NULL_PTR(estimate) || IS_NULL_PTR(valid) || IS_NULL_PTR(model_quality) || IS_NULL_PTR(estimate_gpu)
947 || IS_NULL_PTR(input) || IS_NULL_PTR(lowpass) || IS_NULL_PTR(moment1) || IS_NULL_PTR(moment2)
948 || IS_NULL_PTR(moment3) || IS_NULL_PTR(gain_ab) || IS_NULL_PTR(energy) || IS_NULL_PTR(plane)
949 || IS_NULL_PTR(anchor) || IS_NULL_PTR(hole))
950 goto done_;
951
952 for(int y = 0; y < region_h; y++)
953 for(int x = 0; x < region_w; x++)
954 {
955 const size_t i = (size_t)y * region_w + x;
956 const float base = 0.4f + 0.3f * sinf(0.011f * x) * cosf(0.014f * y);
957 const float texture = 0.05f * sinf(0.9f * x) * sinf(0.75f * y);
958 estimate[i * 4 + 0] = 0.9f * base + 0.05f + texture;
959 estimate[i * 4 + 1] = 1.2f * base + 0.02f + 0.8f * texture;
960 estimate[i * 4 + 2] = 0.7f * base + 0.08f + 1.1f * texture;
961 estimate[i * 4 + 3] = 0.f;
962 const int delta_x = x - region_w / 2;
963 const int delta_y = y - region_h / 2;
964 const int gclip = (delta_x * delta_x + delta_y * delta_y < 100 * 100);
965 const int rclip = (delta_x * delta_x + delta_y * delta_y < 45 * 45);
966 valid[i * 4 + 0] = rclip ? 0.f : 1.f;
967 valid[i * 4 + 1] = gclip ? 0.f : 1.f;
968 valid[i * 4 + 2] = 1.f;
969 valid[i * 4 + 3] = gclip ? 0.f : 1.f;
970 for(int k = 0; k < 4; k++) model_quality[i * 4 + k] = gclip ? 0.65f : 0.f;
971 }
972 memcpy(estimate_gpu, estimate, region_pixels * 4 * sizeof(float));
973
974 double lum_accum = 0.0;
975 size_t lum_count = 0;
976 for(size_t i = 0; i < region_pixels; i++)
977 if(valid[i * 4 + 0] < 0.5f || valid[i * 4 + 1] < 0.5f || valid[i * 4 + 2] < 0.5f)
978 {
979 lum_accum += estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
980 lum_count++;
981 }
982 const float cf_lref = lum_count ? (float)(lum_accum / (double)lum_count) : 0.f;
983 const float cf_binv = (cf_lref > 1e-9f) ? 1.f / (0.35f * cf_lref) : 0.f;
984
985 // ---- CPU replica ----
986 memcpy(input, estimate, region_pixels * 4 * sizeof(float));
987 _region_blur(input, lowpass, region_w, region_h, blur_sigma);
988 for(int mode = 0; mode < 3; mode++)
989 {
990 for(size_t i = 0; i < region_pixels; i++)
991 {
992 const float detail_r = estimate[i * 4 + 0] - lowpass[i * 4 + 0];
993 const float detail_g = estimate[i * 4 + 1] - lowpass[i * 4 + 1];
994 const float detail_b = estimate[i * 4 + 2] - lowpass[i * 4 + 2];
995 const float rgb_sum = estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
996 const float bright_weight = (cf_binv > 0.f) ? sqf(fminf(rgb_sum * cf_binv, 1.f)) : 1.f;
997 const int all_valid = (valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f);
998 const float weight = all_valid ? bright_weight : 0.f;
999 if(mode == 0)
1000 {
1001 input[i * 4 + 0] = weight;
1002 input[i * 4 + 1] = weight * detail_r;
1003 input[i * 4 + 2] = weight * detail_g;
1004 input[i * 4 + 3] = weight * detail_b;
1005 }
1006 else if(mode == 1)
1007 {
1008 input[i * 4 + 0] = weight * detail_r * detail_r;
1009 input[i * 4 + 1] = weight * detail_g * detail_g;
1010 input[i * 4 + 2] = weight * detail_b * detail_b;
1011 input[i * 4 + 3] = weight * detail_r * detail_g;
1012 }
1013 else
1014 {
1015 input[i * 4 + 0] = weight * detail_r * detail_b;
1016 input[i * 4 + 1] = weight * detail_g * detail_b;
1017 input[i * 4 + 2] = all_valid ? 1.f : 0.f;
1018 input[i * 4 + 3] = 0.f;
1019 }
1020 }
1021 _region_blur(input, (mode == 0) ? moment1 : (mode == 1) ? moment2 : moment3, region_w, region_h, cf_sigma);
1022 }
1023 for(int c = 0; c < 3; c++)
1024 {
1025 const int guide1 = (c == 0) ? 1 : 0;
1026 const int guide2 = (c == 2) ? 1 : 2;
1027 for(size_t i = 0; i < region_pixels; i++)
1028 {
1029 const float norm = fmaxf(moment1[i * 4 + 0], 1e-9f);
1030 const float inv_det = 1.f / norm;
1031 const float mean[3]
1032 = { moment1[i * 4 + 1] * inv_det, moment1[i * 4 + 2] * inv_det, moment1[i * 4 + 3] * inv_det };
1033 const float second_moment[3]
1034 = { moment2[i * 4 + 0] * inv_det, moment2[i * 4 + 1] * inv_det, moment2[i * 4 + 2] * inv_det };
1035 const float cross_rg = moment2[i * 4 + 3] * inv_det;
1036 const float cross_rb = moment3[i * 4 + 0] * inv_det;
1037 const float cross_gb = moment3[i * 4 + 1] * inv_det;
1038#define OFF3(chan_a, chan_b) \
1039 (((chan_a) + (chan_b)) == 1 ? cross_rg : (((chan_a) + (chan_b)) == 2 ? cross_rb : cross_gb))
1040 const float mean1 = mean[guide1];
1041 const float mean2 = mean[guide2];
1042 const float mean_target = mean[c];
1043 const float var11 = fmaxf(second_moment[guide1] - mean1 * mean1, 0.f);
1044 const float var22 = fmaxf(second_moment[guide2] - mean2 * mean2, 0.f);
1045 const float var12 = OFF3(guide1, guide2) - mean1 * mean2;
1046 const float cov_tg1 = OFF3(c, guide1) - mean_target * mean1;
1047 const float cov_tg2 = OFF3(c, guide2) - mean_target * mean2;
1048 const float var_target = fmaxf(second_moment[c] - mean_target * mean_target, 0.f);
1049#undef OFF3
1050 const float lambda = 1e-3f * 0.5f * (var11 + var22) + 1e-12f;
1051 const float determinant = fmaxf((var11 + lambda) * (var22 + lambda) - var12 * var12, 1e-18f);
1052 const float slope_a = ((var22 + lambda) * cov_tg1 - var12 * cov_tg2) / determinant;
1053 const float slope_b = ((var11 + lambda) * cov_tg2 - var12 * cov_tg1) / determinant;
1054 const float r_sq = CLAMP((slope_a * cov_tg1 + slope_b * cov_tg2) / (var_target + 1e-12f), 0.f, 1.f);
1055 gain_ab[i * 2 + 0] = slope_a * r_sq;
1056 gain_ab[i * 2 + 1] = slope_b * r_sq;
1057 const int mass_ok = (moment3[i * 4 + 2] > cf_fmin && moment1[i * 4 + 0] > 0.25f * moment3[i * 4 + 2]);
1058 anchor[i] = (mass_ok && valid[i * 4 + c] >= 0.5f && fabsf(gain_ab[i * 2 + 0]) < 64.f
1059 && fabsf(gain_ab[i * 2 + 1]) < 64.f);
1060 }
1061 for(size_t i = 0; i < region_pixels; i++) hole[i] = !anchor[i];
1062 for(int k = 0; k < 2; k++)
1063 {
1064 for(size_t i = 0; i < region_pixels; i++) plane[i] = gain_ab[i * 2 + k];
1065 _cf_harmonic_fill(plane, hole, region_w, region_h, base_downsample, NULL, pipe);
1066 for(size_t i = 0; i < region_pixels; i++) gain_ab[i * 2 + k] = plane[i];
1067 }
1068 for(size_t i = 0; i < region_pixels; i++)
1069 {
1070 const float high_guide = gain_ab[i * 2 + 0] * (estimate[i * 4 + guide1] - lowpass[i * 4 + guide1])
1071 + gain_ab[i * 2 + 1] * (estimate[i * 4 + guide2] - lowpass[i * 4 + guide2]);
1072 const float high_damped
1073 = CLAMP(model_quality[i * 4 + c], 0.f, 1.f) * (estimate[i * 4 + c] - lowpass[i * 4 + c]);
1074 input[i * 4 + 0] = fabsf(high_guide);
1075 input[i * 4 + 1] = fabsf(high_damped);
1076 input[i * 4 + 2] = 0.f;
1077 input[i * 4 + 3] = 0.f;
1078 }
1079 _region_blur(input, energy, region_w, region_h, blur_sigma);
1080 for(size_t i = 0; i < region_pixels; i++)
1081 if(valid[i * 4 + c] < 0.5f && valid[i * 4 + guide1] >= 0.5f && valid[i * 4 + guide2] >= 0.5f)
1082 {
1083 const float high_guide = gain_ab[i * 2 + 0] * (estimate[i * 4 + guide1] - lowpass[i * 4 + guide1])
1084 + gain_ab[i * 2 + 1] * (estimate[i * 4 + guide2] - lowpass[i * 4 + guide2]);
1085 const float high_damped
1086 = CLAMP(model_quality[i * 4 + c], 0.f, 1.f) * (estimate[i * 4 + c] - lowpass[i * 4 + c]);
1087 const float weight_energy
1088 = energy[i * 4 + 1] * energy[i * 4 + 1]
1089 / fmaxf(energy[i * 4 + 1] * energy[i * 4 + 1] + energy[i * 4 + 0] * energy[i * 4 + 0], 1e-18f);
1090 estimate[i * 4 + c]
1091 = lowpass[i * 4 + c] + weight_energy * high_guide + (1.f - weight_energy) * high_damped;
1092 }
1093 }
1094 for(size_t i = 0; i < region_pixels; i++)
1095 {
1096 const int n_valid = (valid[i * 4 + 0] >= 0.5f) + (valid[i * 4 + 1] >= 0.5f) + (valid[i * 4 + 2] >= 0.5f);
1097 if(n_valid != 1) continue;
1098 for(int c = 0; c < 3; c++)
1099 if(valid[i * 4 + c] < 0.5f)
1100 {
1101 const float weight_hf = CLAMP(model_quality[i * 4 + c], 0.f, 1.f);
1102 estimate[i * 4 + c] = lowpass[i * 4 + c] + weight_hf * (estimate[i * 4 + c] - lowpass[i * 4 + c]);
1103 }
1104 }
1105
1106 // ---- GPU ----
1107 {
1108 cl_mem dest = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1109 cl_mem dvld = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1110 cl_mem dbsc = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1111 cl_mem dlsb = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1112 float *luminance = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1113 float max_diff = -1.f;
1114 if(luminance)
1115 for(size_t i = 0; i < region_pixels; i++)
1116 luminance[i] = estimate_gpu[i * 4 + 0] + estimate_gpu[i * 4 + 1] + estimate_gpu[i * 4 + 2];
1117 if(dest && dvld && dbsc && dlsb && luminance
1118 && dt_opencl_write_buffer_to_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
1119 == CL_SUCCESS
1120 && dt_opencl_write_buffer_to_device(devid, valid, dvld, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
1121 == CL_SUCCESS
1122 && dt_opencl_write_buffer_to_device(devid, model_quality, dbsc, 0, sizeof(float) * region_pixels * 4,
1123 CL_TRUE)
1124 == CL_SUCCESS
1125 && dt_opencl_write_buffer_to_device(devid, luminance, dlsb, 0, sizeof(float) * region_pixels, CL_TRUE)
1126 == CL_SUCCESS
1127 && _hf_stage_cl(devid, gd_void, dest, dvld, dbsc, dlsb, NULL, NULL, region_w, region_h, cf_sigma, cf_fmin,
1128 cf_binv)
1129 == CL_SUCCESS
1130 && dt_opencl_read_buffer_from_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4,
1131 CL_TRUE)
1132 == CL_SUCCESS)
1133 {
1134 max_diff = 0.f;
1135 for(size_t i = 0; i < region_pixels; i++)
1136 for(int c = 0; c < 3; c++)
1137 if(valid[i * 4 + c] < 0.5f)
1138 max_diff = fmaxf(max_diff, fabsf(estimate_gpu[i * 4 + c] - estimate[i * 4 + c]));
1139 }
1145 fprintf(stderr, "[hl hf-cl selftest] %dx%d two-disc textured max|gpu-cpu|=%.3e\n", region_w, region_h,
1146 max_diff);
1147 }
1148
1149done_:
1152 dt_pixelpipe_cache_free_align(model_quality);
1153 dt_pixelpipe_cache_free_align(estimate_gpu);
1164}
1165
1166void _selfdome_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
1167{
1168 static int done = 0;
1169 if(done || !getenv("HL_DOMECL_TEST") || devid < 0) return;
1170 done = 1;
1171
1172 // replay mode: HL_DOMECL_TEST=1 with HL_REG_DUMP=<file path> pointing at a previous dump ->
1173 // run the dumped real-region dome through both implementations and compare
1174 {
1175 const char *reg_dump_path = getenv("HL_REG_DUMP");
1176 FILE *dump_file = (reg_dump_path && reg_dump_path[0]) ? g_fopen(reg_dump_path, "rb") : NULL;
1177 if(dump_file)
1178 {
1179 int dump_w;
1180 int dump_h;
1181 int dump_downsample;
1182 if(fread(&dump_w, sizeof(int), 1, dump_file) == 1 && fread(&dump_h, sizeof(int), 1, dump_file) == 1
1183 && fread(&dump_downsample, sizeof(int), 1, dump_file) == 1)
1184 {
1185 const size_t dump_pixels = (size_t)dump_w * dump_h;
1186 float *dump_cpu = dt_pixelpipe_cache_alloc_align_float(dump_pixels, pipe);
1187 float *dump_gpu = dt_pixelpipe_cache_alloc_align_float(dump_pixels, pipe);
1188 uint8_t *dump_hole = (uint8_t *)dt_pixelpipe_cache_alloc_align(dump_pixels, pipe);
1189 if(dump_cpu && dump_gpu && dump_hole
1190 && fread(dump_cpu, sizeof(float), dump_pixels, dump_file) == dump_pixels
1191 && fread(dump_hole, 1, dump_pixels, dump_file) == dump_pixels)
1192 {
1193 memcpy(dump_gpu, dump_cpu, dump_pixels * sizeof(float));
1194 _biharmonic_dome(dump_cpu, dump_hole, dump_w, dump_h, dump_downsample, pipe);
1195 cl_mem val_device = dt_opencl_alloc_device_buffer(devid, sizeof(float) * dump_pixels);
1196 cl_mem hole_device = dt_opencl_alloc_device_buffer(devid, dump_pixels);
1197 float max_diff = -1.f;
1198 size_t n_nan = 0;
1199 if(val_device && hole_device
1200 && dt_opencl_write_buffer_to_device(devid, dump_gpu, val_device, 0, sizeof(float) * dump_pixels,
1201 CL_TRUE)
1202 == CL_SUCCESS
1203 && dt_opencl_write_buffer_to_device(devid, dump_hole, hole_device, 0, dump_pixels, CL_TRUE)
1204 == CL_SUCCESS
1205 && _biharmonic_dome_cl(devid, gd_void, val_device, hole_device, dump_w, dump_h, dump_downsample, pipe)
1206 == CL_SUCCESS
1207 && dt_opencl_read_buffer_from_device(devid, dump_gpu, val_device, 0, sizeof(float) * dump_pixels,
1208 CL_TRUE)
1209 == CL_SUCCESS)
1210 {
1211 max_diff = 0.f;
1212 for(size_t i = 0; i < dump_pixels; i++)
1213 {
1214 if(isnan(dump_gpu[i]))
1215 n_nan++;
1216 else
1217 max_diff = fmaxf(max_diff, fabsf(dump_gpu[i] - dump_cpu[i]));
1218 }
1219 }
1220 fprintf(stderr, "[hl dome-cl REPLAY] %dx%d ds=%d nan=%zu max|gpu-cpu|=%.3e\n", dump_w, dump_h,
1221 dump_downsample, n_nan, max_diff);
1222 dt_opencl_release_mem_object(val_device);
1223 dt_opencl_release_mem_object(hole_device);
1224 }
1228 }
1229 fclose(dump_file);
1230 }
1231 }
1232
1233 const int region_w = 509;
1234 const int region_h = 371;
1235 const size_t region_pixels = (size_t)region_w * region_h;
1236 const float cf_sigma = 24.f;
1237 const float reg_radius = 100.f;
1238 const float epsilon = 1e-6f;
1239
1240 float *estimate = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1241 float *valid = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1242 float *model_quality = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1243 float *clip0 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1244 float *depth = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1245 float *estimate_gpu = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1246 float *luminance = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1247 float *dome_lum = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1248 float *ratio = dt_pixelpipe_cache_alloc_align_float(region_pixels * 3, pipe);
1249 uint8_t *hole = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
1250 if(IS_NULL_PTR(estimate) || IS_NULL_PTR(valid) || IS_NULL_PTR(model_quality) || IS_NULL_PTR(clip0)
1251 || IS_NULL_PTR(depth) || IS_NULL_PTR(estimate_gpu) || IS_NULL_PTR(luminance) || IS_NULL_PTR(dome_lum)
1252 || IS_NULL_PTR(ratio) || IS_NULL_PTR(hole))
1253 goto done_;
1254
1255 size_t n_hole_union = 0;
1256 for(int y = 0; y < region_h; y++)
1257 for(int x = 0; x < region_w; x++)
1258 {
1259 const size_t i = (size_t)y * region_w + x;
1260 const float base = 0.4f + 0.3f * sinf(0.011f * x) * cosf(0.014f * y);
1261 estimate[i * 4 + 0] = 0.9f * base + 0.05f;
1262 estimate[i * 4 + 1] = 1.2f * base + 0.02f;
1263 estimate[i * 4 + 2] = 0.7f * base + 0.08f;
1264 estimate[i * 4 + 3] = 0.f;
1265 const int delta_x = x - region_w / 2;
1266 const int delta_y = y - region_h / 2;
1267 const float dist = sqrtf((float)(delta_x * delta_x + delta_y * delta_y));
1268 const int gclip = (dist < 100.f);
1269 const int rclip = (dist < 45.f);
1270 valid[i * 4 + 0] = rclip ? 0.f : 1.f;
1271 valid[i * 4 + 1] = gclip ? 0.f : 1.f;
1272 valid[i * 4 + 2] = 1.f;
1273 valid[i * 4 + 3] = gclip ? 0.f : 1.f;
1274 for(int k = 0; k < 4; k++)
1275 {
1276 model_quality[i * 4 + k] = gclip ? 0.55f : 0.f;
1277 clip0[i * 4 + k] = 0.5f;
1278 }
1279 depth[i] = fmaxf(100.f - dist, 0.f);
1280 if(gclip) n_hole_union++;
1281 }
1282 memcpy(estimate_gpu, estimate, region_pixels * 4 * sizeof(float));
1283
1284 const int downsample_shared = MAX(1, (int)ceilf(sqrtf((float)n_hole_union / (float)DT_HL_DOME_NMAX_SPARSE)));
1285
1286 // ---- CPU replica: production order ----
1287 for(size_t i = 0; i < region_pixels; i++)
1288 for(int c = 0; c < 3; c++)
1289 if(valid[i * 4 + c] < 0.5f)
1290 {
1291 const float clip_floor = clip0[i * 4 + c];
1292 const float diff = estimate[i * 4 + c] - clip_floor;
1293 const float soft_width = 0.02f * fmaxf(clip_floor, 1e-6f);
1294 estimate[i * 4 + c] = clip_floor + 0.5f * (diff + sqrtf(diff * diff + soft_width * soft_width));
1295 }
1296 for(size_t i = 0; i < region_pixels; i++)
1297 {
1298 luminance[i] = estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
1299 hole[i] = (valid[i * 4 + 0] < 0.5f || valid[i * 4 + 1] < 0.5f || valid[i * 4 + 2] < 0.5f);
1300 dome_lum[i] = luminance[i];
1301 }
1302 _biharmonic_dome(dome_lum, hole, region_w, region_h, downsample_shared, pipe);
1303 {
1304 const int cf_base = (int)(CLAMP(reg_radius / 6.f, 8.f, 64.f) / 4.f);
1305 float *plane = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1306 if(plane)
1307 {
1308 for(int c = 0; c < 3; c++)
1309 {
1310 for(size_t i = 0; i < region_pixels; i++) plane[i] = estimate[i * 4 + c] / fmaxf(luminance[i], epsilon);
1311 _cf_harmonic_fill(plane, hole, region_w, region_h, cf_base, NULL, pipe);
1312 for(size_t i = 0; i < region_pixels; i++) ratio[i * 3 + c] = fmaxf(plane[i], 0.f);
1313 }
1315 }
1316 }
1317 for(size_t i = 0; i < region_pixels; i++)
1318 {
1319 if(!hole[i]) continue;
1320 const float chroma_sum = fmaxf(ratio[i * 3 + 0] + ratio[i * 3 + 1] + ratio[i * 3 + 2], epsilon);
1321 const int anyvalid = (valid[i * 4 + 0] >= 0.5f) || (valid[i * 4 + 1] >= 0.5f) || (valid[i * 4 + 2] >= 0.5f);
1322 for(int c = 0; c < 3; c++)
1323 if(valid[i * 4 + c] < 0.5f)
1324 {
1325 const float quality = CLAMP((model_quality[i * 4 + c] - 0.4f) / 0.45f, 0.f, 1.f);
1326 const float weight_r2 = quality * quality * (3.f - 2.f * quality);
1327 const float depth_t = depth[i] / (1.5f * cf_sigma);
1328 const float depth_gauss = expf(-depth_t * depth_t);
1329 const float weight_sqrt = sqrtf(CLAMP(1.f - (1.f - weight_r2) * depth_gauss, 0.f, 1.f));
1330 const float weight = weight_sqrt * weight_sqrt;
1331 const float dome = dome_lum[i] * (ratio[i * 3 + c] / chroma_sum);
1332 estimate[i * 4 + c] = anyvalid ? (weight * estimate[i * 4 + c] + (1.f - weight) * dome) : dome;
1333 }
1334 }
1335 for(size_t i = 0; i < region_pixels; i++)
1336 for(int c = 0; c < 3; c++)
1337 if(valid[i * 4 + c] < 0.5f) estimate[i * 4 + c] = fmaxf(estimate[i * 4 + c], clip0[i * 4 + c]);
1338
1339 // ---- GPU ----
1340 {
1341 cl_mem dest = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1342 cl_mem dvld = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1343 cl_mem dbsc = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1344 cl_mem dclip = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1345 cl_mem ddep = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels);
1346 float max_diff = -1.f;
1347 if(dest && dvld && dbsc && dclip && ddep
1348 && dt_opencl_write_buffer_to_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
1349 == CL_SUCCESS
1350 && dt_opencl_write_buffer_to_device(devid, valid, dvld, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
1351 == CL_SUCCESS
1352 && dt_opencl_write_buffer_to_device(devid, model_quality, dbsc, 0, sizeof(float) * region_pixels * 4,
1353 CL_TRUE)
1354 == CL_SUCCESS
1355 && dt_opencl_write_buffer_to_device(devid, clip0, dclip, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
1356 == CL_SUCCESS
1357 && dt_opencl_write_buffer_to_device(devid, depth, ddep, 0, sizeof(float) * region_pixels, CL_TRUE)
1358 == CL_SUCCESS
1359 && _selfdome_stage_cl(devid, gd_void, dest, dvld, dbsc, dclip, ddep, region_w, region_h, cf_sigma,
1360 reg_radius, downsample_shared, pipe)
1361 == CL_SUCCESS
1362 && dt_opencl_read_buffer_from_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4,
1363 CL_TRUE)
1364 == CL_SUCCESS)
1365 {
1366 max_diff = 0.f;
1367 for(size_t i = 0; i < region_pixels; i++)
1368 for(int c = 0; c < 3; c++)
1369 if(valid[i * 4 + c] < 0.5f)
1370 max_diff = fmaxf(max_diff, fabsf(estimate_gpu[i * 4 + c] - estimate[i * 4 + c]));
1371 }
1377 fprintf(stderr, "[hl dome-cl selftest] %dx%d two-disc ds=%d max|gpu-cpu|=%.3e\n", region_w, region_h,
1378 downsample_shared, max_diff);
1379 }
1380
1381done_:
1384 dt_pixelpipe_cache_free_align(model_quality);
1387 dt_pixelpipe_cache_free_align(estimate_gpu);
1392}
1393
1394void _joint_core_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
1395{
1396 static int done = 0;
1397 if(done || !getenv("HL_CORECL_TEST") || devid < 0) return;
1398 done = 1;
1399
1400 const int region_w = 509;
1401 const int region_h = 371;
1402 const size_t region_pixels = (size_t)region_w * region_h;
1403 const float solid_color = 0.4f;
1404 const float reg_radius = 100.f;
1405 const float epsilon = 1e-6f;
1406
1407 float *estimate = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1408 float *valid = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1409 float *clip0 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1410 float *estimate_gpu = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1411 float *luminance = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1412 float *dome_lum = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1413 float *chroma = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1414 float *chroma_work = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1415 float *target_buf = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1416 float *diffusion_buf = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1417 float *scratch1 = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1418 float *scratch2 = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1419 float *scratch_sc = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1420 float *weight_feather = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1421 uint8_t *hole = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
1422 if(IS_NULL_PTR(estimate) || IS_NULL_PTR(valid) || IS_NULL_PTR(clip0) || IS_NULL_PTR(estimate_gpu)
1423 || IS_NULL_PTR(luminance) || IS_NULL_PTR(dome_lum) || IS_NULL_PTR(chroma) || IS_NULL_PTR(chroma_work)
1424 || IS_NULL_PTR(target_buf) || IS_NULL_PTR(diffusion_buf) || IS_NULL_PTR(scratch1) || IS_NULL_PTR(scratch2)
1425 || IS_NULL_PTR(scratch_sc) || IS_NULL_PTR(weight_feather) || IS_NULL_PTR(hole))
1426 goto done_;
1427
1428 for(int y = 0; y < region_h; y++)
1429 for(int x = 0; x < region_w; x++)
1430 {
1431 const size_t i = (size_t)y * region_w + x;
1432 const float base = 0.4f + 0.3f * sinf(0.011f * x) * cosf(0.014f * y);
1433 estimate[i * 4 + 0] = 0.9f * base + 0.05f;
1434 estimate[i * 4 + 1] = 1.2f * base + 0.02f;
1435 estimate[i * 4 + 2] = 0.7f * base + 0.08f;
1436 estimate[i * 4 + 3] = 0.f;
1437 const int delta_x = x - region_w / 2;
1438 const int delta_y = y - (region_h - 40);
1439 const float dist = sqrtf((float)(delta_x * delta_x + delta_y * delta_y));
1440 const int allclip = (dist < 95.f); // >16k px core even border-cut: forces the CG path
1441 const int gclip = (dist < 145.f);
1442 valid[i * 4 + 0] = allclip ? 0.f : 1.f;
1443 valid[i * 4 + 1] = gclip ? 0.f : 1.f;
1444 valid[i * 4 + 2] = allclip ? 0.f : 1.f;
1445 valid[i * 4 + 3] = gclip ? 0.f : 1.f;
1446 for(int k = 0; k < 4; k++) clip0[i * 4 + k] = 0.5f;
1447 if(allclip)
1448 for(int c = 0; c < 3; c++) estimate[i * 4 + c] = 0.5f;
1449 else if(gclip)
1450 estimate[i * 4 + 1] = 0.55f + 0.002f * (100.f - dist); // annulus: reconstructed, bright
1451 }
1452 memcpy(estimate_gpu, estimate, region_pixels * 4 * sizeof(float));
1453
1454 // ---- CPU replica: production joint-core block ----
1455 {
1456 for(size_t i = 0; i < region_pixels; i++)
1457 {
1458 hole[i] = (valid[i * 4 + 0] < 0.5f && valid[i * 4 + 1] < 0.5f && valid[i * 4 + 2] < 0.5f);
1459 luminance[i] = estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2];
1460 chroma_work[i] = luminance[i];
1461 }
1462 _biharmonic_dome(chroma_work, hole, region_w, region_h, 0, pipe);
1463 memcpy(dome_lum, chroma_work, region_pixels * sizeof(float));
1464 for(size_t i = 0; i < region_pixels; i++)
1465 if(hole[i]) dome_lum[i] = fmaxf(dome_lum[i], clip0[i * 4 + 0] + clip0[i * 4 + 1] + clip0[i * 4 + 2]);
1466
1467 dt_aligned_pixel_t chroma_mean = { 0.f, 0.f, 0.f, 0.f };
1468 double chroma_accum[3] = { 0.0, 0.0, 0.0 };
1469 double chroma_count = 0.0;
1470 for(size_t i = 0; i < region_pixels; i++)
1471 {
1472 if(!(valid[i * 4 + 0] >= 0.5f && valid[i * 4 + 1] >= 0.5f && valid[i * 4 + 2] >= 0.5f)) continue;
1473 const float inv_lum = 1.f / fmaxf(luminance[i], epsilon);
1474 chroma_accum[0] += (double)(estimate[i * 4 + 0] * inv_lum);
1475 chroma_accum[1] += (double)(estimate[i * 4 + 1] * inv_lum);
1476 chroma_accum[2] += (double)(estimate[i * 4 + 2] * inv_lum);
1477 chroma_count += 1.0;
1478 }
1479 if(chroma_count > 0.0)
1480 for(int c = 0; c < 3; c++) chroma_mean[c] = (float)(chroma_accum[c] / chroma_count);
1481
1482 const float reaction = solid_color * solid_color * 4.f;
1483 for(size_t i = 0; i < region_pixels; i++) diffusion_buf[i] = reaction;
1484
1485 int *sp_pgrid = NULL;
1486 int sp_n_unknowns = 0;
1487 _sp_chol_t *sp_factor = _sp_pde_factor(hole, (reaction > 0.f) ? diffusion_buf : NULL, 1, 1.f, region_w,
1488 region_h, &sp_pgrid, &sp_n_unknowns, pipe);
1489 double *sp_rhs
1490 = sp_factor ? (double *)dt_pixelpipe_cache_alloc_align(sizeof(double) * sp_n_unknowns, pipe) : NULL;
1491 float *cg_residual = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1492 float *cg_search = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1493 float *cg_matvec = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1494 const int max_iter = CLAMP(2 * 150, 200, 2000);
1495 for(int c = 0; c < 3; c++)
1496 {
1497 for(size_t i = 0; i < region_pixels; i++)
1498 {
1499 chroma_work[i] = hole[i] ? chroma_mean[c] : (estimate[i * 4 + c] / fmaxf(luminance[i], epsilon));
1500 target_buf[i] = chroma_mean[c];
1501 }
1502 if(sp_factor && sp_rhs)
1503 _sp_pde_solve(sp_factor, sp_pgrid, chroma_work, hole, (reaction > 0.f) ? diffusion_buf : NULL,
1504 (reaction > 0.f) ? target_buf : NULL, NULL, 1, 1.f, region_w, region_h, sp_rhs, scratch1,
1505 scratch2, scratch_sc);
1506 else if(cg_residual && cg_search && cg_matvec)
1507 _region_pde_solve(chroma_work, hole, (reaction > 0.f) ? diffusion_buf : NULL,
1508 (reaction > 0.f) ? target_buf : NULL, NULL, 1, 1.f, region_w, region_h, cg_residual,
1509 cg_search, cg_matvec, scratch1, scratch2, max_iter);
1510 for(size_t i = 0; i < region_pixels; i++) chroma[i * 4 + c] = fmaxf(chroma_work[i], 0.f);
1511 }
1512 fprintf(stderr, "[hl core-cl selftest] CPU path: %s\n", (sp_factor && sp_rhs) ? "sparse" : "CG");
1513 _sp_chol_free(sp_factor);
1516 dt_pixelpipe_cache_free_align(cg_residual);
1519
1520 for(size_t i = 0; i < region_pixels; i++) chroma_work[i] = hole[i] ? 1.f : 0.f;
1521 _knee_blur(chroma_work, weight_feather, region_w, region_h,
1522 fmaxf(4.f, CLAMP(reg_radius / 6.f, 8.f, 64.f) / 4.f));
1523
1524 for(size_t i = 0; i < region_pixels; i++)
1525 {
1526 const float feather = CLAMP(weight_feather[i], 0.f, 1.f);
1527 const float chroma_sum = fmaxf(chroma[i * 4 + 0] + chroma[i * 4 + 1] + chroma[i * 4 + 2], epsilon);
1528 if(hole[i])
1529 {
1530 for(int c = 0; c < 3; c++) estimate[i * 4 + c] = dome_lum[i] * (chroma[i * 4 + c] / chroma_sum);
1531 }
1532 else if(feather > 1e-4f)
1533 {
1534 for(int c = 0; c < 3; c++)
1535 if(valid[i * 4 + c] < 0.5f)
1536 estimate[i * 4 + c]
1537 = feather * dome_lum[i] * (chroma[i * 4 + c] / chroma_sum) + (1.f - feather) * estimate[i * 4 + c];
1538 }
1539 }
1540 }
1541
1542 // ---- GPU ----
1543 {
1544 cl_mem dest = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1545 cl_mem dvld = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1546 cl_mem dclip = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1547 float max_diff = -1.f;
1548 if(dest && dvld && dclip
1549 && dt_opencl_write_buffer_to_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
1550 == CL_SUCCESS
1551 && dt_opencl_write_buffer_to_device(devid, valid, dvld, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
1552 == CL_SUCCESS
1553 && dt_opencl_write_buffer_to_device(devid, clip0, dclip, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
1554 == CL_SUCCESS
1555 && _joint_core_stage_cl(devid, gd_void, dest, dvld, dclip, region_w, region_h, solid_color, reg_radius, 150,
1556 pipe)
1557 == CL_SUCCESS
1558 && dt_opencl_read_buffer_from_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4,
1559 CL_TRUE)
1560 == CL_SUCCESS)
1561 {
1562 max_diff = 0.f;
1563 for(size_t i = 0; i < region_pixels; i++)
1564 for(int c = 0; c < 3; c++)
1565 max_diff = fmaxf(max_diff, fabsf(estimate_gpu[i * 4 + c] - estimate[i * 4 + c]));
1566 }
1567 fprintf(stderr, "[hl core-cl selftest] %dx%d all-clip disc + annulus max|gpu-cpu|=%.3e\n", region_w, region_h,
1568 max_diff);
1572 }
1573
1574done_:
1578 dt_pixelpipe_cache_free_align(estimate_gpu);
1582 dt_pixelpipe_cache_free_align(chroma_work);
1584 dt_pixelpipe_cache_free_align(diffusion_buf);
1588 dt_pixelpipe_cache_free_align(weight_feather);
1590}
1591
1592void _aniso_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
1593{
1594 static int done = 0;
1595 if(done || !getenv("HL_ANISOCL_TEST") || devid < 0) return;
1596 done = 1;
1597
1598 const int region_w = 509;
1599 const int region_h = 371;
1600 const size_t region_pixels = (size_t)region_w * region_h;
1601 const float epsilon = 1e-6f;
1602
1603 float *estimate = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1604 float *valid = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1605 float *clip0 = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1606 float *estimate_gpu = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1607 float *prev = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1608 float *luminance = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1609 float *chroma = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1610 float *planes = dt_pixelpipe_cache_alloc_align_float(region_pixels * 4, pipe);
1611 if(IS_NULL_PTR(estimate) || IS_NULL_PTR(valid) || IS_NULL_PTR(clip0) || IS_NULL_PTR(estimate_gpu)
1612 || IS_NULL_PTR(prev) || IS_NULL_PTR(luminance) || IS_NULL_PTR(chroma) || IS_NULL_PTR(planes))
1613 goto done_;
1614
1615 for(int y = 0; y < region_h; y++)
1616 for(int x = 0; x < region_w; x++)
1617 {
1618 const size_t i = (size_t)y * region_w + x;
1619 // textured luminance: oriented stripes steer the isophotes
1620 const float base = 0.6f + 0.25f * sinf(0.05f * x + 0.08f * y) + 0.1f * cosf(0.021f * y);
1621 estimate[i * 4 + 0] = 0.9f * base + 0.05f;
1622 estimate[i * 4 + 1] = 1.1f * base + 0.02f;
1623 estimate[i * 4 + 2] = 0.8f * base + 0.08f;
1624 estimate[i * 4 + 3] = 0.f;
1625 const int delta_x = x - region_w / 2;
1626 const int delta_y = y - region_h / 2;
1627 const float dist = sqrtf((float)(delta_x * delta_x + delta_y * delta_y));
1628 const int allclip = (dist < 55.f);
1629 const int gclip = (dist < 90.f);
1630 valid[i * 4 + 0] = allclip ? 0.f : 1.f;
1631 valid[i * 4 + 1] = gclip ? 0.f : 1.f;
1632 valid[i * 4 + 2] = allclip ? 0.f : 1.f;
1633 valid[i * 4 + 3] = gclip ? 0.f : 1.f;
1634 for(int k = 0; k < 4; k++) clip0[i * 4 + k] = 0.5f;
1635 if(allclip)
1636 for(int c = 0; c < 3; c++) estimate[i * 4 + c] = 1.6f + 0.1f * c; // dome-ish core magnitude
1637 }
1638 memcpy(estimate_gpu, estimate, region_pixels * 4 * sizeof(float));
1639
1640 // ---- CPU replica: production aniso block (COEFF_FIELD variant, solver 2) ----
1641 {
1642 for(size_t i = 0; i < region_pixels; i++)
1643 {
1644 const int all_clip = (valid[i * 4 + 0] < 0.5f && valid[i * 4 + 1] < 0.5f && valid[i * 4 + 2] < 0.5f);
1645 for(int c = 0; c < 4; c++) prev[i * 4 + c] = all_clip ? valid[i * 4 + c] : fmaxf(valid[i * 4 + c], 0.6f);
1646 }
1647 for(size_t i = 0; i < region_pixels; i++)
1648 {
1649 const float pixel_lum = fmaxf(estimate[i * 4 + 0] + estimate[i * 4 + 1] + estimate[i * 4 + 2], epsilon);
1650 luminance[i] = pixel_lum;
1651 for(int c = 0; c < 3; c++) chroma[i * 4 + c] = estimate[i * 4 + c] / pixel_lum;
1652 }
1653 if(!_aniso_div_solve(chroma, prev, luminance, planes, region_w, region_h, pipe))
1654 {
1655 fprintf(stderr, "[hl aniso-cl selftest] CPU div solve failed, aborting\n");
1656 goto done_;
1657 }
1658
1659 // full-resolution projected polish (mirrors the production block: obstacle = clip0/L)
1660 {
1661 float *const restrict tensor_xx = planes + 0 * region_pixels;
1662 float *const restrict tensor_xy = planes + 1 * region_pixels;
1663 float *const restrict tensor_yy = planes + 2 * region_pixels;
1664 float *const restrict tensor_scale = planes + 3 * region_pixels;
1665 float *const restrict solve_u = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1666 float *const restrict obstacle = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1667 float *const restrict scratch = dt_pixelpipe_cache_alloc_align_float(region_pixels, pipe);
1668 uint8_t *const restrict hole_flag = (uint8_t *)dt_pixelpipe_cache_alloc_align(region_pixels, pipe);
1669
1670 if(solve_u && obstacle && scratch && hole_flag)
1671 {
1672 _aniso_tensor(luminance, tensor_xx, tensor_xy, tensor_yy, tensor_scale, region_w, region_h);
1673
1674 int box_x0 = region_w;
1675 int box_y0 = region_h;
1676 int box_x1 = -1;
1677 int box_y1 = -1;
1678 for(int y = 0; y < region_h; y++)
1679 for(int x = 0; x < region_w; x++)
1680 {
1681 const size_t i = (size_t)y * region_w + x;
1682 hole_flag[i] = (prev[i * 4 + 0] < 0.5f && prev[i * 4 + 1] < 0.5f && prev[i * 4 + 2] < 0.5f);
1683 if(hole_flag[i])
1684 {
1685 box_x0 = MIN(box_x0, x);
1686 box_x1 = MAX(box_x1, x);
1687 box_y0 = MIN(box_y0, y);
1688 box_y1 = MAX(box_y1, y);
1689 }
1690 }
1691
1692 // activity gate (mirrors production): skip channels whose obstacle can never fire
1693 int active[3] = { 0, 0, 0 };
1694 for(size_t i = 0; i < region_pixels; i++)
1695 {
1696 if(!hole_flag[i]) continue;
1697 const float inv_lum = 1.f / fmaxf(luminance[i], epsilon);
1698 for(int c = 0; c < 3; c++) active[c] |= (chroma[i * 4 + c] <= clip0[i * 4 + c] * inv_lum * 1.001f);
1699 }
1700
1701 if(box_x1 >= box_x0)
1702 for(int c = 0; c < 3; c++)
1703 {
1704 if(!active[c]) continue;
1705 for(size_t i = 0; i < region_pixels; i++)
1706 {
1707 solve_u[i] = chroma[i * 4 + c];
1708 obstacle[i] = clip0[i * 4 + c] / fmaxf(luminance[i], epsilon);
1709 }
1710 _aniso_iterate_obs(solve_u, obstacle, hole_flag, tensor_xx, tensor_xy, tensor_yy, scratch, region_w,
1711 region_h, 60, box_x0, box_y0, box_x1, box_y1);
1712 for(size_t i = 0; i < region_pixels; i++) chroma[i * 4 + c] = solve_u[i];
1713 }
1714 }
1719 }
1720
1721 for(size_t i = 0; i < region_pixels; i++)
1722 {
1723 const float ratio_sum = fmaxf(chroma[i * 4 + 0] + chroma[i * 4 + 1] + chroma[i * 4 + 2], epsilon);
1724 for(int c = 0; c < 3; c++)
1725 if(prev[i * 4 + c] < 0.5f)
1726 {
1727 const float ratio_c = fmaxf(chroma[i * 4 + c], 0.f);
1728 const float value = luminance[i] * ratio_c / ratio_sum;
1729 // soft floor, mirrors the production reassembly
1730 const float clip_floor = clip0[i * 4 + c];
1731 const float diff = value - clip_floor;
1732 const float soft_width = 0.02f * fmaxf(clip_floor, 1e-6f);
1733 estimate[i * 4 + c] = clip_floor + 0.5f * (diff + sqrtf(diff * diff + soft_width * soft_width));
1734 }
1735 }
1736 }
1737
1738 // ---- GPU ----
1739 {
1740 cl_mem dest = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1741 cl_mem dvld = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1742 cl_mem dclip = dt_opencl_alloc_device_buffer(devid, sizeof(float) * region_pixels * 4);
1743 float max_diff = -1.f;
1744 if(dest && dvld && dclip
1745 && dt_opencl_write_buffer_to_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
1746 == CL_SUCCESS
1747 && dt_opencl_write_buffer_to_device(devid, valid, dvld, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
1748 == CL_SUCCESS
1749 && dt_opencl_write_buffer_to_device(devid, clip0, dclip, 0, sizeof(float) * region_pixels * 4, CL_TRUE)
1750 == CL_SUCCESS
1751 && _aniso_stage_cl(devid, gd_void, dest, dvld, dclip, region_w, region_h, 55.f, pipe) == CL_SUCCESS
1752 && dt_opencl_read_buffer_from_device(devid, estimate_gpu, dest, 0, sizeof(float) * region_pixels * 4,
1753 CL_TRUE)
1754 == CL_SUCCESS)
1755 {
1756 max_diff = 0.f;
1757 for(size_t i = 0; i < region_pixels; i++)
1758 for(int c = 0; c < 3; c++)
1759 max_diff = fmaxf(max_diff, fabsf(estimate_gpu[i * 4 + c] - estimate[i * 4 + c]));
1760 }
1761 fprintf(stderr, "[hl aniso-cl selftest] %dx%d all-clip disc textured max|gpu-cpu|=%.3e\n", region_w, region_h,
1762 max_diff);
1766 }
1767
1768done_:
1772 dt_pixelpipe_cache_free_align(estimate_gpu);
1777}
1778
1779void _region_guided_filter_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
1780{
1781 static int done = 0;
1782 if(done || !getenv("HL_REGCL_TEST") || devid < 0) return;
1783 done = 1;
1784
1785 const int width = 700;
1786 const int height = 520;
1787 const size_t n_pixels = (size_t)width * height;
1788
1789 float *interp = dt_pixelpipe_cache_alloc_align_float(n_pixels * 4, pipe);
1790 float *interp_gpu = dt_pixelpipe_cache_alloc_align_float(n_pixels * 4, pipe);
1791 float *mask = dt_pixelpipe_cache_alloc_align_float(n_pixels * 4, pipe);
1792 float *depth = dt_pixelpipe_cache_alloc_align_float(n_pixels, pipe);
1793 if(IS_NULL_PTR(interp) || IS_NULL_PTR(interp_gpu) || IS_NULL_PTR(mask) || IS_NULL_PTR(depth)) goto cleanup;
1794
1795 const float radii[3] = { 60.f, 75.f, 50.f };
1796 for(int y = 0; y < height; y++)
1797 for(int x = 0; x < width; x++)
1798 {
1799 const size_t i = (size_t)y * width + x;
1800 const float base = 0.5f + 0.3f * sinf(0.013f * x) * cosf(0.011f * y) + 0.1f * cosf(0.03f * x);
1801 const float gain[3] = { 0.9f, 1.15f, 0.75f };
1802 const int center_x = width - 80;
1803 const int center_y = height - 70;
1804 const int delta_x = x - center_x;
1805 const int delta_y = y - center_y;
1806 const float dist = sqrtf((float)(delta_x * delta_x + delta_y * delta_y));
1807 const int occluder = (y > center_y - 8 && y < center_y + 8); // dark occluder bar through the blob
1808 int any_clip = 0;
1809 for(int c = 0; c < 3; c++)
1810 {
1811 const int clipped = (dist < radii[c]) && !occluder;
1812 mask[i * 4 + c] = clipped ? 1.f : 0.f;
1813 interp[i * 4 + c] = clipped ? 0.62f * gain[c] : fmaxf(base * gain[c] * (occluder ? 0.15f : 1.f), 0.f);
1814 any_clip |= clipped;
1815 }
1816 mask[i * 4 + 3] = any_clip ? 1.f : 0.f;
1817 interp[i * 4 + 3] = interp[i * 4 + 0] + interp[i * 4 + 1] + interp[i * 4 + 2];
1818 depth[i] = fmaxf(radii[1] - dist, 0.f);
1819 }
1820 memcpy(interp_gpu, interp, n_pixels * 4 * sizeof(float));
1821
1822 _hl_region_t region;
1823 region.x0 = width - 80 - 75;
1824 region.x1 = MIN(width - 80 + 75, width - 1);
1825 region.y0 = height - 70 - 75;
1826 region.y1 = MIN(height - 70 + 75, height - 1);
1827 region.pad = 96;
1828 region.rx0 = MAX(region.x0 - region.pad, 0);
1829 region.ry0 = MAX(region.y0 - region.pad, 0);
1830 region.rx1 = MIN(region.x1 + region.pad, width - 1);
1831 region.ry1 = MIN(region.y1 + region.pad, height - 1);
1832 region.radius = 52.f;
1833 const float solid_color = 0.3f;
1834
1835 _region_guided_filter(interp, mask, depth, width, &region, pipe, solid_color, 30, 0.f);
1836
1837 {
1838 cl_mem interp_device = dt_opencl_alloc_device_buffer(devid, sizeof(float) * n_pixels * 4);
1839 cl_mem mask_device = dt_opencl_alloc_device_buffer(devid, sizeof(float) * n_pixels * 4);
1840 cl_mem depth_device = dt_opencl_alloc_device_buffer(devid, sizeof(float) * n_pixels);
1841 float max_diff = -1.f;
1842 if(interp_device && mask_device && depth_device
1843 && dt_opencl_write_buffer_to_device(devid, interp_gpu, interp_device, 0, sizeof(float) * n_pixels * 4,
1844 CL_TRUE)
1845 == CL_SUCCESS
1846 && dt_opencl_write_buffer_to_device(devid, mask, mask_device, 0, sizeof(float) * n_pixels * 4, CL_TRUE)
1847 == CL_SUCCESS
1848 && dt_opencl_write_buffer_to_device(devid, depth, depth_device, 0, sizeof(float) * n_pixels, CL_TRUE)
1849 == CL_SUCCESS
1850 && _region_guided_filter_cl(devid, gd_void, interp_device, mask_device, depth_device, width, &region, pipe,
1851 solid_color)
1852 == CL_SUCCESS
1853 && dt_opencl_read_buffer_from_device(devid, interp_gpu, interp_device, 0, sizeof(float) * n_pixels * 4,
1854 CL_TRUE)
1855 == CL_SUCCESS)
1856 {
1857 max_diff = 0.f;
1858 double sum_diff = 0.0;
1859 size_t n_big = 0;
1860 for(size_t i = 0; i < n_pixels; i++)
1861 for(int c = 0; c < 3; c++)
1862 {
1863 const float diff = fabsf(interp_gpu[i * 4 + c] - interp[i * 4 + c]);
1864 max_diff = fmaxf(max_diff, diff);
1865 sum_diff += (double)diff;
1866 if(diff > 1e-4f) n_big++;
1867 }
1868 fprintf(stderr, "[hl region-cl selftest] mean=%.3e npix>1e-4: %zu/%zu\n", sum_diff / (double)(n_pixels * 3),
1869 n_big, n_pixels * 3);
1870 }
1871 fprintf(stderr, "[hl region-cl selftest] %dx%d staggered blob r=%g max|gpu-cpu|=%.3e\n", width, height,
1872 region.radius, max_diff);
1873 dt_opencl_release_mem_object(interp_device);
1874 dt_opencl_release_mem_object(mask_device);
1875 dt_opencl_release_mem_object(depth_device);
1876 }
1877
1878cleanup:
1883}
1884
1885void _knee_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
1886{
1887 static int done_ = 0;
1888 if(done_ || !getenv("HL_KNEECL_TEST") || devid < 0) return;
1889 done_ = 1;
1890
1891 const size_t width = 1462;
1892 const size_t height = 1034;
1893 const size_t n_pixels = width * height;
1894 const uint32_t filters = 0x94949494u; // RGGB
1895 const dt_aligned_pixel_t clipraw = { 1.f, 1.f, 1.f, 1.f };
1896 dt_iop_roi_t roi_in = { 0 };
1897
1898 float *raw_mosaic = dt_pixelpipe_cache_alloc_align_float(n_pixels, pipe);
1899 float *corr_cpu = dt_pixelpipe_cache_alloc_align_float(n_pixels, pipe);
1900 float *corr_gpu = dt_pixelpipe_cache_alloc_align_float(n_pixels, pipe);
1901 if(IS_NULL_PTR(raw_mosaic) || IS_NULL_PTR(corr_cpu) || IS_NULL_PTR(corr_gpu)) goto cleanup;
1902
1903 // colour-lines scene: smooth chroma-correlated channels; a soft rolloff compresses the band
1904 for(size_t i = 0; i < height; i++)
1905 for(size_t j = 0; j < width; j++)
1906 {
1907 const int c = FC(i, j, filters);
1908 const float base = 0.55f + 0.45f * sinf(0.006f * j) * cosf(0.008f * i) + 0.12f * sinf(0.03f * (i + j));
1909 const float gain[3] = { 0.95f, 1.05f, 0.85f };
1910 float value = fmaxf(base * gain[c > 2 ? 1 : c], 0.f);
1911 if(value > 0.8f) value = 0.8f + (value - 0.8f) * 0.65f; // rolloff: measured lags the colour line
1912 raw_mosaic[i * width + j] = fminf(value, 1.f);
1913 }
1914
1915 {
1916 _hl_knee_curve_t curve_cpu[3];
1917 _hl_knee_curve_t curve_gpu[3];
1918 _hl_knee_estimate(raw_mosaic, width, height, filters, &roi_in, NULL, clipraw, curve_cpu, pipe);
1919
1920 cl_mem in_device = dt_opencl_alloc_device_buffer(devid, sizeof(float) * n_pixels);
1921 cl_mem out_device = dt_opencl_alloc_device_buffer(devid, sizeof(float) * n_pixels);
1922 float curve_diff = -1.f;
1923 float apply_diff = -1.f;
1924 int engaged_ok = 0;
1925 if(in_device && out_device
1926 && dt_opencl_write_buffer_to_device(devid, raw_mosaic, in_device, 0, sizeof(float) * n_pixels, CL_TRUE)
1927 == CL_SUCCESS
1928 && _hl_knee_estimate_cl(devid, gd_void, in_device, width, height, filters, &roi_in, NULL, 0, clipraw,
1929 curve_gpu, pipe)
1930 == CL_SUCCESS)
1931 {
1932 engaged_ok = (curve_cpu[0].engaged == curve_gpu[0].engaged) && (curve_cpu[1].engaged == curve_gpu[1].engaged)
1933 && (curve_cpu[2].engaged == curve_gpu[2].engaged);
1934 curve_diff = 0.f;
1935 for(int c = 0; c < 3; c++)
1936 for(int i = 0; i < DT_HL_KNEE_BINS; i++)
1937 curve_diff = fmaxf(curve_diff, fabsf(curve_cpu[c].lift[i] - curve_gpu[c].lift[i]));
1938
1939 // apply parity on the CPU curves (isolates the apply kernel)
1940 _hl_knee_apply_cfa(raw_mosaic, corr_cpu, width, height, filters, &roi_in, NULL, clipraw, curve_cpu);
1941 if(_hl_knee_apply_cfa_cl(devid, gd_void, in_device, out_device, width, height, filters, &roi_in, NULL, 0,
1942 clipraw, curve_cpu)
1943 == CL_SUCCESS
1944 && dt_opencl_read_buffer_from_device(devid, corr_gpu, out_device, 0, sizeof(float) * n_pixels, CL_TRUE)
1945 == CL_SUCCESS)
1946 {
1947 apply_diff = 0.f;
1948 for(size_t i = 0; i < n_pixels; i++) apply_diff = fmaxf(apply_diff, fabsf(corr_gpu[i] - corr_cpu[i]));
1949 }
1950 }
1951 fprintf(stderr,
1952 "[hl knee-cl selftest] %zux%zu RGGB engaged cpu=[%d %d %d] match=%d "
1953 "max|curve dcpu-gpu|=%.3e max|apply gpu-cpu|=%.3e\n",
1954 width, height, curve_cpu[0].engaged, curve_cpu[1].engaged, curve_cpu[2].engaged, engaged_ok,
1955 curve_diff, apply_diff);
1957 dt_opencl_release_mem_object(out_device);
1958 }
1959
1960cleanup:
1964}
1965
1966#endif // HAVE_OPENCL
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:164
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
cl_int _region_blur_cl(const int devid, cl_mem in, cl_mem out, const int region_w, const int region_h, const float sigma)
Definition blur.c:76
static void _region_blur(const float *const restrict in, float *const restrict out, const int region_w, const int region_h, const float sigma)
Definition blur.h:34
int _aniso_div_solve(float *const restrict ratios, const float *const restrict valid, const float *const restrict luminance, float *const restrict scratch_planes, const int region_w, const int region_h, const dt_dev_pixelpipe_t *pipe)
Definition chroma.c:155
__DT_CLONE_TARGETS__ void _aniso_iterate_obs(float *const restrict field, const float *const restrict obstacle, const uint8_t *const restrict hole, const float *const restrict tensor_xx, const float *const restrict tensor_xy, const float *const restrict tensor_yy, float *const restrict tmp, const int region_w, const int region_h, const int iters, const int box_x_lo, const int box_y_lo, const int box_x_hi, const int box_y_hi)
Definition chroma.c:102
__DT_CLONE_TARGETS__ void _aniso_tensor(const float *const restrict luminance, float *const restrict tensor_xx, float *const restrict tensor_xy, float *const restrict tensor_yy, float *const restrict scratch, const int region_w, const int region_h)
Definition chroma.c:31
cl_int _cf_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem model_quality, cl_mem luminance, cl_mem steer, const float *const restrict channel_means, dt_gaussian_cl_t *gaussian, const int region_w, const int region_h, const float cf_sigma, const float cf_fmin, const float cf_binv, const int cdeep)
cl_int _cf_harmonic_fill_cl(const int devid, void *gd_void, cl_mem val, cl_mem hole, const int region_w, const int region_h, const int base_ds, const int mask_is_hole, cl_mem steer)
cl_int _cf_joint_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem model_quality, cl_mem mom0, cl_mem mom1, cl_mem mom2, cl_mem steer, const float *const restrict channel_means, const int region_w, const int region_h, const float cf_sigma, const float cf_fmin, const int c, const int guide1, const int guide2)
void _cf_harmonic_fill(float *const restrict val, const uint8_t *const restrict hole, const int region_w, const int region_h, const int base_ds, const float *const restrict steer, const dt_dev_pixelpipe_t *pipe)
cl_int _hf_stage_cl(const int devid, void *gd_void, cl_mem estimate, cl_mem valid, cl_mem model_quality, cl_mem luminance, cl_mem steer, dt_gaussian_cl_t *gaussian, const int region_w, const int region_h, const float cf_sigma, const float cf_fmin, const float cf_binv)
#define 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
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
static int FC(const int row, const int col, const unsigned int filters)
__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 void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:30
int dimension(struct dt_imageio_module_format_t *self, dt_imageio_module_data_t *data, uint32_t *width, uint32_t *height)
static float kernel(const float *x, const float *y)
static const float x
__DT_CLONE_TARGETS__ void _hl_knee_estimate(const float *const restrict input, const size_t width, const size_t height, const uint32_t filters, const dt_iop_roi_t *const roi_in, const uint8_t(*const xtrans)[6], const dt_aligned_pixel_t clipval_raw, _hl_knee_curve_t curves[3], const dt_dev_pixelpipe_t *pipe)
Definition knee.c:105
__DT_CLONE_TARGETS__ void _hl_knee_apply_cfa(const float *const restrict input, float *const restrict input_corr, const size_t width, const size_t height, const uint32_t filters, const dt_iop_roi_t *const roi_in, const uint8_t(*const xtrans)[6], const dt_aligned_pixel_t clipval_raw, const _hl_knee_curve_t curves[3])
Definition knee.c:553
cl_int _hl_knee_estimate_cl(const int devid, void *gd_void, cl_mem dev_in, const size_t width, const size_t height, const uint32_t filters, const dt_iop_roi_t *const roi_in, cl_mem dev_xtrans, const int is_xtrans, const dt_aligned_pixel_t clipval_raw, _hl_knee_curve_t curves[3], const dt_dev_pixelpipe_t *pipe)
Definition knee.c:584
cl_int _hl_knee_apply_cfa_cl(const int devid, void *gd_void, cl_mem dev_in, cl_mem dev_out, const size_t width, const size_t height, const uint32_t filters, const dt_iop_roi_t *const roi_in, cl_mem dev_xtrans, const int is_xtrans, const dt_aligned_pixel_t clipval_raw, const _hl_knee_curve_t curves[3])
Definition knee.c:917
static void _knee_blur(const float *const restrict in, float *const restrict out, const int width, const int height, const float sigma)
Definition knee.h:31
float *const restrict luminance
float *const restrict const size_t k
float dt_aligned_pixel_t[4]
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_copy_device_to_host(const int devid, void *host, void *device, const int width, const int height, const int bpp)
Definition opencl.c:2191
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2504
int dt_opencl_write_buffer_to_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2348
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
int dt_opencl_write_host_to_device(const int devid, void *host, void *device, const int width, const int height, const int bpp)
Definition opencl.c:2244
#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
_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
__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 _sp_chol_cl_kernels_t _hl_sp_chol_kernels(void *gd_void)
Definition pde.h:107
void _region_guided_filter(float *const restrict interp, const float *const restrict mask, const float *const restrict depth, const int width, const _hl_region_t *const region, const dt_dev_pixelpipe_t *pipe, const float solid_color, const int max_iter, const float noise_level)
Definition region.c:149
#define OFF3(chan_a, chan_b)
void _hf_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:918
void _region_guided_filter_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:1779
void _knee_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:1885
#define OFF2(chan_a, chan_b)
void _joint_core_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:1394
void _selfdome_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:1166
void _aniso_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:1592
void _cf_harmonic_fill_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:193
void _cf_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:562
void _region_blur_cl_selftest(const int devid, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:143
void _cf_joint_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:300
void _sp_chol_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:39
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)
const float sigma
#define DT_HL_KNEE_BINS
#define DT_HL_DOME_NMAX_SPARSE
Region of interest passed through the pixelpipe.
Definition imageop.h:72
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