Fix #127392: File Output doesn't write empty inputs
The File Output node doesn't write invalid and single value layers, which can cause differences in EXR structures when rendering animations. To fix this, we write a dummy image that has the same dimensions as the other layers in the file, filled with the value of the single value input. Pull Request: https://projects.blender.org/blender/blender/pulls/128454
This commit is contained in:
@@ -246,8 +246,7 @@ void FileOutputOperation::execute_multi_layer()
|
||||
file_output.add_view(pass_view);
|
||||
|
||||
for (const FileOutputInput &input : file_output_inputs_) {
|
||||
/* We only write images, not single values. */
|
||||
if (!input.image_input || input.image_input->get_flags().is_constant_operation) {
|
||||
if (!input.image_input) {
|
||||
/* Ownership of outputs buffers are transferred to file outputs, so if we are not writing a
|
||||
* file output, we need to free the output buffer here. */
|
||||
if (input.output_buffer) {
|
||||
@@ -285,12 +284,74 @@ static float *float4_to_float3_image(int2 size, float *float4_image)
|
||||
return float3_image;
|
||||
}
|
||||
|
||||
/* Allocates and fills an image buffer of the specified size with the value of the given constant
|
||||
* input. */
|
||||
static float *inflate_input(const FileOutputInput &input, const int2 size)
|
||||
{
|
||||
BLI_assert(input.image_input->get_flags().is_constant_operation);
|
||||
|
||||
switch (input.data_type) {
|
||||
case DataType::Value: {
|
||||
float *buffer = static_cast<float *>(MEM_malloc_arrayN(
|
||||
size_t(size.x) * size.y, sizeof(float), "File Output Inflated Buffer."));
|
||||
|
||||
const float value = input.image_input->get_constant_value_default(0.0f);
|
||||
threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
|
||||
for (const int64_t y : sub_y_range) {
|
||||
for (const int64_t x : IndexRange(size.x)) {
|
||||
buffer[y * size.x + x] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
return buffer;
|
||||
}
|
||||
case DataType::Color: {
|
||||
float *buffer = static_cast<float *>(MEM_malloc_arrayN(
|
||||
size_t(size.x) * size.y, sizeof(float[4]), "File Output Inflated Buffer."));
|
||||
|
||||
const float *value = input.image_input->get_constant_elem_default(nullptr);
|
||||
threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
|
||||
for (const int64_t y : sub_y_range) {
|
||||
for (const int64_t x : IndexRange(size.x)) {
|
||||
copy_v4_v4(buffer + ((y * size.x + x) * 4), value);
|
||||
}
|
||||
}
|
||||
});
|
||||
return buffer;
|
||||
}
|
||||
default:
|
||||
/* Vector types are not possible for File output, see get_input_data_type in
|
||||
* COM_FileOutputNode.cc for more information. Other types are internal and needn't be
|
||||
* handled by operations. */
|
||||
break;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FileOutputOperation::add_pass_for_input(realtime_compositor::FileOutput &file_output,
|
||||
const FileOutputInput &input,
|
||||
const char *pass_name,
|
||||
const char *view_name)
|
||||
{
|
||||
const int2 size = int2(input.image_input->get_width(), input.image_input->get_height());
|
||||
/* For constant operations, we fill a buffer that covers the canvas of the operation with the
|
||||
* value of the operation. */
|
||||
const int2 size = input.image_input->get_flags().is_constant_operation ?
|
||||
int2(this->get_width(), this->get_height()) :
|
||||
int2(input.image_input->get_width(), input.image_input->get_height());
|
||||
|
||||
/* The image buffer in the file output will take ownership of this buffer and freeing it will be
|
||||
* its responsibility. So if we don't use the output buffer, we need to free it here. */
|
||||
float *buffer = nullptr;
|
||||
if (input.image_input->get_flags().is_constant_operation) {
|
||||
buffer = inflate_input(input, size);
|
||||
MEM_freeN(input.output_buffer);
|
||||
}
|
||||
else {
|
||||
buffer = input.output_buffer;
|
||||
}
|
||||
|
||||
switch (input.original_data_type) {
|
||||
case DataType::Color:
|
||||
/* Use lowercase rgba for Cryptomatte layers because the EXR internal compression rules
|
||||
@@ -299,23 +360,22 @@ void FileOutputOperation::add_pass_for_input(realtime_compositor::FileOutput &fi
|
||||
if (input.image_input->get_meta_data() &&
|
||||
input.image_input->get_meta_data()->is_cryptomatte_layer())
|
||||
{
|
||||
file_output.add_pass(pass_name, view_name, "rgba", input.output_buffer);
|
||||
file_output.add_pass(pass_name, view_name, "rgba", buffer);
|
||||
}
|
||||
else {
|
||||
file_output.add_pass(pass_name, view_name, "RGBA", input.output_buffer);
|
||||
file_output.add_pass(pass_name, view_name, "RGBA", buffer);
|
||||
}
|
||||
break;
|
||||
case DataType::Vector:
|
||||
if (input.image_input->get_meta_data() && input.image_input->get_meta_data()->is_4d_vector) {
|
||||
file_output.add_pass(pass_name, view_name, "XYZW", input.output_buffer);
|
||||
file_output.add_pass(pass_name, view_name, "XYZW", buffer);
|
||||
}
|
||||
else {
|
||||
file_output.add_pass(
|
||||
pass_name, view_name, "XYZ", float4_to_float3_image(size, input.output_buffer));
|
||||
file_output.add_pass(pass_name, view_name, "XYZ", float4_to_float3_image(size, buffer));
|
||||
}
|
||||
break;
|
||||
case DataType::Value:
|
||||
file_output.add_pass(pass_name, view_name, "V", input.output_buffer);
|
||||
file_output.add_pass(pass_name, view_name, "V", buffer);
|
||||
break;
|
||||
case DataType::Float2:
|
||||
/* An internal type that needn't be handled. */
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_fileops.h"
|
||||
#include "BLI_index_range.hh"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_string_utf8.h"
|
||||
@@ -627,11 +628,6 @@ class FileOutputOperation : public NodeOperation {
|
||||
|
||||
for (const bNodeSocket *input : this->node()->input_sockets()) {
|
||||
const Result &input_result = get_input(input->identifier);
|
||||
/* We only write images, not single values. */
|
||||
if (input_result.is_single_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *pass_name = (static_cast<NodeImageMultiFileSocket *>(input->storage))->layer;
|
||||
add_pass_for_result(file_output, input_result, pass_name, pass_view);
|
||||
|
||||
@@ -646,12 +642,22 @@ class FileOutputOperation : public NodeOperation {
|
||||
const char *pass_name,
|
||||
const char *view_name)
|
||||
{
|
||||
/* For single values, we fill a buffer that covers the domain of the operation with the value
|
||||
* of the result. */
|
||||
const int2 size = result.is_single_value() ? this->compute_domain().size :
|
||||
result.domain().size;
|
||||
|
||||
/* The image buffer in the file output will take ownership of this buffer and freeing it will
|
||||
* be its responsibility. */
|
||||
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
|
||||
float *buffer = static_cast<float *>(GPU_texture_read(result, GPU_DATA_FLOAT, 0));
|
||||
float *buffer = nullptr;
|
||||
if (result.is_single_value()) {
|
||||
buffer = this->inflate_result(result, size);
|
||||
}
|
||||
else {
|
||||
GPU_memory_barrier(GPU_BARRIER_TEXTURE_UPDATE);
|
||||
buffer = static_cast<float *>(GPU_texture_read(result, GPU_DATA_FLOAT, 0));
|
||||
}
|
||||
|
||||
const int2 size = result.domain().size;
|
||||
switch (result.type()) {
|
||||
case ResultType::Color:
|
||||
/* Use lowercase rgba for Cryptomatte layers because the EXR internal compression rules
|
||||
@@ -682,6 +688,52 @@ class FileOutputOperation : public NodeOperation {
|
||||
}
|
||||
}
|
||||
|
||||
/* Allocates and fills an image buffer of the specified size with the value of the given single
|
||||
* value result. */
|
||||
float *inflate_result(const Result &result, const int2 size)
|
||||
{
|
||||
BLI_assert(result.is_single_value());
|
||||
|
||||
switch (result.type()) {
|
||||
case ResultType::Float: {
|
||||
float *buffer = static_cast<float *>(MEM_malloc_arrayN(
|
||||
size_t(size.x) * size.y, sizeof(float), "File Output Inflated Buffer."));
|
||||
|
||||
const float value = result.get_float_value();
|
||||
threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
|
||||
for (const int64_t y : sub_y_range) {
|
||||
for (const int64_t x : IndexRange(size.x)) {
|
||||
buffer[y * size.x + x] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
return buffer;
|
||||
}
|
||||
case ResultType::Vector:
|
||||
case ResultType::Color: {
|
||||
float *buffer = static_cast<float *>(MEM_malloc_arrayN(
|
||||
size_t(size.x) * size.y, sizeof(float[4]), "File Output Inflated Buffer."));
|
||||
|
||||
const float4 value = result.type() == ResultType::Color ? result.get_color_value() :
|
||||
result.get_vector_value();
|
||||
threading::parallel_for(IndexRange(size.y), 1, [&](const IndexRange sub_y_range) {
|
||||
for (const int64_t y : sub_y_range) {
|
||||
for (const int64_t x : IndexRange(size.x)) {
|
||||
copy_v4_v4(buffer + ((y * size.x + x) * 4), value);
|
||||
}
|
||||
}
|
||||
});
|
||||
return buffer;
|
||||
}
|
||||
default:
|
||||
/* Other types are internal and needn't be handled by operations. */
|
||||
break;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Read the data stored in the GPU texture of the given result and add a view of the given name
|
||||
* and read buffer. */
|
||||
void add_view_for_result(FileOutput &file_output, const Result &result, const char *view_name)
|
||||
|
||||
Reference in New Issue
Block a user