Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
box_filters.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2020-2021 Hubert Kowalski.
4 Copyright (C) 2020-2021 Pascal Obry.
5 Copyright (C) 2020-2021 Ralf Brown.
6 Copyright (C) 2022 Martin Bařinka.
7 Copyright (C) 2023 Luca Zulberti.
8 Copyright (C) 2025-2026 Aurélien PIERRE.
9
10 darktable is free software: you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation, either version 3 of the License, or
13 (at your option) any later version.
14
15 darktable is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with darktable. If not, see <http://www.gnu.org/licenses/>.
22*/
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#endif
27#include <assert.h>
28#include <math.h>
29#include <stdlib.h>
30
31#include "pixel/box_filters.h"
32#include "system/macros.h"
33#include "system/openmp.h"
35#include "system/mem_alloc.h"
36#include "system/simd.h"
38#include "math/math.h"
39
40#if defined(__x86_64__) || defined(__i386__)
41#define DT_PREFETCH(addr) _mm_prefetch(addr, _MM_HINT_T2)
42#define PREFETCH_NTA(addr) _mm_prefetch(addr, _MM_HINT_NTA)
43#elif defined(__GNUC__) && __GNUC__ > 7
44#define DT_PREFETCH(addr) __builtin_prefetch(addr,1,1)
45#define PREFETCH_NTA(addr) __builtin_prefetch(addr,1,0)
46#else
47#define DT_PREFETCH(addr)
48#define PREFETCH_NTA(addr)
49#endif
50
52static void blur_horizontal_1ch(float *const restrict buf, const int height, const int width, const int radius,
53 float *const restrict scanlines, const size_t padded_size)
54{
56 for(int y = 0; y < height; y++)
57 {
58 float L = 0;
59 int hits = 0;
60 const size_t index = (size_t)y * width;
61 float *const restrict scanline = dt_get_perthread(scanlines,padded_size);
62 // add up the left half of the window
63 for (int x = 0; x < MIN(radius,width) ; x++)
64 {
65 L += buf[index+x];
66 hits++;
67 }
68 // process the blur up to the point where we start removing values
69 int x;
70 for (x = 0; (x <= radius) && ((x + radius) < width); x++)
71 {
72 const int np = x + radius;
73 if(np < width)
74 {
75 L += buf[index + np];
76 hits++;
77 }
78 scanline[x] = L / hits;
79 }
80 // if radius > width/2, we have pixels for which we can neither add new values (x+radius >= width) nor
81 // remove old values (x-radius < 0)
82 for(; x <= radius && x < width; x++)
83 {
84 scanline[x] = L / hits;
85 }
86 // process the blur for the bulk of the scan line
87 for(; x + radius < width; x++)
88 {
89 const int op = x - radius - 1;
90 const int np = x + radius;
91 L -= buf[index + op];
92 L += buf[index + np];
93 scanline[x] = L / hits;
94 }
95 // process the right end where we have no more values to add to the running sum
96 for(; x < width; x++)
97 {
98 const int op = x - radius - 1;
99 L -= buf[index + op];
100 hits--;
101 scanline[x] = L / hits;
102 }
103 // copy blurred values back to original location in buffer
104 for(x = 0; x < width; x++)
105 buf[index + x] = scanline[x];
106 }
107 return;
108}
109
111static void blur_horizontal_2ch(float *const restrict buf, const int height, const int width, const int radius,
112 float *const restrict scanlines, const size_t padded_size)
113{
115 for(int y = 0; y < height; y++)
116 {
117 float *const restrict scanline = dt_get_perthread(scanlines, padded_size);
118 float L1 = 0.0f, L2 = 0.0f;
119 int hits = 0;
120 const size_t index = (size_t)2 * y * width;
121 // add up the left half of the window
122 for (int x = 0; x < MIN(radius, width) ; x++)
123 {
124 hits++;
125 L1 += buf[index + 2*x];
126 L2 += buf[index + 2*x + 1];
127 }
128 // process the blur up to the point where we start removing values
129 int x;
130 for (x = 0; (x <= radius) && ((x + radius) < width); x++)
131 {
132 const int np = x + radius;
133 if(np < width)
134 {
135 hits++;
136 L1 += buf[index + 2*np];
137 L2 += buf[index + 2*np + 1];
138 }
139 scanline[2*x] = L1 / hits;
140 scanline[2*x+1] = L2 / hits;
141 }
142 // if radius > width/2, we have pixels for which we can neither add new values (x+radius >= width) nor
143 // remove old values (x-radius < 0)
144 for(; x <= radius && x < width; x++)
145 {
146 scanline[2*x] = L1 / hits;
147 scanline[2*x+1] = L2 / hits;
148 }
149 // process the blur for the bulk of the scan line
150 for(; x + radius < width; x++)
151 {
152 const int op = x - radius - 1;
153 const int np = x + radius;
154 L1 = L1 - buf[index + 2*op] + buf[index + 2*np];
155 L2 = L2 - buf[index + 2*op + 1] + buf[index + 2*np + 1];
156 scanline[2*x] = L1 / hits;
157 scanline[2*x+1] = L2 / hits;
158 }
159 // process the right end where we have no more values to add to the running sum
160 for(; x < width; x++)
161 {
162 const int op = x - radius - 1;
163 hits--;
164 L1 -= buf[index + 2*op];
165 L2 -= buf[index + 2*op + 1];
166 scanline[2*x] = L1 / hits;
167 scanline[2*x+1] = L2 / hits;
168 }
169 // copy blurred values back to original location in buffer
170 for(x = 0; x < 2*width; x++)
171 {
172 buf[index + x] = scanline[x];
173 }
174 }
175 return;
176}
177
178// Put the to-be-vectorized loop into a function by itself to nudge the compiler into actually vectorizing...
179// With optimization enabled, this gets inlined and interleaved with other instructions as though it had been
180// written in place, so we get a net win from better vectorization.
181static inline __attribute__((always_inline)) void load_add_4wide(float *const restrict out, dt_aligned_pixel_t accum, const float *const restrict values)
182{
183 for_four_channels(c,aligned(accum, out))
184 {
185 const float v = values[c];
186 accum[c] += v;
187 out[c] = v;
188 }
189}
190
191static inline __attribute__((always_inline)) void sub_4wide(float *const restrict accum, const dt_aligned_pixel_t values)
192{
193 for_four_channels(c,aligned(accum))
194 accum[c] -= values[c];
195}
196
197// Put the to-be-vectorized loop into a function by itself to nudge the compiler into actually vectorizing...
198// With optimization enabled, this gets inlined and interleaved with other instructions as though it had been
199// written in place, so we get a net win from better vectorization.
200static inline __attribute__((always_inline)) void load_add_4wide_Kahan(float *const restrict out, dt_aligned_pixel_t accum,
201 const float *const restrict values, float *const restrict comp)
202{
203 for_four_channels(c,aligned(accum, comp, out))
204 {
205 const float v = values[c];
206 out[c] = v;
207 // Kahan (compensated) summation
208 const float t1 = v - comp[c];
209 const float t2 = accum[c] + t1;
210 comp[c] = (t2 - accum[c]) - t1;
211 accum[c] = t2;
212 }
213}
214
215static inline __attribute__((always_inline)) void sub_4wide_Kahan(float *const restrict accum, const dt_aligned_pixel_t values,
216 float *const restrict comp)
217{
218 for_four_channels(c,aligned(accum,comp,values))
219 {
220 // Kahan (compensated) summation
221 const float t1 = -values[c] - comp[c];
222 const float t2 = accum[c] + t1;
223 comp[c] = (t2 - accum[c]) - t1;
224 accum[c] = t2;
225 }
226}
227
228static inline __attribute__((always_inline)) void store_scaled_4wide(float *const restrict out, const dt_aligned_pixel_t in, const float scale)
229{
230 for_four_channels(c,aligned(in))
231 out[c] = in[c] / scale;
232}
233
235static void sub_16wide(float *const restrict accum, const float *const restrict values)
236{
237 __OMP_SIMD__(aligned(accum : 64) aligned(values : 16))
238 for(size_t c = 0; c < 16; c++)
239 accum[c] -= values[c];
240}
241
242// copy 16 floats from a possibly-unaligned buffer into aligned temporary space, and also add to accumulator
244static void load_add_16wide(float *const restrict out, float *const restrict accum, const float *const restrict in)
245{
246 __OMP_SIMD__(aligned(accum, out : 64))
247 for (size_t c = 0; c < 16; c++)
248 {
249 const float v = in[c];
250 accum[c] += v;
251 out[c] = v;
252 }
253}
254
256static void sub_16wide_Kahan(float *const restrict accum, const float *const restrict values,
257 float *const restrict comp)
258{
259 __OMP_SIMD__(aligned(accum,comp : 64) aligned(values : 16))
260 for(size_t c = 0; c < 16; c++)
261 {
262 const float v = -values[c];
263 // Kahan (compensated) summation
264 const float t1 = v - comp[c];
265 const float t2 = accum[c] + t1;
266 comp[c] = (t2 - accum[c]) - t1;
267 accum[c] = t2;
268 }
269}
270
271// copy 16 floats from a possibly-unaligned buffer into aligned temporary space, and also add to accumulator
273static void load_add_16wide_Kahan(float *const restrict out, float *const restrict accum,
274 const float *const restrict in, float *const restrict comp)
275{
276 __OMP_SIMD__(aligned(accum, comp, out : 64))
277 for (size_t c = 0; c < 16; c++)
278 {
279 const float v = in[c];
280 out[c] = v;
281 // Kahan (compensated) summation
282 const float t1 = v - comp[c];
283 const float t2 = accum[c] + t1;
284 comp[c] = (t2 - accum[c]) - t1;
285 accum[c] = t2;
286 }
287}
288
289// copy 16 floats from aligned temporary space back to the possibly-unaligned user buffer
291static void store_16wide(float *const restrict out, const float *const restrict in)
292{
293 __OMP_SIMD__(aligned(in : 64))
294 for (size_t c = 0; c < 16; c++)
295 out[c] = in[c];
296}
297
299static void store_scaled_16wide(float *const restrict out, const float *const restrict in, const float scale)
300{
301 __OMP_SIMD__(aligned(in : 64))
302 for(size_t c = 0; c < 16; c++)
303 out[c] = in[c] / scale;
304}
305
307static void sub_Nwide_Kahan(const size_t N, float *const restrict accum, const float *const restrict values,
308 float *const restrict comp)
309{
310 __OMP_SIMD__(aligned(accum,comp : 64))
311 for(size_t c = 0; c < N; c++)
312 {
313 const float v = -values[c];
314 // Kahan (compensated) summation
315 const float t1 = v - comp[c];
316 const float t2 = accum[c] + t1;
317 comp[c] = (t2 - accum[c]) - t1;
318 accum[c] = t2;
319 }
320}
321
322// copy N (<=16) floats from a possibly-unaligned buffer into aligned temporary space, and also add to accumulator
324static void load_add_Nwide_Kahan(const size_t N, float *const restrict out, float *const restrict accum,
325 const float *const restrict in, float *const restrict comp)
326{
327 __OMP_SIMD__(aligned(accum, comp : 64))
328 for (size_t c = 0; c < N; c++)
329 {
330 const float v = in[c];
331 out[c] = v;
332 // Kahan (compensated) summation
333 const float t1 = v - comp[c];
334 const float t2 = accum[c] + t1;
335 comp[c] = (t2 - accum[c]) - t1;
336 accum[c] = t2;
337 }
338}
339
341static void store_scaled_Nwide(const size_t N, float *const restrict out, const float *const restrict in,
342 const float scale)
343{
344 __OMP_SIMD__(aligned(in : 64))
345 for(size_t c = 0; c < N; c++)
346 out[c] = in[c] / scale;
347}
348
349
351static void blur_horizontal_4ch(float *const restrict buf, const size_t height, const size_t width, const size_t radius,
352 float *const restrict scanlines, const size_t padded_size)
353{
355 for(int y = 0; y < height; y++)
356 {
357 float *const restrict scratch = dt_get_perthread(scanlines,padded_size);
358 dt_aligned_pixel_t L = { 0, 0, 0, 0 };
359 size_t hits = 0;
360 const size_t index = (size_t)4 * y * width;
361 float *const restrict bufp = buf + index;
362 // add up the left half of the window
363 for (size_t x = 0; x < MIN(radius,width) ; x++)
364 {
365 hits++;
366 load_add_4wide(scratch + 4*x, L, bufp + 4*x);
367 }
368 // process the blur up to the point where we start removing values
369 size_t x;
370 for (x = 0; (x <= radius) && ((x + radius) < width); x++)
371 {
372 const int np = x + radius;
373 hits++;
374 load_add_4wide(scratch + 4*np, L, bufp + 4*np);
375 store_scaled_4wide(bufp + 4*x, L, hits);
376 }
377 // if radius > width/2, we have pixels for which we can neither add new values (x+radius >= width) nor
378 // remove old values (x-radius < 0)
379 for(; x <= radius && x < width; x++)
380 {
381 store_scaled_4wide(bufp + 4*x, L, hits);
382 }
383 // process the blur for the bulk of the scan line
384 for(; x + radius < width; x++)
385 {
386 //very strange: if any of the 'op' or 'np' variables in this function are changed to either
387 // 'unsigned' or 'size_t', the function runs a fair bit slower....
388 const int op = x - radius - 1;
389 const int np = x + radius;
390 sub_4wide(L, scratch + 4*op);
391 load_add_4wide(scratch + 4*np, L, bufp + 4*np);
392 store_scaled_4wide(bufp + 4*x, L, hits);
393 }
394 // process the right end where we have no more values to add to the running sum
395 for(; x < width; x++)
396 {
397 const int op = x - radius - 1;
398 hits--;
399 sub_4wide(L, scratch + 4*op);
400 store_scaled_4wide(bufp + 4*x, L, hits);
401 }
402 }
403 return;
404}
405
406// invoked inside an OpenMP parallel for, so no need to parallelize
408static void blur_horizontal_4ch_Kahan(float *const restrict buf, const size_t width,
409 const size_t radius, float *const restrict scratch)
410{
411 dt_aligned_pixel_t L = { 0, 0, 0, 0 };
412 dt_aligned_pixel_t comp = { 0, 0, 0, 0 };
413 size_t hits = 0;
414 // add up the left half of the window
415 for (size_t x = 0; x < MIN(radius,width) ; x++)
416 {
417 hits++;
418 load_add_4wide_Kahan(scratch + 4*x, L, buf + 4*x, comp);
419 }
420 // process the blur up to the point where we start removing values from the moving average
421 size_t x;
422 for (x = 0; (x <= radius) && ((x + radius) < width); x++)
423 {
424 const int np = x + radius;
425 hits++;
426 load_add_4wide_Kahan(scratch + 4*np, L, buf + 4*np, comp);
427 store_scaled_4wide(buf + 4*x, L, hits);
428 }
429 // if radius > width/2, we have pixels for which we can neither add new values (x+radius >= width) nor
430 // remove old values (x-radius < 0)
431 for(; x <= radius && x < width; x++)
432 {
433 store_scaled_4wide(buf + 4*x, L, hits);
434 }
435 // process the blur for the bulk of the scan line
436 for(; x + radius < width; x++)
437 {
438 const int op = x - radius - 1;
439 const int np = x + radius;
440 sub_4wide_Kahan(L, scratch + 4*op, comp);
441 load_add_4wide_Kahan(scratch + 4*np, L, buf + 4*np, comp);
442 store_scaled_4wide(buf + 4*x, L, hits);
443 }
444 // process the right end where we have no more values to add to the running sum
445 for(; x < width; x++)
446 {
447 const int op = x - radius - 1;
448 hits--;
449 sub_4wide_Kahan(L, scratch + 4*op, comp);
450 store_scaled_4wide(buf + 4*x, L, hits);
451 }
452 return;
453}
454
455// invoked inside an OpenMP parallel for, so no need to parallelize
457static void blur_horizontal_Nch_Kahan(const size_t N, float *const restrict buf, const size_t width,
458 const size_t radius, float *const restrict scratch)
459{
460 if (N > 16) return;
461 if (N != 9) return; // since we only use 9 channels at the moment, give the compiler a big hint
462
463 float DT_ALIGNED_ARRAY L[16] = { 0, 0, 0, 0 };
464 float DT_ALIGNED_ARRAY comp[16] = { 0, 0, 0, 0 };
465 size_t hits = 0;
466 // add up the left half of the window
467 for (size_t x = 0; x < MIN(radius,width) ; x++)
468 {
469 hits++;
470 load_add_Nwide_Kahan(N, scratch + N*x, L, buf + N*x, comp);
471 }
472 // process the blur up to the point where we start removing values from the moving average
473 size_t x;
474 for (x = 0; (x <= radius) && ((x + radius) < width); x++)
475 {
476 const int np = x + radius;
477 hits++;
478 load_add_Nwide_Kahan(N, scratch + N*np, L, buf + N*np, comp);
479 store_scaled_Nwide(N, buf + N*x, L, hits);
480 }
481 // if radius > width/2, we have pixels for which we can neither add new values (x+radius >= width) nor
482 // remove old values (x-radius < 0)
483 for(; x <= radius && x < width; x++)
484 {
485 store_scaled_Nwide(N, buf + N*x, L, hits);
486 }
487 // process the blur for the bulk of the scan line
488 for(; x + radius < width; x++)
489 {
490 const int op = x - radius - 1;
491 const int np = x + radius;
492 sub_Nwide_Kahan(N, L, scratch + N*op, comp);
493 load_add_Nwide_Kahan(N, scratch + N*np, L, buf + N*np, comp);
494 store_scaled_Nwide(N, buf + N*x, L, hits);
495 }
496 // process the right end where we have no more values to add to the running sum
497 for(; x < width; x++)
498 {
499 const int op = x - radius - 1;
500 hits--;
501 sub_Nwide_Kahan(N, L, scratch + N*op, comp);
502 store_scaled_Nwide(N, buf + N*x, L, hits);
503 }
504 return;
505}
506
507// invoked inside an OpenMP parallel for, so no need to parallelize
509static void blur_vertical_1wide(float *const restrict buf, const size_t height, const size_t width,
510 const size_t radius, float *const restrict scratch)
511{
512 // To improve cache hit rates, we copy the final result from the scratch space back to the original
513 // location in the buffer as soon as we finish the final read of the buffer. To reduce the working
514 // set and further improve cache hits, we can treat the scratch space as a circular buffer and cycle
515 // through it repeatedly. To use a simple bitmask instead of a division, the size we cycle through
516 // needs to be the power of two larger than the window size (2*radius+1).
517 size_t mask = 1;
518 for(size_t r = (2*radius+1); r > 1 ; r >>= 1) mask = (mask << 1) | 1;
519
520 float L = 0.0f;
521 size_t hits = 0;
522 // add up the left half of the window
523 for (size_t y = 0; y < MIN(radius, height); y++)
524 {
525 PREFETCH_NTA(buf + (y+16)*width);
526 hits++;
527 const float v = buf[y*width];
528 L += v;
529 scratch[y&mask] = v;
530 }
531 // process up to the point where we start removing values from the moving average
532 size_t y;
533 for (y = 0; y <= radius && y + radius < height; y++)
534 {
535 // weirdly, changing any of the 'np' or 'op' variables in this function to 'size_t' yields a substantial slowdown!
536 const int np = y + radius;
537 PREFETCH_NTA(buf + (np+16)*width);
538 hits++;
539 const float v = buf[np*width];
540 L += v;
541 scratch[np&mask] = v;
542 buf[y*width] = L / hits;
543 }
544 // if radius > height/2, we have pixels for which we can neither add new values (y+radius >= height) nor
545 // remove old values (y-radius < 0)
546 for(; y <= radius && y < height; y++)
547 {
548 buf[y*width] = L / hits;
549 }
550 // process the bulk of the column
551 for( ; y + radius < height; y++)
552 {
553 const int np = y + radius;
554 const int op = y - radius - 1;
555 PREFETCH_NTA(buf + (np+16)*width);
556 L -= scratch[op&mask];
557 const float v = buf[np*width];
558 L += v;
559 scratch[np&mask] = v;
560 // update the means
561 buf[y*width] = L / hits;
562 }
563 // process the end of the column, where we don't have any more values to add to the mean
564 for( ; y < height; y++)
565 {
566 const int op = y - radius - 1;
567 hits--;
568 L -= scratch[op&mask];
569 // update the means
570 buf[y*width] = L / hits;
571 }
572 return;
573}
574
575// invoked inside an OpenMP parallel for, so no need to parallelize
577static void blur_vertical_1wide_Kahan(float *const restrict buf, const size_t height, const size_t width,
578 const size_t radius, float *const restrict scratch)
579{
580 // To improve cache hit rates, we copy the final result from the scratch space back to the original
581 // location in the buffer as soon as we finish the final read of the buffer. To reduce the working
582 // set and further improve cache hits, we can treat the scratch space as a circular buffer and cycle
583 // through it repeatedly. To use a simple bitmask instead of a division, the size we cycle through
584 // needs to be the power of two larger than the window size (2*radius+1).
585 size_t mask = 1;
586 for(size_t r = (2*radius+1); r > 1 ; r >>= 1) mask = (mask << 1) | 1;
587
588 float L = 0.0f;
589 float c = 0.0f;
590 size_t hits = 0;
591 // add up the left half of the window
592 for (size_t y = 0; y < MIN(radius, height); y++)
593 {
594 PREFETCH_NTA(buf + (y+16)*width);
595 hits++;
596 const float v = buf[y*width];
597 L = Kahan_sum(L, &c, v);
598 scratch[y&mask] = v;
599 }
600 // process up to the point where we start removing values from the moving average
601 size_t y;
602 for (y = 0; y <= radius && y + radius < height; y++)
603 {
604 // weirdly, changing any of the 'np' or 'op' variables in this function to 'size_t' yields a substantial slowdown!
605 const int np = y + radius;
606 hits++;
607 PREFETCH_NTA(buf + (np+16)*width);
608 const float v = buf[np*width];
609 L = Kahan_sum(L, &c, v);
610 scratch[np&mask] = v;
611 buf[y*width] = L / hits;
612 }
613 // if radius > height/2, we have pixels for which we can neither add new values (y+radius >= height) nor
614 // remove old values (y-radius < 0)
615 for(; y <= radius && y < height; y++)
616 {
617 buf[y*width] = L / hits;
618 }
619 // process the bulk of the column
620 for( ; y + radius < height; y++)
621 {
622 const int np = y + radius;
623 const int op = y - radius - 1;
624 PREFETCH_NTA(buf + (np+16)*width);
625 L = Kahan_sum(L, &c, -scratch[op&mask]);
626 const float v = buf[np*width];
627 L = Kahan_sum(L, &c, v);
628 scratch[np&mask] = v;
629 // update the means
630 buf[y*width] = L / hits;
631 }
632 // process the end of the column, where we don't have any more values to add to the mean
633 for( ; y < height; y++)
634 {
635 const int op = y - radius - 1;
636 hits--;
637 L = Kahan_sum(L, &c, scratch[op&mask]);
638 // update the means
639 buf[y*width] = L / hits;
640 }
641 return;
642}
643
644// invoked inside an OpenMP parallel for, so no need to parallelize
646static void blur_vertical_4wide(float *const restrict buf, const size_t height, const size_t width, const size_t radius,
647 float *const restrict scratch)
648{
649 // To improve cache hit rates, we copy the final result from the scratch space back to the original
650 // location in the buffer as soon as we finish the final read of the buffer. To reduce the working
651 // set and further improve cache hits, we can treat the scratch space as a circular buffer and cycle
652 // through it repeatedly. To use a simple bitmask instead of a division, the size we cycle through
653 // needs to be the power of two larger than the window size (2*radius+1).
654 size_t mask = 1;
655 for(size_t r = (2*radius+1); r > 1 ; r >>= 1) mask = (mask << 1) | 1;
656
657 dt_aligned_pixel_t L = { 0, 0, 0, 0 };
658 size_t hits = 0;
659 // add up the left half of the window
660 for (size_t y = 0; y < MIN(radius, height); y++)
661 {
662 DT_PREFETCH(buf + (y+16)*width);
663 hits++;
664 load_add_4wide(scratch + 4*(y&mask), L, buf + y * width);
665 }
666 // process the blur up to the point where we start removing values
667 size_t y;
668 for (y = 0; y <= radius && y + radius < height; y++)
669 {
670 // weirdly, changing any of the 'np' or 'op' variables in this function to 'size_t' yields a substantial slowdown!
671 const int np = y + radius;
672 hits++;
673 DT_PREFETCH(buf + (np+16)*width);
674 load_add_4wide(scratch + 4*(np&mask), L, buf + np*width);
675 store_scaled_4wide(buf + y*width, L, hits);
676 }
677 // if radius > height/2, we have pixels for which we can neither add new values (y+radius >= height) nor
678 // remove old values (y-radius < 0)
679 for(; y <= radius && y < height; y++)
680 {
681 store_scaled_4wide(buf + y*width, L, hits);
682 }
683 // process the blur for the bulk of the column
684 for ( ; y + radius < height; y++)
685 {
686 const int np = y + radius;
687 const int op = y - radius - 1;
688 DT_PREFETCH(buf + (np+16)*width);
689 sub_4wide(L, scratch + 4*(op&mask));
690 load_add_4wide(scratch + 4*(np&mask), L, buf + np*width);
691 store_scaled_4wide(buf + y*width, L, hits);
692 }
693 // process the blur for the end of the scan line, where we don't have any more values to add to the mean
694 for ( ; y < height; y++)
695 {
696 const int op = y - radius - 1;
697 hits--;
698 sub_4wide(L, scratch + 4*(op&mask));
699 store_scaled_4wide(buf + y*width, L, hits);
700 }
701 return;
702}
703
704// invoked inside an OpenMP parallel for, so no need to parallelize
706static void blur_vertical_4wide_Kahan(float *const restrict buf, const size_t height, const size_t width,
707 const size_t radius, float *const restrict scratch)
708{
709 // To improve cache hit rates, we copy the final result from the scratch space back to the original
710 // location in the buffer as soon as we finish the final read of the buffer. To reduce the working
711 // set and further improve cache hits, we can treat the scratch space as a circular buffer and cycle
712 // through it repeatedly. To use a simple bitmask instead of a division, the size we cycle through
713 // needs to be the power of two larger than the window size (2*radius+1).
714 size_t mask = 1;
715 for(size_t r = (2*radius+1); r > 1 ; r >>= 1) mask = (mask << 1) | 1;
716
717 dt_aligned_pixel_t L = { 0, 0, 0, 0 };
718 dt_aligned_pixel_t comp = { 0, 0, 0, 0 };
719 size_t hits = 0;
720 // add up the left half of the window
721 for (size_t y = 0; y < MIN(radius, height); y++)
722 {
723 DT_PREFETCH(buf + (y+16)*width);
724 hits++;
725 load_add_4wide_Kahan(scratch + 4*(y&mask), L, buf + y * width, comp);
726 }
727 // process the blur up to the point where we start removing values
728 size_t y;
729 for (y = 0; y <= radius && y + radius < height; y++)
730 {
731 // weirdly, changing any of the 'np' or 'op' variables in this function to 'size_t' yields a substantial slowdown!
732 const int np = y + radius;
733 hits++;
734 DT_PREFETCH(buf + (np+16)*width);
735 load_add_4wide_Kahan(scratch + 4*(np&mask), L, buf + np*width, comp);
736 store_scaled_4wide(buf + y*width, L, hits);
737 }
738 // if radius > height/2, we have pixels for which we can neither add new values (y+radius >= height) nor
739 // remove old values (y-radius < 0)
740 for(; y <= radius && y < height; y++)
741 {
742 store_scaled_4wide(buf + y*width, L, hits);
743 }
744 // process the blur for the bulk of the scan line
745 for ( ; y + radius < height; y++)
746 {
747 const int np = y + radius;
748 const int op = y - radius - 1;
749 DT_PREFETCH(buf + (np+16)*width);
750 sub_4wide_Kahan(L, scratch + 4*(op&mask), comp);
751 load_add_4wide_Kahan(scratch + 4*(np&mask), L, buf + np*width, comp);
752 store_scaled_4wide(buf + y*width, L, hits);
753 }
754 // process the blur for the end of the scan line, where we don't have any more values to add to the mean
755 for ( ; y < height; y++)
756 {
757 const int op = y - radius - 1;
758 hits--;
759 sub_4wide_Kahan(L, scratch + 4*(op&mask), comp);
760 store_scaled_4wide(buf + y*width, L, hits);
761 }
762 return;
763}
764
765// invoked inside an OpenMP parallel for, so no need to parallelize
767static void blur_vertical_16wide(float *const restrict buf, const size_t height, const size_t width,
768 const size_t radius, float *const restrict scratch)
769{
770 // To improve cache hit rates, we copy the final result from the scratch space back to the original
771 // location in the buffer as soon as we finish the final read of the buffer. To reduce the working
772 // set and further improve cache hits, we can treat the scratch space as a circular buffer and cycle
773 // through it repeatedly. To use a simple bitmask instead of a division, the size we cycle through
774 // needs to be the power of two larger than the window size (2*radius+1).
775 size_t mask = 1;
776 for(size_t r = (2*radius+1); r > 1 ; r >>= 1) mask = (mask << 1) | 1;
777
778 float DT_ALIGNED_ARRAY L[16] = { 0, 0, 0, 0 };
779 float hits = 0;
780 // add up the left half of the window
781 for (size_t y = 0; y < MIN(radius, height); y++)
782 {
783 PREFETCH_NTA(buf + (y+16)*width);
784 hits++;
785 load_add_16wide(scratch + 16 * (y&mask), L, buf + y*width);
786 }
787 // process the blur up to the point where we start removing values from the moving average
788 size_t y;
789 for (y = 0; y <= radius && y + radius < height; y++)
790 {
791 // weirdly, changing any of the 'np' or 'op' variables in this function to 'size_t' yields a substantial slowdown!
792 const int np = y + radius;
793 hits++;
794 PREFETCH_NTA(buf + (np+16)*width);
795 load_add_16wide(scratch + 16 * (np&mask), L, buf + np*width);
796 store_scaled_16wide(buf + y*width, L, hits);
797 }
798 // if radius > height/2, we have pixels for which we can neither add new values (y+radius >= height) nor
799 // remove old values (y-radius < 0)
800 for(; y <= radius && y < height; y++)
801 {
802 store_scaled_16wide(buf + y*width, L, hits);
803 }
804 // process the blur for the bulk of the column
805 for ( ; y + radius < height; y++)
806 {
807 const int np = y + radius;
808 const int op = y - radius - 1;
809 PREFETCH_NTA(buf + (np+16)*width);
810 sub_16wide(L, scratch + 16*(op&mask));
811 load_add_16wide(scratch + 16*(np&mask), L, buf + np*width);
812 // update the means
813 store_scaled_16wide(buf + y*width, L, hits);
814 }
815 // process the blur for the end of the scan line, where we don't have any more values to add to the mean
816 for ( ; y < height; y++)
817 {
818 const int op = y - radius - 1;
819 hits--;
820 sub_16wide(L, scratch + 16*(op&mask));
821 // update the means
822 store_scaled_16wide(buf + y*width, L, hits);
823 }
824 return;
825}
826
827// invoked inside an OpenMP parallel for, so no need to parallelize
829static void blur_vertical_16wide_Kahan(float *const restrict buf, const size_t height, const size_t width,
830 const size_t radius, float *const restrict scratch)
831{
832 // To improve cache hit rates, we copy the final result from the scratch space back to the original
833 // location in the buffer as soon as we finish the final read of the buffer. To reduce the working
834 // set and further improve cache hits, we can treat the scratch space as a circular buffer and cycle
835 // through it repeatedly. To use a simple bitmask instead of a division, the size we cycle through
836 // needs to be the power of two larger than the window size (2*radius+1).
837 size_t mask = 1;
838 for(size_t r = (2*radius+1); r > 1 ; r >>= 1) mask = (mask << 1) | 1;
839
840 float DT_ALIGNED_ARRAY L[16] = { 0, 0, 0, 0 };
841 float DT_ALIGNED_ARRAY comp[16] = { 0, 0, 0, 0 };
842 float hits = 0;
843 // add up the left half of the window
844 for (size_t y = 0; y < MIN(radius, height); y++)
845 {
846 DT_PREFETCH(buf + (y+16)*width);
847 hits++;
848 load_add_16wide_Kahan(scratch + 16 * (y&mask), L, buf + y*width, comp);
849 }
850 // process the blur up to the point where we start removing values from the moving average
851 size_t y;
852 for (y = 0; y <= radius && y + radius < height; y++)
853 {
854 // weirdly, changing any of the 'np' or 'op' variables in this function to 'size_t' yields a substantial slowdown!
855 const int np = y + radius;
856 hits++;
857 DT_PREFETCH(buf + (np+16)*width);
858 load_add_16wide_Kahan(scratch + 16 * (np&mask), L, buf + np*width, comp);
859 store_scaled_16wide(buf + y*width, L, hits);
860 }
861 // if radius > height/2, we have pixels for which we can neither add new values (y+radius >= height) nor
862 // remove old values (y-radius < 0)
863 for(; y <= radius && y < height; y++)
864 {
865 store_scaled_16wide(buf + y*width, L, hits);
866 }
867 // process the blur for the bulk of the column
868 for ( ; y + radius < height; y++)
869 {
870 const int np = y + radius;
871 const int op = y - radius - 1;
872 DT_PREFETCH(buf + (np+16)*width);
873 sub_16wide_Kahan(L, scratch + 16*(op&mask), comp);
874 load_add_16wide_Kahan(scratch + 16*(np&mask), L, buf + np*width, comp);
875 // update the means
876 store_scaled_16wide(buf + y*width, L, hits);
877 }
878 // process the blur for the end of the scan line, where we don't have any more values to add to the mean
879 for ( ; y < height; y++)
880 {
881 const int op = y - radius - 1;
882 hits--;
883 sub_16wide_Kahan(L, scratch + 16*(op&mask), comp);
884 // update the means
885 store_scaled_16wide(buf + y*width, L, hits);
886 }
887 return;
888}
889
891static void blur_vertical_1ch(float *const restrict buf, const size_t height, const size_t width, const size_t radius,
892 float *const restrict scanlines, const size_t padded_size)
893{
895 for(int x = 0; x < width; x += 16)
896 {
897 float *const restrict scratch = dt_get_perthread(scanlines,padded_size);
898 if (x + 16 <= width)
899 {
900 blur_vertical_16wide(buf + x, height, width, radius, scratch);
901 }
902 else
903 {
904 // handle the leftover 1..15 columns, first in groups of four, then the final 0..3 singly
905 int col = x;
906 for( ; col < (width & ~3); col += 4)
907 blur_vertical_4wide(buf + col, height, width, radius, scratch);
908 for( ; col < width; col++)
909 blur_vertical_1wide(buf + col, height, width, radius, scratch);
910 }
911 }
912 return;
913}
914
915// determine the size of the scratch buffer needed for vertical passes of the box-mean filter
916// filter_window = 2**ceil(lg2(2*radius+1))
918static size_t _compute_effective_height(const size_t height, const size_t radius)
919{
920 size_t eff_height = 2;
921 for(size_t r = (2*radius+1); r > 1 ; r >>= 1) eff_height <<= 1;
922 eff_height = MIN(eff_height,height);
923 return eff_height;
924}
925
927static int dt_box_mean_1ch(float *const buf, const size_t height, const size_t width, const size_t radius,
928 const unsigned iterations)
929{
930 // scratch space needed per thread:
931 // width floats to store one row during horizontal pass
932 // 16*filter_window floats for vertical pass
933 const size_t eff_height = _compute_effective_height(height,radius);
934 const size_t size = MAX(width,16*eff_height);
935 size_t padded_size;
936 float *const restrict scanlines = dt_pixelpipe_cache_alloc_perthread_float(size, &padded_size);
937 if(IS_NULL_PTR(scanlines)) return 1;
938
939 for(unsigned iteration = 0; iteration < iterations; iteration++)
940 {
941 blur_horizontal_1ch(buf, height, width, radius, scanlines, padded_size);
942 blur_vertical_1ch(buf, height, width, radius, scanlines, padded_size);
943 }
944
946 return 0;
947}
948
950static int dt_box_mean_4ch(float *const buf, const int height, const int width, const int radius,
951 const unsigned iterations)
952{
953 // scratch space needed per thread:
954 // 4*width floats to store one row during horizontal pass
955 // 16*filter_window floats for vertical pass
956 const size_t eff_height = _compute_effective_height(height,radius);
957 const size_t size = MAX(4*width,16*eff_height);
958 size_t padded_size;
959 float *const restrict scanlines = dt_pixelpipe_cache_alloc_perthread_float(size, &padded_size);
960 if(IS_NULL_PTR(scanlines)) return 1;
961
962 for(unsigned iteration = 0; iteration < iterations; iteration++)
963 {
964 blur_horizontal_4ch(buf, height, width, radius, scanlines, padded_size);
965 // we need to multiply width by 4 to get the correct stride for the vertical blur
966 blur_vertical_1ch(buf, height, 4*width, radius, scanlines, padded_size);
967 }
968
970 return 0;
971}
972
974static int box_mean_vert_1ch_Kahan(float *const buf, const int height, const size_t width, const size_t radius)
975{
976 const size_t eff_height = _compute_effective_height(height,radius);
977 size_t padded_size;
978 float *const restrict scratch_buf = dt_pixelpipe_cache_alloc_perthread_float(16*eff_height,&padded_size);
979 if(IS_NULL_PTR(scratch_buf)) return 1;
981 for (size_t col = 0; col < width; col += 16)
982 {
983 float *const restrict scratch = dt_get_perthread(scratch_buf,padded_size);
984 if (col + 16 <= width)
985 {
986 blur_vertical_16wide_Kahan(buf + col, height, width, radius, scratch);
987 }
988 else
989 {
990 // handle the 1..15 remaining columns
991 size_t col_ = col;
992 for( ; col_ < (width & ~3); col_ += 4)
993 blur_vertical_4wide_Kahan(buf + col_, height, width, radius, scratch);
994 for( ; col_ < width; col_++)
995 blur_vertical_1wide_Kahan(buf + col_, height, width, radius, scratch);
996 }
997 }
998
1000 return 0;
1001}
1002
1004static int dt_box_mean_4ch_Kahan(float *const buf, const size_t height, const size_t width, const int radius,
1005 const unsigned iterations)
1006{
1007
1008 for(unsigned iteration = 0; iteration < iterations; iteration++)
1009 {
1010 size_t padded_size;
1011 float *const restrict scanlines = dt_pixelpipe_cache_alloc_perthread_float(4*width,&padded_size);
1012 if(IS_NULL_PTR(scanlines)) return 1;
1014 for (size_t row = 0; row < height; row++)
1015 {
1016 float *const restrict scratch = dt_get_perthread(scanlines,padded_size);
1017 blur_horizontal_4ch_Kahan(buf + row * 4 * width, width, radius, scratch);
1018 }
1019
1021
1022 if(box_mean_vert_1ch_Kahan(buf, height, 4*width, radius) != 0) return 1;
1023 }
1024
1025 return 0;
1026}
1027
1028static inline int box_mean_2ch(float *const restrict in, const size_t height, const size_t width,
1029 const int radius, const unsigned iterations)
1030{
1031 // Compute in-place a box average (filter) on a multi-channel image over a window of size 2*radius + 1
1032 // We make use of the separable nature of the filter kernel to speed-up the computation
1033 // by convolving along columns and rows separately (complexity O(2 x radius) instead of O(radius²)).
1034
1035 const size_t eff_height = _compute_effective_height(height, radius);
1036 const size_t Ndim = MAX(4*width,16*eff_height);
1037 size_t padded_size;
1038 float *const restrict temp = dt_pixelpipe_cache_alloc_perthread_float(Ndim, &padded_size);
1039 if (IS_NULL_PTR(temp)) return 1;
1040
1041 for (unsigned iteration = 0; iteration < iterations; iteration++)
1042 {
1043 blur_horizontal_2ch(in, height, width, radius, temp, padded_size);
1044 blur_vertical_1ch(in, height, 2*width, radius, temp, padded_size);
1045 }
1047 return 0;
1048}
1049
1050int dt_box_mean(float *const buf, const size_t height, const size_t width, const int ch,
1051 const int radius, const unsigned iterations)
1052{
1053 if (ch == 1)
1054 {
1055 return dt_box_mean_1ch(buf,height,width,radius,iterations);
1056 }
1057 else if (ch == 4)
1058 {
1059 return dt_box_mean_4ch(buf,height,width,radius,iterations);
1060 }
1061 else if (ch == (4|BOXFILTER_KAHAN_SUM))
1062 {
1063 return dt_box_mean_4ch_Kahan(buf,height,width,radius,iterations);
1064 }
1065 else if (ch == 2) // used by fast_guided_filter.h
1066 {
1067 return box_mean_2ch(buf,height,width,radius,iterations);
1068 }
1069 else
1071 return 1;
1072}
1073
1074int dt_box_mean_horizontal(float *const restrict buf, const size_t width, const int ch, const int radius,
1075 float *const restrict user_scratch)
1076{
1077 if (ch == (4|BOXFILTER_KAHAN_SUM))
1078 {
1079 float *const restrict scratch = user_scratch ? user_scratch
1081 if(IS_NULL_PTR(scratch)) return 1;
1082
1083 blur_horizontal_4ch_Kahan(buf, width, radius, scratch);
1084 if (IS_NULL_PTR(user_scratch))
1086 return 0;
1087 }
1088 else if (ch == (9|BOXFILTER_KAHAN_SUM))
1089 {
1090 float *const restrict scratch = user_scratch ? user_scratch
1092 if(IS_NULL_PTR(scratch)) return 1;
1093
1094 blur_horizontal_Nch_Kahan(9, buf, width, radius, scratch);
1095 if (IS_NULL_PTR(user_scratch))
1097 return 0;
1098 }
1099 else
1101 return 1;
1102}
1103
1104int dt_box_mean_vertical(float *const buf, const size_t height, const size_t width, const int ch, const int radius)
1105{
1106 if ((ch & BOXFILTER_KAHAN_SUM) && (ch & ~BOXFILTER_KAHAN_SUM) <= 16)
1107 {
1108 size_t channels = ch & ~BOXFILTER_KAHAN_SUM;
1109 return box_mean_vert_1ch_Kahan(buf, height, channels*width, radius);
1110 }
1111 else
1113 return 1;
1114}
1115
1116static inline float window_max(const float *x, int n)
1117{
1118 float m = -(FLT_MAX);
1119 __OMP_SIMD__(reduction(max : m))
1120 for(int j = 0; j < n; j++)
1121 m = MAX(m, x[j]);
1122 return m;
1123}
1124
1125// calculate the one-dimensional moving maximum over a window of size 2*w+1
1126// input array x has stride 1, output array y has stride stride_y
1127static inline void box_max_1d(int N, const float *const restrict x, float *const restrict y, size_t stride_y, int w)
1128{
1129 float m = window_max(x, MIN(w + 1, N));
1130 for(int i = 0; i < N; i++)
1131 {
1132 // store maximum of current window at center position
1133 y[i * stride_y] = m;
1134 // if the earliest member of the current window is the max, we need to
1135 // rescan the window to determine the new maximum
1136 if(i - w >= 0 && x[i - w] == m)
1137 {
1138 const int start = i - w + 1;
1139 m = window_max(x + start, MIN(i + w + 2, N) - start);
1140 }
1141 // if the window has not yet exceeded the end of the row/column, update the maximum value
1142 if(i + w + 1 < N)
1143 m = MAX(x[i + w + 1], m);
1144 }
1145}
1146
1148static void set_16wide(float *const restrict out, const float value)
1149{
1150 __OMP_SIMD__(aligned(out : 64))
1151 for (size_t c = 0; c < 16; c++)
1152 out[c] = value;
1153}
1154
1155static inline void update_max_16wide(float m[16], const float *const restrict base)
1156{
1157 __OMP_SIMD__(aligned(m, base : 64))
1158 for (size_t c = 0; c < 16; c++)
1159 {
1160 m[c] = fmaxf(m[c], base[c]);
1161 }
1162}
1163
1164static inline void load_update_max_16wide(float *const restrict out, float m[16], const float *const restrict base)
1165{
1166 __OMP_SIMD__(aligned(out, m : 64))
1167 for (size_t c = 0; c < 16; c++)
1168 {
1169 const float v = base[c];
1170 out[c] = v;
1171 m[c] = fmaxf(m[c], v);
1172 }
1173}
1174
1175// calculate the one-dimensional moving maximum on four adjacent columns over a window of size 2*w+1
1176// input/output array 'buf' has stride 'stride' and we will write 16 consecutive elements every stride elements
1177// (thus processing a cache line at a time)
1178static inline void box_max_vert_16wide(const int N, float *const restrict scratch, float *const restrict buf,
1179 const int stride, const int w, const size_t mask)
1180{
1181 float DT_ALIGNED_ARRAY m[16] = { -(FLT_MAX), -(FLT_MAX), -(FLT_MAX), -(FLT_MAX),
1182 -(FLT_MAX), -(FLT_MAX), -(FLT_MAX), -(FLT_MAX),
1183 -(FLT_MAX), -(FLT_MAX), -(FLT_MAX), -(FLT_MAX),
1184 -(FLT_MAX), -(FLT_MAX), -(FLT_MAX), -(FLT_MAX) };
1185 for(size_t i = 0; i < MIN(w + 1, N); i++)
1186 {
1187 PREFETCH_NTA(buf + stride*(i+24));
1188 load_update_max_16wide(scratch + 16 * (i&mask),m, buf + stride*i);
1189 }
1190 for(size_t i = 0; i < N; i++)
1191 {
1192 PREFETCH_NTA(buf + stride*(i+24));
1193 // store maximum of current window at center position
1194 store_16wide(buf + stride * i, m);
1195 // If the earliest member of the current window is the max, we need to
1196 // rescan the window to determine the new maximum
1197 if (i >= w)
1198 {
1199 set_16wide(m, -(FLT_MAX)); // reset max values to lowest possible
1200 for(int j = i - w + 1; j < MIN(i + w + 1, N); j++)
1201 {
1202 update_max_16wide(m,scratch + 16*(j&mask));
1203 }
1204 }
1205 // if the window has not yet exceeded the end of the row/column, update the maximum value
1206 const size_t n = i + w + 1;
1207 if(n < N)
1208 {
1209 load_update_max_16wide(scratch + 16 * (n&mask), m, buf + stride * n);
1210 }
1211 }
1212}
1213
1214// calculate the two-dimensional moving maximum over a box of size (2*w+1) x (2*w+1)
1215// does the calculation in-place if input and output images are identical
1217static int box_max_1ch(float *const buf, const size_t height, const size_t width, const unsigned w)
1218{
1219 const size_t eff_height = _compute_effective_height(height, w);
1220 const size_t scratch_size = MAX(width,MAX(height,16*eff_height));
1221 size_t allocsize;
1222 float *const restrict scratch_buffers = dt_pixelpipe_cache_alloc_perthread_float(scratch_size,&allocsize);
1223 if(IS_NULL_PTR(scratch_buffers)) return 1;
1225 for(size_t row = 0; row < height; row++)
1226 {
1227 float *const restrict scratch = dt_get_perthread(scratch_buffers,allocsize);
1228 memcpy(scratch, buf + row * width, sizeof(float) * width);
1229 box_max_1d(width, scratch, buf + row * width, 1, w);
1230 }
1232 for(int col = 0; col < (width & ~15); col += 16)
1233 {
1234 float *const restrict scratch = dt_get_perthread(scratch_buffers,allocsize);
1235 box_max_vert_16wide(height, scratch, buf + col, width, w, eff_height-1);
1236 }
1237 // handle the leftover 0..15 columns
1238 for (size_t col = width & ~15 ; col < width; col++)
1239 {
1240 float *const restrict scratch = scratch_buffers;
1241 for(size_t row = 0; row < height; row++)
1242 scratch[row] = buf[row * width + col];
1243 box_max_1d(height, scratch, buf + col, width, w);
1244 }
1245 dt_pixelpipe_cache_free_align(scratch_buffers);
1246 return 0;
1247}
1248
1249
1250// in-place calculate the two-dimensional moving maximum over a box of size (2*radius+1) x (2*radius+1)
1251int dt_box_max(float *const buf, const size_t height, const size_t width, const int ch, const int radius)
1252{
1253 if (ch == 1)
1254 return box_max_1ch(buf, height, width, radius);
1255 else
1256 //TODO: 4ch version if needed
1258 return 1;
1259}
1260
1261static inline float window_min(const float *x, int n)
1262{
1263 float m = FLT_MAX;
1264 __OMP_SIMD__(reduction(min : m))
1265 for(int j = 0; j < n; j++)
1266 m = MIN(m, x[j]);
1267 return m;
1268}
1269
1270// calculate the one-dimensional moving minimum over a window of size 2*w+1
1271// input array x has stride 1, output array y has stride stride_y
1272static inline void box_min_1d(int N, const float *x, float *y, size_t stride_y, int w)
1273{
1274 float m = window_min(x, MIN(w + 1, N));
1275 for(int i = 0; i < N; i++)
1276 {
1277 y[i * stride_y] = m;
1278 if(i - w >= 0 && x[i - w] == m)
1279 {
1280 const int start = (i - w + 1);
1281 m = window_min(x + start, MIN((i + w + 2), N) - start);
1282 }
1283 // if the window has not yet exceeded the end of the row/column, update the minimum value
1284 if(i + w + 1 < N)
1285 m = MIN(x[i + w + 1], m);
1286 }
1287}
1288
1289static inline void update_min_16wide(float m[16], const float *const restrict base)
1290{
1291 __OMP_SIMD__(aligned(m, base : 64))
1292 for (size_t c = 0; c < 16; c++)
1293 {
1294 m[c] = fminf(m[c], base[c]);
1295 }
1296}
1297
1298static inline void load_update_min_16wide(float *const restrict out, float m[16], const float *const restrict base)
1299{
1300 __OMP_SIMD__(aligned(out, m : 64))
1301 for (size_t c = 0; c < 16; c++)
1302 {
1303 const float v = base[c];
1304 out[c] = v;
1305 m[c] = fminf(m[c], v);
1306 }
1307}
1308
1309// calculate the one-dimensional moving minimum on four adjacent columns over a window of size 2*w+1
1310// input/output array 'buf' has stride 'stride' and we will write 16 consecutive elements every stride elements
1311// (thus processing a cache line at a time)
1312static inline void box_min_vert_16wide(const int N, float *const restrict scratch, float *const restrict buf,
1313 const int stride, const int w, const size_t mask)
1314{
1315 float DT_ALIGNED_ARRAY m[16] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX,
1316 FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX,
1317 FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX,
1318 FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX };
1319 for(size_t i = 0; i < MIN(w + 1, N); i++)
1320 {
1321 PREFETCH_NTA(buf + stride*(i+24));
1322 load_update_min_16wide(scratch + 16*(i&mask), m, buf + stride*i);
1323 }
1324 for(size_t i = 0; i < N; i++)
1325 {
1326 PREFETCH_NTA(buf + stride*(i+24));
1327 // store minimum of current window at center position
1328 store_16wide(buf + i * stride, m);
1329 // If the earliest member of the current window is the min, we need to
1330 // rescan the window to determine the new minimum
1331 if (i >= w)
1332 {
1333 set_16wide(m, FLT_MAX); // reset min values to the highest possible
1334 for(int j = i - w + 1; j < MIN(i + w + 1, N); j++)
1335 {
1336 update_min_16wide(m,scratch + 16*(j&mask));
1337 }
1338 }
1339 // if the window has not yet exceeded the end of the row/column, update the minimum value
1340 const size_t n = i + w + 1;
1341 if(n < N)
1342 {
1343 load_update_min_16wide(scratch + 16 * (n&mask), m, buf + stride * n);
1344 }
1345 }
1346}
1347
1348
1349// calculate the two-dimensional moving minimum over a box of size (2*w+1) x (2*w+1)
1350// does the calculation in-place if input and output images are identical
1352static int box_min_1ch(float *const buf, const size_t height, const size_t width, const int w)
1353{
1354 const size_t eff_height = _compute_effective_height(height, w);
1355 const size_t scratch_size = MAX(width,MAX(height,16*eff_height));
1356 size_t allocsize;
1357 float *const restrict scratch_buffers = dt_pixelpipe_cache_alloc_perthread_float(scratch_size,&allocsize);
1358 if(IS_NULL_PTR(scratch_buffers)) return 1;
1360 for(size_t row = 0; row < height; row++)
1361 {
1362 float *const restrict scratch = dt_get_perthread(scratch_buffers,allocsize);
1363 memcpy(scratch, buf + row * width, sizeof(float) * width);
1364 box_min_1d(width, scratch, buf + row * width, 1, w);
1365 }
1367 for(size_t col = 0; col < (width & ~15); col += 16)
1368 {
1369 float *const restrict scratch = dt_get_perthread(scratch_buffers,allocsize);
1370 box_min_vert_16wide(height, scratch, buf + col, width, w, eff_height-1);
1371 }
1372 // handle the leftover 0..15 columns
1373 for (size_t col = width & ~15 ; col < width; col++)
1374 {
1375 float *const restrict scratch = scratch_buffers;
1376 for(size_t row = 0; row < height; row++)
1377 scratch[row] = buf[row * width + col];
1378 box_min_1d(height, scratch, buf + col, width, w);
1379 }
1380
1381 dt_pixelpipe_cache_free_align(scratch_buffers);
1382 return 0;
1383}
1384
1385int dt_box_min(float *const buf, const size_t height, const size_t width, const int ch, const int radius)
1386{
1387 if (ch == 1)
1388 return box_min_1ch(buf, height, width, radius);
1389 else
1390 //TODO: 4ch version if needed
1392 return 1;
1393}
1394// clang-format off
1395// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1396// vim: shiftwidth=2 expandtab tabstop=2 cindent
1397// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1398// clang-format on
#define m
Definition basecurve.c:283
static __DT_CLONE_TARGETS__ void blur_vertical_16wide(float *const restrict buf, const size_t height, const size_t width, const size_t radius, float *const restrict scratch)
static __DT_CLONE_TARGETS__ void set_16wide(float *const restrict out, const float value)
static void update_max_16wide(float m[16], const float *const restrict base)
static __DT_CLONE_TARGETS__ void blur_vertical_1wide(float *const restrict buf, const size_t height, const size_t width, const size_t radius, float *const restrict scratch)
static void load_update_max_16wide(float *const restrict out, float m[16], const float *const restrict base)
#define PREFETCH_NTA(addr)
Definition box_filters.c:48
static void update_min_16wide(float m[16], const float *const restrict base)
static float window_max(const float *x, int n)
static int box_mean_2ch(float *const restrict in, const size_t height, const size_t width, const int radius, const unsigned iterations)
static __DT_CLONE_TARGETS__ int box_max_1ch(float *const buf, const size_t height, const size_t width, const unsigned w)
static __DT_CLONE_TARGETS__ void blur_vertical_16wide_Kahan(float *const restrict buf, const size_t height, const size_t width, const size_t radius, float *const restrict scratch)
static __DT_CLONE_TARGETS__ void sub_16wide_Kahan(float *const restrict accum, const float *const restrict values, float *const restrict comp)
int dt_box_max(float *const buf, const size_t height, const size_t width, const int ch, const int radius)
static __DT_CLONE_TARGETS__ void blur_vertical_1ch(float *const restrict buf, const size_t height, const size_t width, const size_t radius, float *const restrict scanlines, const size_t padded_size)
static __DT_CLONE_TARGETS__ int dt_box_mean_4ch_Kahan(float *const buf, const size_t height, const size_t width, const int radius, const unsigned iterations)
static __DT_CLONE_TARGETS__ void blur_horizontal_4ch(float *const restrict buf, const size_t height, const size_t width, const size_t radius, float *const restrict scanlines, const size_t padded_size)
static __DT_CLONE_TARGETS__ void blur_vertical_4wide_Kahan(float *const restrict buf, const size_t height, const size_t width, const size_t radius, float *const restrict scratch)
static __DT_CLONE_TARGETS__ void blur_horizontal_1ch(float *const restrict buf, const int height, const int width, const int radius, float *const restrict scanlines, const size_t padded_size)
Definition box_filters.c:52
static __DT_CLONE_TARGETS__ void blur_horizontal_2ch(float *const restrict buf, const int height, const int width, const int radius, float *const restrict scanlines, const size_t padded_size)
static float window_min(const float *x, int n)
static __DT_CLONE_TARGETS__ int dt_box_mean_1ch(float *const buf, const size_t height, const size_t width, const size_t radius, const unsigned iterations)
static __DT_CLONE_TARGETS__ int dt_box_mean_4ch(float *const buf, const int height, const int width, const int radius, const unsigned iterations)
#define DT_PREFETCH(addr)
Definition box_filters.c:47
static __DT_CLONE_TARGETS__ void blur_vertical_4wide(float *const restrict buf, const size_t height, const size_t width, const size_t radius, float *const restrict scratch)
static __DT_CLONE_TARGETS__ void store_scaled_Nwide(const size_t N, float *const restrict out, const float *const restrict in, const float scale)
static __DT_CLONE_TARGETS__ void sub_Nwide_Kahan(const size_t N, float *const restrict accum, const float *const restrict values, float *const restrict comp)
static __DT_CLONE_TARGETS__ void sub_16wide(float *const restrict accum, const float *const restrict values)
static __DT_CLONE_TARGETS__ void store_16wide(float *const restrict out, const float *const restrict in)
static __DT_CLONE_TARGETS__ int box_mean_vert_1ch_Kahan(float *const buf, const int height, const size_t width, const size_t radius)
int dt_box_mean_horizontal(float *const restrict buf, const size_t width, const int ch, const int radius, float *const restrict user_scratch)
static __DT_CLONE_TARGETS__ void blur_vertical_1wide_Kahan(float *const restrict buf, const size_t height, const size_t width, const size_t radius, float *const restrict scratch)
int dt_box_mean(float *const buf, const size_t height, const size_t width, const int ch, const int radius, const unsigned iterations)
static void box_max_1d(int N, const float *const restrict x, float *const restrict y, size_t stride_y, int w)
static __DT_CLONE_TARGETS__ int box_min_1ch(float *const buf, const size_t height, const size_t width, const int w)
int dt_box_mean_vertical(float *const buf, const size_t height, const size_t width, const int ch, const int radius)
static __DT_CLONE_TARGETS__ void store_scaled_16wide(float *const restrict out, const float *const restrict in, const float scale)
static void load_update_min_16wide(float *const restrict out, float m[16], const float *const restrict base)
static __DT_CLONE_TARGETS__ void blur_horizontal_4ch_Kahan(float *const restrict buf, const size_t width, const size_t radius, float *const restrict scratch)
static __DT_CLONE_TARGETS__ void blur_horizontal_Nch_Kahan(const size_t N, float *const restrict buf, const size_t width, const size_t radius, float *const restrict scratch)
static void box_min_vert_16wide(const int N, float *const restrict scratch, float *const restrict buf, const int stride, const int w, const size_t mask)
static __DT_CLONE_TARGETS__ void load_add_Nwide_Kahan(const size_t N, float *const restrict out, float *const restrict accum, const float *const restrict in, float *const restrict comp)
static void box_max_vert_16wide(const int N, float *const restrict scratch, float *const restrict buf, const int stride, const int w, const size_t mask)
static __DT_CLONE_TARGETS__ void load_add_16wide(float *const restrict out, float *const restrict accum, const float *const restrict in)
static __DT_CLONE_TARGETS__ size_t _compute_effective_height(const size_t height, const size_t radius)
int dt_box_min(float *const buf, const size_t height, const size_t width, const int ch, const int radius)
static __DT_CLONE_TARGETS__ void load_add_16wide_Kahan(float *const restrict out, float *const restrict accum, const float *const restrict in, float *const restrict comp)
static void box_min_1d(int N, const float *x, float *y, size_t stride_y, int w)
#define BOXFILTER_KAHAN_SUM
Definition box_filters.h:37
static const float x
const float v
static const float const float const float min
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
float *const restrict const size_t const size_t ch
#define dt_unreachable_codepath()
Mark a branch as impossible.
Definition macros.h:141
#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 Kahan_sum(const float m, float *const __restrict__ c, const float add)
Definition math.h:105
#define DT_ALIGNED_ARRAY
Align an object on a cacheline boundary, so AVX2 can load it whole.
Definition mem_alloc.h:80
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define N
#define __OMP_SIMD__(...)
Definition openmp.h:99
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, 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
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
#define for_four_channels(_var,...)
Definition simd.h:89
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float r
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29