From 276eebb274744d819dcdab8a95770dd7382c0664 Mon Sep 17 00:00:00 2001 From: Manuel Castilla Date: Sun, 19 Sep 2021 20:12:53 +0200 Subject: Compositor: Add OIDN prefiltering option to Denoise node It's equivalent to the OpenImageDenoise prefiltering option in Cycles. See D12043. Prefilter modes: - None: No prefiltering, use when guiding passes are noise-free. - Fast: Denoise image and guiding passes together. Improves quality when guiding passes are noisy using least amount of extra processing time. - Accurate: Prefilter noisy guiding passes before denoising image. Improves quality when guiding passes are noisy using extra processing time. Reviewed By: #compositing, jbakker, sergey Differential Revision: https://developer.blender.org/D12342 --- source/blender/compositor/nodes/COM_DenoiseNode.cc | 30 +- .../compositor/operations/COM_DenoiseOperation.cc | 345 ++++++++++++++------- .../compositor/operations/COM_DenoiseOperation.h | 50 ++- 3 files changed, 307 insertions(+), 118 deletions(-) (limited to 'source/blender/compositor') diff --git a/source/blender/compositor/nodes/COM_DenoiseNode.cc b/source/blender/compositor/nodes/COM_DenoiseNode.cc index e58a9c7ba9a..cc9328414ef 100644 --- a/source/blender/compositor/nodes/COM_DenoiseNode.cc +++ b/source/blender/compositor/nodes/COM_DenoiseNode.cc @@ -31,6 +31,12 @@ DenoiseNode::DenoiseNode(bNode *editorNode) : Node(editorNode) void DenoiseNode::convertToOperations(NodeConverter &converter, const CompositorContext & /*context*/) const { + if (!COM_is_denoise_supported()) { + converter.mapOutputSocket(getOutputSocket(0), + converter.addInputProxy(getInputSocket(0), false)); + return; + } + bNode *node = this->getbNode(); NodeDenoise *denoise = (NodeDenoise *)node->storage; @@ -39,8 +45,28 @@ void DenoiseNode::convertToOperations(NodeConverter &converter, operation->setDenoiseSettings(denoise); converter.mapInputSocket(getInputSocket(0), operation->getInputSocket(0)); - converter.mapInputSocket(getInputSocket(1), operation->getInputSocket(1)); - converter.mapInputSocket(getInputSocket(2), operation->getInputSocket(2)); + if (denoise && denoise->prefilter == CMP_NODE_DENOISE_PREFILTER_ACCURATE) { + { + DenoisePrefilterOperation *normal_prefilter = new DenoisePrefilterOperation( + DataType::Vector); + normal_prefilter->set_image_name("normal"); + converter.addOperation(normal_prefilter); + converter.mapInputSocket(getInputSocket(1), normal_prefilter->getInputSocket(0)); + converter.addLink(normal_prefilter->getOutputSocket(), operation->getInputSocket(1)); + } + { + DenoisePrefilterOperation *albedo_prefilter = new DenoisePrefilterOperation(DataType::Color); + albedo_prefilter->set_image_name("albedo"); + converter.addOperation(albedo_prefilter); + converter.mapInputSocket(getInputSocket(2), albedo_prefilter->getInputSocket(0)); + converter.addLink(albedo_prefilter->getOutputSocket(), operation->getInputSocket(2)); + } + } + else { + converter.mapInputSocket(getInputSocket(1), operation->getInputSocket(1)); + converter.mapInputSocket(getInputSocket(2), operation->getInputSocket(2)); + } + converter.mapOutputSocket(getOutputSocket(0), operation->getOutputSocket(0)); } diff --git a/source/blender/compositor/operations/COM_DenoiseOperation.cc b/source/blender/compositor/operations/COM_DenoiseOperation.cc index e7f2d5a740a..0c660e0b723 100644 --- a/source/blender/compositor/operations/COM_DenoiseOperation.cc +++ b/source/blender/compositor/operations/COM_DenoiseOperation.cc @@ -28,6 +28,137 @@ static pthread_mutex_t oidn_lock = BLI_MUTEX_INITIALIZER; namespace blender::compositor { +bool COM_is_denoise_supported() +{ +#ifdef WITH_OPENIMAGEDENOISE + /* Always supported through Accelerate framework BNNS on macOS. */ +# ifdef __APPLE__ + return true; +# else + return BLI_cpu_support_sse41(); +# endif + +#else + return false; +#endif +} + +class DenoiseFilter { + private: +#ifdef WITH_OPENIMAGEDENOISE + oidn::DeviceRef device; + oidn::FilterRef filter; +#endif + bool initialized_ = false; + + public: + ~DenoiseFilter() + { + BLI_assert(!initialized_); + } + +#ifdef WITH_OPENIMAGEDENOISE + void init_and_lock_denoiser(MemoryBuffer *output) + { + /* Since it's memory intensive, it's better to run only one instance of OIDN at a time. + * OpenImageDenoise is multithreaded internally and should use all available cores + * nonetheless. */ + BLI_mutex_lock(&oidn_lock); + + device = oidn::newDevice(); + device.commit(); + filter = device.newFilter("RT"); + initialized_ = true; + set_image("output", output); + } + + void deinit_and_unlock_denoiser() + { + BLI_mutex_unlock(&oidn_lock); + initialized_ = false; + } + + void set_image(const StringRef name, MemoryBuffer *buffer) + { + BLI_assert(initialized_); + BLI_assert(!buffer->is_a_single_elem()); + filter.setImage(name.data(), + buffer->getBuffer(), + oidn::Format::Float3, + buffer->getWidth(), + buffer->getHeight(), + 0, + buffer->get_elem_bytes_len()); + } + + template void set(const StringRef option_name, T value) + { + BLI_assert(initialized_); + filter.set(option_name.data(), value); + } + + void execute() + { + BLI_assert(initialized_); + filter.commit(); + filter.execute(); + } + +#else + void init_and_lock_denoiser(MemoryBuffer *UNUSED(output)) + { + } + + void deinit_and_unlock_denoiser() + { + } + + void set_image(const StringRef UNUSED(name), MemoryBuffer *UNUSED(buffer)) + { + } + + template void set(const StringRef UNUSED(option_name), T UNUSED(value)) + { + } + + void execute() + { + } +#endif +}; + +DenoiseBaseOperation::DenoiseBaseOperation() +{ + flags.is_fullframe_operation = true; + output_rendered_ = false; +} + +bool DenoiseBaseOperation::determineDependingAreaOfInterest(rcti * /*input*/, + ReadBufferOperation *readOperation, + rcti *output) +{ + if (isCached()) { + return false; + } + + rcti newInput; + newInput.xmax = this->getWidth(); + newInput.xmin = 0; + newInput.ymax = this->getHeight(); + newInput.ymin = 0; + return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); +} + +void DenoiseBaseOperation::get_area_of_interest(const int UNUSED(input_idx), + const rcti &UNUSED(output_area), + rcti &r_input_area) +{ + r_input_area.xmin = 0; + r_input_area.xmax = this->getWidth(); + r_input_area.ymin = 0; + r_input_area.ymax = this->getHeight(); +} + DenoiseOperation::DenoiseOperation() { this->addInputSocket(DataType::Color); @@ -35,8 +166,6 @@ DenoiseOperation::DenoiseOperation() this->addInputSocket(DataType::Color); this->addOutputSocket(DataType::Color); this->m_settings = nullptr; - flags.is_fullframe_operation = true; - output_rendered_ = false; } void DenoiseOperation::initExecution() { @@ -54,6 +183,25 @@ void DenoiseOperation::deinitExecution() SingleThreadedOperation::deinitExecution(); } +static bool are_guiding_passes_noise_free(NodeDenoise *settings) +{ + switch (settings->prefilter) { + case CMP_NODE_DENOISE_PREFILTER_NONE: + case CMP_NODE_DENOISE_PREFILTER_ACCURATE: /* Prefiltered with #DenoisePrefilterOperation. */ + return true; + case CMP_NODE_DENOISE_PREFILTER_FAST: + default: + return false; + } +} + +void DenoiseOperation::hash_output_params() +{ + if (m_settings) { + hash_params((int)m_settings->hdr, are_guiding_passes_noise_free(m_settings)); + } +} + MemoryBuffer *DenoiseOperation::createMemoryBuffer(rcti *rect2) { MemoryBuffer *tileColor = (MemoryBuffer *)this->m_inputProgramColor->initializeTileData(rect2); @@ -69,22 +217,6 @@ MemoryBuffer *DenoiseOperation::createMemoryBuffer(rcti *rect2) return result; } -bool DenoiseOperation::determineDependingAreaOfInterest(rcti * /*input*/, - ReadBufferOperation *readOperation, - rcti *output) -{ - if (isCached()) { - return false; - } - - rcti newInput; - newInput.xmax = this->getWidth(); - newInput.xmin = 0; - newInput.ymax = this->getHeight(); - newInput.ymin = 0; - return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output); -} - void DenoiseOperation::generateDenoise(MemoryBuffer *output, MemoryBuffer *input_color, MemoryBuffer *input_normal, @@ -96,104 +228,46 @@ void DenoiseOperation::generateDenoise(MemoryBuffer *output, return; } -#ifdef WITH_OPENIMAGEDENOISE - /* Always supported through Accelerate framework BNNS on macOS. */ -# ifndef __APPLE__ - if (BLI_cpu_support_sse41()) -# endif - { - /* OpenImageDenoise needs full buffers. */ - MemoryBuffer *buf_color = input_color->is_a_single_elem() ? input_color->inflate() : - input_color; - MemoryBuffer *buf_normal = input_normal && input_normal->is_a_single_elem() ? - input_normal->inflate() : - input_normal; - MemoryBuffer *buf_albedo = input_albedo && input_albedo->is_a_single_elem() ? - input_albedo->inflate() : - input_albedo; + BLI_assert(COM_is_denoise_supported()); + /* OpenImageDenoise needs full buffers. */ + MemoryBuffer *buf_color = input_color->is_a_single_elem() ? input_color->inflate() : input_color; + MemoryBuffer *buf_normal = input_normal && input_normal->is_a_single_elem() ? + input_normal->inflate() : + input_normal; + MemoryBuffer *buf_albedo = input_albedo && input_albedo->is_a_single_elem() ? + input_albedo->inflate() : + input_albedo; - /* Since it's memory intensive, it's better to run only one instance of OIDN at a time. - * OpenImageDenoise is multithreaded internally and should use all available cores nonetheless. - */ - BLI_mutex_lock(&oidn_lock); + DenoiseFilter filter; + filter.init_and_lock_denoiser(output); - oidn::DeviceRef device = oidn::newDevice(); - device.commit(); + filter.set_image("color", buf_color); + filter.set_image("normal", buf_normal); + filter.set_image("albedo", buf_albedo); - oidn::FilterRef filter = device.newFilter("RT"); - filter.setImage("color", - buf_color->getBuffer(), - oidn::Format::Float3, - buf_color->getWidth(), - buf_color->getHeight(), - 0, - sizeof(float[4])); - if (buf_normal && buf_normal->getBuffer()) { - filter.setImage("normal", - buf_normal->getBuffer(), - oidn::Format::Float3, - buf_normal->getWidth(), - buf_normal->getHeight(), - 0, - sizeof(float[3])); - } - if (buf_albedo && buf_albedo->getBuffer()) { - filter.setImage("albedo", - buf_albedo->getBuffer(), - oidn::Format::Float3, - buf_albedo->getWidth(), - buf_albedo->getHeight(), - 0, - sizeof(float[4])); - } - filter.setImage("output", - output->getBuffer(), - oidn::Format::Float3, - buf_color->getWidth(), - buf_color->getHeight(), - 0, - sizeof(float[4])); - - BLI_assert(settings); - if (settings) { - filter.set("hdr", settings->hdr); - filter.set("srgb", false); - } - - filter.commit(); - filter.execute(); - BLI_mutex_unlock(&oidn_lock); + BLI_assert(settings); + if (settings) { + filter.set("hdr", settings->hdr); + filter.set("srgb", false); + filter.set("cleanAux", are_guiding_passes_noise_free(settings)); + } - /* Copy the alpha channel, OpenImageDenoise currently only supports RGB. */ - output->copy_from(input_color, input_color->get_rect(), 3, COM_DATA_TYPE_VALUE_CHANNELS, 3); + filter.execute(); + filter.deinit_and_unlock_denoiser(); - /* Delete inflated buffers. */ - if (input_color->is_a_single_elem()) { - delete buf_color; - } - if (input_normal && input_normal->is_a_single_elem()) { - delete buf_normal; - } - if (input_albedo && input_albedo->is_a_single_elem()) { - delete buf_albedo; - } + /* Copy the alpha channel, OpenImageDenoise currently only supports RGB. */ + output->copy_from(input_color, input_color->get_rect(), 3, COM_DATA_TYPE_VALUE_CHANNELS, 3); - return; + /* Delete inflated buffers. */ + if (input_color->is_a_single_elem()) { + delete buf_color; + } + if (input_normal && input_normal->is_a_single_elem()) { + delete buf_normal; + } + if (input_albedo && input_albedo->is_a_single_elem()) { + delete buf_albedo; } -#endif - /* If built without OIDN or running on an unsupported CPU, just pass through. */ - UNUSED_VARS(input_albedo, input_normal, settings); - output->copy_from(input_color, input_color->get_rect()); -} - -void DenoiseOperation::get_area_of_interest(const int UNUSED(input_idx), - const rcti &UNUSED(output_area), - rcti &r_input_area) -{ - r_input_area.xmin = 0; - r_input_area.xmax = this->getWidth(); - r_input_area.ymin = 0; - r_input_area.ymax = this->getHeight(); } void DenoiseOperation::update_memory_buffer(MemoryBuffer *output, @@ -206,4 +280,57 @@ void DenoiseOperation::update_memory_buffer(MemoryBuffer *output, } } +DenoisePrefilterOperation::DenoisePrefilterOperation(DataType data_type) +{ + this->addInputSocket(data_type); + this->addOutputSocket(data_type); + image_name_ = ""; +} + +void DenoisePrefilterOperation::hash_output_params() +{ + hash_param(image_name_); +} + +MemoryBuffer *DenoisePrefilterOperation::createMemoryBuffer(rcti *rect2) +{ + MemoryBuffer *input = (MemoryBuffer *)this->get_input_operation(0)->initializeTileData(rect2); + rcti rect; + BLI_rcti_init(&rect, 0, getWidth(), 0, getHeight()); + + MemoryBuffer *result = new MemoryBuffer(getOutputSocket()->getDataType(), rect); + generate_denoise(result, input); + + return result; +} + +void DenoisePrefilterOperation::generate_denoise(MemoryBuffer *output, MemoryBuffer *input) +{ + BLI_assert(COM_is_denoise_supported()); + + /* Denoising needs full buffers. */ + MemoryBuffer *input_buf = input->is_a_single_elem() ? input->inflate() : input; + + DenoiseFilter filter; + filter.init_and_lock_denoiser(output); + filter.set_image(image_name_, input_buf); + filter.execute(); + filter.deinit_and_unlock_denoiser(); + + /* Delete inflated buffers. */ + if (input->is_a_single_elem()) { + delete input_buf; + } +} + +void DenoisePrefilterOperation::update_memory_buffer(MemoryBuffer *output, + const rcti &UNUSED(area), + Span inputs) +{ + if (!output_rendered_) { + this->generate_denoise(output, inputs[0]); + output_rendered_ = true; + } +} + } // namespace blender::compositor diff --git a/source/blender/compositor/operations/COM_DenoiseOperation.h b/source/blender/compositor/operations/COM_DenoiseOperation.h index 48209c3eacf..1b053b79c2d 100644 --- a/source/blender/compositor/operations/COM_DenoiseOperation.h +++ b/source/blender/compositor/operations/COM_DenoiseOperation.h @@ -23,7 +23,24 @@ namespace blender::compositor { -class DenoiseOperation : public SingleThreadedOperation { +bool COM_is_denoise_supported(); + +class DenoiseBaseOperation : public SingleThreadedOperation { + protected: + bool output_rendered_; + + protected: + DenoiseBaseOperation(); + + public: + bool determineDependingAreaOfInterest(rcti *input, + ReadBufferOperation *readOperation, + rcti *output) override; + + void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; +}; + +class DenoiseOperation : public DenoiseBaseOperation { private: /** * \brief Cached reference to the input programs @@ -37,8 +54,6 @@ class DenoiseOperation : public SingleThreadedOperation { */ NodeDenoise *m_settings; - bool output_rendered_; - public: DenoiseOperation(); /** @@ -55,16 +70,13 @@ class DenoiseOperation : public SingleThreadedOperation { { this->m_settings = settings; } - bool determineDependingAreaOfInterest(rcti *input, - ReadBufferOperation *readOperation, - rcti *output) override; - void get_area_of_interest(int input_idx, const rcti &output_area, rcti &r_input_area) override; void update_memory_buffer(MemoryBuffer *output, const rcti &area, Span inputs) override; protected: + void hash_output_params() override; void generateDenoise(MemoryBuffer *output, MemoryBuffer *input_color, MemoryBuffer *input_normal, @@ -74,4 +86,28 @@ class DenoiseOperation : public SingleThreadedOperation { MemoryBuffer *createMemoryBuffer(rcti *rect) override; }; +class DenoisePrefilterOperation : public DenoiseBaseOperation { + private: + std::string image_name_; + + public: + DenoisePrefilterOperation(DataType data_type); + + void set_image_name(StringRef name) + { + image_name_ = name; + } + + void update_memory_buffer(MemoryBuffer *output, + const rcti &area, + Span inputs) override; + + protected: + void hash_output_params() override; + MemoryBuffer *createMemoryBuffer(rcti *rect) override; + + private: + void generate_denoise(MemoryBuffer *output, MemoryBuffer *input); +}; + } // namespace blender::compositor -- cgit v1.2.3