diff options
author | Manuel Castilla <manzanillawork@gmail.com> | 2021-09-19 21:12:53 +0300 |
---|---|---|
committer | Manuel Castilla <manzanillawork@gmail.com> | 2021-09-19 22:13:51 +0300 |
commit | 276eebb274744d819dcdab8a95770dd7382c0664 (patch) | |
tree | 2f1ab6ee7c3b87eb297143c32f4a2c09bd714f0c /source/blender | |
parent | f256bfb3e26c32af12c82dbd32d4b3bcfba252f3 (diff) |
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
Diffstat (limited to 'source/blender')
7 files changed, 344 insertions, 118 deletions
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<typename T> 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<typename T> 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<MemoryBuffer *> 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<MemoryBuffer *> 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<MemoryBuffer *> inputs) override; + + protected: + void hash_output_params() override; + MemoryBuffer *createMemoryBuffer(rcti *rect) override; + + private: + void generate_denoise(MemoryBuffer *output, MemoryBuffer *input); +}; + } // namespace blender::compositor diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 4b859de0ac9..62f40152416 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -2865,6 +2865,8 @@ static void node_composit_buts_denoise(uiLayout *layout, bContext *UNUSED(C), Po # endif #endif + uiItemL(layout, IFACE_("Prefilter:"), ICON_NONE); + uiItemR(layout, ptr, "prefilter", DEFAULT_FLAGS, nullptr, ICON_NONE); uiItemR(layout, ptr, "use_hdr", DEFAULT_FLAGS, nullptr, ICON_NONE); } diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index ee1da6be3f2..74ed22ecafd 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1167,6 +1167,7 @@ typedef struct NodeCryptomatte { typedef struct NodeDenoise { char hdr; + char prefilter; } NodeDenoise; typedef struct NodeAttributeClamp { @@ -1840,6 +1841,14 @@ typedef enum CMPNodeSetAlphaMode { CMP_NODE_SETALPHA_MODE_REPLACE_ALPHA = 1, } CMPNodeSetAlphaMode; +/* Denoise Node. */ +/* `NodeDenoise.prefilter` */ +typedef enum CMPNodeDenoisePrefilter { + CMP_NODE_DENOISE_PREFILTER_FAST = 0, + CMP_NODE_DENOISE_PREFILTER_NONE = 1, + CMP_NODE_DENOISE_PREFILTER_ACCURATE = 2 +} CMPNodeDenoisePrefilter; + #define CMP_NODE_PLANETRACKDEFORM_MBLUR_SAMPLES_MAX 64 /* Point Density shader node */ diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 86e134799aa..d685692c8fa 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -8890,12 +8890,37 @@ static void def_cmp_denoise(StructRNA *srna) { PropertyRNA *prop; + static const EnumPropertyItem prefilter_items[] = { + {CMP_NODE_DENOISE_PREFILTER_NONE, + "NONE", + 0, + "None", + "No prefiltering, use when guiding passes are noise-free"}, + {CMP_NODE_DENOISE_PREFILTER_FAST, + "FAST", + 0, + "Fast", + "Denoise image and guiding passes together. Improves quality when guiding passes are noisy " + "using least amount of extra processing time"}, + {CMP_NODE_DENOISE_PREFILTER_ACCURATE, + "ACCURATE", + 0, + "Accurate", + "Prefilter noisy guiding passes before denoising image. Improves quality when guiding " + "passes are noisy using extra processing time"}, + {0, NULL, 0, NULL, NULL}}; + RNA_def_struct_sdna_from(srna, "NodeDenoise", "storage"); prop = RNA_def_property(srna, "use_hdr", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "hdr", 0); RNA_def_property_ui_text(prop, "HDR", "Process HDR images"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + + prop = RNA_def_property(srna, "prefilter", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, prefilter_items); + RNA_def_property_ui_text(prop, "", "Denoising prefilter"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); } static void def_cmp_antialiasing(StructRNA *srna) diff --git a/source/blender/nodes/composite/nodes/node_composite_denoise.c b/source/blender/nodes/composite/nodes/node_composite_denoise.c index 040b350627e..e2c7c7b995f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_denoise.c +++ b/source/blender/nodes/composite/nodes/node_composite_denoise.c @@ -36,6 +36,7 @@ static void node_composit_init_denonise(bNodeTree *UNUSED(ntree), bNode *node) { NodeDenoise *ndg = MEM_callocN(sizeof(NodeDenoise), "node denoise data"); ndg->hdr = true; + ndg->prefilter = CMP_NODE_DENOISE_PREFILTER_ACCURATE; node->storage = ndg; } |