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