411 lines
16 KiB
C++
411 lines
16 KiB
C++
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
#include "BLI_assert.h"
|
|
#include "BLI_cpp_type.hh"
|
|
#include "BLI_generic_span.hh"
|
|
#include "BLI_index_mask.hh"
|
|
#include "BLI_map.hh"
|
|
#include "BLI_math_base.hh"
|
|
#include "BLI_math_vector_types.hh"
|
|
#include "BLI_string_ref.hh"
|
|
#include "BLI_vector.hh"
|
|
|
|
#include "FN_multi_function.hh"
|
|
#include "FN_multi_function_builder.hh"
|
|
#include "FN_multi_function_context.hh"
|
|
#include "FN_multi_function_data_type.hh"
|
|
#include "FN_multi_function_procedure.hh"
|
|
#include "FN_multi_function_procedure_builder.hh"
|
|
#include "FN_multi_function_procedure_executor.hh"
|
|
#include "FN_multi_function_procedure_optimization.hh"
|
|
|
|
#include "DNA_node_types.h"
|
|
|
|
#include "NOD_derived_node_tree.hh"
|
|
#include "NOD_multi_function.hh"
|
|
#include "NOD_node_declaration.hh"
|
|
|
|
#include "COM_context.hh"
|
|
#include "COM_domain.hh"
|
|
#include "COM_input_descriptor.hh"
|
|
#include "COM_multi_function_procedure_operation.hh"
|
|
#include "COM_pixel_operation.hh"
|
|
#include "COM_result.hh"
|
|
#include "COM_scheduler.hh"
|
|
#include "COM_utilities.hh"
|
|
|
|
namespace blender::compositor {
|
|
|
|
using namespace nodes::derived_node_tree_types;
|
|
|
|
MultiFunctionProcedureOperation::MultiFunctionProcedureOperation(Context &context,
|
|
PixelCompileUnit &compile_unit,
|
|
const Schedule &schedule)
|
|
: PixelOperation(context, compile_unit, schedule), procedure_builder_(procedure_)
|
|
{
|
|
this->build_procedure();
|
|
procedure_executor_ = std::make_unique<mf::ProcedureExecutor>(procedure_);
|
|
}
|
|
|
|
static const CPPType &get_cpp_type(ResultType type)
|
|
{
|
|
switch (type) {
|
|
case ResultType::Float:
|
|
return CPPType::get<float>();
|
|
case ResultType::Vector:
|
|
case ResultType::Color:
|
|
return CPPType::get<float4>();
|
|
default:
|
|
/* Other types are internal and needn't be handled by operations. */
|
|
break;
|
|
}
|
|
|
|
BLI_assert_unreachable();
|
|
return CPPType::get<float>();
|
|
}
|
|
|
|
/* Adds the single value parameter of the given input to the given parameter_builder. */
|
|
static void add_single_value_parameter(mf::ParamsBuilder ¶meter_builder, const Result &input)
|
|
{
|
|
BLI_assert(input.is_single_value());
|
|
switch (input.type()) {
|
|
case ResultType::Float:
|
|
parameter_builder.add_readonly_single_input_value(input.get_float_value());
|
|
return;
|
|
case ResultType::Color:
|
|
parameter_builder.add_readonly_single_input_value(input.get_color_value());
|
|
return;
|
|
case ResultType::Vector:
|
|
parameter_builder.add_readonly_single_input_value(input.get_vector_value());
|
|
return;
|
|
default:
|
|
/* Other types are internal and needn't be handled by operations. */
|
|
BLI_assert_unreachable();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MultiFunctionProcedureOperation::execute()
|
|
{
|
|
const Domain domain = compute_domain();
|
|
const int64_t size = int64_t(domain.size.x) * domain.size.y;
|
|
const IndexMask mask = IndexMask(size);
|
|
mf::ParamsBuilder parameter_builder{*procedure_executor_, &mask};
|
|
|
|
/* For each of the parameters, either add an input or an output depending on its interface type,
|
|
* allocating the outputs when needed. */
|
|
for (int i = 0; i < procedure_.params().size(); i++) {
|
|
if (procedure_.params()[i].type == mf::ParamType::InterfaceType::Input) {
|
|
Result &input = get_input(parameter_identifiers_[i]);
|
|
if (input.is_single_value()) {
|
|
add_single_value_parameter(parameter_builder, input);
|
|
}
|
|
else {
|
|
const GSpan span{get_cpp_type(input.type()), input.float_texture(), size};
|
|
parameter_builder.add_readonly_single_input(span);
|
|
}
|
|
}
|
|
else {
|
|
Result &result = get_result(parameter_identifiers_[i]);
|
|
result.allocate_texture(domain);
|
|
const GMutableSpan span{get_cpp_type(result.type()), result.float_texture(), size};
|
|
parameter_builder.add_uninitialized_single_output(span);
|
|
}
|
|
}
|
|
|
|
mf::ContextBuilder context_builder;
|
|
procedure_executor_->call_auto(mask, parameter_builder, context_builder);
|
|
}
|
|
|
|
void MultiFunctionProcedureOperation::build_procedure()
|
|
{
|
|
for (DNode node : compile_unit_) {
|
|
/* Get the multi-function of the node. */
|
|
auto &multi_function_builder = *node_multi_functions_.lookup_or_add_cb(node, [&]() {
|
|
return std::make_unique<nodes::NodeMultiFunctionBuilder>(*node.bnode(),
|
|
node.context()->btree());
|
|
});
|
|
node->typeinfo->build_multi_function(multi_function_builder);
|
|
const mf::MultiFunction &multi_function = multi_function_builder.function();
|
|
|
|
/* Get the variables of the inputs of the node, creating inputs to the operation/procedure if
|
|
* needed. */
|
|
Vector<mf::Variable *> input_variables = this->get_input_variables(node);
|
|
|
|
/* Call the node multi-function, getting the variables for its outputs. */
|
|
Vector<mf::Variable *> output_variables = procedure_builder_.add_call(multi_function,
|
|
input_variables);
|
|
|
|
/* Assign the output variables to the node's respective outputs, creating outputs for the
|
|
* operation/procedure if needed. */
|
|
this->assign_output_variables(node, output_variables);
|
|
}
|
|
|
|
/* Add destructor calls for the variables. */
|
|
for (const auto &item : output_to_variable_map_.items()) {
|
|
/* Variables that are used by the outputs should not be destructed. */
|
|
if (!output_sockets_to_output_identifiers_map_.contains(item.key)) {
|
|
procedure_builder_.add_destruct(*item.value);
|
|
}
|
|
}
|
|
for (mf::Variable *variable : implicit_variables_) {
|
|
procedure_builder_.add_destruct(*variable);
|
|
}
|
|
|
|
mf::ReturnInstruction &return_instruction = procedure_builder_.add_return();
|
|
mf::procedure_optimization::move_destructs_up(procedure_, return_instruction);
|
|
BLI_assert(procedure_.validate());
|
|
}
|
|
|
|
Vector<mf::Variable *> MultiFunctionProcedureOperation::get_input_variables(DNode node)
|
|
{
|
|
Vector<mf::Variable *> input_variables;
|
|
for (int i = 0; i < node->input_sockets().size(); i++) {
|
|
const DInputSocket input{node.context(), node->input_sockets()[i]};
|
|
|
|
if (!input->is_available()) {
|
|
continue;
|
|
}
|
|
|
|
/* The origin socket is an input, that means the input is unlinked and we generate a constant
|
|
* variable for it. */
|
|
const DSocket origin = get_input_origin_socket(input);
|
|
if (origin->is_input()) {
|
|
input_variables.append(this->get_constant_input_variable(DInputSocket(origin)));
|
|
continue;
|
|
}
|
|
|
|
/* Otherwise, the origin socket is an output, which means it is linked. */
|
|
const DOutputSocket output = DOutputSocket(origin);
|
|
|
|
/* If the origin node is part of the multi-function procedure operation, then the output has an
|
|
* existing variable for it. */
|
|
if (compile_unit_.contains(output.node())) {
|
|
input_variables.append(output_to_variable_map_.lookup(output));
|
|
}
|
|
else {
|
|
/* Otherwise, the origin node is not part of the multi-function procedure operation, and a
|
|
* variable that represents an input to the multi-function procedure operation is used. */
|
|
input_variables.append(this->get_multi_function_input_variable(input, output));
|
|
}
|
|
|
|
/* Implicitly convert the variable type if needed by adding a call to an implicit conversion
|
|
* function. */
|
|
input_variables.last() = this->do_variable_implicit_conversion(
|
|
input, output, input_variables.last());
|
|
}
|
|
|
|
return input_variables;
|
|
}
|
|
|
|
mf::Variable *MultiFunctionProcedureOperation::get_constant_input_variable(DInputSocket input)
|
|
{
|
|
const mf::MultiFunction *constant_function = nullptr;
|
|
switch (input->type) {
|
|
case SOCK_FLOAT: {
|
|
const float value = input->default_value_typed<bNodeSocketValueFloat>()->value;
|
|
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float>>(value);
|
|
break;
|
|
}
|
|
case SOCK_VECTOR: {
|
|
const float3 value = float3(input->default_value_typed<bNodeSocketValueVector>()->value);
|
|
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float4>>(
|
|
float4(value, 0.0f));
|
|
break;
|
|
}
|
|
case SOCK_RGBA: {
|
|
const float4 value = float4(input->default_value_typed<bNodeSocketValueRGBA>()->value);
|
|
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float4>>(value);
|
|
break;
|
|
}
|
|
default:
|
|
BLI_assert_unreachable();
|
|
break;
|
|
}
|
|
|
|
mf::Variable *constant_variable = procedure_builder_.add_call<1>(*constant_function)[0];
|
|
implicit_variables_.append(constant_variable);
|
|
return constant_variable;
|
|
}
|
|
|
|
mf::Variable *MultiFunctionProcedureOperation::get_multi_function_input_variable(
|
|
DInputSocket input_socket, DOutputSocket output_socket)
|
|
{
|
|
/* An input was already declared for that same output socket, so no need to declare it again and
|
|
* we just return its variable. */
|
|
if (output_to_variable_map_.contains(output_socket)) {
|
|
/* But first we update the domain priority of the input descriptor to be the higher priority of
|
|
* the existing descriptor and the descriptor of the new input socket. That's because the same
|
|
* output might be connected to multiple inputs inside the multi-function procedure operation
|
|
* which have different priorities. */
|
|
const std::string input_identifier = outputs_to_declared_inputs_map_.lookup(output_socket);
|
|
InputDescriptor &input_descriptor = this->get_input_descriptor(input_identifier);
|
|
input_descriptor.domain_priority = math::min(
|
|
input_descriptor.domain_priority,
|
|
input_descriptor_from_input_socket(input_socket.bsocket()).domain_priority);
|
|
|
|
/* Increment the input's reference count. */
|
|
inputs_to_reference_counts_map_.lookup(input_identifier)++;
|
|
|
|
return output_to_variable_map_.lookup(output_socket);
|
|
}
|
|
|
|
const int input_index = inputs_to_linked_outputs_map_.size();
|
|
const std::string input_identifier = "input" + std::to_string(input_index);
|
|
|
|
/* Declare the input descriptor for this input and prefer to declare its type to be the same as
|
|
* the type of the output socket because doing type conversion in the multi-function procedure is
|
|
* cheaper. */
|
|
InputDescriptor input_descriptor = input_descriptor_from_input_socket(input_socket.bsocket());
|
|
input_descriptor.type = get_node_socket_result_type(output_socket.bsocket());
|
|
declare_input_descriptor(input_identifier, input_descriptor);
|
|
|
|
mf::Variable &variable = procedure_builder_.add_input_parameter(
|
|
mf::DataType::ForSingle(get_cpp_type(input_descriptor.type)), input_identifier);
|
|
parameter_identifiers_.append(input_identifier);
|
|
|
|
/* Map the output socket to the variable that was created for it. */
|
|
output_to_variable_map_.add(output_socket, &variable);
|
|
|
|
/* Map the identifier of the operation input to the output socket it is linked to. */
|
|
inputs_to_linked_outputs_map_.add_new(input_identifier, output_socket);
|
|
|
|
/* Map the output socket to the identifier of the operation input that was declared for it. */
|
|
outputs_to_declared_inputs_map_.add_new(output_socket, input_identifier);
|
|
|
|
/* Map the identifier of the operation input to a reference count of 1, this will later be
|
|
* incremented if that same output was referenced again. */
|
|
inputs_to_reference_counts_map_.add_new(input_identifier, 1);
|
|
|
|
return &variable;
|
|
}
|
|
|
|
/* Returns a multi-function that implicitly converts from the given variable type to the given
|
|
* expected type. nullptr will be returned if no conversion is needed. */
|
|
static mf::MultiFunction *get_conversion_function(const ResultType variable_type,
|
|
const ResultType expected_type)
|
|
{
|
|
/* No conversion needed. */
|
|
if (expected_type == variable_type) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (variable_type == ResultType::Float && expected_type == ResultType::Vector) {
|
|
static auto float_to_vector_function = mf::build::SI1_SO<float, float4>(
|
|
"Float To Vector",
|
|
[](const float &input) -> float4 { return float4(float3(input), 1.0f); },
|
|
mf::build::exec_presets::AllSpanOrSingle());
|
|
return &float_to_vector_function;
|
|
}
|
|
|
|
if (variable_type == ResultType::Float && expected_type == ResultType::Color) {
|
|
static auto float_to_color_function = mf::build::SI1_SO<float, float4>(
|
|
"Float To Color",
|
|
[](const float &input) -> float4 { return float4(float3(input), 1.0f); },
|
|
mf::build::exec_presets::AllSpanOrSingle());
|
|
return &float_to_color_function;
|
|
}
|
|
|
|
if (variable_type == ResultType::Vector && expected_type == ResultType::Float) {
|
|
static auto vector_to_float_function = mf::build::SI1_SO<float4, float>(
|
|
"Vector To Float",
|
|
[](const float4 &input) -> float { return (input.x + input.y + input.z) / 3.0f; },
|
|
mf::build::exec_presets::AllSpanOrSingle());
|
|
return &vector_to_float_function;
|
|
}
|
|
|
|
if (variable_type == ResultType::Vector && expected_type == ResultType::Color) {
|
|
static auto vector_to_color_function = mf::build::SI1_SO<float4, float4>(
|
|
"Vector To Color",
|
|
[](const float4 &input) -> float4 { return float4(input.xyz(), 1.0f); },
|
|
mf::build::exec_presets::AllSpanOrSingle());
|
|
return &vector_to_color_function;
|
|
}
|
|
|
|
if (variable_type == ResultType::Color && expected_type == ResultType::Float) {
|
|
static auto color_to_float_function = mf::build::SI1_SO<float4, float>(
|
|
"Color To Float",
|
|
[](const float4 &input) -> float { return (input.x + input.y + input.z) / 3.0f; },
|
|
mf::build::exec_presets::AllSpanOrSingle());
|
|
return &color_to_float_function;
|
|
}
|
|
|
|
if (variable_type == ResultType::Color && expected_type == ResultType::Vector) {
|
|
/* No conversion needed. */
|
|
return nullptr;
|
|
}
|
|
|
|
BLI_assert_unreachable();
|
|
return nullptr;
|
|
}
|
|
|
|
mf::Variable *MultiFunctionProcedureOperation::do_variable_implicit_conversion(
|
|
DInputSocket input_socket, DOutputSocket output_socket, mf::Variable *variable)
|
|
{
|
|
const ResultType expected_type = get_node_socket_result_type(input_socket.bsocket());
|
|
const ResultType variable_type = get_node_socket_result_type(output_socket.bsocket());
|
|
|
|
const mf::MultiFunction *function = get_conversion_function(variable_type, expected_type);
|
|
if (!function) {
|
|
return variable;
|
|
}
|
|
|
|
mf::Variable *converted_variable = procedure_builder_.add_call<1>(*function, {variable})[0];
|
|
implicit_variables_.append(converted_variable);
|
|
return converted_variable;
|
|
}
|
|
|
|
void MultiFunctionProcedureOperation::assign_output_variables(DNode node,
|
|
Vector<mf::Variable *> &variables)
|
|
{
|
|
const DOutputSocket preview_output = find_preview_output_socket(node);
|
|
|
|
for (int i = 0; i < node->output_sockets().size(); i++) {
|
|
const DOutputSocket output{node.context(), node->output_sockets()[i]};
|
|
|
|
output_to_variable_map_.add_new(output, variables[i]);
|
|
|
|
/* If any of the nodes linked to the output are not part of the multi-function procedure
|
|
* operation but are part of the execution schedule, then an output result needs to be
|
|
* populated for it. */
|
|
const bool is_operation_output = is_output_linked_to_node_conditioned(output, [&](DNode node) {
|
|
return schedule_.contains(node) && !compile_unit_.contains(node);
|
|
});
|
|
|
|
/* If the output is used as the node preview, then an output result needs to be populated for
|
|
* it, and we additionally keep track of that output to later compute the previews from. */
|
|
const bool is_preview_output = output == preview_output;
|
|
if (is_preview_output) {
|
|
preview_outputs_.add(output);
|
|
}
|
|
|
|
if (is_operation_output || is_preview_output) {
|
|
this->populate_operation_result(output, variables[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MultiFunctionProcedureOperation::populate_operation_result(DOutputSocket output_socket,
|
|
mf::Variable *variable)
|
|
{
|
|
const uint output_id = output_sockets_to_output_identifiers_map_.size();
|
|
const std::string output_identifier = "output" + std::to_string(output_id);
|
|
|
|
const ResultType result_type = get_node_socket_result_type(output_socket.bsocket());
|
|
const Result result = context().create_result(result_type);
|
|
populate_result(output_identifier, result);
|
|
|
|
/* Map the output socket to the identifier of the newly populated result. */
|
|
output_sockets_to_output_identifiers_map_.add_new(output_socket, output_identifier);
|
|
|
|
procedure_builder_.add_output_parameter(*variable);
|
|
parameter_identifiers_.append(output_identifier);
|
|
}
|
|
|
|
} // namespace blender::compositor
|