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