Blender V4.5
fog_glow_kernel.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <complex>
6#include <cstdint>
7#include <memory>
8#include <numeric>
9
10#if defined(WITH_FFTW3)
11# include <fftw3.h>
12#endif
13
15#include "BLI_hash.hh"
16#include "BLI_index_range.hh"
17#include "BLI_math_base.h"
18#include "BLI_math_base.hh"
19#include "BLI_math_numbers.hh"
21#include "BLI_task.hh"
22
24
25namespace blender::compositor {
26
27/* --------------------------------------------------------------------
28 * Fog Glow Kernel Key.
29 */
30
35
40
42{
43 return a.kernel_size == b.kernel_size && a.spatial_size == b.spatial_size;
44}
45
46/* --------------------------------------------------------------------
47 * Fog Glow Kernel.
48 */
49
50/* Given the x and y location in the range from 0 to kernel_size - 1, where kernel_size is odd,
51 * compute the fog glow kernel value. The equations are arbitrary and were chosen using visual
52 * judgment. The kernel is not normalized and need normalization. */
53[[maybe_unused]] static float compute_fog_glow_kernel_value(int x, int y, int kernel_size)
54{
55 const int half_kernel_size = kernel_size / 2;
56 const float scale = 0.25f * math::sqrt(math::square(kernel_size));
57 const float v = ((y - half_kernel_size) / float(half_kernel_size));
58 const float u = ((x - half_kernel_size) / float(half_kernel_size));
59 const float r = (math::square(u) + math::square(v)) * scale;
60 const float d = -math::sqrt(math::sqrt(math::sqrt(r))) * 9.0f;
61 const float kernel_value = math::exp(d);
62
63 const float window = (0.5f + 0.5f * math::cos(u * math::numbers::pi)) *
64 (0.5f + 0.5f * math::cos(v * math::numbers::pi));
65 const float windowed_kernel_value = window * kernel_value;
66
67 return windowed_kernel_value;
68}
69
70FogGlowKernel::FogGlowKernel(int kernel_size, int2 spatial_size)
71{
72#if defined(WITH_FFTW3)
73
74 /* The FFTW real to complex transforms utilizes the hermitian symmetry of real transforms and
75 * stores only half the output since the other half is redundant, so we only allocate half of
76 * the first dimension. See Section 4.3.4 Real-data DFT Array Format in the FFTW manual for
77 * more information. */
78 const int2 frequency_size = int2(spatial_size.x / 2 + 1, spatial_size.y);
79
80 float *kernel_spatial_domain = fftwf_alloc_real(spatial_size.x * spatial_size.y);
81 frequencies_ = reinterpret_cast<std::complex<float> *>(
82 fftwf_alloc_complex(frequency_size.x * frequency_size.y));
83
84 /* Create a real to complex plan to transform the kernel to the frequency domain. */
85 fftwf_plan forward_plan = fftwf_plan_dft_r2c_2d(spatial_size.y,
86 spatial_size.x,
87 kernel_spatial_domain,
88 reinterpret_cast<fftwf_complex *>(frequencies_),
89 FFTW_ESTIMATE);
90
91 /* Use a double to sum the kernel since floats are not stable with threaded summation. */
92 threading::EnumerableThreadSpecific<double> sum_by_thread([]() { return 0.0; });
93
94 /* Compute the kernel while zero padding to match the padded image size. */
95 threading::parallel_for(IndexRange(spatial_size.y), 1, [&](const IndexRange sub_y_range) {
96 double &sum = sum_by_thread.local();
97 for (const int64_t y : sub_y_range) {
98 for (const int64_t x : IndexRange(spatial_size.x)) {
99 /* We offset the computed kernel with wrap around such that it is centered at the zero
100 * point, which is the expected format for doing circular convolutions in the frequency
101 * domain. */
102 const int half_kernel_size = kernel_size / 2;
103 int64_t output_x = mod_i(x - half_kernel_size, spatial_size.x);
104 int64_t output_y = mod_i(y - half_kernel_size, spatial_size.y);
105
106 const bool is_inside_kernel = x < kernel_size && y < kernel_size;
107 if (is_inside_kernel) {
108 const float kernel_value = compute_fog_glow_kernel_value(x, y, kernel_size);
109 kernel_spatial_domain[output_x + output_y * spatial_size.x] = kernel_value;
110 sum += kernel_value;
111 }
112 else {
113 kernel_spatial_domain[output_x + output_y * spatial_size.x] = 0.0f;
114 }
115 }
116 }
117 });
118
119 fftwf_execute_dft_r2c(
120 forward_plan, kernel_spatial_domain, reinterpret_cast<fftwf_complex *>(frequencies_));
121 fftwf_destroy_plan(forward_plan);
122 fftwf_free(kernel_spatial_domain);
123
124 /* The computed kernel is not normalized and should be normalized, but instead of normalizing the
125 * kernel during computation, we normalize it in the frequency domain when convolving the kernel
126 * to the image since we will be doing sample normalization anyways. This is okay since the
127 * Fourier transform is linear. */
128 normalization_factor_ = float(std::accumulate(sum_by_thread.begin(), sum_by_thread.end(), 0.0));
129#else
130 UNUSED_VARS(kernel_size, spatial_size);
131#endif
132}
133
135{
136#if defined(WITH_FFTW3)
137 fftwf_free(frequencies_);
138#endif
139}
140
141std::complex<float> *FogGlowKernel::frequencies() const
142{
143 return frequencies_;
144}
145
147{
148 return normalization_factor_;
149}
150
151/* --------------------------------------------------------------------
152 * Fog Glow Kernel Container.
153 */
154
156{
157 /* First, delete all resources that are no longer needed. */
158 map_.remove_if([](auto item) { return !item.value->needed; });
159
160 /* Second, reset the needed status of the remaining resources to false to ready them to track
161 * their needed status for the next evaluation. */
162 for (auto &value : map_.values()) {
163 value->needed = false;
164 }
165}
166
167FogGlowKernel &FogGlowKernelContainer::get(int kernel_size, int2 spatial_size)
168{
169 const FogGlowKernelKey key(kernel_size, spatial_size);
170
171 auto &kernel = *map_.lookup_or_add_cb(
172 key, [&]() { return std::make_unique<FogGlowKernel>(kernel_size, spatial_size); });
173
174 kernel.needed = true;
175 return kernel;
176}
177
178} // namespace blender::compositor
#define UNUSED_VARS(...)
ATTR_WARN_UNUSED_RESULT const BMVert * v
unsigned long long int uint64_t
FogGlowKernel & get(int kernel_size, int2 spatial_size)
FogGlowKernelKey(int kernel_size, int2 spatial_size)
FogGlowKernel(int kernel_size, int2 spatial_size)
std::complex< float > * frequencies() const
bool operator==(const BokehKernelKey &a, const BokehKernelKey &b)
static float compute_fog_glow_kernel_value(int x, int y, int kernel_size)
T cos(const AngleRadianBase< T > &a)
T sqrt(const T &a)
T exp(const T &x)
T square(const T &a)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
uint64_t get_default_hash(const T &v, const Args &...args)
Definition BLI_hash.hh:233
VecBase< int32_t, 2 > int2