Blender V5.0
node_composite_denoise.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2019 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#ifndef __APPLE__
10# include "BLI_system.h"
11#endif
12
13#include "BLI_span.hh"
14
15#include "MEM_guardedalloc.h"
16
18#include "UI_resources.hh"
19
20#include "GPU_state.hh"
21#include "GPU_texture.hh"
22
23#include "DNA_node_types.h"
24
27#include "COM_node_operation.hh"
28#include "COM_utilities.hh"
29#include "COM_utilities_oidn.hh"
30
32
33#ifdef WITH_OPENIMAGEDENOISE
34# include <OpenImageDenoise/oidn.hpp>
35#endif
36
38
41 "NONE",
42 0,
43 N_("None"),
44 N_("No prefiltering, use when guiding passes are noise-free")},
46 "FAST",
47 0,
48 N_("Fast"),
49 N_("Denoise image and guiding passes together. Improves quality when guiding passes are "
50 "noisy using least amount of extra processing time.")},
52 "ACCURATE",
53 0,
54 N_("Accurate"),
55 N_("Prefilter noisy guiding passes before denoising image. Improves quality when guiding "
56 "passes are noisy using extra processing time.")},
57 {0, nullptr, 0, nullptr, nullptr}};
58
61 "FOLLOW_SCENE",
62 0,
63 N_("Follow Scene"),
64 N_("Use the scene's denoising quality setting")},
65 {CMP_NODE_DENOISE_QUALITY_HIGH, "HIGH", 0, "High", "High quality"},
67 "BALANCED",
68 0,
69 N_("Balanced"),
70 N_("Balanced between performance and quality")},
71 {CMP_NODE_DENOISE_QUALITY_FAST, "FAST", 0, "Fast", "High performance"},
72 {0, nullptr, 0, nullptr, nullptr}};
73
75{
76 b.use_custom_socket_order();
77 b.allow_any_socket_order();
78 b.add_input<decl::Color>("Image")
79 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
80 .hide_value()
81 .structure_type(StructureType::Dynamic);
82 b.add_output<decl::Color>("Image").structure_type(StructureType::Dynamic).align_with_previous();
83
84 b.add_input<decl::Color>("Albedo")
85 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
86 .hide_value()
87 .structure_type(StructureType::Dynamic);
88 b.add_input<decl::Vector>("Normal")
89 .default_value({0.0f, 0.0f, 0.0f})
90 .min(-1.0f)
91 .max(1.0f)
92 .hide_value()
93 .structure_type(StructureType::Dynamic);
94 b.add_input<decl::Bool>("HDR").default_value(true);
95 b.add_input<decl::Menu>("Prefilter")
97 .static_items(prefilter_items)
99 b.add_input<decl::Menu>("Quality")
100 .default_value(CMP_NODE_DENOISE_QUALITY_SCENE)
101 .static_items(quality_items)
103}
104
105static void node_composit_init_denonise(bNodeTree * /*ntree*/, bNode *node)
106{
107 /* Unused, kept for forward compatibility. */
108 NodeDenoise *ndg = MEM_callocN<NodeDenoise>(__func__);
109 node->storage = ndg;
110}
111
112static bool is_oidn_supported()
113{
114#ifdef WITH_OPENIMAGEDENOISE
115# if defined(__APPLE__)
116 /* Always supported through Accelerate framework BNNS. */
117 return true;
118# elif defined(__aarch64__) || defined(_M_ARM64)
119 /* OIDN 2.2 and up supports ARM64 on Windows and Linux. */
120 return true;
121# else
122 return BLI_cpu_support_sse42();
123# endif
124#else
125 return false;
126#endif
127}
128
129static void node_composit_buts_denoise(uiLayout *layout, bContext * /*C*/, PointerRNA * /*ptr*/)
130{
131#ifndef WITH_OPENIMAGEDENOISE
132 layout->label(RPT_("Disabled. Built without OpenImageDenoise"), ICON_ERROR);
133#else
134 if (!is_oidn_supported()) {
135 layout->label(RPT_("Disabled. Platform not supported"), ICON_ERROR);
136 }
137#endif
138}
139
140using namespace blender::compositor;
141
142/* A callback to cancel the filter operations by evaluating the context's is_canceled method. The
143 * API specifies that true indicates the filter should continue, while false indicates it should
144 * stop, so invert the condition. This callback can also be used to track progress using the given
145 * n argument, but we currently don't make use of it. See OIDNProgressMonitorFunction in the API
146 * for more information. */
147[[maybe_unused]] static bool oidn_progress_monitor_function(void *user_ptr, double /*n*/)
148{
149 const Context *context = static_cast<const Context *>(user_ptr);
150 return !context->is_canceled();
151}
152
154 public:
156
157 void execute() override
158 {
159 const Result &input_image = get_input("Image");
160 Result &output_image = get_result("Image");
161
162 if (!is_oidn_supported() || input_image.is_single_value()) {
163 output_image.share_data(input_image);
164 return;
165 }
166
167 output_image.allocate_texture(input_image.domain());
168
169#ifdef WITH_OPENIMAGEDENOISE
170 oidn::DeviceRef device = create_oidn_device(this->context());
171 device.set("setAffinity", false);
172 device.commit();
173
174 const int width = input_image.domain().size.x;
175 const int height = input_image.domain().size.y;
176 const int pixel_stride = sizeof(float) * 4;
177 const eGPUDataFormat data_format = GPU_DATA_FLOAT;
178
179 Vector<float *> temporary_buffers_to_free;
180
181 float *input_color = nullptr;
182 float *output_color = nullptr;
183 if (this->context().use_gpu()) {
184 /* Download the input texture and set it as both the input and output of the filter to
185 * denoise it in-place. Make sure to track the downloaded buffer to be later freed. */
187 input_color = static_cast<float *>(GPU_texture_read(input_image, data_format, 0));
188 output_color = input_color;
189 temporary_buffers_to_free.append(input_color);
190 }
191 else {
192 input_color = const_cast<float *>(static_cast<const float *>(input_image.cpu_data().data()));
193 output_color = static_cast<float *>(output_image.cpu_data().data());
194 }
195
196 const int64_t buffer_size = int64_t(width) * height * input_image.channels_count();
197 const MutableSpan<float> input_buffer_span = MutableSpan<float>(input_color, buffer_size);
198 oidn::BufferRef input_buffer = create_oidn_buffer(device, input_buffer_span);
199 const MutableSpan<float> output_buffer_span = MutableSpan<float>(output_color, buffer_size);
200 oidn::BufferRef output_buffer = create_oidn_buffer(device, output_buffer_span);
201
202 oidn::FilterRef filter = device.newFilter("RT");
203 filter.setImage("color", input_buffer, oidn::Format::Float3, width, height, 0, pixel_stride);
204 filter.setImage("output", output_buffer, oidn::Format::Float3, width, height, 0, pixel_stride);
205 filter.set("hdr", use_hdr());
206 filter.set("cleanAux", auxiliary_passes_are_clean());
207 this->set_filter_quality(filter);
208 filter.setProgressMonitorFunction(oidn_progress_monitor_function, &context());
209
210 /* If the albedo input is not a single value input, set it to the albedo input of the filter,
211 * denoising it if needed. */
212 Result &input_albedo = this->get_input("Albedo");
213 if (!input_albedo.is_single_value()) {
214 float *albedo = nullptr;
216 albedo = input_albedo.derived_resources()
218 .get(this->context(),
219 input_albedo,
220 DenoisedAuxiliaryPassType::Albedo,
221 this->get_quality())
222 .denoised_buffer;
223 }
224 else {
225 if (this->context().use_gpu()) {
226 albedo = static_cast<float *>(GPU_texture_read(input_albedo, data_format, 0));
227 temporary_buffers_to_free.append(albedo);
228 }
229 else {
230 albedo = static_cast<float *>(input_albedo.cpu_data().data());
231 }
232 }
233
234 const MutableSpan<float> albedo_buffer_span = MutableSpan<float>(albedo, buffer_size);
235 oidn::BufferRef albedo_buffer = create_oidn_buffer(device, albedo_buffer_span);
236
237 filter.setImage(
238 "albedo", albedo_buffer, oidn::Format::Float3, width, height, 0, pixel_stride);
239 }
240
241 /* If the albedo and normal inputs are not single value inputs, set the normal input to the
242 * albedo input of the filter, denoising it if needed. Notice that we also consider the albedo
243 * input because OIDN doesn't support denoising with only the normal auxiliary pass. */
244 Result &input_normal = this->get_input("Normal");
245 if (!input_albedo.is_single_value() && !input_normal.is_single_value()) {
246 float *normal = nullptr;
248 normal = input_normal.derived_resources()
250 .get(this->context(),
251 input_normal,
252 DenoisedAuxiliaryPassType::Normal,
253 this->get_quality())
254 .denoised_buffer;
255 }
256 else {
257 if (this->context().use_gpu()) {
258 normal = static_cast<float *>(GPU_texture_read(input_normal, data_format, 0));
259 temporary_buffers_to_free.append(normal);
260 }
261 else {
262 normal = static_cast<float *>(input_normal.cpu_data().data());
263 }
264 }
265
266 /* Float3 results might be stored in 4-component textures due to hardware limitations, so we
267 * need to use the pixel stride of the texture. */
268 const int normal_channels_count = this->context().use_gpu() ?
270 GPU_texture_format(input_normal)) :
271 input_normal.channels_count();
272 int normal_pixel_stride = sizeof(float) * normal_channels_count;
273
274 const int64_t normal_buffer_size = int64_t(width) * height * normal_channels_count;
275 const MutableSpan<float> normal_buffer_span = MutableSpan<float>(normal, normal_buffer_size);
276 oidn::BufferRef normal_buffer = create_oidn_buffer(device, normal_buffer_span);
277
278 filter.setImage(
279 "normal", normal_buffer, oidn::Format::Float3, width, height, 0, normal_pixel_stride);
280 }
281
282 filter.commit();
283 filter.execute();
284
285 if (output_buffer.getStorage() != oidn::Storage::Host) {
286 output_buffer.read(0, buffer_size * sizeof(float), output_color);
287 }
288
289 if (this->context().use_gpu()) {
290 GPU_texture_update(output_image, data_format, output_color);
291 }
292 else {
293 /* OIDN already wrote to the output directly, however, OIDN skips the alpha channel, so we
294 * need to restore it. */
295 parallel_for(int2(width, height), [&](const int2 texel) {
296 const float alpha = input_image.load_pixel<float4>(texel).w;
297 output_image.store_pixel(texel,
298 float4(output_image.load_pixel<float4>(texel).xyz(), alpha));
299 });
300 }
301
302 for (float *buffer : temporary_buffers_to_free) {
303 MEM_freeN(buffer);
304 }
305#endif
306 }
307
308 /* If the pre-filter mode is set to CMP_NODE_DENOISE_PREFILTER_NONE, that it means the supplied
309 * auxiliary passes are already noise-free, if it is set to CMP_NODE_DENOISE_PREFILTER_ACCURATE,
310 * the auxiliary passes will be denoised before denoising the main image, so in both cases, the
311 * auxiliary passes are considered clean. If it is set to CMP_NODE_DENOISE_PREFILTER_FAST on the
312 * other hand, the auxiliary passes are assumed to be noisy and are thus not clean, and will be
313 * denoised while denoising the main image. */
318
319 /* Returns whether the auxiliary passes should be denoised, see the auxiliary_passes_are_clean
320 * method for more information. */
325
326#ifdef WITH_OPENIMAGEDENOISE
327# if OIDN_VERSION_MAJOR >= 2
328 oidn::Quality get_quality()
329 {
330 const CMPNodeDenoiseQuality node_quality = this->get_quality_mode();
331
332 if (node_quality == CMP_NODE_DENOISE_QUALITY_SCENE) {
333 const eCompositorDenoiseQaulity scene_quality = context().get_denoise_quality();
334 switch (scene_quality) {
335# if OIDN_VERSION >= 20300
337 return oidn::Quality::Fast;
338# endif
340 return oidn::Quality::Balanced;
342 return oidn::Quality::High;
343 }
344
345 return oidn::Quality::High;
346 }
347
348 switch (node_quality) {
349# if OIDN_VERSION >= 20300
351 return oidn::Quality::Fast;
352# endif
354 return oidn::Quality::Balanced;
357 return oidn::Quality::High;
358 }
359
360 return oidn::Quality::High;
361 }
362# endif /* OIDN_VERSION_MAJOR >= 2 */
363
364 void set_filter_quality([[maybe_unused]] oidn::FilterRef &filter)
365 {
366# if OIDN_VERSION_MAJOR >= 2
367 oidn::Quality quality = this->get_quality();
368 filter.set("quality", quality);
369# endif
370 }
371#endif /* WITH_OPENIMAGEDENOISE */
372
373 bool use_hdr()
374 {
375 return this->get_input("HDR").get_single_value_default(true);
376 }
377
379 {
380 const Result &input = this->get_input("Prefilter");
381 const MenuValue default_menu_value = MenuValue(CMP_NODE_DENOISE_PREFILTER_ACCURATE);
382 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
383 return static_cast<CMPNodeDenoisePrefilter>(menu_value.value);
384 }
385
387 {
388 const Result &input = this->get_input("Quality");
389 const MenuValue default_menu_value = MenuValue(CMP_NODE_DENOISE_QUALITY_SCENE);
390 const MenuValue menu_value = input.get_single_value_default(default_menu_value);
391 return static_cast<CMPNodeDenoiseQuality>(menu_value.value);
392 }
393};
394
396{
397 return new DenoiseOperation(context, node);
398}
399
400} // namespace blender::nodes::node_composite_denoise_cc
401
403{
405
406 static blender::bke::bNodeType ntype;
407
408 cmp_node_type_base(&ntype, "CompositorNodeDenoise", CMP_NODE_DENOISE);
409 ntype.ui_name = "Denoise";
410 ntype.ui_description = "Denoise renders from Cycles and other ray tracing renderers";
411 ntype.enum_name_legacy = "DENOISE";
413 ntype.declare = file_ns::cmp_node_denoise_declare;
414 ntype.draw_buttons = file_ns::node_composit_buts_denoise;
415 ntype.initfunc = file_ns::node_composit_init_denonise;
418 ntype.get_compositor_operation = file_ns::get_compositor_operation;
419
421}
#define NODE_CLASS_OP_FILTER
Definition BKE_node.hh:451
#define CMP_NODE_DENOISE
int BLI_cpu_support_sse42(void)
Definition system.cc:168
#define RPT_(msgid)
CMPNodeDenoiseQuality
@ CMP_NODE_DENOISE_QUALITY_BALANCED
@ CMP_NODE_DENOISE_QUALITY_FAST
@ CMP_NODE_DENOISE_QUALITY_SCENE
@ CMP_NODE_DENOISE_QUALITY_HIGH
CMPNodeDenoisePrefilter
@ CMP_NODE_DENOISE_PREFILTER_FAST
@ CMP_NODE_DENOISE_PREFILTER_NONE
@ CMP_NODE_DENOISE_PREFILTER_ACCURATE
eCompositorDenoiseQaulity
@ SCE_COMPOSITOR_DENOISE_FAST
@ SCE_COMPOSITOR_DENOISE_BALANCED
@ SCE_COMPOSITOR_DENOISE_HIGH
@ GPU_BARRIER_TEXTURE_UPDATE
Definition GPU_state.hh:39
void GPU_memory_barrier(GPUBarrier barrier)
Definition gpu_state.cc:326
size_t GPU_texture_component_len(blender::gpu::TextureFormat format)
blender::gpu::TextureFormat GPU_texture_format(const blender::gpu::Texture *texture)
eGPUDataFormat
@ GPU_DATA_FLOAT
void * GPU_texture_read(blender::gpu::Texture *texture, eGPUDataFormat data_format, int mip_level)
void GPU_texture_update(blender::gpu::Texture *texture, eGPUDataFormat data_format, const void *data)
Read Guarded memory(de)allocation.
#define NOD_REGISTER_NODE(REGISTER_FUNC)
BMesh const char void * data
long long int int64_t
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
Definition btQuadWord.h:119
void append(const T &value)
eCompositorDenoiseQaulity get_denoise_quality() const
virtual bool use_gpu() const =0
DenoisedAuxiliaryPassContainer denoised_auxiliary_passes
NodeOperation(Context &context, DNode node)
Result & get_result(StringRef identifier)
Definition operation.cc:39
Result & get_input(StringRef identifier) const
Definition operation.cc:138
void share_data(const Result &source)
Definition result.cc:523
T get_single_value_default(const T &default_value) const
void store_pixel(const int2 &texel, const T &pixel_value)
void allocate_texture(const Domain domain, const bool from_pool=true, const std::optional< ResultStorageType > storage_type=std::nullopt)
Definition result.cc:389
const Domain & domain() const
T load_pixel(const int2 &texel) const
int64_t channels_count() const
DerivedResources & derived_resources()
Definition result.cc:726
bool is_single_value() const
Definition result.cc:758
nullptr float
#define input
#define filter
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
void node_register_type(bNodeType &ntype)
Definition node.cc:2416
void node_type_storage(bNodeType &ntype, std::optional< StringRefNull > storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:5414
void parallel_for(const int2 range, const Function &function)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static void cmp_node_denoise_declare(NodeDeclarationBuilder &b)
static void node_composit_buts_denoise(uiLayout *layout, bContext *, PointerRNA *)
static bool oidn_progress_monitor_function(void *user_ptr, double)
static void node_composit_init_denonise(bNodeTree *, bNode *node)
static const EnumPropertyItem quality_items[]
static const EnumPropertyItem prefilter_items[]
VecBase< float, 4 > float4
VecBase< int32_t, 2 > int2
static void register_node_type_cmp_denoise()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:42
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:54
#define min(a, b)
Definition sort.cc:36
void * storage
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:348
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
const char * enum_name_legacy
Definition BKE_node.hh:247
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:259
NodeDeclareFunction declare
Definition BKE_node.hh:362
void label(blender::StringRef name, int icon)
#define N_(msgid)