Blender V4.5
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
17#include "UI_interface.hh"
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
40
42{
43 b.add_input<decl::Color>("Image")
44 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
45 .compositor_domain_priority(0);
46 b.add_input<decl::Vector>("Normal")
47 .default_value({0.0f, 0.0f, 0.0f})
48 .min(-1.0f)
49 .max(1.0f)
50 .hide_value()
51 .compositor_domain_priority(2);
52 b.add_input<decl::Color>("Albedo")
53 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
54 .hide_value()
55 .compositor_domain_priority(1);
56 b.add_input<decl::Bool>("HDR").default_value(true).compositor_expects_single_value();
57
58 b.add_output<decl::Color>("Image");
59}
60
68
69static bool is_oidn_supported()
70{
71#ifdef WITH_OPENIMAGEDENOISE
72# if defined(__APPLE__)
73 /* Always supported through Accelerate framework BNNS. */
74 return true;
75# elif defined(__aarch64__) || defined(_M_ARM64)
76 /* OIDN 2.2 and up supports ARM64 on Windows and Linux. */
77 return true;
78# else
79 return BLI_cpu_support_sse42();
80# endif
81#else
82 return false;
83#endif
84}
85
87{
88#ifndef WITH_OPENIMAGEDENOISE
89 layout->label(RPT_("Disabled. Built without OpenImageDenoise"), ICON_ERROR);
90#else
91 if (!is_oidn_supported()) {
92 layout->label(RPT_("Disabled. Platform not supported"), ICON_ERROR);
93 }
94#endif
95
96 layout->label(IFACE_("Prefilter:"), ICON_NONE);
97 layout->prop(ptr, "prefilter", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
98 layout->label(IFACE_("Quality:"), ICON_NONE);
99 layout->prop(ptr, "quality", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE);
100}
101
102using namespace blender::compositor;
103
104/* A callback to cancel the filter operations by evaluating the context's is_canceled method. The
105 * API specifies that true indicates the filter should continue, while false indicates it should
106 * stop, so invert the condition. This callback can also be used to track progress using the given
107 * n argument, but we currently don't make use of it. See OIDNProgressMonitorFunction in the API
108 * for more information. */
109[[maybe_unused]] static bool oidn_progress_monitor_function(void *user_ptr, double /*n*/)
110{
111 const Context *context = static_cast<const Context *>(user_ptr);
112 return !context->is_canceled();
113}
114
116 public:
118
119 void execute() override
120 {
121 const Result &input_image = get_input("Image");
122 Result &output_image = get_result("Image");
123
124 if (!is_oidn_supported() || input_image.is_single_value()) {
125 output_image.share_data(input_image);
126 return;
127 }
128
129 output_image.allocate_texture(input_image.domain());
130
131#ifdef WITH_OPENIMAGEDENOISE
132 oidn::DeviceRef device = create_oidn_device(this->context());
133 device.set("setAffinity", false);
134 device.commit();
135
136 const int width = input_image.domain().size.x;
137 const int height = input_image.domain().size.y;
138 const int pixel_stride = sizeof(float) * 4;
139 const eGPUDataFormat data_format = GPU_DATA_FLOAT;
140
141 Vector<float *> temporary_buffers_to_free;
142
143 float *input_color = nullptr;
144 float *output_color = nullptr;
145 if (this->context().use_gpu()) {
146 /* Download the input texture and set it as both the input and output of the filter to
147 * denoise it in-place. Make sure to track the downloaded buffer to be later freed. */
149 input_color = static_cast<float *>(GPU_texture_read(input_image, data_format, 0));
150 output_color = input_color;
151 temporary_buffers_to_free.append(input_color);
152 }
153 else {
154 input_color = const_cast<float *>(static_cast<const float *>(input_image.cpu_data().data()));
155 output_color = static_cast<float *>(output_image.cpu_data().data());
156 }
157
158 const int64_t buffer_size = int64_t(width) * height * input_image.channels_count();
159 const MutableSpan<float> input_buffer_span = MutableSpan<float>(input_color, buffer_size);
160 oidn::BufferRef input_buffer = create_oidn_buffer(device, input_buffer_span);
161 const MutableSpan<float> output_buffer_span = MutableSpan<float>(output_color, buffer_size);
162 oidn::BufferRef output_buffer = create_oidn_buffer(device, output_buffer_span);
163
164 oidn::FilterRef filter = device.newFilter("RT");
165 filter.setImage("color", input_buffer, oidn::Format::Float3, width, height, 0, pixel_stride);
166 filter.setImage("output", output_buffer, oidn::Format::Float3, width, height, 0, pixel_stride);
167 filter.set("hdr", use_hdr());
168 filter.set("cleanAux", auxiliary_passes_are_clean());
169 this->set_filter_quality(filter);
170 filter.setProgressMonitorFunction(oidn_progress_monitor_function, &context());
171
172 /* If the albedo input is not a single value input, set it to the albedo input of the filter,
173 * denoising it if needed. */
174 Result &input_albedo = this->get_input("Albedo");
175 if (!input_albedo.is_single_value()) {
176 float *albedo = nullptr;
178 albedo = input_albedo.derived_resources()
180 .get(this->context(),
181 input_albedo,
182 DenoisedAuxiliaryPassType::Albedo,
183 this->get_quality())
184 .denoised_buffer;
185 }
186 else {
187 if (this->context().use_gpu()) {
188 albedo = static_cast<float *>(GPU_texture_read(input_albedo, data_format, 0));
189 temporary_buffers_to_free.append(albedo);
190 }
191 else {
192 albedo = static_cast<float *>(input_albedo.cpu_data().data());
193 }
194 }
195
196 const MutableSpan<float> albedo_buffer_span = MutableSpan<float>(albedo, buffer_size);
197 oidn::BufferRef albedo_buffer = create_oidn_buffer(device, albedo_buffer_span);
198
199 filter.setImage(
200 "albedo", albedo_buffer, oidn::Format::Float3, width, height, 0, pixel_stride);
201 }
202
203 /* If the albedo and normal inputs are not single value inputs, set the normal input to the
204 * albedo input of the filter, denoising it if needed. Notice that we also consider the albedo
205 * input because OIDN doesn't support denoising with only the normal auxiliary pass. */
206 Result &input_normal = this->get_input("Normal");
207 if (!input_albedo.is_single_value() && !input_normal.is_single_value()) {
208 float *normal = nullptr;
210 normal = input_normal.derived_resources()
212 .get(this->context(),
213 input_normal,
214 DenoisedAuxiliaryPassType::Normal,
215 this->get_quality())
216 .denoised_buffer;
217 }
218 else {
219 if (this->context().use_gpu()) {
220 normal = static_cast<float *>(GPU_texture_read(input_normal, data_format, 0));
221 temporary_buffers_to_free.append(normal);
222 }
223 else {
224 normal = static_cast<float *>(input_normal.cpu_data().data());
225 }
226 }
227
228 /* Float3 results might be stored in 4-component textures due to hardware limitations, so we
229 * need to use the pixel stride of the texture. */
230 const int normal_channels_count = this->context().use_gpu() ?
232 GPU_texture_format(input_normal)) :
233 input_normal.channels_count();
234 int normal_pixel_stride = sizeof(float) * normal_channels_count;
235
236 const int64_t normal_buffer_size = int64_t(width) * height * normal_channels_count;
237 const MutableSpan<float> normal_buffer_span = MutableSpan<float>(normal, normal_buffer_size);
238 oidn::BufferRef normal_buffer = create_oidn_buffer(device, normal_buffer_span);
239
240 filter.setImage(
241 "normal", normal_buffer, oidn::Format::Float3, width, height, 0, normal_pixel_stride);
242 }
243
244 filter.commit();
245 filter.execute();
246
247 if (output_buffer.getStorage() != oidn::Storage::Host) {
248 output_buffer.read(0, buffer_size * sizeof(float), output_color);
249 }
250
251 if (this->context().use_gpu()) {
252 GPU_texture_update(output_image, data_format, output_color);
253 }
254 else {
255 /* OIDN already wrote to the output directly, however, OIDN skips the alpha channel, so we
256 * need to restore it. */
257 parallel_for(int2(width, height), [&](const int2 texel) {
258 const float alpha = input_image.load_pixel<float4>(texel).w;
259 output_image.store_pixel(texel,
260 float4(output_image.load_pixel<float4>(texel).xyz(), alpha));
261 });
262 }
263
264 for (float *buffer : temporary_buffers_to_free) {
265 MEM_freeN(buffer);
266 }
267#endif
268 }
269
270 /* If the pre-filter mode is set to CMP_NODE_DENOISE_PREFILTER_NONE, that it means the supplied
271 * auxiliary passes are already noise-free, if it is set to CMP_NODE_DENOISE_PREFILTER_ACCURATE,
272 * the auxiliary passes will be denoised before denoising the main image, so in both cases, the
273 * auxiliary passes are considered clean. If it is set to CMP_NODE_DENOISE_PREFILTER_FAST on the
274 * other hand, the auxiliary passes are assumed to be noisy and are thus not clean, and will be
275 * denoised while denoising the main image. */
280
281 /* Returns whether the auxiliary passes should be denoised, see the auxiliary_passes_are_clean
282 * method for more information. */
287
288 bool use_hdr()
289 {
290 return this->get_input("HDR").get_single_value_default(true);
291 }
292
294 {
295 return static_cast<CMPNodeDenoisePrefilter>(node_storage(bnode()).prefilter);
296 }
297
298#ifdef WITH_OPENIMAGEDENOISE
299# if OIDN_VERSION_MAJOR >= 2
300 oidn::Quality get_quality()
301 {
302 const CMPNodeDenoiseQuality node_quality = static_cast<CMPNodeDenoiseQuality>(
303 node_storage(bnode()).quality);
304
305 if (node_quality == CMP_NODE_DENOISE_QUALITY_SCENE) {
306 const eCompositorDenoiseQaulity scene_quality = context().get_denoise_quality();
307 switch (scene_quality) {
308# if OIDN_VERSION >= 20300
310 return oidn::Quality::Fast;
311# endif
313 return oidn::Quality::Balanced;
315 default:
316 return oidn::Quality::High;
317 }
318 }
319
320 switch (node_quality) {
321# if OIDN_VERSION >= 20300
323 return oidn::Quality::Fast;
324# endif
326 return oidn::Quality::Balanced;
328 default:
329 return oidn::Quality::High;
330 }
331 }
332# endif /* OIDN_VERSION_MAJOR >= 2 */
333
334 void set_filter_quality([[maybe_unused]] oidn::FilterRef &filter)
335 {
336# if OIDN_VERSION_MAJOR >= 2
337 oidn::Quality quality = this->get_quality();
338 filter.set("quality", quality);
339# endif
340 }
341#endif /* WITH_OPENIMAGEDENOISE */
342};
343
345{
346 return new DenoiseOperation(context, node);
347}
348
349} // namespace blender::nodes::node_composite_denoise_cc
350
352{
354
355 static blender::bke::bNodeType ntype;
356
357 cmp_node_type_base(&ntype, "CompositorNodeDenoise", CMP_NODE_DENOISE);
358 ntype.ui_name = "Denoise";
359 ntype.ui_description = "Denoise renders from Cycles and other ray tracing renderers";
360 ntype.enum_name_legacy = "DENOISE";
362 ntype.declare = file_ns::cmp_node_denoise_declare;
363 ntype.draw_buttons = file_ns::node_composit_buts_denoise;
364 ntype.initfunc = file_ns::node_composit_init_denonise;
367 ntype.get_compositor_operation = file_ns::get_compositor_operation;
368
370}
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1215
#define NODE_CLASS_OP_FILTER
Definition BKE_node.hh:437
#define CMP_NODE_DENOISE
int BLI_cpu_support_sse42(void)
Definition system.cc:165
#define RPT_(msgid)
#define IFACE_(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_ACCURATE
eCompositorDenoiseQaulity
@ SCE_COMPOSITOR_DENOISE_FAST
@ SCE_COMPOSITOR_DENOISE_BALANCED
@ SCE_COMPOSITOR_DENOISE_HIGH
void GPU_memory_barrier(eGPUBarrier barrier)
Definition gpu_state.cc:385
@ GPU_BARRIER_TEXTURE_UPDATE
Definition GPU_state.hh:39
void * GPU_texture_read(GPUTexture *texture, eGPUDataFormat data_format, int mip_level)
eGPUDataFormat
@ GPU_DATA_FLOAT
size_t GPU_texture_component_len(eGPUTextureFormat format)
void GPU_texture_update(GPUTexture *texture, eGPUDataFormat data_format, const void *data)
eGPUTextureFormat GPU_texture_format(const GPUTexture *texture)
Read Guarded memory(de)allocation.
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ UI_ITEM_R_SPLIT_EMPTY_NAME
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)
virtual eCompositorDenoiseQaulity get_denoise_quality() const =0
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:401
T get_single_value_default(const T &default_value) const
void allocate_texture(Domain domain, bool from_pool=true)
Definition result.cc:309
void store_pixel(const int2 &texel, const T &pixel_value)
const Domain & domain() const
T load_pixel(const int2 &texel) const
int64_t channels_count() const
DerivedResources & derived_resources()
Definition result.cc:593
bool is_single_value() const
Definition result.cc:625
#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:2748
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:5603
void parallel_for(const int2 range, const Function &function)
static void node_composit_buts_denoise(uiLayout *layout, bContext *, PointerRNA *ptr)
static NodeOperation * get_compositor_operation(Context &context, DNode node)
static void cmp_node_denoise_declare(NodeDeclarationBuilder &b)
static bool oidn_progress_monitor_function(void *user_ptr, double)
static void node_composit_init_denonise(bNodeTree *, bNode *node)
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
void * storage
Defines a node type.
Definition BKE_node.hh:226
std::string ui_description
Definition BKE_node.hh:232
NodeGetCompositorOperationFunction get_compositor_operation
Definition BKE_node.hh:336
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:277
const char * enum_name_legacy
Definition BKE_node.hh:235
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:355
void label(blender::StringRef name, int icon)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
max
Definition text_draw.cc:251
PointerRNA * ptr
Definition wm_files.cc:4227