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