Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
process.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// Top-level Bayer/X-Trans CPU drivers and the hybrid OpenCL driver. (implementation; see process.h for the public
20// API.)
21
22#include "common/darktable.h"
24#include "common/gaussian.h"
27#include "iop/highlights/blur.h"
29#include "iop/highlights/knee.h"
34#include <math.h>
35#include <stdlib.h>
36#include <string.h>
37
40 const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid,
41 void *const restrict ovoid, const dt_iop_roi_t *const roi_in,
42 const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips)
43{
44 int err_code = 0;
45
46 // Every helper below (normalization, knee estimate/apply, gather, remosaic) reads
47 // FC(row, col, filters) with tile-local row/col (0-based within this buffer, no roi offset
48 // added), so filters must be pre-shifted for roi_in's crop position here -- mirrors
49 // demosaic.c's tile-local algorithms.
50 const uint32_t filters = dt_dev_get_roi_filters(piece, roi_in);
51
52 const size_t height = roi_in->height;
53 const size_t width = roi_in->width;
54 const size_t size = roi_in->width * roi_in->height;
55
56 float *const restrict interpolated
57 = dt_pixelpipe_cache_alloc_align_float(size * 4, pipe); // [R, G, B, norm] for each pixel
58 float *const restrict clipping_mask
59 = dt_pixelpipe_cache_alloc_align_float(size * 4, pipe); // [R, G, B, norm] for each pixel
60
61 if(IS_NULL_PTR(interpolated) || IS_NULL_PTR(clipping_mask))
62 {
63 err_code = 1;
64 goto error;
65 }
66
67 const float *const restrict input = (const float *const restrict)ivoid;
68 float *const restrict output = (float *const restrict)ovoid;
69 dt_aligned_pixel_t normalization = { 1.f, 1.f, 1.f, 1.f };
70 _compute_laplacian_normalization(input, roi_in, filters, NULL, normalization);
71
72 // Rolloff estimation FIRST (raw-based, mask-independent): its engagement decides, per
73 // channel, whether the detection extends into the band (the band override,
74 // DT_HL_BAND_OVR = 0.9, compile-time). Only channels with a
75 // MEASURED rolloff get the override -- on hard-clipping sensors the band is trustworthy
76 // data and stays valid.
77 _hl_knee_curve_t knee[3];
78 dt_aligned_pixel_t clipvaln = { 1.f, 1.f, 1.f, 1.f };
79 dt_aligned_pixel_t knee_clipraw = { 1.f, 1.f, 1.f, 1.f };
80 for(int c = 0; c < 3; c++)
81 {
82 clipvaln[c] = clips[c] / (DT_HL_KNEE_DET * fmaxf(normalization[c], 1e-9f));
83 knee_clipraw[c] = clips[c] / DT_HL_KNEE_DET;
84 }
85
86 // FLOW step 2 (knee): estimate the per-channel sensor-rolloff inverse from the raw mosaic (step-2 maths
87 // annotated on _hl_knee_estimate below). Runs on the raw values, before the gather, so the correction
88 // is mask-independent; applied to the interpolated planes just below via _hl_knee_apply_interpolated.
89 _hl_knee_estimate(input, width, height, filters, roi_in, NULL, knee_clipraw, knee, pipe);
90 const int knee_on = knee[0].engaged || knee[1].engaged || knee[2].engaged;
91
92 dt_aligned_pixel_t det_scale = { 1.f, 1.f, 1.f, 1.f };
93 for(int c = 0; c < 3; c++)
94 if(knee[c].engaged) det_scale[c] = DT_HL_BAND_OVR;
95
96 // FLOW step 1a (gather): bilinear interpolation of the raw mosaic into [R, G, B, norm] planes + the
97 // binary per-channel clip masks -- the article's "interpolate + masks" node, input to every later step.
98 _interpolate_and_mask(input, interpolated, clipping_mask, clips, det_scale, normalization, filters, width,
99 height);
100 // No mask feathering in this mode: the masks stay BINARY end to end. The per-channel
101 // validity masks define measurement validity for every fit (feathering them reclassified
102 // rim-clipped photosites -- raw values biased at the detection threshold -- as valid anchors
103 // and dragged oblique rims toward the clip level), and the compositing alpha is a hard
104 // switch (measured equivalent to the feathered composite once validity is binary and clipped
105 // raw values are floors -- see the graveyard of the companion article).
106
107 // Rolloff pre-correction of the working planes (the estimation ran before the gather; the
108 // lift is value-based and independent of the mask, so band values -- including any the
109 // override reclassified as reconstructable -- carry their corrected level, which the region
110 // gather then freezes into the per-pixel floors clip0).
111 if(knee_on) _hl_knee_apply_interpolated(interpolated, size, clipvaln, normalization, knee);
112
113 // MATHS BRIDGE -- Step 1 (segmentation + depth), article "The algorithm" step 1: the any-clip mask's
114 // Euclidean distance transform gives each clipped pixel its depth delta(x) (distance to the nearest
115 // valid pixel); connected-component segmentation then groups clipped pixels into regions, each
116 // carrying its reconstruction radius R = max delta over the region.
117 //
118 // Per-pixel reconstruction depth = distance from each clipped pixel to the nearest valid one
119 // (Euclidean distance transform of the any-clip mask). A hole's reconstruction radius is the max
120 // of this over the hole -- its true "reach needed", independent of the bbox shape.
121 const size_t npix = (size_t)width * height;
122 float *const restrict depth = dt_pixelpipe_cache_alloc_align_float(npix, pipe);
123 if(!depth)
124 {
125 err_code = 1;
126 goto error;
127 }
128 uint8_t *const restrict maskb = (uint8_t *)dt_pixelpipe_cache_alloc_align(npix, pipe);
129 if(!maskb)
130 {
132 err_code = 1;
133 goto error;
134 }
136 for(size_t i = 0; i < npix; i++)
137 {
138 // seed the distance transform: clipped pixels = +inf (to be filled with delta), valid = 0
139 depth[i] = (clipping_mask[i * 4 + 3] > 0.5f) ? (float)DT_DISTANCE_TRANSFORM_MAX : 0.f;
140 maskb[i] = (clipping_mask[i * 4 + 3] >= 1e-3f); // binary any-clip mask for the connected-component pass
141 }
142 dt_image_distance_transform(NULL, depth, width, height, 0.f,
143 DT_DISTANCE_TRANSFORM_NONE); // depth[] <- delta(x) (EDT)
144
145 // Segment the clipped areas into connected regions and reconstruct each at full resolution with a
146 // coarse->fine full-value guided filter (only clipped neighbourhoods are touched). Each region is
147 // padded by its reconstruction radius (the deepest clip-to-valid distance), so the padding gives
148 // the colour-line fit a valid rim as far out as the deepest pixel needs, and no farther.
149 const dt_iop_highlights_data_t *const data = (const dt_iop_highlights_data_t *)piece->data;
150 _hl_region_t *regions = NULL;
151 // 8-neighbour connected components; pad = ceil(1.25 * R) clamped to [8, 256] px around each region
152 const int nreg = _segment_clipped_regions(maskb, depth, width, height, 1.25f, 8, 256, &regions);
153
154
155 // FLOW steps 3-8 (per region): reconstruct each connected clipped region on its padded window. Regions
156 // are independent (their padded read boxes were merged when they overlapped, in _segment_clipped_regions),
157 // so this loop is embarrassingly parallel across regions and linear in the total padded area.
158 for(int region_index = 0; region_index < nreg; region_index++)
159 _region_guided_filter(interpolated, clipping_mask, depth, width, &regions[region_index], pipe,
160 data->solid_color, data->iterations, data->noise_level);
161
162 free(regions);
165
166 // The composition reads `input` back for unmasked pixels, so the band correction must also go
167 // through a corrected CFA copy -- otherwise the output band would keep the biased values the
168 // reconstruction no longer agrees with (the seam would reappear at the detection contour).
169 const float *remosaic_input = input;
170 float *input_corr = NULL;
171
172 if(knee_on)
173 {
174 input_corr = dt_pixelpipe_cache_alloc_align_float(size, pipe);
175
176 if(!IS_NULL_PTR(input_corr))
177 {
178 _hl_knee_apply_cfa(input, input_corr, width, height, filters, roi_in, NULL, knee_clipraw, knee);
179 remosaic_input = input_corr;
180 }
181 }
182
183 // FLOW: remosaic + composite (the flowchart's terminal node). Scatter the reconstructed RGB back onto
184 // the Bayer grid: out = opacity*rec + (1 - opacity)*base with opacity the binary any-clip mask, and
185 // (clip_is_floor = TRUE here) base = max(raw, rec) on a clipped photosite -- so the reconstruction can
186 // only lift a rolloff-biased sample toward its true level, never pull a valid one down. remosaic_input
187 // is the knee-corrected CFA when the knee engaged (so unmasked pixels match the reconstruction's basis).
188 _remosaic_and_replace(remosaic_input, input, interpolated, clipping_mask, output, normalization, clips, TRUE,
189 filters, width, height);
190
191 if(!IS_NULL_PTR(input_corr)) dt_pixelpipe_cache_free_align(input_corr);
192
193error:;
194 dt_pixelpipe_cache_free_align(interpolated);
195 dt_pixelpipe_cache_free_align(clipping_mask);
197 return err_code;
198}
199
202 const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid,
203 void *const restrict ovoid, const dt_iop_roi_t *const roi_in,
204 const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips)
205{
206 // Mirror of process_harmonic_bayer: the reconstruction is CFA-agnostic (it works on the
207 // interpolated RGB planes and masks); only the gather (bilinear interpolation), the scatter
208 // (remosaic) and the knee's raw-mosaic access differ, through their X-Trans variants.
209 int err_code = 0;
210
211 const size_t height = roi_in->height;
212 const size_t width = roi_in->width;
213 const size_t size = roi_in->width * roi_in->height;
214 const uint8_t(*const xtrans)[6] = (const uint8_t(*const)[6])piece->dsc_in.xtrans;
215
216 float *const restrict interpolated = dt_pixelpipe_cache_alloc_align_float(size * 4, pipe);
217 float *const restrict clipping_mask = dt_pixelpipe_cache_alloc_align_float(size * 4, pipe);
218
219 if(IS_NULL_PTR(interpolated) || IS_NULL_PTR(clipping_mask))
220 {
221 err_code = 1;
222 goto error;
223 }
224
225 const float *const restrict input = (const float *const restrict)ivoid;
226 float *const restrict output = (float *const restrict)ovoid;
227 dt_aligned_pixel_t normalization = { 1.f, 1.f, 1.f, 1.f };
228 _compute_laplacian_normalization(input, roi_in, 9u, xtrans, normalization);
229
230 int32_t lookup[6][6][32] = { { { 0 } } };
231 _build_xtrans_bilinear_lookup(lookup, roi_in, xtrans);
232 // Rolloff estimation FIRST (raw-based, mask-independent), so its per-channel engagement
233 // decides the band override of the detection -- see the Bayer path for the why.
234 _hl_knee_curve_t knee[3];
235 dt_aligned_pixel_t clipvaln = { 1.f, 1.f, 1.f, 1.f };
236 dt_aligned_pixel_t knee_clipraw = { 1.f, 1.f, 1.f, 1.f };
237 for(int c = 0; c < 3; c++)
238 {
239 clipvaln[c] = clips[c] / (DT_HL_KNEE_DET * fmaxf(normalization[c], 1e-9f));
240 knee_clipraw[c] = clips[c] / DT_HL_KNEE_DET;
241 }
242
243 // FLOW step 2 (knee): X-Trans rolloff estimate on the raw mosaic (6x6 binning), same role as the Bayer
244 // path -- applied to the interpolated planes below via _hl_knee_apply_interpolated when engaged.
245 _hl_knee_estimate(input, width, height, 9u, roi_in, xtrans, knee_clipraw, knee, pipe);
246 const int knee_on = knee[0].engaged || knee[1].engaged || knee[2].engaged;
247
248 dt_aligned_pixel_t det_scale = { 1.f, 1.f, 1.f, 1.f };
249 for(int c = 0; c < 3; c++)
250 if(knee[c].engaged) det_scale[c] = DT_HL_BAND_OVR;
251
252 dt_aligned_pixel_t eff_clips;
253 for_four_channels(c) eff_clips[c] = clips[c] * det_scale[c];
254
255 // FLOW step 1a (gather): X-Trans variant of the gather -- bilinear interpolation through the 6x6 lookup
256 // into [R, G, B, norm] planes + the binary per-channel clip masks. Feeds every later step.
257 _interpolate_and_mask_xtrans(input, interpolated, clipping_mask, eff_clips, normalization, roi_in, lookup,
258 xtrans, width, height);
259 // No mask feathering in this mode: the masks stay BINARY end to end. The per-channel
260 // validity masks define measurement validity for every fit (feathering them reclassified
261 // rim-clipped photosites -- raw values biased at the detection threshold -- as valid anchors
262 // and dragged oblique rims toward the clip level), and the compositing alpha is a hard
263 // switch (measured equivalent to the feathered composite once validity is binary and clipped
264 // raw values are floors -- see the graveyard of the companion article).
265
266 // Rolloff pre-correction of the working planes (estimation ran before the gather; the 6x6
267 // X-Trans binning estimator is otherwise identical to the Bayer path).
268 if(knee_on) _hl_knee_apply_interpolated(interpolated, size, clipvaln, normalization, knee);
269
270 // MATHS BRIDGE -- Step 1 (segmentation + depth), same as process_harmonic_bayer: the any-clip mask's
271 // Euclidean distance transform gives each clipped pixel its depth delta(x); connected-component
272 // segmentation groups clipped pixels into regions, each carrying its reconstruction radius R = max delta.
273 const size_t npix = (size_t)width * height;
274 float *const restrict depth = dt_pixelpipe_cache_alloc_align_float(npix, pipe);
275 if(!depth)
276 {
277 err_code = 1;
278 goto error;
279 }
280 uint8_t *const restrict maskb = (uint8_t *)dt_pixelpipe_cache_alloc_align(npix, pipe);
281 if(!maskb)
282 {
284 err_code = 1;
285 goto error;
286 }
288 for(size_t i = 0; i < npix; i++)
289 {
290 // seed the distance transform: clipped pixels = +inf (to be filled with delta), valid = 0
291 depth[i] = (clipping_mask[i * 4 + 3] > 0.5f) ? (float)DT_DISTANCE_TRANSFORM_MAX : 0.f;
292 maskb[i] = (clipping_mask[i * 4 + 3] >= 1e-3f); // binary any-clip mask for the connected-component pass
293 }
294 dt_image_distance_transform(NULL, depth, width, height, 0.f,
295 DT_DISTANCE_TRANSFORM_NONE); // depth[] <- delta(x) (EDT)
296
297 const dt_iop_highlights_data_t *const data = (const dt_iop_highlights_data_t *)piece->data;
298 _hl_region_t *regions = NULL;
299 // 8-neighbour connected components; pad = ceil(1.25 * R) clamped to [8, 256] px around each region
300 const int nreg = _segment_clipped_regions(maskb, depth, width, height, 1.25f, 8, 256, &regions);
301
302
303 // FLOW steps 3-8 (per region): same CFA-agnostic per-region reconstruction as the Bayer path.
304 for(int region_index = 0; region_index < nreg; region_index++)
305 _region_guided_filter(interpolated, clipping_mask, depth, width, &regions[region_index], pipe,
306 data->solid_color, data->iterations, data->noise_level);
307
308 free(regions);
311
312 const float *remosaic_input = input;
313 float *input_corr = NULL;
314
315 if(knee_on)
316 {
317 input_corr = dt_pixelpipe_cache_alloc_align_float(size, pipe);
318
319 if(!IS_NULL_PTR(input_corr))
320 {
321 _hl_knee_apply_cfa(input, input_corr, width, height, 9u, roi_in, xtrans, knee_clipraw, knee);
322 remosaic_input = input_corr;
323 }
324 }
325
326 // FLOW: remosaic + composite (terminal node). Same rule as the Bayer path -- out = opacity*rec +
327 // (1 - opacity)*base with base = max(raw, rec) on a clipped X-Trans photosite (clip_is_floor = TRUE).
328 _remosaic_and_replace_xtrans(remosaic_input, input, interpolated, clipping_mask, output, normalization, clips,
329 TRUE, roi_in, xtrans, width, height);
330
331 if(!IS_NULL_PTR(input_corr)) dt_pixelpipe_cache_free_align(input_corr);
332
333error:;
334 dt_pixelpipe_cache_free_align(interpolated);
335 dt_pixelpipe_cache_free_align(clipping_mask);
337 (void)roi_out;
338 return err_code;
339}
340
341// ============================ OpenCL ============================
342
343#ifdef HAVE_OPENCL
344
345// Shared host middle of the harmonic reconstruction: knee estimation/correction, distance
346// transform, segmentation and the per-region rebuild -- everything between the gather and the
347// remosaic, CFA-agnostic. Used by the OpenCL hybrid driver after its GPU gather; the CPU
348// drivers keep their historical inline copies (same code, kept verbatim to avoid touching the
349// validated path -- unify when the CPU drivers next change).
350// On success *remosaic_input_out points to `input` or to a knee-corrected CFA copy
351// (*input_corr_out, caller frees with dt_pixelpipe_cache_free_align).
352//
353// MATHS/PIPELINE BRIDGE -- the CPU "middle" of the OpenCL pipe (article §"The OpenCL pipe": GPU gather
354// and GPU remosaic bracket a host middle). It runs the once-per-image steps BETWEEN the gather and the
355// remosaic on host planes the GPU already produced: step 2 knee application (_hl_knee_apply_interpolated),
356// step 1b depth + segmentation (distance transform + _segment_clipped_regions), then steps 3-8 per region
357// via the CPU _region_guided_filter. Identical code to process_harmonic_bayer/xtrans' middle (kept as a
358// separate copy to avoid touching the validated CPU drivers); it is the fallback the device middle
359// (_harmonic_reconstruct_cl) drops to when the GPU middle cannot run.
362 const dt_dev_pixelpipe_iop_t *piece, const float *const restrict input,
363 float *const restrict interpolated, float *const restrict clipping_mask,
364 const dt_iop_roi_t *const roi_in, const dt_aligned_pixel_t clips,
365 const dt_aligned_pixel_t normalization, const float **remosaic_input_out,
366 float **input_corr_out, const _hl_knee_curve_t knee_pre[3])
367{
368 // _hl_knee_apply_cfa below reads FC(row, col, filters) with tile-local row/col, so filters
369 // must be pre-shifted for roi_in's crop position (mirrors process_harmonic_bayer).
370 const uint32_t filters = dt_dev_get_roi_filters(piece, roi_in);
371 const uint8_t(*const xtrans)[6] = (filters == 9u) ? (const uint8_t(*const)[6])piece->dsc_in.xtrans : NULL;
372 const size_t width = roi_in->width;
373 const size_t height = roi_in->height;
374 const size_t size = width * height;
375
376 *remosaic_input_out = input;
377 *input_corr_out = NULL;
378
379 // the knee was estimated by the caller BEFORE the gather (its engagement drives the band
380 // override of the detection); reuse the curves here
381 _hl_knee_curve_t knee[3];
382 memcpy(knee, knee_pre, sizeof(knee));
383 dt_aligned_pixel_t clipvaln = { 1.f, 1.f, 1.f, 1.f };
384 dt_aligned_pixel_t knee_clipraw = { 1.f, 1.f, 1.f, 1.f };
385 for(int c = 0; c < 3; c++)
386 {
387 clipvaln[c] = clips[c] / (DT_HL_KNEE_DET * fmaxf(normalization[c], 1e-9f));
388 knee_clipraw[c] = clips[c] / DT_HL_KNEE_DET;
389 }
390 const int knee_on = knee[0].engaged || knee[1].engaged || knee[2].engaged;
391
392 if(knee_on) _hl_knee_apply_interpolated(interpolated, size, clipvaln, normalization, knee);
393
394 const size_t npix = size;
395 float *const restrict depth = dt_pixelpipe_cache_alloc_align_float(npix, pipe);
396 if(!depth) return 1;
397 uint8_t *const restrict maskb = (uint8_t *)dt_pixelpipe_cache_alloc_align(npix, pipe);
398 if(!maskb)
399 {
401 return 1;
402 }
404 for(size_t i = 0; i < npix; i++)
405 {
406 depth[i] = (clipping_mask[i * 4 + 3] > 0.5f) ? (float)DT_DISTANCE_TRANSFORM_MAX : 0.f;
407 maskb[i] = (clipping_mask[i * 4 + 3] >= 1e-3f);
408 }
410
411 const dt_iop_highlights_data_t *const data = (const dt_iop_highlights_data_t *)piece->data;
412 _hl_region_t *regions = NULL;
413 const int nreg = _segment_clipped_regions(maskb, depth, width, height, 1.25f, 8, 256, &regions);
414
415 // FLOW steps 3-8 (per region): CPU per-region reconstruction, same call as the CPU drivers.
416 for(int region_index = 0; region_index < nreg; region_index++)
417 _region_guided_filter(interpolated, clipping_mask, depth, width, &regions[region_index], pipe,
418 data->solid_color, data->iterations, data->noise_level);
419
420 free(regions);
423
424 if(knee_on)
425 {
426 float *input_corr = dt_pixelpipe_cache_alloc_align_float(size, pipe);
427 if(!IS_NULL_PTR(input_corr))
428 {
429 _hl_knee_apply_cfa(input, input_corr, width, height, filters, roi_in, xtrans, knee_clipraw, knee);
430 *remosaic_input_out = input_corr;
431 *input_corr_out = input_corr;
432 }
433 }
434
436 return 0;
437}
438
439#define HL_CL_RELEASE(mem_obj) \
440 do \
441 { \
442 dt_opencl_release_mem_object(mem_obj); \
443 (mem_obj) = NULL; \
444 } while(0)
445
446// Harmonic transposition on an OpenCL pipe: hybrid CPU-orchestrated, stage 1.
447// The reconstruction's heart is CPU by design (sparse Cholesky factorizations, per-region
448// segmentation and orchestration), so the module roundtrips the single-channel raw through
449// the host and runs the exact CPU pipeline -- the output is BIT-IDENTICAL to the CPU path
450// by construction, and the pipe keeps its CL chain (up/downstream modules stay on the GPU,
451// no scheduler-level fallback). Stage 2 (planned) slots GPU kernels into this driver where
452// they pay: the gather/remosaic kernels already exist from the a-trous path, and the
453// region moment blurs + harmonic fills are the dominant remaining cost -- at the price of
454// bit-identity with the CPU, so it must go through the full validation protocol.
455// Stage-1 fallback: full host roundtrip running the exact CPU driver (bit-identical to the
456// CPU pipe by construction). Used when any GPU gather/remosaic step fails.
457//
458// PIPELINE BRIDGE (article §"The OpenCL pipe", the bit-identical fallback): the single-channel raw
459// crosses the bus ONCE to the host (dt_opencl_copy_device_to_host), the whole CPU driver
460// (process_harmonic_bayer/xtrans -- all 8 steps, gather through remosaic) runs on it, and the result is
461// written back once (dt_opencl_write_host_to_device). No GPU kernels of this mode are used, so the output
462// is byte-for-byte the CPU path; the surrounding pipe still stays on the GPU (no scheduler-level fallback).
463static cl_int _harmonic_cl_roundtrip(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
464 const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
465 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
466 const dt_aligned_pixel_t clips)
467{
468 const int devid = pipe->devid;
469 const uint32_t filters = piece->dsc_in.filters;
470 const size_t n_in = (size_t)roi_in->width * roi_in->height;
471 const size_t n_out = (size_t)roi_out->width * roi_out->height;
472
473 float *host_in = dt_pixelpipe_cache_alloc_align_float(n_in, pipe);
474 float *host_out = dt_pixelpipe_cache_alloc_align_float(n_out, pipe);
475 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
476
477 if(IS_NULL_PTR(host_in) || IS_NULL_PTR(host_out)) goto error;
478
479 // bus crossing 1/2: pull the raw mosaic down to the host
480 cl_err = dt_opencl_copy_device_to_host(devid, host_in, dev_in, roi_in->width, roi_in->height, sizeof(float));
481 if(cl_err != CL_SUCCESS) goto error;
482
483 // run the exact CPU driver (all 8 steps) on the host copy -> bit-identical to the CPU pipe
484 if((filters == 9u && process_harmonic_xtrans(self, pipe, piece, host_in, host_out, roi_in, roi_out, clips))
485 || (filters != 9u && process_harmonic_bayer(self, pipe, piece, host_in, host_out, roi_in, roi_out, clips)))
486 {
488 goto error;
489 }
490
491 // bus crossing 2/2: push the reconstructed CFA back to the device
492 cl_err
493 = dt_opencl_write_host_to_device(devid, host_out, dev_out, roi_out->width, roi_out->height, sizeof(float));
494
495error:
498 return cl_err;
499}
500
501// Stage 2: the gather (normalization reduce, bilinear interpolation + clip mask, mask
502// feathering) and the scatter (remosaic) run on the GPU with the kernels shared with the
503// a-trous path; the reconstruction middle (knee, segmentation, regions -- the solvers are CPU
504// by design) runs on downloaded host planes. Any GPU failure falls back to the stage-1
505// roundtrip above.
506
507// GPU middle of the harmonic pipeline: knee estimation + application, segmentation support
508// (byte masks down, depth up -- the EDT and flood fill stay on the host, exact), and the
509// per-region reconstruction, all on device buffers. Returns CL_SUCCESS when the whole middle
510// ran on the GPU; any failure leaves the caller to run the host middle instead. corr_out
511// receives the knee-corrected 1-channel CFA buffer when the knee engages (caller releases).
512//
513// PIPELINE BRIDGE (article §"The OpenCL pipe", the device-resident middle): the once-per-image steps
514// run on device buffers -- step 2 knee, step 1b segmentation SUPPORT (only the byte seed/member masks
515// come down and the depth plane goes up; the Euclidean distance transform and connected-component flood
516// fill stay on the host because they are inherently serial), then steps 3-8 per region. The per-region
517// loop ROUTES each region by size: big regions stay device-resident (_region_guided_filter_cl); regions
518// at or below DT_HL_CL_CPU_REGION_PX cross the bus once and take the CPU path (_region_cpu_offload_cl),
519// because a device region pays ~1000 kernel launches that a small hole cannot amortize. Only byte masks,
520// the depth plane and small reduction partials ever cross the bus.
521static cl_int _harmonic_reconstruct_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
522 const dt_dev_pixelpipe_iop_t *piece, cl_mem raw_buf, cl_mem interp_buf,
523 cl_mem mask_buf, cl_mem *corr_out, const dt_iop_roi_t *const roi_in,
524 const dt_aligned_pixel_t clips, const dt_aligned_pixel_t norm,
525 cl_mem dev_xtrans, const _hl_knee_curve_t knee_pre[3])
526{
528 const dt_iop_highlights_data_t *const data = (const dt_iop_highlights_data_t *)piece->data;
529 const int devid = pipe->devid;
530 const uint32_t filters = piece->dsc_in.filters;
531 const int width = roi_in->width;
532 const int height = roi_in->height;
533 const size_t npix = (size_t)width * height;
534 const int is_xtrans = (filters == 9u);
535 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
536
537 if(data->noise_level > 0.f) return cl_err; // grain epilogue is not ported
538
539 size_t sizes[3] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
540
541 cl_mem seed = NULL;
542 cl_mem member = NULL;
543 cl_mem depth_dev = NULL;
544 cl_mem corr = NULL;
545 uint8_t *h_seed = NULL;
546 uint8_t *h_member = NULL;
547 float *depth = NULL;
548 _hl_region_t *regions = NULL;
549 *corr_out = NULL;
550
551 {
552 // the knee was estimated by the caller BEFORE the gather (its engagement drives the band
553 // override of the detection); reuse the curves here
554 _hl_knee_curve_t knee[3];
555 memcpy(knee, knee_pre, sizeof(knee));
556 dt_aligned_pixel_t clipvaln = { 1.f, 1.f, 1.f, 1.f };
557 dt_aligned_pixel_t knee_clipraw = { 1.f, 1.f, 1.f, 1.f };
558 for(int c = 0; c < 3; c++)
559 {
560 clipvaln[c] = clips[c] / (DT_HL_KNEE_DET * fmaxf(norm[c], 1e-9f));
561 knee_clipraw[c] = clips[c] / DT_HL_KNEE_DET;
562 }
563 const int knee_on = knee[0].engaged || knee[1].engaged || knee[2].engaged;
564
565 if(knee_on)
566 {
567 // band correction on the interpolated RGBN planes (reconstruction fits unbiased data)
568 float lift[3 * DT_HL_KNEE_BINS];
569 for(int c = 0; c < 3; c++) memcpy(lift + c * DT_HL_KNEE_BINS, knee[c].lift, sizeof(knee[c].lift));
570 cl_mem dev_lift = _sp_cl_upload(devid, lift, sizeof(lift));
571 if(!dev_lift)
572 {
574 goto out;
575 }
576 const int kernel = global_data->kernel_hl_knee_apply_interp;
577 const cl_float4 clip4 = { { clipvaln[0], clipvaln[1], clipvaln[2], 1.f } };
578 const cl_float4 wb4 = { { norm[0], norm[1], norm[2], 1.f } };
579 const cl_int4 engaged_flags = { { knee[0].engaged, knee[1].engaged, knee[2].engaged, 0 } };
580 const float knee_lo = DT_HL_KNEE_LO;
581 const float knee_det = DT_HL_KNEE_DET;
582 const int bins = DT_HL_KNEE_BINS;
583 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &interp_buf);
584 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), &width);
585 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &height);
586 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_float4), &clip4);
587 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_float4), &wb4);
588 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(cl_mem), &dev_lift);
589 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(cl_int4), &engaged_flags);
590 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), &knee_lo);
591 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(float), &knee_det);
592 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(int), &bins);
593 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
595 if(cl_err != CL_SUCCESS) goto out;
596
597 // corrected CFA copy for the remosaic composition
598 corr = dt_opencl_alloc_device_buffer(devid, sizeof(float) * npix);
599 if(!corr)
600 {
602 goto out;
603 }
604 cl_err = _hl_knee_apply_cfa_cl(devid, global_data, raw_buf, corr, width, height, filters, roi_in, dev_xtrans,
605 is_xtrans, knee_clipraw, knee);
606 if(cl_err != CL_SUCCESS) goto out;
607 }
608 }
609
610 // ---- segmentation support: byte masks down, exact host EDT + flood fill, depth up ----
611 seed = dt_opencl_alloc_device_buffer(devid, npix);
612 member = dt_opencl_alloc_device_buffer(devid, npix);
613 h_seed = (uint8_t *)dt_pixelpipe_cache_alloc_align(npix, pipe);
614 h_member = (uint8_t *)dt_pixelpipe_cache_alloc_align(npix, pipe);
615 depth = dt_pixelpipe_cache_alloc_align_float(npix, pipe);
616 if(!seed || !member || !h_seed || !h_member || !depth)
617 {
619 goto out;
620 }
621 {
622 const int kernel = global_data->kernel_hl_mask_pack;
623 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &mask_buf);
624 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &seed);
625 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &member);
626 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &width);
627 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), &height);
628 cl_err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
629 if(cl_err != CL_SUCCESS) goto out;
630 }
631 cl_err = dt_opencl_read_buffer_from_device(devid, h_seed, seed, 0, npix, CL_TRUE);
632 if(cl_err == CL_SUCCESS) cl_err = dt_opencl_read_buffer_from_device(devid, h_member, member, 0, npix, CL_TRUE);
633 if(cl_err != CL_SUCCESS) goto out;
634
636 for(size_t i = 0; i < npix; i++) depth[i] = h_seed[i] ? (float)DT_DISTANCE_TRANSFORM_MAX : 0.f;
638
639 const int nreg = _segment_clipped_regions(h_member, depth, width, height, 1.25f, 8, 256, &regions);
640
641 depth_dev = dt_opencl_alloc_device_buffer(devid, sizeof(float) * npix);
642 if(!depth_dev)
643 {
645 goto out;
646 }
647 cl_err = dt_opencl_write_buffer_to_device(devid, depth, depth_dev, 0, sizeof(float) * npix, CL_TRUE);
648 if(cl_err != CL_SUCCESS) goto out;
649
650 {
651 // ROUTING THRESHOLD (article §"The OpenCL pipe"): DT_HL_CL_CPU_REGION_PX (~1 Mpx padded window),
652 // env-overridable for tuning. It is the padded-window pixel count above which the ~1000-launch GPU
653 // per-region path pays off; below it, one bus crossing to the CPU is cheaper.
654 size_t cpu_px = DT_HL_CL_CPU_REGION_PX;
655 const char *override_env = getenv("HL_CL_CPU_PX");
656 if(override_env) cpu_px = (size_t)strtoull(override_env, NULL, 10);
657 for(int region_index = 0; region_index < nreg && cl_err == CL_SUCCESS; region_index++)
658 {
659 // routing key = the PADDED read-window area (rx0..rx1 x ry0..ry1), the same window either path
660 // reconstructs -- not the bare clipped bbox
661 const size_t region_px = (size_t)(regions[region_index].rx1 - regions[region_index].rx0 + 1)
662 * (size_t)(regions[region_index].ry1 - regions[region_index].ry0 + 1);
663 if(region_px <= cpu_px)
664 {
665 // small region: cross the bus once and reconstruct on the CPU (bit-identical to the CPU driver)
666 cl_err = _region_cpu_offload_cl(devid, global_data, interp_buf, mask_buf, depth_dev, width,
667 &regions[region_index], pipe, data->solid_color, data->iterations,
668 data->noise_level);
669 }
670 else
671 // big region: stay device-resident for the whole rebuild
672 cl_err = _region_guided_filter_cl(devid, global_data, interp_buf, mask_buf, depth_dev, width,
673 &regions[region_index], pipe, data->solid_color);
674 }
675 }
676
677 if(cl_err == CL_SUCCESS)
678 {
679 dt_opencl_finish(devid);
680 }
681
682out:
683 _hl_gauss_cache_flush(); // the CPU-offloaded regions run _region_blur on this thread
690 free(regions);
691 if(cl_err == CL_SUCCESS)
692 *corr_out = corr;
693 else
695 return cl_err;
696}
697
699 const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
700 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
701 const dt_aligned_pixel_t clips)
702{
703 _sp_chol_cl_selftest(pipe->devid, self->global_data, pipe);
704 _region_blur_cl_selftest(pipe->devid, pipe);
707 _cf_stage_cl_selftest(pipe->devid, self->global_data, pipe);
708 _hf_stage_cl_selftest(pipe->devid, self->global_data, pipe);
711 _knee_cl_selftest(pipe->devid, self->global_data, pipe);
712 _aniso_stage_cl_selftest(pipe->devid, self->global_data, pipe);
714
716 const int devid = pipe->devid;
717 // _hl_knee_estimate_cl and the hl_knee_* kernels are self-correcting: they take this raw
718 // filters value PLUS roi_in->x/y as separate kernel args and add them themselves. The shared
719 // interpolate_and_mask/remosaic_and_replace Bayer kernels (and the host-side
720 // _compute_laplacian_normalization call below) have no roi offset arg at all -- they need
721 // filters pre-shifted for roi_in's crop position instead (mirrors the CPU driver's fix).
722 const uint32_t filters = piece->dsc_in.filters;
723 const uint32_t filters_shifted = dt_dev_get_roi_filters(piece, roi_in);
724 const int width = roi_in->width;
725 const int height = roi_in->height;
726 const size_t npix = (size_t)width * height;
727 const int is_xtrans = (filters == 9u);
728
729 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
730
731 cl_mem interpolated = NULL;
732 cl_mem clipping_mask = NULL;
733 cl_mem temp = NULL;
734 cl_mem clips_cl = NULL;
735 cl_mem normalization_final = NULL;
736 cl_mem dev_xtrans = NULL;
737 cl_mem lookup_cl = NULL;
738 cl_mem corr_cl = NULL;
739 cl_mem det_clips_cl = NULL;
740 float *h_interp = NULL;
741 float *h_mask = NULL;
742 float *h_raw = NULL;
743 float *input_corr = NULL;
744 const float *remosaic_input = NULL;
745 cl_int cl_err = DT_OPENCL_DEFAULT_ERROR;
746
747 interpolated = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
748 clipping_mask = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
749 temp = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
750 clips_cl = dt_opencl_copy_host_to_device_constant(devid, 4 * sizeof(float), (float *)clips);
751 if(IS_NULL_PTR(interpolated) || IS_NULL_PTR(clipping_mask) || IS_NULL_PTR(temp) || IS_NULL_PTR(clips_cl))
752 goto fallback;
753
754 if(is_xtrans)
755 {
756 dev_xtrans = dt_opencl_copy_host_to_device_constant(devid, sizeof(piece->dsc_in.xtrans),
757 (void *)piece->dsc_in.xtrans);
758 int32_t lookup[6][6][32] = { { { 0 } } };
759 _build_xtrans_bilinear_lookup(lookup, roi_in, (const uint8_t(*const)[6])piece->dsc_in.xtrans);
760 lookup_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(lookup), lookup);
761 if(IS_NULL_PTR(dev_xtrans) || IS_NULL_PTR(lookup_cl)) goto fallback;
762 }
763
764 // ---- per-channel normalization: computed on the HOST with the exact CPU function ----
765 // The raw is needed on the host anyway (knee estimation reads the mosaic), and the GPU
766 // max-reduce kernels are not bit-faithful to _compute_laplacian_normalization: the tiny
767 // normalization difference shifted the clip mask by a few hundred pixels and the whole
768 // reconstruction with it. Downloading first keeps the mask identical to the CPU path.
769 h_raw = dt_pixelpipe_cache_alloc_align_float(npix, pipe);
770 if(IS_NULL_PTR(h_raw)) goto fallback;
771 cl_err = dt_opencl_copy_device_to_host(devid, h_raw, dev_in, width, height, sizeof(float));
772 if(cl_err != CL_SUCCESS) goto fallback;
773
774 dt_aligned_pixel_t norm_host = { 1.f, 1.f, 1.f, 1.f };
775 _compute_laplacian_normalization(h_raw, roi_in, filters_shifted,
776 is_xtrans ? (const uint8_t(*const)[6])piece->dsc_in.xtrans : NULL, norm_host);
777 normalization_final = dt_opencl_copy_host_to_device_constant(devid, 4 * sizeof(float), norm_host);
778 if(IS_NULL_PTR(normalization_final)) goto fallback;
779
780 // ---- rolloff estimation FIRST (raw-based): its per-channel engagement drives the band
781 // override of the detection thresholds, exactly like the CPU drivers ----
782 _hl_knee_curve_t knee[3];
783 {
784 dt_aligned_pixel_t knee_clipraw = { 1.f, 1.f, 1.f, 1.f };
785 for(int c = 0; c < 3; c++) knee_clipraw[c] = clips[c] / DT_HL_KNEE_DET;
786
787 // the knee kernels read the raw as a BUFFER; dev_in is an image2d -> copy first
788 cl_mem knee_raw = dt_opencl_alloc_device_buffer(devid, sizeof(float) * npix);
789 if(IS_NULL_PTR(knee_raw)) goto fallback;
790 size_t korigin[3] = { 0, 0, 0 };
791 size_t kregion[3] = { (size_t)width, (size_t)height, 1 };
792 cl_err = dt_opencl_enqueue_copy_image_to_buffer(devid, dev_in, knee_raw, korigin, kregion, 0);
793 if(cl_err != CL_SUCCESS)
794 {
796 goto fallback;
797 }
798
799 cl_err = _hl_knee_estimate_cl(devid, global_data, knee_raw, width, height, filters, roi_in, dev_xtrans,
800 is_xtrans, knee_clipraw, knee, pipe);
802 if(cl_err != CL_SUCCESS) goto fallback;
803 }
804
805 dt_aligned_pixel_t eff_clips;
806 for_four_channels(c) eff_clips[c] = clips[c];
807 for(int c = 0; c < 3; c++)
808 if(knee[c].engaged) eff_clips[c] = clips[c] * DT_HL_BAND_OVR;
809 det_clips_cl = dt_opencl_copy_host_to_device_constant(devid, 4 * sizeof(float), eff_clips);
810 if(IS_NULL_PTR(det_clips_cl)) goto fallback;
811
812 // ---- gather: bilinear interpolation + clip mask, then 5x5 feathering ----
813 if(is_xtrans)
814 {
815 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask_xtrans, 0, sizeof(cl_mem),
816 &dev_in);
817 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask_xtrans, 1, sizeof(cl_mem),
818 &interpolated);
819 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask_xtrans, 2, sizeof(cl_mem),
820 &clipping_mask);
821 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask_xtrans, 3, sizeof(cl_mem),
822 &det_clips_cl);
823 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask_xtrans, 4, sizeof(cl_mem),
824 &normalization_final);
826 &width);
828 &height);
830 &roi_in->x);
832 &roi_in->y);
833 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask_xtrans, 9, sizeof(cl_mem),
834 &dev_xtrans);
835 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask_xtrans, 10, sizeof(cl_mem),
836 &lookup_cl);
838 }
839 else
840 {
841 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask, 0, sizeof(cl_mem), &dev_in);
842 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask, 1, sizeof(cl_mem),
843 &interpolated);
844 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask, 2, sizeof(cl_mem),
845 &clipping_mask);
846 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask, 3, sizeof(cl_mem),
847 &det_clips_cl);
848 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask, 4, sizeof(cl_mem),
849 &normalization_final);
850 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask, 5, sizeof(int),
851 &filters_shifted);
852 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask, 6, sizeof(int),
853 &roi_out->width);
854 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_bilinear_and_mask, 7, sizeof(int),
855 &roi_out->height);
856 cl_err = dt_opencl_enqueue_kernel_2d(devid, global_data->kernel_highlights_bilinear_and_mask, sizes);
857 }
858 if(cl_err != CL_SUCCESS) goto fallback;
859
860 // ---- GPU middle first: knee + segmentation support + per-region reconstruction on device
861 // buffers; only byte masks, the depth plane and reduction partials cross the bus ----
862 {
863 cl_mem raw_buf = dt_opencl_alloc_device_buffer(devid, sizeof(float) * npix);
864 cl_mem interp_buf = dt_opencl_alloc_device_buffer(devid, sizeof(float) * npix * 4);
865 cl_mem mask_buf = dt_opencl_alloc_device_buffer(devid, sizeof(float) * npix * 4);
866 cl_mem corr_buf = NULL;
867 size_t origin[3] = { 0, 0, 0 };
868 size_t region1[3] = { (size_t)width, (size_t)height, 1 };
869 cl_int gpu_err = (raw_buf && interp_buf && mask_buf) ? CL_SUCCESS : DT_OPENCL_DEFAULT_ERROR;
870 if(gpu_err == CL_SUCCESS)
871 gpu_err = dt_opencl_enqueue_copy_image_to_buffer(devid, dev_in, raw_buf, origin, region1, 0);
872 if(gpu_err == CL_SUCCESS)
873 gpu_err = dt_opencl_enqueue_copy_image_to_buffer(devid, interpolated, interp_buf, origin, region1, 0);
874 if(gpu_err == CL_SUCCESS)
875 gpu_err = dt_opencl_enqueue_copy_image_to_buffer(devid, clipping_mask, mask_buf, origin, region1, 0);
876 int staged = 0; // 1 = the three images below were released and must be re-created
877 if(gpu_err == CL_SUCCESS)
878 {
879 // the middle works on the buffers: release the three full-image images (~1.7 GB on a
880 // 36 Mpx raw) so the region planes and stage temporaries fit in vRAM; the two that are
881 // consumed downstream are re-created from the buffers right after. The HL_MIDDLE_AB
882 // diagnostic keeps them alive instead: its reference run needs the PRISTINE planes.
883 dt_opencl_finish(devid); // the async image->buffer copies must land first
884 if(!getenv("HL_MIDDLE_AB"))
885 {
886 HL_CL_RELEASE(temp);
887 HL_CL_RELEASE(interpolated);
888 HL_CL_RELEASE(clipping_mask);
889 staged = 1;
890 }
891 // preference 1: the device-resident middle (steps 2 + 1b + 3-8 on device, small regions offloaded)
892 gpu_err = _harmonic_reconstruct_cl(self, pipe, piece, raw_buf, interp_buf, mask_buf, &corr_buf, roi_in,
893 clips, norm_host, dev_xtrans, knee);
894 }
895 // materialize the knee-corrected mosaic BEFORE restoring the working images: a failure
896 // here must take the same pristine re-gather road as a mid-middle failure. Falling
897 // through with the reconstruction already copied back would hand the host middle
898 // knee-lifted, partially reconstructed planes -- the knee would be applied twice and
899 // the regions re-solved on reconstructed anchors, silently.
900 if(gpu_err == CL_SUCCESS && corr_buf)
901 {
902 corr_cl = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float));
903 if(corr_cl)
904 gpu_err = dt_opencl_enqueue_copy_buffer_to_image(devid, corr_buf, corr_cl, 0, origin, region1);
905 else
906 gpu_err = DT_OPENCL_DEFAULT_ERROR;
907 if(gpu_err != CL_SUCCESS) HL_CL_RELEASE(corr_cl);
908 }
909 // HL_MIDDLE_AB=1 (diagnostic): run the host middle on the pristine planes (kept alive
910 // above) and print its divergence from the device middle; the device result still ships
911 if(gpu_err == CL_SUCCESS && getenv("HL_MIDDLE_AB"))
912 {
913 float *gpu_interp = dt_pixelpipe_cache_alloc_align_float(npix * 4, pipe);
914 float *host_interp = dt_pixelpipe_cache_alloc_align_float(npix * 4, pipe);
915 float *host_mask = dt_pixelpipe_cache_alloc_align_float(npix * 4, pipe);
916 float *host_raw = dt_pixelpipe_cache_alloc_align_float(npix, pipe);
917 if(gpu_interp && host_interp && host_mask && host_raw
918 && dt_opencl_read_buffer_from_device(devid, gpu_interp, interp_buf, 0, sizeof(float) * npix * 4, CL_TRUE)
919 == CL_SUCCESS
920 && dt_opencl_copy_device_to_host(devid, host_interp, interpolated, width, height, sizeof(float) * 4)
921 == CL_SUCCESS
922 && dt_opencl_copy_device_to_host(devid, host_mask, clipping_mask, width, height, sizeof(float) * 4)
923 == CL_SUCCESS
924 && dt_opencl_copy_device_to_host(devid, host_raw, dev_in, width, height, sizeof(float)) == CL_SUCCESS)
925 {
926 const float *remosaic_ptr = NULL;
927 float *input_corr_ab = NULL;
928 if(!_harmonic_reconstruct_host(self, pipe, piece, host_raw, host_interp, host_mask, roi_in, clips,
929 norm_host, &remosaic_ptr, &input_corr_ab, knee))
930 {
931 float max_diff = 0.f;
932 double sum_diff = 0.0;
933 size_t arg_index = 0;
934 for(size_t i = 0; i < npix * 4; i++)
935 {
936 const float diff = fabsf(gpu_interp[i] - host_interp[i]);
937 if(diff > max_diff)
938 {
939 max_diff = diff;
940 arg_index = i;
941 }
942 sum_diff += (double)diff;
943 }
944 fprintf(stderr, "[hl middle AB] max=%.3e mean=%.3e at px=(%llu,%llu) c=%llu gpu=%f cpu=%f\n", max_diff,
945 sum_diff / (double)(npix * 4), (unsigned long long)((arg_index / 4) % width),
946 (unsigned long long)((arg_index / 4) / width), (unsigned long long)(arg_index % 4),
947 gpu_interp[arg_index], host_interp[arg_index]);
948 }
949 dt_pixelpipe_cache_free_align(input_corr_ab);
950 }
955 }
956 // restore the images for the downstream blend/remosaic -- ONLY when they were released:
957 // an early staging failure leaves them alive and pristine, and reallocating over the
958 // live handles would leak them (~1.7 GB) while doubling vRAM demand under the very
959 // pressure that made staging fail. On success, interpolated carries
960 // the reconstruction and the mask is untouched (buffer copy-back). On FAILURE the middle
961 // may have partially scattered and knee-lifted interp_buf, so the host fallback must NOT
962 // reuse it: re-run the interpolation + mask blur from the still-alive inputs instead.
963 if(staged)
964 {
965 interpolated = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
966 clipping_mask = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
967 cl_int restore_err = (interpolated && clipping_mask) ? CL_SUCCESS : DT_OPENCL_DEFAULT_ERROR;
968 if(restore_err == CL_SUCCESS && gpu_err == CL_SUCCESS)
969 {
970 restore_err = dt_opencl_enqueue_copy_buffer_to_image(devid, interp_buf, interpolated, 0, origin, region1);
971 if(restore_err == CL_SUCCESS)
972 restore_err = dt_opencl_enqueue_copy_buffer_to_image(devid, mask_buf, clipping_mask, 0, origin, region1);
973 }
974 else if(restore_err == CL_SUCCESS)
975 {
976 temp = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float) * 4);
977 if(!temp) restore_err = DT_OPENCL_DEFAULT_ERROR;
978 if(restore_err == CL_SUCCESS)
979 {
980 if(is_xtrans)
981 {
982 const int kernel = global_data->kernel_highlights_bilinear_and_mask_xtrans;
983 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &dev_in);
984 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &interpolated);
985 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &temp);
986 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &clips_cl);
987 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &normalization_final);
988 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &roi_out->width);
989 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &roi_out->height);
990 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &roi_in->x);
991 dt_opencl_set_kernel_arg(devid, kernel, 8, sizeof(int), &roi_in->y);
992 dt_opencl_set_kernel_arg(devid, kernel, 9, sizeof(cl_mem), &dev_xtrans);
993 dt_opencl_set_kernel_arg(devid, kernel, 10, sizeof(cl_mem), &lookup_cl);
994 restore_err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
995 }
996 else
997 {
998 const int kernel = global_data->kernel_highlights_bilinear_and_mask;
999 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &dev_in);
1000 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &interpolated);
1001 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), &temp);
1002 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), &clips_cl);
1003 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), &normalization_final);
1004 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), &filters_shifted);
1005 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(int), &roi_out->width);
1006 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(int), &roi_out->height);
1007 restore_err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
1008 }
1009 }
1010 if(restore_err == CL_SUCCESS)
1011 {
1012 const int kernel = global_data->kernel_highlights_box_blur;
1013 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), &temp);
1014 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), &clipping_mask);
1015 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), &roi_out->width);
1016 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), &roi_out->height);
1017 restore_err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
1018 }
1019 }
1020 if(restore_err != CL_SUCCESS)
1021 {
1023 dt_opencl_release_mem_object(interp_buf);
1026 cl_err = restore_err;
1027 goto fallback;
1028 }
1029 }
1030 else if(gpu_err == CL_SUCCESS)
1031 {
1032 // diagnostic mode kept the images alive: publish the device result into them now
1033 cl_int restore_err
1034 = dt_opencl_enqueue_copy_buffer_to_image(devid, interp_buf, interpolated, 0, origin, region1);
1035 if(restore_err == CL_SUCCESS)
1036 restore_err = dt_opencl_enqueue_copy_buffer_to_image(devid, mask_buf, clipping_mask, 0, origin, region1);
1037 if(restore_err != CL_SUCCESS)
1038 {
1040 dt_opencl_release_mem_object(interp_buf);
1043 cl_err = restore_err;
1044 goto fallback;
1045 }
1046 }
1048 dt_opencl_release_mem_object(interp_buf);
1051 if(gpu_err == CL_SUCCESS) goto remosaic; // device middle succeeded -> straight to the GPU remosaic
1052 // GPU middle unavailable (fp64 device, grain requested, oversized hole...): host middle below
1053 HL_CL_RELEASE(corr_cl);
1054 dt_print(DT_DEBUG_OPENCL, "[opencl_highlights] harmonic GPU middle failed (%i), using the host middle\n",
1055 gpu_err);
1056 }
1057
1058 // ---- host-middle path (preference 2): the GPU gather succeeded but the device middle could not run.
1059 // Pull the gathered working planes down (the raw h_raw is already resident) so the CPU middle can
1060 // run the same steps 2 + 1b + 3-8 it would on a CPU pipe ----
1061 h_interp = dt_pixelpipe_cache_alloc_align_float(npix * 4, pipe);
1062 h_mask = dt_pixelpipe_cache_alloc_align_float(npix * 4, pipe);
1063 if(IS_NULL_PTR(h_interp) || IS_NULL_PTR(h_mask)) goto fallback;
1064
1065 cl_err = dt_opencl_copy_device_to_host(devid, h_interp, interpolated, width, height, sizeof(float) * 4);
1066 if(cl_err != CL_SUCCESS) goto fallback;
1067 cl_err = dt_opencl_copy_device_to_host(devid, h_mask, clipping_mask, width, height, sizeof(float) * 4);
1068 if(cl_err != CL_SUCCESS) goto fallback;
1069
1070 // ---- CPU middle: knee, segmentation, per-region reconstruction ----
1071 if(_harmonic_reconstruct_host(self, pipe, piece, h_raw, h_interp, h_mask, roi_in, clips, norm_host,
1072 &remosaic_input, &input_corr, knee))
1073 goto fallback;
1074
1075 // ---- upload the reconstructed planes back to the device so the GPU remosaic below closes the pipe ----
1076 cl_err = dt_opencl_write_host_to_device(devid, h_interp, interpolated, width, height, sizeof(float) * 4);
1077 if(cl_err != CL_SUCCESS) goto fallback;
1078
1079remosaic:;
1080 // FLOW: GPU remosaic + composite (the pipe's terminal node, reached from either middle). Pick the base
1081 // CFA the composite reads on unmasked sites: the knee-corrected copy (corr_cl / input_corr) when the
1082 // knee engaged, else the pristine dev_in -- so valid pixels match the reconstruction's basis.
1083 cl_mem remosaic_in_cl = dev_in;
1084 if(corr_cl)
1085 remosaic_in_cl = corr_cl;
1086 else if(remosaic_input != h_raw && remosaic_input != NULL)
1087 {
1088 corr_cl = dt_opencl_alloc_device(devid, sizes[0], sizes[1], sizeof(float));
1089 if(IS_NULL_PTR(corr_cl)) goto fallback;
1090 cl_err = dt_opencl_write_host_to_device(devid, input_corr, corr_cl, width, height, sizeof(float));
1091 if(cl_err != CL_SUCCESS) goto fallback;
1092 remosaic_in_cl = corr_cl;
1093 }
1094
1095 if(is_xtrans)
1096 {
1097 const int clip_floor_on = TRUE; // clipped raw values are floors, never blend targets
1098 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace_xtrans, 0, sizeof(cl_mem),
1099 &remosaic_in_cl);
1100 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace_xtrans, 1, sizeof(cl_mem),
1101 &dev_in);
1102 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace_xtrans, 2, sizeof(cl_mem),
1103 &interpolated);
1104 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace_xtrans, 3, sizeof(cl_mem),
1105 &clipping_mask);
1106 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace_xtrans, 4, sizeof(cl_mem),
1107 &dev_out);
1108 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace_xtrans, 5, sizeof(cl_mem),
1109 &normalization_final);
1110 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace_xtrans, 6, sizeof(cl_mem),
1111 &clips_cl);
1113 &clip_floor_on);
1115 &width);
1117 &height);
1119 &roi_in->x);
1121 &roi_in->y);
1122 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace_xtrans, 12, sizeof(cl_mem),
1123 &dev_xtrans);
1125 }
1126 else
1127 {
1128 const int clip_floor_on = TRUE; // clipped raw values are floors, never blend targets
1129 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace, 0, sizeof(cl_mem),
1130 &remosaic_in_cl);
1131 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace, 1, sizeof(cl_mem),
1132 &dev_in);
1133 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace, 2, sizeof(cl_mem),
1134 &interpolated);
1135 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace, 3, sizeof(cl_mem),
1136 &clipping_mask);
1137 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace, 4, sizeof(cl_mem),
1138 &dev_out);
1139 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace, 5, sizeof(cl_mem),
1140 &normalization_final);
1141 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace, 6, sizeof(cl_mem),
1142 &clips_cl);
1143 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace, 7, sizeof(int),
1144 &clip_floor_on);
1145 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace, 8, sizeof(int),
1146 &filters_shifted);
1147 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace, 9, sizeof(int), &width);
1148 dt_opencl_set_kernel_arg(devid, global_data->kernel_highlights_remosaic_and_replace, 10, sizeof(int), &height);
1149 cl_err = dt_opencl_enqueue_kernel_2d(devid, global_data->kernel_highlights_remosaic_and_replace, sizes);
1150 }
1151 if(cl_err != CL_SUCCESS) goto fallback;
1152
1153 // success: release and return
1154 HL_CL_RELEASE(clips_cl);
1155 HL_CL_RELEASE(det_clips_cl);
1156 HL_CL_RELEASE(normalization_final);
1157 HL_CL_RELEASE(interpolated);
1158 HL_CL_RELEASE(clipping_mask);
1159 HL_CL_RELEASE(temp);
1160 HL_CL_RELEASE(dev_xtrans);
1161 HL_CL_RELEASE(lookup_cl);
1162 HL_CL_RELEASE(corr_cl);
1167 return CL_SUCCESS;
1168
1169fallback:
1171 "[opencl_highlights] harmonic GPU gather failed (%i), falling back to the host roundtrip\n", cl_err);
1172 HL_CL_RELEASE(clips_cl);
1173 HL_CL_RELEASE(det_clips_cl);
1174 HL_CL_RELEASE(normalization_final);
1175 HL_CL_RELEASE(interpolated);
1176 HL_CL_RELEASE(clipping_mask);
1177 HL_CL_RELEASE(temp);
1178 HL_CL_RELEASE(dev_xtrans);
1179 HL_CL_RELEASE(lookup_cl);
1180 HL_CL_RELEASE(corr_cl);
1185 // preference 3 (last resort): a GPU gather/remosaic step failed -> the bit-identical host roundtrip.
1186 return _harmonic_cl_roundtrip(self, pipe, piece, dev_in, dev_out, roi_in, roi_out, clips);
1187}
1188#endif // HAVE_OPENCL
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
void _hl_gauss_cache_flush(void)
Definition blur.c:49
static float lookup(read_only image2d_t lut, const float x)
const dt_colormatrix_t dt_aligned_pixel_t out
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
@ DT_DEBUG_OPENCL
Definition darktable.h:744
#define dt_pixelpipe_cache_alloc_align(size, pipe)
Definition darktable.h:449
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
#define dt_pixelpipe_cache_alloc_align_float(pixels, pipe)
Definition darktable.h:454
#define __DT_CLONE_TARGETS__
Definition darktable.h:379
#define for_four_channels(_var,...)
Definition darktable.h:686
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:293
@ DT_DISTANCE_TRANSFORM_NONE
float dt_image_distance_transform(float *const restrict src, float *const restrict out, const size_t width, const size_t height, const float clip, const dt_distance_transform_t mode)
#define DT_DISTANCE_TRANSFORM_MAX
__DT_CLONE_TARGETS__ void _compute_laplacian_normalization(const float *const restrict input, const dt_iop_roi_t *const roi_in, const uint32_t filters, const uint8_t(*const xtrans)[6], dt_aligned_pixel_t normalization)
Definition gather.c:222
__DT_CLONE_TARGETS__ void _remosaic_and_replace_xtrans(const float *const restrict input, const float *const restrict input_raw, const float *const restrict interpolated, const float *const restrict clipping_mask, float *const restrict output, const dt_aligned_pixel_t white_balance, const dt_aligned_pixel_t clips, const int clip_is_floor, const dt_iop_roi_t *const roi_in, const uint8_t(*const xtrans)[6], const size_t width, const size_t height)
Definition gather.c:436
__DT_CLONE_TARGETS__ void _interpolate_and_mask(const float *const restrict input, float *const restrict interpolated, float *const restrict clipping_mask, const dt_aligned_pixel_t clips_in, const dt_aligned_pixel_t det_scale, const dt_aligned_pixel_t white_balance, const uint32_t filters, const size_t width, const size_t height)
Definition gather.c:66
__DT_CLONE_TARGETS__ void _remosaic_and_replace(const float *const restrict input, const float *const restrict input_raw, const float *const restrict interpolated, const float *const restrict clipping_mask, float *const restrict output, const dt_aligned_pixel_t white_balance, const dt_aligned_pixel_t clips, const int clip_is_floor, const uint32_t filters, const size_t width, const size_t height)
Definition gather.c:405
__DT_CLONE_TARGETS__ void _interpolate_and_mask_xtrans(const float *const restrict input, float *const restrict interpolated, float *const restrict clipping_mask, const dt_aligned_pixel_t clips, const dt_aligned_pixel_t white_balance, const dt_iop_roi_t *const roi_in, const int32_t lookup[6][6][32], const uint8_t(*const xtrans)[6], const size_t width, const size_t height)
Definition gather.c:298
__DT_CLONE_TARGETS__ void _build_xtrans_bilinear_lookup(int32_t lookup[6][6][32], const dt_iop_roi_t *const roi_in, const uint8_t(*const xtrans)[6])
Definition gather.c:258
uint32_t dt_dev_get_roi_filters(const dt_dev_pixelpipe_iop_t *const piece, const dt_iop_roi_t *const roi_in)
Definition imageop.c:136
void *const ovoid
static float kernel(const float *x, const float *y)
__DT_CLONE_TARGETS__ void _hl_knee_apply_interpolated(float *const restrict interpolated, const size_t npix, const dt_aligned_pixel_t clipvaln, const dt_aligned_pixel_t wb4, const _hl_knee_curve_t curves[3])
Definition knee.c:515
__DT_CLONE_TARGETS__ void _hl_knee_estimate(const float *const restrict input, const size_t width, const size_t height, const uint32_t filters, const dt_iop_roi_t *const roi_in, const uint8_t(*const xtrans)[6], const dt_aligned_pixel_t clipval_raw, _hl_knee_curve_t curves[3], const dt_dev_pixelpipe_t *pipe)
Definition knee.c:105
__DT_CLONE_TARGETS__ void _hl_knee_apply_cfa(const float *const restrict input, float *const restrict input_corr, const size_t width, const size_t height, const uint32_t filters, const dt_iop_roi_t *const roi_in, const uint8_t(*const xtrans)[6], const dt_aligned_pixel_t clipval_raw, const _hl_knee_curve_t curves[3])
Definition knee.c:553
cl_int _hl_knee_estimate_cl(const int devid, void *gd_void, cl_mem dev_in, const size_t width, const size_t height, const uint32_t filters, const dt_iop_roi_t *const roi_in, cl_mem dev_xtrans, const int is_xtrans, const dt_aligned_pixel_t clipval_raw, _hl_knee_curve_t curves[3], const dt_dev_pixelpipe_t *pipe)
Definition knee.c:584
cl_int _hl_knee_apply_cfa_cl(const int devid, void *gd_void, cl_mem dev_in, cl_mem dev_out, const size_t width, const size_t height, const uint32_t filters, const dt_iop_roi_t *const roi_in, cl_mem dev_xtrans, const int is_xtrans, const dt_aligned_pixel_t clipval_raw, const _hl_knee_curve_t curves[3])
Definition knee.c:917
size_t size
Definition mipmap_cache.c:3
float dt_aligned_pixel_t[4]
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2164
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:2580
int dt_opencl_enqueue_copy_buffer_to_image(const int devid, cl_mem src_buffer, cl_mem dst_image, size_t offset, size_t *origin, size_t *region)
Definition opencl.c:2312
int dt_opencl_copy_device_to_host(const int devid, void *host, void *device, const int width, const int height, const int bpp)
Definition opencl.c:2191
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2504
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2360
int dt_opencl_write_buffer_to_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2348
int dt_opencl_read_buffer_from_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2337
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2155
gboolean dt_opencl_finish(const int devid)
Definition opencl.c:1375
int dt_opencl_enqueue_copy_image_to_buffer(const int devid, cl_mem src_image, cl_mem dst_buffer, size_t *origin, size_t *region, size_t offset)
Definition opencl.c:2300
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2415
int dt_opencl_write_host_to_device(const int devid, void *host, void *device, const int width, const int height, const int bpp)
Definition opencl.c:2244
#define DT_OPENCL_DEFAULT_ERROR
Definition opencl.h:57
#define ROUNDUPDHT(a, b)
Definition opencl.h:82
#define ROUNDUPDWD(a, b)
Definition opencl.h:81
cl_int process_harmonic_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips)
Definition process.c:698
__DT_CLONE_TARGETS__ int process_harmonic_bayer(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid, void *const restrict ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips)
Definition process.c:39
#define HL_CL_RELEASE(mem_obj)
Definition process.c:439
static __DT_CLONE_TARGETS__ int _harmonic_reconstruct_host(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const float *const restrict input, float *const restrict interpolated, float *const restrict clipping_mask, const dt_iop_roi_t *const roi_in, const dt_aligned_pixel_t clips, const dt_aligned_pixel_t normalization, const float **remosaic_input_out, float **input_corr_out, const _hl_knee_curve_t knee_pre[3])
Definition process.c:361
static cl_int _harmonic_cl_roundtrip(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips)
Definition process.c:463
__DT_CLONE_TARGETS__ int process_harmonic_xtrans(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const restrict ivoid, void *const restrict ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const dt_aligned_pixel_t clips)
Definition process.c:201
static cl_int _harmonic_reconstruct_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem raw_buf, cl_mem interp_buf, cl_mem mask_buf, cl_mem *corr_out, const dt_iop_roi_t *const roi_in, const dt_aligned_pixel_t clips, const dt_aligned_pixel_t norm, cl_mem dev_xtrans, const _hl_knee_curve_t knee_pre[3])
Definition process.c:521
void _region_guided_filter(float *const restrict interp, const float *const restrict mask, const float *const restrict depth, const int width, const _hl_region_t *const region, const dt_dev_pixelpipe_t *pipe, const float solid_color, const int max_iter, const float noise_level)
Definition region.c:149
int _segment_clipped_regions(const uint8_t *const restrict maskb, const float *const restrict depth, const int width, const int height, const float pad_factor, const int pad_min, const int pad_max, _hl_region_t **regions_out)
void _hf_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:918
void _region_guided_filter_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:1779
void _knee_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:1885
void _joint_core_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:1394
void _selfdome_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:1166
void _aniso_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:1592
void _cf_harmonic_fill_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:193
void _cf_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:562
void _region_blur_cl_selftest(const int devid, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:143
void _cf_joint_stage_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:300
void _sp_chol_cl_selftest(const int devid, void *gd_void, const dt_dev_pixelpipe_t *pipe)
Definition selftests.c:39
static cl_mem _sp_cl_upload(const int devid, const void *data, const size_t bytes)
#define DT_HL_CL_CPU_REGION_PX
#define DT_HL_KNEE_DET
#define DT_HL_BAND_OVR
#define DT_HL_KNEE_LO
#define DT_HL_KNEE_BINS
dt_iop_buffer_dsc_t dsc_in
struct dt_iop_module_t *void * data
uint32_t filters
Definition format.h:60
uint8_t xtrans[6][6]
Definition format.h:70
dt_iop_global_data_t * global_data
Definition imageop.h:351
Region of interest passed through the pixelpipe.
Definition imageop.h:72
typedef double((*spd)(unsigned long int wavelength, double TempK))