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