Realtime Compositor: Implement Denoise node #107375

Merged
Omar Emara merged 1 commits from OmarEmaraDev/blender:denoise-node into main 2023-05-09 11:09:14 +02:00
2 changed files with 158 additions and 7 deletions
Showing only changes of commit 468633fa06 - Show all commits

View File

@ -146,6 +146,15 @@ endif()
if(WITH_OPENIMAGEDENOISE)
add_definitions(-DWITH_OPENIMAGEDENOISE)
add_definitions(-DOIDN_STATIC_LIB)
list(APPEND INC_SYS
${OPENIMAGEDENOISE_INCLUDE_DIRS}
${TBB_INCLUDE_DIRS}
)
list(APPEND LIB
${OPENIMAGEDENOISE_LIBRARIES}
${TBB_LIBRARIES}
)
endif()
blender_add_lib(bf_nodes_composite "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -9,24 +9,43 @@
#include "BLT_translation.h"
#include "MEM_guardedalloc.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "GPU_state.h"
#include "GPU_texture.h"
#include "DNA_node_types.h"
#include "COM_node_operation.hh"
#include "node_composite_util.hh"
#ifdef WITH_OPENIMAGEDENOISE
# include <OpenImageDenoise/oidn.hpp>
#endif
namespace blender::nodes::node_composite_denoise_cc {
NODE_STORAGE_FUNCS(NodeDenoise)
static void cmp_node_denoise_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Color>(N_("Image")).default_value({1.0f, 1.0f, 1.0f, 1.0f});
b.add_input<decl::Color>(N_("Image"))
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
.compositor_domain_priority(0);
b.add_input<decl::Vector>(N_("Normal"))
.default_value({0.0f, 0.0f, 0.0f})
.min(-1.0f)
.max(1.0f)
.hide_value();
b.add_input<decl::Color>(N_("Albedo")).default_value({1.0f, 1.0f, 1.0f, 1.0f}).hide_value();
.hide_value()
.compositor_domain_priority(2);
b.add_input<decl::Color>(N_("Albedo"))
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
.hide_value()
.compositor_domain_priority(1);
b.add_output<decl::Color>(N_("Image"));
}
@ -64,8 +83,133 @@ class DenoiseOperation : public NodeOperation {
void execute() override
{
get_input("Image").pass_through(get_result("Image"));
context().set_info_message("Viewport compositor setup not fully supported");
Result &input_image = get_input("Image");
Result &output_image = get_result("Image");
if (!is_oidn_supported() || input_image.is_single_value()) {
input_image.pass_through(output_image);
return;
}
#ifdef WITH_OPENIMAGEDENOISE
oidn::DeviceRef device = oidn::newDevice();
device.commit();
const int width = input_image.domain().size.x;
const int height = input_image.domain().size.y;
const int pixel_stride = sizeof(float) * 4;
const eGPUDataFormat data_format = GPU_DATA_FLOAT;
/* Download the input texture and set it as both the input and output of the filter to denoise
* it in-place. */
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
float *color = static_cast<float *>(GPU_texture_read(input_image.texture(), data_format, 0));
oidn::FilterRef filter = device.newFilter("RT");
filter.setImage("color", color, oidn::Format::Float3, width, height, 0, pixel_stride);
filter.setImage("output", color, oidn::Format::Float3, width, height, 0, pixel_stride);
filter.set("hdr", use_hdr());
filter.set("cleanAux", auxiliary_passes_are_clean());
/* If the albedo input is not a single value input, download the albedo texture, denoise it
* in-place if denoising auxiliary passes is needed, and set it to the main filter. */
float *albedo = nullptr;
Result &input_albedo = get_input("Albedo");
if (!input_albedo.is_single_value()) {
albedo = static_cast<float *>(GPU_texture_read(input_albedo.texture(), data_format, 0));
if (should_denoise_auxiliary_passes()) {
oidn::FilterRef albedoFilter = device.newFilter("RT");
albedoFilter.setImage(
"albedo", albedo, oidn::Format::Float3, width, height, 0, pixel_stride);
albedoFilter.setImage(
"output", albedo, oidn::Format::Float3, width, height, 0, pixel_stride);
albedoFilter.commit();
albedoFilter.execute();
}
filter.setImage("albedo", albedo, oidn::Format::Float3, width, height, 0, pixel_stride);
}
/* If the albedo and normal inputs are not single value inputs, download the normal texture,
* denoise it in-place if denoising auxiliary passes is needed, and set it to the main filter.
* Notice that we also consider the albedo input because OIDN doesn't support denoising with
* only the normal auxiliary pass. */
float *normal = nullptr;
Result &input_normal = get_input("Normal");
if (albedo && !input_normal.is_single_value()) {
normal = static_cast<float *>(GPU_texture_read(input_normal.texture(), data_format, 0));
if (should_denoise_auxiliary_passes()) {
oidn::FilterRef normalFilter = device.newFilter("RT");
normalFilter.setImage(
"normal", normal, oidn::Format::Float3, width, height, 0, pixel_stride);
normalFilter.setImage(
"output", normal, oidn::Format::Float3, width, height, 0, pixel_stride);
normalFilter.commit();
normalFilter.execute();
}
filter.setImage("normal", normal, oidn::Format::Float3, width, height, 0, pixel_stride);
}
filter.commit();
filter.execute();
output_image.allocate_texture(input_image.domain());
GPU_texture_update(output_image.texture(), data_format, color);
MEM_freeN(color);
if (albedo) {
MEM_freeN(albedo);
}
if (normal) {
MEM_freeN(normal);
}
#endif
}
/* If the pre-filter mode is set to CMP_NODE_DENOISE_PREFILTER_NONE, that it means the supplied
* auxiliary passes are already noise-free, if it is set to CMP_NODE_DENOISE_PREFILTER_ACCURATE,
* the auxiliary passes will be denoised before denoising the main image, so in both cases, the
* auxiliary passes are considered clean. If it is set to CMP_NODE_DENOISE_PREFILTER_FAST on the
* other hand, the auxiliary passes are assumed to be noisy and are thus not clean, and will be
* denoised while denoising the main image. */
bool auxiliary_passes_are_clean()
{
return get_prefilter_mode() != CMP_NODE_DENOISE_PREFILTER_FAST;
}
/* Returns whether the auxiliary passes should be denoised, see the auxiliary_passes_are_clean
* method for more information. */
bool should_denoise_auxiliary_passes()
{
return get_prefilter_mode() == CMP_NODE_DENOISE_PREFILTER_ACCURATE;
}
bool use_hdr()
{
return node_storage(bnode()).hdr;
}
CMPNodeDenoisePrefilter get_prefilter_mode()
{
return static_cast<CMPNodeDenoisePrefilter>(node_storage(bnode()).prefilter);
}
/* OIDN can be disabled as a build option, so check WITH_OPENIMAGEDENOISE. Additionally, it is
* only supported at runtime for CPUs that supports SSE4.1, except for MacOS where it is always
* supported through the Accelerate framework BNNS on macOS. */
bool is_oidn_supported()
{
#ifndef WITH_OPENIMAGEDENOISE
return false;
#else
# ifdef __APPLE__
return true;
# else
return BLI_cpu_support_sse41();
# endif
#endif
}
};
@ -88,8 +232,6 @@ void register_node_type_cmp_denoise()
ntype.initfunc = file_ns::node_composit_init_denonise;
node_type_storage(&ntype, "NodeDenoise", node_free_standard_storage, node_copy_standard_storage);
ntype.get_compositor_operation = file_ns::get_compositor_operation;
ntype.realtime_compositor_unsupported_message = N_(
"Node not supported in the Viewport compositor");
nodeRegisterType(&ntype);
}