Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
nlmeans_core.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2020 Hubert Kowalski.
4 Copyright (C) 2020-2021 Pascal Obry.
5 Copyright (C) 2020-2021 Ralf Brown.
6 Copyright (C) 2021 Roman Khatko.
7 Copyright (C) 2022 Hanno Schwalm.
8 Copyright (C) 2022 Martin Bařinka.
9 Copyright (C) 2024 Alynx Zhou.
10 Copyright (C) 2025-2026 Aurélien PIERRE.
11 Copyright (C) 2026 Guillaume Stutin.
12
13 darktable is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 darktable is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with darktable. If not, see <http://www.gnu.org/licenses/>.
25*/
26#ifdef HAVE_CONFIG_H
27#include "config.h"
29#endif
30#include "system/macros.h"
31#include "system/openmp.h"
33#include "system/simd.h"
34#include "math/math.h"
35#include "common/opencl.h"
36#include "develop/imageop.h"
37#include "iop/iop_api.h"
38#include "pixel/nlmeans_core.h"
39#include <stdbool.h>
40#include <stdlib.h>
41
42// to avoid accumulation of rounding errors, we should do a full recomputation of the patch differences
43// every so many rows of the image. We'll also use that interval as the target maximum chunk size for
44// parallelization
45// in addition, to keep the working set within L1 cache, we need to limit the width of the chunks that
46// are processed. The working set uses (2*radius+3)*(ceil(width/4)+1) + (2*radius+3)*(ceil(width/16)+1)
47// 64-byte cache lines. The typical x86 CPU has an L1 cache containing 256 lines, and we'll need to
48// reserve a few for variables in the stack frame and the like. That results in a maximal width of
49// 96 pixels for radius=2, 72 pixels for radius=3, and 56 for radius=4 (default patch radius is 2)
50
51// lower values for SLICE_HEIGHT reduce the accumulation of rounding errors at the cost of more computation;
52// to avoid excessive overhead, width*height should be at least 2000. Keeping width*height below 10000 or so
53// will greatly improve L2/L3 cache hit rates and help with scaling beyond 16 threads. Note that the values
54// specified here are targets and may be adjusted slightly to avoid having extremely small chunks at the
55// right/bottom edge of the images (width will only be reduced, height could be either reduced or increased)
56#define SLICE_WIDTH 72
57#define SLICE_HEIGHT 60
58
59// try to speed up processing by caching pixel differences? If cached, they won't need to be computed a
60// second time when sliding the patch window away from the pixel. Testing shows it to be slower than
61// recomputing for both scalar and SSE on a Threadripper due to increased memory writes; this may differ on
62// architectures with slower multiplication.
63//#define CACHE_PIXDIFFS
64//#define CACHE_PIXDIFFS_SSE
65
66// number of intermediate buffers used by OpenCL code path. If you change this, you must also change
67// the definition in src/iop/nlmeans.c and src/iop/denoiseprofile.c
68#define NUM_BUCKETS 4
69
70// a structure to collect together the items which define the location of a patch relative to the pixel
71// being denoised
72struct patch_t
73{
74 short rows; // number of rows difference
75 short cols; // number of columns difference
76 int offset; // array distance between corresponding pixels
77};
78typedef struct patch_t patch_t;
79
80// avoid cluttering the scalar codepath with #ifdefs by hiding the dependency on SSE2
81#if !(defined(__x86_64__) || defined(__i386__))
82# define _mm_prefetch(where,hint)
83#endif
84
85static inline float gh(const float f)
86{
87 return dt_fast_mexp2f(f) ;
88}
89
90static inline int sign(int a)
91{
92 return (a > 0) - (a < 0);
93}
94
95// map the basic row/column offset into a possible much larger offset based on a user parameter
96static int scatter(const float scale, const float scattering, const int index1, const int index2)
97{
98 // this formula is designed to
99 // - produce an identity mapping when scattering = 0
100 // - avoiding duplicate patches provided that 0 <= scattering <= 1
101 // - avoiding grid artifacts by trying to take patches on various rows and columns
102 const int abs_i1 = abs(index1);
103 const int abs_i2 = abs(index2);
104 return scale * ((abs_i1 * abs_i1 * abs_i1 + 7.0 * abs_i1 * sqrt(abs_i2)) * sign(index1) * scattering / 6.0 + index1);
105}
106
107// allocate and fill an array of patch definitions
108static struct patch_t*
109define_patches(const dt_nlmeans_param_t *const params, const int stride, int *num_patches, int *max_shift)
110{
111 const int search_radius = params->search_radius;
112 const float scale = params->scale;
113 const float scattering = params->scattering;
114 int decimate = params->decimate;
115 // determine how many patches we have
116 int n_patches = (2 * search_radius + 1) * (2 * search_radius + 1);
117 if (decimate)
118 n_patches = (n_patches + 1) / 2;
119 *num_patches = n_patches ;
120 // allocate a cacheline-aligned buffer
121 struct patch_t *patches = dt_pixelpipe_cache_alloc_align_cache(sizeof(struct patch_t) * n_patches, 0);
122 if(IS_NULL_PTR(patches)) return NULL;
123
124 // set up the patch offsets
125 int patch_num = 0;
126 int shift = 0;
127 for (int row_index = -search_radius; row_index <= search_radius; row_index++)
128 {
129 for (int col_index = -search_radius; col_index <= search_radius; col_index++)
130 {
131 if (decimate && (++decimate & 1)) continue; // skip every other patch
132 int r = scatter(scale,scattering,row_index,col_index);
133 int c = scatter(scale,scattering,col_index,row_index);
134 patches[patch_num].rows = r;
135 patches[patch_num].cols = c;
136 if (r > shift) shift = r;
137 else if (-r > shift) shift = -r;
138 if (c > shift) shift = c;
139 else if (-c > shift) shift = -c;
140 patches[patch_num].offset = (r * stride + c * 4);
141 patch_num++;
142 }
143 }
144 *max_shift = shift;
145 return patches;
146}
147
148static float compute_center_pixel_norm(const float center_weight, const int radius)
149{
150 // scale the central pixel's contribution by the size of the patch so that the center-weight
151 // setting can be independent of patch size
152 const int width = 2 * radius + 1;
153 return center_weight * width * width;
154}
155
156// compute the channel-normed squared difference between two pixels
157static inline float pixel_difference(const float* const pix1, const float* pix2, const dt_aligned_pixel_t norm)
158{
159 dt_aligned_pixel_t sum = { 0.f, 0.f, 0.f, 0.f };
160 for_each_channel(i, aligned(sum:16))
161 {
162 const float diff = pix1[i] - pix2[i];
163 sum[i] = diff * diff * norm[i];
164 }
165 return sum[0] + sum[1] + sum[2];
166}
167
168// optimized: pixel_difference(pix1, pix2, norm) - pixel_difference(pix3, pix4, norm)
169static inline float diff_of_pixels_diff(const float* const pix1, const float* pix2,
170 const float* const pix3, const float* pix4,
171 const dt_aligned_pixel_t norm)
172{
173 dt_aligned_pixel_t sum = { 0.f, 0.f, 0.f, 0.f };
174 for_each_channel(i, aligned(sum:16))
175 {
176 const float diff1 = pix1[i] - pix2[i];
177 const float diff2 = pix3[i] - pix4[i];
178 sum[i] = (diff1 * diff1 - diff2 * diff2) * norm[i];
179 }
180 return sum[0] + sum[1] + sum[2];
181}
182
183#if defined(CACHE_PIXDIFFS) || defined(CACHE_PIXDIFFS_SSE)
184static inline float get_pixdiff(const float *const col_sums, const int radius, const int row, const int col)
185{
186 const int stride = 2*(radius+1);
187 const int modrow = 1 + (row + stride) % stride;
188 const float *const pixrow = col_sums + (SLICE_WIDTH + 2*radius)*modrow;
189 return pixrow[col];
190}
191#endif
192
193#if defined(CACHE_PIXDIFFS) || defined(CACHE_PIXDIFFS_SSE)
194static inline void set_pixdiff(float *const col_sums, const int radius, const int row, const int col,
195 const float diff)
196{
197 const int stride = 2*(radius+1);
198 const int modrow = 1 + (row + stride) % stride;
199 float *const pixrow = col_sums + (SLICE_WIDTH + 2*radius)*modrow;
200 pixrow[col] = diff;
201}
202#endif
203
204#if defined(CACHE_PIXDIFFS) || defined(CACHE_PIXDIFFS_SSE)
205static inline float pixdiff_column_sum(const float *const col_sums, const int radius, const int col)
206{
207 const int stride = SLICE_WIDTH + 2*radius;
208 float sum = col_sums[stride+col];
209 for (int i = 2; i <= (2*radius+1) ; i++)
210 sum += col_sums[i*stride+col];
211 return sum;
212}
213#endif
214
215static void init_column_sums(float *const col_sums, const patch_t *const patch, const float *const in,
216 const int row, const int chunk_left, const int chunk_right,
217 const int height, const int width, const int stride,
218 const int radius, const float *const norm)
219{
220 // Compute column sums from scratch. Needed for the very first row, and at intervals thereafter
221 // to limit accumulation of rounding errors
222
223 // figure out which columns can possibly contribute to patches whose centers lie within the RoI
224 // we can go up to 'radius' columns beyond the current chunk provided that the patch does not
225 // lie in the same direction from the pixel being denoised and that we're still in the RoI
226 const int scol = patch->cols;
227 const int col_min = chunk_left - MIN(radius,MIN(chunk_left,chunk_left+scol));
228 const int col_max = chunk_right + MIN(radius,MIN(width-chunk_right,width-(chunk_right+scol)));
229 // adjust bounds if the patch extends past top/bottom of RoI
230 const int srow = patch->rows;
231 const int rmin = row - MIN(radius,MIN(row,row+srow));
232 const int rmax = row + MIN(radius,MIN(height-1-row,height-1-(row+srow)));
233 for (int col = chunk_left-radius-1; col < MIN(col_min,chunk_right+radius); col++)
234 {
235 col_sums[col] = 0;
236#ifdef CACHE_PIXDIFFS
237 for(int i = row-radius; i <= row+radius; i++)
238 set_pixdiff(col_sums,radius,i,col,0.0f);
239#endif
240 }
241 for (int col = col_min; col < col_max; col++)
242 {
243 float sum = 0;
244 for (int r = rmin; r <= rmax; r++)
245 {
246 const float *pixel = in + r*stride + 4*col;
247 const float diff = pixel_difference(pixel,pixel+patch->offset,norm);
248#ifdef CACHE_PIXDIFFS
249 set_pixdiff(col_sums,radius,r,col,diff);
250#endif
251 sum += diff;
252 }
253 col_sums[col] = sum;
254 }
255 // clear out any columns where the patch column would be outside the RoI, as well as our overrun area
256 for (int col = MAX(col_min,col_max); col < chunk_right + radius; col++)
257 {
258 col_sums[col] = 0;
259#ifdef CACHE_PIXDIFFS
260 for(int i = row-radius; i <= row+radius; i++)
261 set_pixdiff(col_sums,radius,i,col,0.0f);
262#endif
263 }
264 return;
265}
266
267// determine the height of the horizontal slice each thread will process
268static int compute_slice_height(const int height)
269{
270 if (height % SLICE_HEIGHT == 0)
271 return SLICE_HEIGHT;
272 // try to make the heights of the chunks as even as possible
273 int best = height % SLICE_HEIGHT;
274 int best_incr = 0;
275 for (int incr = 1; incr < 10; incr++)
276 {
277 int plus_rem = height % (SLICE_HEIGHT + incr);
278 if (plus_rem == 0)
279 return SLICE_HEIGHT + incr;
280 else if (plus_rem > best)
281 {
282 best_incr = +incr;
283 best = plus_rem;
284 }
285 int minus_rem = height % (SLICE_HEIGHT - incr);
286 if (minus_rem == 0)
287 return SLICE_HEIGHT - incr;
288 else if (minus_rem > best)
289 {
290 best_incr = -incr;
291 best = minus_rem;
292 }
293 }
294 return SLICE_HEIGHT + best_incr;
295}
296
297// determine the width of the horizontal slice each thread will process
298static int compute_slice_width(const int width)
299{
300 int sl_width = SLICE_WIDTH;
301 // if there's just a sliver left over for the last column, see whether slicing a few pixels off each gives
302 // us a more nearly full final chunk
303 int rem = width % sl_width;
304 if (rem < SLICE_WIDTH/2 && (width % (sl_width-4)) > rem)
305 {
306 sl_width -= 4;
307 // check whether removing an additional sliver improves things even more
308 rem = width % sl_width;
309 if (rem < SLICE_WIDTH/2 && (width % (sl_width-4)) > rem)
310 sl_width -= 4;
311 }
312 return sl_width;
313}
314
316int nlmeans_denoise(const float *const inbuf, float *const outbuf,
317 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out,
318 const dt_nlmeans_param_t *const params)
319{
320 // define the factors for applying blending between the original image and the denoised version
321 // if running in RGB space, 'luma' should equal 'chroma'
322 const dt_aligned_pixel_t weight = { params->luma, params->chroma, params->chroma, 1.0f };
323 const dt_aligned_pixel_t invert = { 1.0f - params->luma, 1.0f - params->chroma, 1.0f - params->chroma, 0.0f };
324 const bool skip_blend = (params->luma == 1.0 && params->chroma == 1.0);
325
326 // define the normalization to convert central pixel differences into central pixel weights
327 const float cp_norm = compute_center_pixel_norm(params->center_weight,params->patch_radius);
328 const dt_aligned_pixel_t center_norm = { cp_norm, cp_norm, cp_norm, 1.0f };
329
330 // define the patches to be compared when denoising a pixel
331 const size_t stride = 4 * roi_in->width;
332 int num_patches;
333 int max_shift;
334 struct patch_t* patches = define_patches(params,stride,&num_patches,&max_shift);
335 // allocate scratch space, including an overrun area on each end so we don't need a boundary check on every access
336 const int radius = params->patch_radius;
337#if defined(CACHE_PIXDIFFS)
338 const size_t scratch_size = (2*radius+3)*(SLICE_WIDTH + 2*radius + 1);
339#else
340 const size_t scratch_size = SLICE_WIDTH + 2*radius + 1 + 48; // getting false sharing without the +48....
341#endif /* CACHE_PIXDIFFS */
342 size_t padded_scratch_size;
343 float *const restrict scratch_buf = dt_pixelpipe_cache_alloc_perthread_float(scratch_size, &padded_scratch_size);
344 if(IS_NULL_PTR(scratch_buf))
345 {
347 return 1;
348 }
349
350 const int chk_height = compute_slice_height(roi_out->height);
351 const int chk_width = compute_slice_width(roi_out->width);
352 // The pipe abandoned this run: the chunks not started yet are skipped and the run fails. A chunk
353 // goes through every patch, so it is the step the run can be stopped at from inside the parallel
354 // loop. A new request may lower the killswitch again before the loop ends, hence the latch,
355 // shared since the macro makes every variable firstprivate.
356 dt_atomic_int abandoned = FALSE;
357 __OMP_PARALLEL_FOR__(num_threads(dt_get_num_openmp_threads()) collapse(2) shared(abandoned))
358 for (int chunk_top = 0 ; chunk_top < roi_out->height; chunk_top += chk_height)
359 {
360 for (int chunk_left = 0; chunk_left < roi_out->width; chunk_left += chk_width)
361 {
363 {
364 dt_atomic_set_int(&abandoned, TRUE);
365 continue;
366 }
367 // locate our scratch space within the big buffer allocated above
368 // we'll offset by chunk_left so that we don't have to subtract on every access
369 float *const restrict tmpbuf = dt_get_perthread(scratch_buf, padded_scratch_size);
370 float *const col_sums = tmpbuf + (radius+1) - chunk_left;
371 // determine which horizontal slice of the image to process
372 const int chunk_bot = MIN(chunk_top + chk_height, roi_out->height);
373 // determine which vertical slice of the image to process
374 const int chunk_right = MIN(chunk_left + chk_width, roi_out->width);
375 // we want to incrementally sum results (especially weights in col[3]), so clear the output buffer to zeros
376 for (int i = chunk_top; i < chunk_bot; i++)
377 {
378 memset(outbuf + 4*(i*roi_out->width+chunk_left), '\0', sizeof(float) * 4 * (chunk_right-chunk_left));
379 }
380 // cycle through all of the patches over our slice of the image
381 for (int p = 0; p < num_patches; p++)
382 {
383 // retrieve info about the current patch
384 const patch_t *patch = &patches[p];
385 // skip any rows where the patch center would be above top of RoI or below bottom of RoI
386 const int height = roi_out->height;
387 const int row_min = MAX(chunk_top,MAX(0,-patch->rows));
388 const int row_max = MIN(chunk_bot,height - MAX(0,patch->rows));
389 // figure out which rows at top and bottom result in patches extending outside the RoI, even though the
390 // center pixel is inside
391 const int row_top = MAX(row_min,MAX(radius,radius-patch->rows));
392 const int row_bot = MIN(row_max,height-1-MAX(radius,radius+patch->rows));
393 // skip any columns where the patch center would be to the left or the right of the RoI
394 const int width = roi_out->width;
395 const int scol = patch->cols;
396 const int col_min = MAX(chunk_left,-scol);
397 const int col_max = MIN(chunk_right,roi_out->width - scol);
398
399 init_column_sums(col_sums,patch,inbuf,row_min,chunk_left,chunk_right,height,width,
400 stride,radius,params->norm);
401 for (int row = row_min; row < row_max; row++)
402 {
403 // add up the initial columns of the sliding window of total patch distortion
404 float distortion = 0.0;
405 for (int i = col_min - radius; i < MIN(col_min+radius, col_max); i++)
406 {
407 distortion += col_sums[i];
408 }
409 // now proceed down the current row of the image
410 const float *in = inbuf + stride * row;
411 float *const out = outbuf + (size_t)4 * width * row;
412 const int offset = patch->offset;
413 const float sharpness = params->sharpness;
414 if (params->center_weight < 0)
415 {
416 // computation as used by denoise(non-local) iop
417 for (int col = col_min; col < col_max; col++)
418 {
419 distortion += (col_sums[col+radius] - col_sums[col-radius-1]);
420 const float wt = gh(distortion * sharpness);
421 const float *const inpx = in+4*col;
422 const dt_aligned_pixel_t pixel = { inpx[offset], inpx[offset+1], inpx[offset+2], 1.0f };
423 for_four_channels(c,aligned(pixel,out:16))
424 {
425 out[4*col+c] += pixel[c] * wt;
426 }
427 _mm_prefetch(in+4*col+offset+stride,_MM_HINT_T0); // try to ensure next row is ready in time
428 }
429 }
430 else
431 {
432 // computation as used by denoiseprofiled iop with non-local means
433 for (int col = col_min; col < col_max; col++)
434 {
435 distortion += (col_sums[col+radius] - col_sums[col-radius-1]);
436 const float dissimilarity = (distortion + pixel_difference(in+4*col,in+4*col+offset,center_norm))
437 / (1.0f + params->center_weight);
438 const float wt = gh(fmaxf(0.0f, dissimilarity * sharpness - 2.0f));
439 const float *const inpx = in + 4*col;
440 const dt_aligned_pixel_t pixel = { inpx[offset], inpx[offset+1], inpx[offset+2], 1.0f };
441 for_four_channels(c,aligned(pixel,out:16))
442 {
443 out[4*col+c] += pixel[c] * wt;
444 }
445 _mm_prefetch(in+4*col+offset+stride,_MM_HINT_T0); // try to ensure next row is ready in time
446 }
447 }
448 const int pcol_min = chunk_left - MIN(radius,MIN(chunk_left,chunk_left+scol));
449 const int pcol_max = chunk_right + MIN(radius,MIN(width-chunk_right,width-(chunk_right+scol)));
450 if (row < MIN(row_top, row_bot))
451 {
452 // top edge of patch was above top of RoI, so it had a value of zero; just add in the new row
453 const float *bot_row = inbuf + (row+1+radius)*stride;
454 for (int col = pcol_min; col < pcol_max; col++)
455 {
456 const float *const bot_px = bot_row + 4*col;
457 const float diff = pixel_difference(bot_px,bot_px+offset,params->norm);
458 _mm_prefetch(bot_px+stride, _MM_HINT_T0);
459#ifdef CACHE_PIXDIFFS
460 set_pixdiff(col_sums,radius,row+radius+1,col,diff);
461#endif
462 col_sums[col] += diff;
463 _mm_prefetch(bot_px+offset+stride, _MM_HINT_T0);
464 }
465 }
466 else if (row < row_bot)
467 {
468#ifndef CACHE_PIXDIFFS
469 const float *const top_row = inbuf + (row-radius)*stride /* +(2*radius+1)*stride*/ ;
470#endif /* !CACHE_PIXDIFFS */
471 const float *const bot_row = inbuf + (row+1+radius)*stride ;
472 // both prior and new positions are entirely within the RoI, so subtract the old row and add the new one
473 for (int col = pcol_min; col < pcol_max; col++)
474 {
475#ifdef CACHE_PIXDIFFS
476 const float *const bot_px = bot_row + 4*col;
477 const float diff = pixel_difference(bot_px,bot_px+offset,params->norm);
478 col_sums[col] += diff - get_pixdiff(col_sums,radius,row-radius,col);
479 _mm_prefetch(bot_px+stride, _MM_HINT_T0);
480 set_pixdiff(col_sums,radius,row+1+radius,col,diff);
481#else
482 const float *const top_px = top_row + 4*col;
483 const float *const bot_px = bot_row + 4*col;
484 const float diff = diff_of_pixels_diff(bot_px,bot_px+offset,top_px,top_px+offset,params->norm);
485 _mm_prefetch(bot_px+stride, _MM_HINT_T0);
486 col_sums[col] += diff;
487#endif /* CACHE_PIXDIFFS */
488 _mm_prefetch(bot_px+offset+stride, _MM_HINT_T0);
489 }
490 }
491 else if (row >= row_top && row + 1 < row_max) // don't bother updating if last iteration
492 {
493 // new row of the patch is below the bottom of RoI, so its value is zero; just subtract the old row
494#ifndef CACHE_PIXDIFFS
495 const float *top_row = inbuf + (row-radius)*stride;
496#endif /* !CACHE_PIXDIFFS */
497 for (int col = pcol_min; col < pcol_max; col++)
498 {
499#ifdef CACHE_PIXDIFFS
500 col_sums[col] -= get_pixdiff(col_sums,radius,row-radius,col);
501#else
502 const float *const top_px = top_row + 4*col;
503 col_sums[col] -= pixel_difference(top_px,top_px+offset,params->norm);
504#endif /* CACHE_PIXDIFFS */
505 }
506 }
507 }
508 }
509 if (skip_blend)
510 {
511 // normalize the pixels
512 for (int row = chunk_top; row < chunk_bot; row++)
513 {
514 float *const out = outbuf + 4 * row * roi_out->width;
515 for (int col = chunk_left; col < chunk_right; col++)
516 {
517 for_each_channel(c,aligned(out:16))
518 {
519 out[4*col+c] /= out[4*col+3];
520 }
521 }
522 }
523 }
524 else
525 {
526 // normalize and apply chroma/luma blending
527 for (int row = chunk_top; row < chunk_bot; row++)
528 {
529 const float *in = inbuf + row * stride;
530 float *out = outbuf + row * 4 * roi_out->width;
531 for (int col = chunk_left; col < chunk_right; col++)
532 {
533 for_each_channel(c,aligned(in,out,weight,invert:16))
534 {
535 out[4*col+c] = (in[4*col+c] * invert[c]) + (out[4*col+c] / out[4*col+3] * weight[c]);
536 }
537 }
538 }
539 }
540 }
541 }
542
543 // clean up: free the work space
546 return dt_atomic_get_int(&abandoned);
547}
548
549/**************************************************************/
550/**************************************************************/
551/* Everything from here to end of file is WIP!! */
552/**************************************************************/
553/**************************************************************/
554
555#ifdef HAVE_OPENCL
556static int bucket_next(unsigned int *state, unsigned int max)
557{
558 unsigned int current = *state;
559 unsigned int next = (current >= max - 1 ? 0 : current + 1);
560
561 *state = next;
562
563 return next;
564}
565#endif /* HAVE_OPENCL */
566
567#ifdef HAVE_OPENCL
568static void get_blocksizes(int *h, int *v, const int radius, const int devid,
569 const int horiz_kernel, const int vert_kernel)
570{
572 = (dt_opencl_local_buffer_t){ .xoffset = 2 * radius, .xfactor = 1, .yoffset = 0, .yfactor = 1,
573 .cellsize = sizeof(float), .overhead = 0,
574 .sizex = 1 << 16, .sizey = 1 };
575
576 *h = dt_opencl_local_buffer_opt(devid, horiz_kernel, &hlocopt) ? hlocopt.sizex : 1;
577
579 = (dt_opencl_local_buffer_t){ .xoffset = 1, .xfactor = 1, .yoffset = 2 * radius, .yfactor = 1,
580 .cellsize = sizeof(float), .overhead = 0,
581 .sizex = 1, .sizey = 1 << 16 };
582
583 *v = dt_opencl_local_buffer_opt(devid, vert_kernel, &vlocopt) ? vlocopt.sizey : 1;
584 return;
585}
586#endif /* HAVE_OPENCL */
587
588#ifdef HAVE_OPENCL
589// zero output pixels, as we will be accumulating them one patch at a time
590static inline cl_int nlmeans_cl_init(const int devid, const int kernel, cl_mem dev_out, const int height,
591 const int width)
592{
593 const size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
594 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_out);
595 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&width);
596 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&height);
597 return dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
598}
599#endif /* HAVE_OPENCL */
600
601#ifdef HAVE_OPENCL
602// horizontal pass, add together columns of each patch
603static inline cl_int nlmeans_cl_horiz(const int devid, const int kernel, cl_mem dev_U4, cl_mem dev_U4_t,
604 const int P, const int q[2], const int height, const int width,
605 const int bwidth, const int hblocksize)
606{
607 const size_t sizesl[3] = { bwidth, ROUNDUPDHT(height, devid), 1 };
608 const size_t local[3] = { hblocksize, 1, 1 };
609 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_U4);
610 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_U4_t);
611 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&width);
612 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&height);
613 dt_opencl_set_kernel_arg(devid, kernel, 4, 2 * sizeof(int), (void *)&q);
614 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), (void *)&P);
615 dt_opencl_set_kernel_arg(devid, kernel, 6, (hblocksize + 2 * P) * sizeof(float), NULL);
616 return dt_opencl_enqueue_kernel_2d_with_local(devid, kernel, sizesl, local);
617}
618#endif /* HAVE_OPENCL */
619
620#ifdef HAVE_OPENCL
621// add difference-weighted proportion of patch-center pixel to output pixel
622static inline cl_int nlmeans_cl_accu(const int devid, const int kernel, cl_mem dev_in, cl_mem dev_U4_tt,
623 cl_mem dev_out, const int q[2], const int height, const int width,
624 const size_t sizes[3])
625{
626 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
627 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_out);
628 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_U4_tt);
629 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&width);
630 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), (void *)&height);
631 dt_opencl_set_kernel_arg(devid, kernel, 5, 2 * sizeof(int), (void *)&q);
632 return dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
633}
634#endif /* HAVE_OPENCL */
635
636#ifdef HAVE_OPENCL
637// The patch loop of both kernel sets. The profiled one (`profile`) gives no channel norms to its distance
638// kernel, gives the central pixel weight and the distances to its vertical kernel, and no nap to the GPU.
639static int nlmeans_cl_run(const dt_nlmeans_param_t *const params, const int devid, cl_mem dev_in,
640 cl_mem dev_out, const dt_iop_roi_t *const roi_in, const gboolean profile)
641{
642 const int width = roi_in->width;
643 const int height = roi_in->height;
644 const int P = params->patch_radius;
645 const float nL2 = params->norm[0] * params->norm[0];
646 const float nC2 = params->norm[1] * params->norm[1];
647
648 // define the patches to be compared when denoising a pixel
649 const size_t stride = 4 * roi_in->width;
650 int num_patches;
651 int max_shift;
652 struct patch_t* patches = define_patches(params,stride,&num_patches,&max_shift);
653
654 cl_int err = -999;
655 cl_mem buckets[NUM_BUCKETS] = { NULL };
656 unsigned int state = 0;
657 for(int k = 0; k < NUM_BUCKETS; k++)
658 {
659 buckets[k] = dt_opencl_alloc_device_buffer(devid, sizeof(float) * width * height);
660 if(buckets[k] == NULL) goto error;
661 }
662
663 int hblocksize;
664 int vblocksize;
665 get_blocksizes(&hblocksize, &vblocksize, P, devid, params->kernel_horiz, params->kernel_vert);
666
667 // zero the output buffer into which we will be accumulating results
668 err = nlmeans_cl_init(devid,params->kernel_init,dev_out,height,width);
669 if(err != CL_SUCCESS) goto error;
670
671 const size_t bwidth = ROUNDUP(width, hblocksize);
672 const size_t bheight = ROUNDUP(height, vblocksize);
673 const size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
674
675 for(int p = 0; p < num_patches; p++)
676 {
677 // The pipe abandoned this run: stop between two patches. The host enqueues the whole run far
678 // ahead of the device, so the killswitch is read once the previous patch has run on it.
679 if(p > 0 && dt_opencl_finish(devid) && dt_dev_pixelpipe_has_shutdown(params->pipe))
680 {
682 break;
683 }
684 const patch_t *patch = &patches[p];
685 int q[2] = { patch->rows, patch->cols };
686
687 // compute squared differences between input pixels and shifted (by q) pixels, channel-normed
688 // unless profiled
689 cl_mem dev_U4 = buckets[bucket_next(&state, NUM_BUCKETS)];
690 dt_opencl_set_kernel_arg(devid, params->kernel_dist, 0, sizeof(cl_mem), (void *)&dev_in);
691 dt_opencl_set_kernel_arg(devid, params->kernel_dist, 1, sizeof(cl_mem), (void *)&dev_U4);
692 dt_opencl_set_kernel_arg(devid, params->kernel_dist, 2, sizeof(int), (void *)&width);
693 dt_opencl_set_kernel_arg(devid, params->kernel_dist, 3, sizeof(int), (void *)&height);
694 dt_opencl_set_kernel_arg(devid, params->kernel_dist, 4, 2 * sizeof(int), (void *)&q);
695 if(!profile)
696 {
697 dt_opencl_set_kernel_arg(devid, params->kernel_dist, 5, sizeof(float), (void *)&nL2);
698 dt_opencl_set_kernel_arg(devid, params->kernel_dist, 6, sizeof(float), (void *)&nC2);
699 }
700 err = dt_opencl_enqueue_kernel_2d(devid, params->kernel_dist, sizes);
701 if(err != CL_SUCCESS) break;
702
703 // add up individual columns
704 cl_mem dev_U4_t = buckets[bucket_next(&state, NUM_BUCKETS)];
705 err = nlmeans_cl_horiz(devid,params->kernel_horiz,dev_U4,dev_U4_t,P,q,height,width,bwidth,hblocksize);
706 if(err != CL_SUCCESS) break;
707
708 // add together the column sums and compute the weighting of the current patch for each pixel
709 const size_t sizesl[3] = { ROUNDUPDWD(width, devid), bheight, 1 };
710 const size_t local[3] = { 1, vblocksize, 1 };
711 const float sharpness = params->sharpness;
712 const float central_pixel_weight = params->center_weight;
713 cl_mem dev_U4_tt = buckets[bucket_next(&state, NUM_BUCKETS)];
714 dt_opencl_set_kernel_arg(devid, params->kernel_vert, 0, sizeof(cl_mem), (void *)&dev_U4_t);
715 dt_opencl_set_kernel_arg(devid, params->kernel_vert, 1, sizeof(cl_mem), (void *)&dev_U4_tt);
716 dt_opencl_set_kernel_arg(devid, params->kernel_vert, 2, sizeof(int), (void *)&width);
717 dt_opencl_set_kernel_arg(devid, params->kernel_vert, 3, sizeof(int), (void *)&height);
718 dt_opencl_set_kernel_arg(devid, params->kernel_vert, 4, 2 * sizeof(int), (void *)&q);
719 dt_opencl_set_kernel_arg(devid, params->kernel_vert, 5, sizeof(int), (void *)&P);
720 dt_opencl_set_kernel_arg(devid, params->kernel_vert, 6, sizeof(float), (void *)&sharpness);
721 dt_opencl_set_kernel_arg(devid, params->kernel_vert, 7, (vblocksize + 2 * P) * sizeof(float), NULL);
722 if(profile)
723 {
724 dt_opencl_set_kernel_arg(devid, params->kernel_vert, 8, sizeof(float), (void *)&central_pixel_weight);
725 dt_opencl_set_kernel_arg(devid, params->kernel_vert, 9, sizeof(cl_mem), ((void *)&dev_U4));
726 }
727 err = dt_opencl_enqueue_kernel_2d_with_local(devid, params->kernel_vert, sizesl, local);
728 if(err != CL_SUCCESS) break;
729
730 // add weighted proportion of patch's center pixel to output pixel
731 err = nlmeans_cl_accu(devid,params->kernel_accu,dev_in,dev_U4_tt,dev_out,q,height,width,sizes);
732 if(err != CL_SUCCESS) break;
733
734 // indirectly give gpu some air to breathe (and to do display related stuff)
735 if(!profile) dt_iop_nap(dt_opencl_micro_nap(devid));
736 }
737
738error:
739 // clean up and return status
741 for(int k = 0; k < NUM_BUCKETS; k++)
742 {
744 }
745 return err;
746}
747#endif /* HAVE_OPENCL */
748
749#ifdef HAVE_OPENCL
750int nlmeans_denoise_cl(const dt_nlmeans_param_t *const params, const int devid,
751 cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in)
752{
753 return nlmeans_cl_run(params, devid, dev_in, dev_out, roi_in, FALSE);
754}
755#endif /* HAVE_OPENCL */
756
757#ifdef HAVE_OPENCL
758int nlmeans_denoiseprofile_cl(const dt_nlmeans_param_t *const params, const int devid,
759 cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in)
760{
761 return nlmeans_cl_run(params, devid, dev_in, dev_out, roi_in, TRUE);
762}
763#endif /* HAVE_OPENCL */
764// clang-format off
765// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
766// vim: shiftwidth=2 expandtab tabstop=2 cindent
767// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
768// clang-format on
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_atomic_set_int(dt_atomic_int *var, int value)
int dt_atomic_get_int(dt_atomic_int *var)
atomic_int dt_atomic_int
Definition atomic.h:68
const float f
const float v
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
#define P(V, params)
int dt_get_num_openmp_threads(void)
Number of OpenMP threads the application decided to use.
Definition darktable.c:521
static void weight(const float *c1, const float *c2, const float sharpen, dt_aligned_pixel_t weight)
Definition eaw.c:29
void dt_iop_nap(int32_t usec)
Definition imageop.c:1669
static float kernel(const float *x, const float *y)
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
static float dt_fast_mexp2f(const float x)
Definition math.h:290
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
static int bucket_next(unsigned int *state, unsigned int max)
static float gh(const float f)
static void init_column_sums(float *const col_sums, const patch_t *const patch, const float *const in, const int row, const int chunk_left, const int chunk_right, const int height, const int width, const int stride, const int radius, const float *const norm)
#define NUM_BUCKETS
static float pixel_difference(const float *const pix1, const float *pix2, const dt_aligned_pixel_t norm)
static int nlmeans_cl_run(const dt_nlmeans_param_t *const params, const int devid, cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in, const gboolean profile)
__DT_CLONE_TARGETS__ int nlmeans_denoise(const float *const inbuf, float *const outbuf, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const dt_nlmeans_param_t *const params)
static int compute_slice_height(const int height)
static void get_blocksizes(int *h, int *v, const int radius, const int devid, const int horiz_kernel, const int vert_kernel)
static cl_int nlmeans_cl_accu(const int devid, const int kernel, cl_mem dev_in, cl_mem dev_U4_tt, cl_mem dev_out, const int q[2], const int height, const int width, const size_t sizes[3])
static int scatter(const float scale, const float scattering, const int index1, const int index2)
#define SLICE_HEIGHT
int nlmeans_denoise_cl(const dt_nlmeans_param_t *const params, const int devid, cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in)
int nlmeans_denoiseprofile_cl(const dt_nlmeans_param_t *const params, const int devid, cl_mem dev_in, cl_mem dev_out, const dt_iop_roi_t *const roi_in)
#define SLICE_WIDTH
#define _mm_prefetch(where, hint)
static struct patch_t * define_patches(const dt_nlmeans_param_t *const params, const int stride, int *num_patches, int *max_shift)
static float compute_center_pixel_norm(const float center_weight, const int radius)
static cl_int nlmeans_cl_horiz(const int devid, const int kernel, cl_mem dev_U4, cl_mem dev_U4_t, const int P, const int q[2], const int height, const int width, const int bwidth, const int hblocksize)
static int compute_slice_width(const int width)
static float diff_of_pixels_diff(const float *const pix1, const float *pix2, const float *const pix3, const float *pix4, const dt_aligned_pixel_t norm)
static cl_int nlmeans_cl_init(const int devid, const int kernel, cl_mem dev_out, const int height, const int width)
int dt_opencl_local_buffer_opt(const int devid, const int kernel, dt_opencl_local_buffer_t *factors)
Definition opencl.c:3786
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_micro_nap(const int devid)
Definition opencl.c:225
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_kernel_2d_with_local(const int dev, const int kernel, const size_t *sizes, const size_t *local)
Definition opencl.c:2618
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2863
#define DT_OPENCL_DEFAULT_ERROR
Definition opencl.h:61
#define ROUNDUP(a, n)
Definition opencl.h:82
#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_cache(size, id)
#define dt_pixelpipe_cache_free_align(mem)
#define dt_get_perthread(buf, padsize)
#define dt_pixelpipe_cache_alloc_perthread_float(n, padded_size)
static gboolean dt_dev_pixelpipe_has_shutdown(const dt_dev_pixelpipe_t *pipe)
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#define for_each_channel(_var,...)
Definition simd.h:87
static const dt_aligned_pixel_simd_t sign
Definition simd.h:118
#define for_four_channels(_var,...)
Definition simd.h:89
const float uint32_t state[4]
const float r
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
short cols
short rows
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29