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
This commit is contained in:
Manuel Castilla 2021-09-19 20:12:53 +02:00
parent f256bfb3e2
commit 276eebb274
Notes: blender-bot 2023-02-14 09:24:53 +01:00
Referenced by issue #101270, Regression: 'Object Info' 'Random' always gives the same seed for instanced bezier curves
Referenced by issue #91543, Crash on file open (VSE)
Referenced by issue #90919, Compositor: Add OpenImageDenoising prefiltering option to Denoising node.
7 changed files with 346 additions and 120 deletions

View File

@ -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));
}

View File

@ -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);
/* 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);
/* 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;
}
return;
BLI_assert(settings);
if (settings) {
filter.set("hdr", settings->hdr);
filter.set("srgb", false);
filter.set("cleanAux", are_guiding_passes_noise_free(settings));
}
#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();
filter.execute();
filter.deinit_and_unlock_denoiser();
/* 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);
/* 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;
}
}
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

View File

@ -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

View File

@ -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);
}

View File

@ -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 */

View File

@ -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)

View File

@ -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;
}