diff --git a/source/blender/blenlib/intern/cpp_types.cc b/source/blender/blenlib/intern/cpp_types.cc index b97af1efbb1..03f19fcebbb 100644 --- a/source/blender/blenlib/intern/cpp_types.cc +++ b/source/blender/blenlib/intern/cpp_types.cc @@ -50,6 +50,7 @@ BLI_CPP_TYPE_MAKE(bool, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(float, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(blender::float2, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(blender::float3, CPPTypeFlags::BasicType) +BLI_CPP_TYPE_MAKE(blender::float4, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(blender::float4x4, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(int8_t, CPPTypeFlags::BasicType) diff --git a/source/blender/compositor/realtime_compositor/CMakeLists.txt b/source/blender/compositor/realtime_compositor/CMakeLists.txt index 8988eb800dc..8974fc95618 100644 --- a/source/blender/compositor/realtime_compositor/CMakeLists.txt +++ b/source/blender/compositor/realtime_compositor/CMakeLists.txt @@ -9,6 +9,7 @@ set(INC ../../blenkernel ../../blentranslation ../../draw + ../../functions ../../gpu ../../imbuf ../../makesrna @@ -29,6 +30,7 @@ set(SRC intern/evaluator.cc intern/input_single_value_operation.cc intern/meta_data.cc + intern/multi_function_procedure_operation.cc intern/node_operation.cc intern/operation.cc intern/pixel_operation.cc @@ -53,6 +55,7 @@ set(SRC COM_input_descriptor.hh COM_input_single_value_operation.hh COM_meta_data.hh + COM_multi_function_procedure_operation.hh COM_node_operation.hh COM_operation.hh COM_pixel_operation.hh @@ -146,6 +149,7 @@ set(LIB bf_render PRIVATE bf::blenlib bf_blenkernel + bf_functions ) set(GLSL_SRC diff --git a/source/blender/compositor/realtime_compositor/COM_multi_function_procedure_operation.hh b/source/blender/compositor/realtime_compositor/COM_multi_function_procedure_operation.hh new file mode 100644 index 00000000000..0f5a7a52d4b --- /dev/null +++ b/source/blender/compositor/realtime_compositor/COM_multi_function_procedure_operation.hh @@ -0,0 +1,110 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include + +#include "BLI_map.hh" +#include "BLI_string_ref.hh" +#include "BLI_vector.hh" +#include "BLI_vector_set.hh" + +#include "FN_multi_function_procedure.hh" +#include "FN_multi_function_procedure_builder.hh" +#include "FN_multi_function_procedure_executor.hh" + +#include "NOD_derived_node_tree.hh" +#include "NOD_multi_function.hh" + +#include "COM_context.hh" +#include "COM_operation.hh" +#include "COM_pixel_operation.hh" +#include "COM_scheduler.hh" + +namespace blender::realtime_compositor { + +using namespace nodes::derived_node_tree_types; + +/* ------------------------------------------------------------------------------------------------ + * Multi-Function Procedure Operation + * + * A pixel operation that evaluates a multi-function procedure built from the pixel compile unit + * using the multi-function procedure builder, see FN_multi_function_procedure_builder.hh for more + * information. Also see the PixelOperation class for more information on pixel operations. */ +class MultiFunctionProcedureOperation : public PixelOperation { + private: + /* The multi-function procedure, its builder, and executor that are backing the operation. This + * is created and compiled during construction. */ + mf::Procedure procedure_; + mf::ProcedureBuilder procedure_builder_; + std::unique_ptr procedure_executor_; + /* A map that associates each node in the compile unit with an instance of its multi-function + * builder. */ + Map> node_multi_functions_; + /* A map that associates the output sockets of each node to the variables that were created for + * them. */ + Map output_to_variable_map_; + /* A vector that stores the intermediate variables that were implicitly created for the procedure + * but are not associated with a node output. Those variables are for such multi-functions like + * constant inputs and implicit conversion. */ + Vector implicit_variables_; + /* A vector that stores the identifiers of the parameters of the multi-function procedure in + * order. The parameters include both inputs and outputs. This is used to retrieve the input and + * output results for each of the parameters in the procedure. Note that parameters have no + * identifiers and are identified solely by their order. */ + Vector parameter_identifiers_; + + public: + /* Build a multi-function procedure as well as an executor for it from the given pixel compile + * unit and execution schedule. */ + MultiFunctionProcedureOperation(Context &context, + PixelCompileUnit &compile_unit, + const Schedule &schedule); + + /* Calls the multi-function procedure executor on the domain of the operator passing in the + * inputs and outputs as parameters. */ + void execute() override; + + private: + /* Builds the procedure by going over the nodes in the compile unit, calling their + * multi-functions and creating any necessary inputs or outputs to the operation/procedure. */ + void build_procedure(); + + /* Get the variables corresponding to the inputs of the given node. The variables can be those + * that were returned by a previous call to a multi-function, those that were generated as + * constants for unlinked inputs, or those that were added as inputs to the operation/procedure + * itself. */ + Vector get_input_variables(DNode node); + + /* Returns a constant variable that was created by calling a constant function carrying the value + * of the given input socket. */ + mf::Variable *get_constant_input_variable(DInputSocket input); + + /* Given an input in a node that is part of the compile unit that is connected to an output that + * is in a non that is not part of the compile unit. Declare an input to the operation/procedure + * for that output if not done already and return a variable that represent that input. */ + mf::Variable *get_multi_function_input_variable(DInputSocket input_socket, + DOutputSocket output_socket); + + /* Implicitly convert the type of the given variable that is passed from the given output socket + * to the given input socket if needed. This is done by adding an implicit conversion function + * whose output variable will be returned. If no conversion is needed, the given variable is + * returned as is. */ + mf::Variable *do_variable_implicit_conversion(DInputSocket input_socket, + DOutputSocket output_socket, + mf::Variable *variable); + + /* Given the variables that were returned by calling the multi-function for the given node, + * assign the variables to their corresponding outputs. And if an output is connected to a node + * outside of the compile unit or is used as the preview of the node, declare an output to the + * operation/procedure for it. */ + void assign_output_variables(DNode node, Vector &variables); + + /* Populate an output to the operator/procedure for the given output socket whose value is stored + * in the given variable. */ + void populate_operation_result(DOutputSocket output_socket, mf::Variable *variable); +}; + +} // namespace blender::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/COM_pixel_operation.hh b/source/blender/compositor/realtime_compositor/COM_pixel_operation.hh index f78364c7c5b..6e8c052218c 100644 --- a/source/blender/compositor/realtime_compositor/COM_pixel_operation.hh +++ b/source/blender/compositor/realtime_compositor/COM_pixel_operation.hh @@ -74,6 +74,9 @@ class PixelOperation : public Operation { /* A map that associates the identifier of each input of the operation with the output socket it * is linked to. This is needed to help the compiler establish links between operations. */ Map inputs_to_linked_outputs_map_; + /* A map that associates the output socket of a node that is not part of the pixel operation to + * the identifier of the input of the operation that was declared for it. */ + Map outputs_to_declared_inputs_map_; /* A map that associates the output socket that provides the result of an output of the operation * with the identifier of that output. This is needed to help the compiler establish links * between operations. */ @@ -85,6 +88,17 @@ class PixelOperation : public Operation { public: PixelOperation(Context &context, PixelCompileUnit &compile_unit, const Schedule &schedule); + /* Create one of the concrete subclasses based on the context. Deleting the operation is the + * caller's responsibility. */ + static PixelOperation *create_operation(Context &context, + PixelCompileUnit &compile_unit, + const Schedule &schedule); + + /* Returns the maximum number of outputs that the PixelOperation can have. Pixel compile units + * need to be split into smaller units if the numbers of outputs they have is more than the + * number returned by this method. */ + static int maximum_number_of_outputs(Context &context); + /* Compute a node preview for all nodes in the pixel operations if the node requires a preview. * * Previews are computed from results that are populated for outputs that are used to compute diff --git a/source/blender/compositor/realtime_compositor/COM_shader_operation.hh b/source/blender/compositor/realtime_compositor/COM_shader_operation.hh index e5c7488aed3..9e269cbfee3 100644 --- a/source/blender/compositor/realtime_compositor/COM_shader_operation.hh +++ b/source/blender/compositor/realtime_compositor/COM_shader_operation.hh @@ -26,10 +26,6 @@ namespace blender::realtime_compositor { using namespace nodes::derived_node_tree_types; -/* A type representing a contiguous subset of the node execution schedule that will be compiled - * into a Pixel Operation. */ -using PixelCompileUnit = VectorSet; - /* ------------------------------------------------------------------------------------------------ * Shader Operation * @@ -70,9 +66,6 @@ class ShaderOperation : public PixelOperation { * the attribute that was created for it. This is used to share the same attribute with all * inputs that are linked to the same output socket. */ Map output_to_material_attribute_map_; - /* A map that associates the output socket of a node that is not part of the shader operation to - * the identifier of the input of the operation that was declared for it. */ - Map outputs_to_declared_inputs_map_; public: /* Construct and compile a GPU material from the given shader compile unit and execution schedule diff --git a/source/blender/compositor/realtime_compositor/intern/evaluator.cc b/source/blender/compositor/realtime_compositor/intern/evaluator.cc index c5327d90e31..dc57bccd5c4 100644 --- a/source/blender/compositor/realtime_compositor/intern/evaluator.cc +++ b/source/blender/compositor/realtime_compositor/intern/evaluator.cc @@ -163,7 +163,7 @@ void Evaluator::compile_and_evaluate_pixel_compile_unit(CompileState &compile_st { PixelCompileUnit &compile_unit = compile_state.get_pixel_compile_unit(); - /* GPUs have hardware limitations on the number of output images shaders can have, so we might + /* Pixel operations might have limitations on the number of outputs they can have, so we might * have to split the compile unit into smaller units to workaround this limitation. In practice, * splitting will almost always never happen due to the scheduling strategy we use, so the base * case remains fast. */ @@ -172,9 +172,7 @@ void Evaluator::compile_and_evaluate_pixel_compile_unit(CompileState &compile_st const DNode node = compile_unit[i]; number_of_outputs += compile_state.compute_pixel_node_operation_outputs_count(node); - /* The GPU module currently only supports up to 8 output images in shaders, but once this - * limitation is lifted, we can replace that with GPU_max_images(). */ - if (number_of_outputs <= 8) { + if (number_of_outputs <= PixelOperation::maximum_number_of_outputs(context_)) { continue; } @@ -202,7 +200,7 @@ void Evaluator::compile_and_evaluate_pixel_compile_unit(CompileState &compile_st } const Schedule &schedule = compile_state.get_schedule(); - PixelOperation *operation = new ShaderOperation(context_, compile_unit, schedule); + PixelOperation *operation = PixelOperation::create_operation(context_, compile_unit, schedule); for (DNode node : compile_unit) { compile_state.map_node_to_pixel_operation(node, operation); diff --git a/source/blender/compositor/realtime_compositor/intern/multi_function_procedure_operation.cc b/source/blender/compositor/realtime_compositor/intern/multi_function_procedure_operation.cc new file mode 100644 index 00000000000..3b944b7dc8a --- /dev/null +++ b/source/blender/compositor/realtime_compositor/intern/multi_function_procedure_operation.cc @@ -0,0 +1,399 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include +#include + +#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::realtime_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(procedure_); +} + +static const CPPType &get_cpp_type(ResultType type) +{ + switch (type) { + case ResultType::Float: + return CPPType::get(); + case ResultType::Vector: + case ResultType::Color: + return CPPType::get(); + default: + /* Other types are internal and needn't be handled by operations. */ + break; + } + + BLI_assert_unreachable(); + return CPPType::get(); +} + +/* 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(*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 input_variables = this->get_input_variables(node); + + /* Call the node multi-function, getting the variables for its outputs. */ + Vector 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 MultiFunctionProcedureOperation::get_input_variables(DNode node) +{ + Vector 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; + } + + /* Get the output linked to the input. If it is null, that means the input is unlinked and we + * generate a constant variable for it. */ + const DOutputSocket output = get_output_linked_to_input(input); + if (!output) { + input_variables.append(this->get_constant_input_variable(input)); + continue; + } + + /* 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()->value; + constant_function = &procedure_.construct_function>(value); + break; + } + case SOCK_VECTOR: { + const float3 value = float3(input->default_value_typed()->value); + constant_function = &procedure_.construct_function>( + float4(value, 0.0f)); + break; + } + case SOCK_RGBA: { + const float4 value = float4(input->default_value_typed()->value); + constant_function = &procedure_.construct_function>(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. But 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. */ + if (output_to_variable_map_.contains(output_socket)) { + 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); + + 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); + + 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 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 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( + "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( + "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( + "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 &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::realtime_compositor diff --git a/source/blender/compositor/realtime_compositor/intern/pixel_operation.cc b/source/blender/compositor/realtime_compositor/intern/pixel_operation.cc index a23f1c44968..b6d4b24692e 100644 --- a/source/blender/compositor/realtime_compositor/intern/pixel_operation.cc +++ b/source/blender/compositor/realtime_compositor/intern/pixel_operation.cc @@ -2,6 +2,7 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include #include #include "BLI_map.hh" @@ -11,10 +12,12 @@ #include "COM_algorithm_compute_preview.hh" #include "COM_context.hh" +#include "COM_multi_function_procedure_operation.hh" #include "COM_operation.hh" #include "COM_pixel_operation.hh" #include "COM_result.hh" #include "COM_scheduler.hh" +#include "COM_shader_operation.hh" #include "COM_utilities.hh" namespace blender::realtime_compositor { @@ -28,6 +31,28 @@ PixelOperation::PixelOperation(Context &context, { } +PixelOperation *PixelOperation::create_operation(Context &context, + PixelCompileUnit &compile_unit, + const Schedule &schedule) +{ + if (context.use_gpu()) { + return new ShaderOperation(context, compile_unit, schedule); + } + + return new MultiFunctionProcedureOperation(context, compile_unit, schedule); +} + +int PixelOperation::maximum_number_of_outputs(Context &context) +{ + if (context.use_gpu()) { + /* The GPU module currently only supports up to 8 output images in shaders, but once this + * limitation is lifted, we can replace that with GPU_max_images(). */ + return 8; + } + + return std::numeric_limits::max(); +} + void PixelOperation::compute_preview() { for (const DOutputSocket &output : preview_outputs_) { diff --git a/source/blender/compositor/realtime_compositor/intern/shader_operation.cc b/source/blender/compositor/realtime_compositor/intern/shader_operation.cc index 1d67258a182..a2d4034734d 100644 --- a/source/blender/compositor/realtime_compositor/intern/shader_operation.cc +++ b/source/blender/compositor/realtime_compositor/intern/shader_operation.cc @@ -25,7 +25,6 @@ #include "NOD_node_declaration.hh" #include "COM_context.hh" -#include "COM_operation.hh" #include "COM_pixel_operation.hh" #include "COM_result.hh" #include "COM_scheduler.hh" @@ -184,7 +183,7 @@ void ShaderOperation::link_node_input_external(DInputSocket input_socket, * But 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 shader operation which have - * different proprieties. */ + * 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( diff --git a/source/blender/functions/FN_multi_function_builder.hh b/source/blender/functions/FN_multi_function_builder.hh index 55bd64bb660..2bdf213c33b 100644 --- a/source/blender/functions/FN_multi_function_builder.hh +++ b/source/blender/functions/FN_multi_function_builder.hh @@ -540,6 +540,19 @@ inline auto build_multi_function_with_n_inputs_one_output(const char *name, return CustomMF(name, call_fn, param_tags); } +template +inline auto build_multi_function_with_n_inputs_two_outputs(const char *name, + const ElementFn element_fn, + const ExecPreset exec_preset, + TypeSequence /*in_types*/) +{ + constexpr auto param_tags = TypeSequence..., + ParamTag, + ParamTag>(); + auto call_fn = build_multi_function_call_from_element_fn(element_fn, exec_preset, param_tags); + return CustomMF(name, call_fn, param_tags); +} + } // namespace detail /** Build multi-function with 1 single-input and 1 single-output parameter. */ @@ -647,6 +660,20 @@ inline auto SM(const char *name, return detail::CustomMF(name, call_fn, param_tags); } +/** Build multi-function with 1 single-input and 2 single-output parameter. */ +template +inline auto SI1_SO2(const char *name, + const ElementFn element_fn, + const ExecPreset exec_preset = exec_presets::Materialized()) +{ + return detail::build_multi_function_with_n_inputs_two_outputs( + name, element_fn, exec_preset, TypeSequence()); +} + } // namespace blender::fn::multi_function::build namespace blender::fn::multi_function { diff --git a/source/blender/nodes/NOD_math_functions.hh b/source/blender/nodes/NOD_math_functions.hh index 8d2c1140d85..22803e14aa2 100644 --- a/source/blender/nodes/NOD_math_functions.hh +++ b/source/blender/nodes/NOD_math_functions.hh @@ -13,8 +13,12 @@ #include "FN_multi_function_builder.hh" +#include "NOD_multi_function.hh" + namespace blender::nodes { +void node_math_build_multi_function(NodeMultiFunctionBuilder &builder); + struct FloatMathOperationInfo { StringRefNull title_case_name; StringRefNull shader_name; diff --git a/source/blender/nodes/NOD_multi_function.hh b/source/blender/nodes/NOD_multi_function.hh index 7fc2514e5d0..beff18b60e7 100644 --- a/source/blender/nodes/NOD_multi_function.hh +++ b/source/blender/nodes/NOD_multi_function.hh @@ -40,8 +40,17 @@ class NodeMultiFunctionBuilder : NonCopyable, NonMovable { */ template void construct_and_set_matching_fn(Args &&...args); + /** + * Similar to #construct_and_set_matching_fn, but can be used when the type name of the + * multi-function is not known (e.g. when using `mf::build::SI1_SO`). + * + * \param create_multi_function: A function that returns the multi-function by value. + */ + template void construct_and_set_matching_fn_cb(Fn &&create_multi_function); + const bNode &node(); const bNodeTree &tree(); + const mf::MultiFunction &function(); }; /** @@ -82,6 +91,11 @@ inline const bNodeTree &NodeMultiFunctionBuilder::tree() return tree_; } +inline const mf::MultiFunction &NodeMultiFunctionBuilder::function() +{ + return *built_fn_; +} + inline void NodeMultiFunctionBuilder::set_matching_fn(const mf::MultiFunction *fn) { built_fn_ = fn; @@ -99,6 +113,15 @@ inline void NodeMultiFunctionBuilder::construct_and_set_matching_fn(Args &&...ar built_fn_ = &*owned_built_fn_; } +template +inline void NodeMultiFunctionBuilder::construct_and_set_matching_fn_cb(Fn &&create_multi_function) +{ + using T = decltype(create_multi_function()); + T *allocated_function = new T(create_multi_function()); + owned_built_fn_ = std::shared_ptr(allocated_function); + built_fn_ = &*owned_built_fn_; +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/nodes/composite/CMakeLists.txt b/source/blender/nodes/composite/CMakeLists.txt index ff2c8238d76..a7b2b6a9cc7 100644 --- a/source/blender/nodes/composite/CMakeLists.txt +++ b/source/blender/nodes/composite/CMakeLists.txt @@ -129,6 +129,7 @@ set(LIB PRIVATE bf::blenlib PRIVATE bf::depsgraph PRIVATE bf::dna + bf_functions PRIVATE bf::intern::guardedalloc PRIVATE bf::extern::fmtlib bf_realtime_compositor diff --git a/source/blender/nodes/composite/nodes/node_composite_brightness.cc b/source/blender/nodes/composite/nodes/node_composite_brightness.cc index 75fb38bfbc4..b6c473d3b73 100644 --- a/source/blender/nodes/composite/nodes/node_composite_brightness.cc +++ b/source/blender/nodes/composite/nodes/node_composite_brightness.cc @@ -6,6 +6,14 @@ * \ingroup cmpnodes */ +#include + +#include "BLI_math_color.h" + +#include "FN_multi_function_builder.hh" + +#include "NOD_multi_function.hh" + #include "UI_interface.hh" #include "UI_resources.hh" @@ -71,6 +79,66 @@ static ShaderNode *get_compositor_shader_node(DNode node) return new BrightContrastShaderNode(node); } +/* The algorithm is by Werner D. Streidt, extracted of OpenCV demhist.c: + * http://visca.com/ffactory/archives/5-99/msg00021.html */ +template +static float4 brightness_and_contrast(const float4 &color, + const float brightness, + const float contrast) +{ + float scaled_brightness = brightness / 100.0f; + float delta = contrast / 200.0f; + + float multiplier, offset; + if (contrast > 0.0f) { + multiplier = 1.0f - delta * 2.0f; + multiplier = 1.0f / math::max(multiplier, std::numeric_limits::epsilon()); + offset = multiplier * (scaled_brightness - delta); + } + else { + delta *= -1.0f; + multiplier = math::max(1.0f - delta * 2.0f, 0.0f); + offset = multiplier * scaled_brightness + delta; + } + + float4 input_color = color; + if constexpr (UsePremultiply) { + premul_to_straight_v4(input_color); + } + + float4 result = float4(input_color.xyz() * multiplier + offset, input_color.w); + + if constexpr (UsePremultiply) { + straight_to_premul_v4(result); + } + return result; +} + +static void node_build_multi_function(blender::nodes::NodeMultiFunctionBuilder &builder) +{ + static auto premultiply_used_function = mf::build::SI3_SO( + "Bright And Contrast Use Premultiply", + [](const float4 &color, const float brightness, const float contrast) -> float4 { + return brightness_and_contrast(color, brightness, contrast); + }, + mf::build::exec_presets::SomeSpanOrSingle<0>()); + + static auto no_premultiply_function = mf::build::SI3_SO( + "Bright And Contrast No Premultiply", + [](const float4 &color, const float brightness, const float contrast) -> float4 { + return brightness_and_contrast(color, brightness, contrast); + }, + mf::build::exec_presets::SomeSpanOrSingle<0>()); + + const bool use_premultiply = builder.node().custom1; + if (use_premultiply) { + builder.set_matching_fn(premultiply_used_function); + } + else { + builder.set_matching_fn(no_premultiply_function); + } +} + } // namespace blender::nodes::node_composite_brightness_cc void register_node_type_cmp_brightcontrast() @@ -84,6 +152,7 @@ void register_node_type_cmp_brightcontrast() ntype.draw_buttons = file_ns::node_composit_buts_brightcontrast; ntype.initfunc = file_ns::node_composit_init_brightcontrast; ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; + ntype.build_multi_function = file_ns::node_build_multi_function; blender::bke::node_register_type(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_gamma.cc b/source/blender/nodes/composite/nodes/node_composite_gamma.cc index 6f34b910da6..e63c2a3e813 100644 --- a/source/blender/nodes/composite/nodes/node_composite_gamma.cc +++ b/source/blender/nodes/composite/nodes/node_composite_gamma.cc @@ -6,6 +6,13 @@ * \ingroup cmpnodes */ +#include "BLI_math_vector.hh" +#include "BLI_math_vector_types.hh" + +#include "NOD_multi_function.hh" + +#include "FN_multi_function_builder.hh" + #include "GPU_material.hh" #include "COM_shader_node.hh" @@ -50,6 +57,17 @@ static ShaderNode *get_compositor_shader_node(DNode node) return new GammaShaderNode(node); } +static void node_build_multi_function(blender::nodes::NodeMultiFunctionBuilder &builder) +{ + static auto gamma_function = mf::build::SI2_SO( + "Gamma", + [](const float4 &color, const float gamma) -> float4 { + return float4(math::safe_pow(color.xyz(), gamma), color.w); + }, + mf::build::exec_presets::SomeSpanOrSingle<0>()); + builder.set_matching_fn(gamma_function); +} + } // namespace blender::nodes::node_composite_gamma_cc void register_node_type_cmp_gamma() @@ -61,6 +79,7 @@ void register_node_type_cmp_gamma() cmp_node_type_base(&ntype, CMP_NODE_GAMMA, "Gamma", NODE_CLASS_OP_COLOR); ntype.declare = file_ns::cmp_node_gamma_declare; ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; + ntype.build_multi_function = file_ns::node_build_multi_function; blender::bke::node_register_type(&ntype); } diff --git a/source/blender/nodes/composite/nodes/node_composite_math.cc b/source/blender/nodes/composite/nodes/node_composite_math.cc index 3be0efc3a08..62723f38491 100644 --- a/source/blender/nodes/composite/nodes/node_composite_math.cc +++ b/source/blender/nodes/composite/nodes/node_composite_math.cc @@ -11,6 +11,7 @@ #include "COM_shader_node.hh" #include "NOD_math_functions.hh" +#include "NOD_multi_function.hh" #include "NOD_socket_search_link.hh" #include "RNA_enum_types.hh" @@ -129,6 +130,7 @@ void register_node_type_cmp_math() ntype.labelfunc = node_math_label; ntype.updatefunc = node_math_update; ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; + ntype.build_multi_function = blender::nodes::node_math_build_multi_function; ntype.gather_link_search_ops = file_ns::node_gather_link_searches; blender::bke::node_register_type(&ntype); diff --git a/source/blender/nodes/composite/nodes/node_composite_normal.cc b/source/blender/nodes/composite/nodes/node_composite_normal.cc index 04c922f49b3..f350ae1d04f 100644 --- a/source/blender/nodes/composite/nodes/node_composite_normal.cc +++ b/source/blender/nodes/composite/nodes/node_composite_normal.cc @@ -6,6 +6,10 @@ * \ingroup cmpnodes */ +#include "FN_multi_function_builder.hh" + +#include "NOD_multi_function.hh" + #include "GPU_material.hh" #include "COM_shader_node.hh" @@ -66,6 +70,23 @@ static ShaderNode *get_compositor_shader_node(DNode node) return new NormalShaderNode(node); } +static void node_build_multi_function(blender::nodes::NodeMultiFunctionBuilder &builder) +{ + const bNodeSocket &normal_output = builder.node().output_by_identifier("Normal"); + const float3 node_normal = normal_output.default_value_typed()->value; + const float3 normalized_node_normal = math::normalize(node_normal); + + builder.construct_and_set_matching_fn_cb([=]() { + return mf::build::SI1_SO2( + "Normal And Dot", + [=](const float4 &normal, float4 &output_normal, float &dot) -> void { + output_normal = float4(normalized_node_normal, 0.0f); + dot = -math::dot(normal.xyz(), normalized_node_normal); + }, + mf::build::exec_presets::AllSpanOrSingle()); + }); +} + } // namespace blender::nodes::node_composite_normal_cc void register_node_type_cmp_normal() @@ -77,6 +98,7 @@ void register_node_type_cmp_normal() cmp_node_type_base(&ntype, CMP_NODE_NORMAL, "Normal", NODE_CLASS_OP_VECTOR); ntype.declare = file_ns::cmp_node_normal_declare; ntype.get_compositor_shader_node = file_ns::get_compositor_shader_node; + ntype.build_multi_function = file_ns::node_build_multi_function; blender::bke::node_register_type(&ntype); } diff --git a/source/blender/nodes/intern/math_functions.cc b/source/blender/nodes/intern/math_functions.cc index de3070379cc..717e5bf1356 100644 --- a/source/blender/nodes/intern/math_functions.cc +++ b/source/blender/nodes/intern/math_functions.cc @@ -6,6 +6,83 @@ namespace blender::nodes { +static const mf::MultiFunction *get_base_multi_function(const bNode &node) +{ + const int mode = node.custom1; + const mf::MultiFunction *base_fn = nullptr; + + try_dispatch_float_math_fl_to_fl( + mode, [&](auto devi_fn, auto function, const FloatMathOperationInfo &info) { + static auto fn = mf::build::SI1_SO( + info.title_case_name.c_str(), function, devi_fn); + base_fn = &fn; + }); + if (base_fn != nullptr) { + return base_fn; + } + + try_dispatch_float_math_fl_fl_to_fl( + mode, [&](auto devi_fn, auto function, const FloatMathOperationInfo &info) { + static auto fn = mf::build::SI2_SO( + info.title_case_name.c_str(), function, devi_fn); + base_fn = &fn; + }); + if (base_fn != nullptr) { + return base_fn; + } + + try_dispatch_float_math_fl_fl_fl_to_fl( + mode, [&](auto devi_fn, auto function, const FloatMathOperationInfo &info) { + static auto fn = mf::build::SI3_SO( + info.title_case_name.c_str(), function, devi_fn); + base_fn = &fn; + }); + if (base_fn != nullptr) { + return base_fn; + } + + return nullptr; +} + +class ClampWrapperFunction : public mf::MultiFunction { + private: + const mf::MultiFunction &fn_; + + public: + ClampWrapperFunction(const mf::MultiFunction &fn) : fn_(fn) + { + this->set_signature(&fn.signature()); + } + + void call(const IndexMask &mask, mf::Params params, mf::Context context) const override + { + fn_.call(mask, params, context); + + /* Assumes the output parameter is the last one. */ + const int output_param_index = this->param_amount() - 1; + /* This has actually been initialized in the call above. */ + MutableSpan results = params.uninitialized_single_output(output_param_index); + + mask.foreach_index_optimized([&](const int i) { + float &value = results[i]; + CLAMP(value, 0.0f, 1.0f); + }); + } +}; + +void node_math_build_multi_function(NodeMultiFunctionBuilder &builder) +{ + const mf::MultiFunction *base_function = get_base_multi_function(builder.node()); + + const bool clamp_output = builder.node().custom2 != 0; + if (clamp_output) { + builder.construct_and_set_matching_fn(*base_function); + } + else { + builder.set_matching_fn(base_function); + } +} + const FloatMathOperationInfo *get_float_math_operation_info(const int operation) { diff --git a/source/blender/nodes/shader/nodes/node_shader_math.cc b/source/blender/nodes/shader/nodes/node_shader_math.cc index d9f7a9bde4c..4a59a406964 100644 --- a/source/blender/nodes/shader/nodes/node_shader_math.cc +++ b/source/blender/nodes/shader/nodes/node_shader_math.cc @@ -103,83 +103,6 @@ static int gpu_shader_math(GPUMaterial *mat, return 0; } -static const mf::MultiFunction *get_base_multi_function(const bNode &node) -{ - const int mode = node.custom1; - const mf::MultiFunction *base_fn = nullptr; - - try_dispatch_float_math_fl_to_fl( - mode, [&](auto devi_fn, auto function, const FloatMathOperationInfo &info) { - static auto fn = mf::build::SI1_SO( - info.title_case_name.c_str(), function, devi_fn); - base_fn = &fn; - }); - if (base_fn != nullptr) { - return base_fn; - } - - try_dispatch_float_math_fl_fl_to_fl( - mode, [&](auto devi_fn, auto function, const FloatMathOperationInfo &info) { - static auto fn = mf::build::SI2_SO( - info.title_case_name.c_str(), function, devi_fn); - base_fn = &fn; - }); - if (base_fn != nullptr) { - return base_fn; - } - - try_dispatch_float_math_fl_fl_fl_to_fl( - mode, [&](auto devi_fn, auto function, const FloatMathOperationInfo &info) { - static auto fn = mf::build::SI3_SO( - info.title_case_name.c_str(), function, devi_fn); - base_fn = &fn; - }); - if (base_fn != nullptr) { - return base_fn; - } - - return nullptr; -} - -class ClampWrapperFunction : public mf::MultiFunction { - private: - const mf::MultiFunction &fn_; - - public: - ClampWrapperFunction(const mf::MultiFunction &fn) : fn_(fn) - { - this->set_signature(&fn.signature()); - } - - void call(const IndexMask &mask, mf::Params params, mf::Context context) const override - { - fn_.call(mask, params, context); - - /* Assumes the output parameter is the last one. */ - const int output_param_index = this->param_amount() - 1; - /* This has actually been initialized in the call above. */ - MutableSpan results = params.uninitialized_single_output(output_param_index); - - mask.foreach_index_optimized([&](const int i) { - float &value = results[i]; - CLAMP(value, 0.0f, 1.0f); - }); - } -}; - -static void sh_node_math_build_multi_function(NodeMultiFunctionBuilder &builder) -{ - const mf::MultiFunction *base_function = get_base_multi_function(builder.node()); - - const bool clamp_output = builder.node().custom2 != 0; - if (clamp_output) { - builder.construct_and_set_matching_fn(*base_function); - } - else { - builder.set_matching_fn(base_function); - } -} - static void node_eval_elem(value_elem::ElemEvalParams ¶ms) { using namespace value_elem; @@ -444,7 +367,7 @@ void register_node_type_sh_math() ntype.labelfunc = node_math_label; ntype.gpu_fn = file_ns::gpu_shader_math; ntype.updatefunc = node_math_update; - ntype.build_multi_function = file_ns::sh_node_math_build_multi_function; + ntype.build_multi_function = blender::nodes::node_math_build_multi_function; ntype.gather_link_search_ops = file_ns::sh_node_math_gather_link_searches; ntype.materialx_fn = file_ns::node_shader_materialx; ntype.eval_elem = file_ns::node_eval_elem;