Blender V5.0
multi_function_procedure_operation.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <memory>
6#include <string>
7
8#include "BLI_assert.h"
9#include "BLI_color.hh"
10#include "BLI_cpp_type.hh"
11#include "BLI_generic_span.hh"
12#include "BLI_index_mask.hh"
13#include "BLI_map.hh"
14#include "BLI_math_base.hh"
16#include "BLI_string_ref.hh"
17#include "BLI_vector.hh"
18
19#include "FN_multi_function.hh"
27
28#include "DNA_node_types.h"
29
31
33#include "NOD_multi_function.hh"
34
35#include "COM_context.hh"
36#include "COM_domain.hh"
40#include "COM_result.hh"
41#include "COM_scheduler.hh"
42#include "COM_utilities.hh"
43
44namespace blender::compositor {
45
46using namespace nodes::derived_node_tree_types;
47
49 PixelCompileUnit &compile_unit,
50 const Schedule &schedule)
51 : PixelOperation(context, compile_unit, schedule), procedure_builder_(procedure_)
52{
53 this->build_procedure();
54 procedure_executor_ = std::make_unique<mf::ProcedureExecutor>(procedure_);
55}
56
58{
59 const Domain domain = compute_domain();
60 const int64_t size = int64_t(domain.size.x) * domain.size.y;
62 mf::ParamsBuilder parameter_builder{*procedure_executor_, &mask};
63
64 const bool is_single_value = this->is_single_value_operation();
65
66 /* For each of the parameters, either add an input or an output depending on its interface type,
67 * allocating the outputs when needed. */
68 for (int i = 0; i < procedure_.params().size(); i++) {
69 if (procedure_.params()[i].type == mf::ParamType::InterfaceType::Input) {
70 const Result &input = get_input(parameter_identifiers_[i]);
71 if (input.is_single_value()) {
72 parameter_builder.add_readonly_single_input(input.single_value());
73 }
74 else {
75 parameter_builder.add_readonly_single_input(input.cpu_data());
76 }
77 }
78 else {
79 Result &output = get_result(parameter_identifiers_[i]);
80 if (is_single_value) {
81 output.allocate_single_value();
82 parameter_builder.add_uninitialized_single_output(
83 GMutableSpan(output.get_cpp_type(), output.single_value().get(), 1));
84 }
85 else {
86 output.allocate_texture(domain);
87 parameter_builder.add_uninitialized_single_output(output.cpu_data());
88 }
89 }
90 }
91
92 mf::ContextBuilder context_builder;
93 procedure_executor_->call_auto(mask, parameter_builder, context_builder);
94
95 /* In case of single value execution, update single value data. */
96 if (is_single_value) {
97 for (int i = 0; i < procedure_.params().size(); i++) {
98 if (procedure_.params()[i].type == mf::ParamType::InterfaceType::Output) {
99 Result &output = get_result(parameter_identifiers_[i]);
100 output.update_single_value_data();
101 }
102 }
103 }
104}
105
106void MultiFunctionProcedureOperation::build_procedure()
107{
108 for (DNode node : compile_unit_) {
109 /* Get the multi-function of the node. */
110 auto &multi_function_builder = *node_multi_functions_.lookup_or_add_cb(node, [&]() {
111 return std::make_unique<nodes::NodeMultiFunctionBuilder>(*node.bnode(),
112 node.context()->btree());
113 });
114 node->typeinfo->build_multi_function(multi_function_builder);
115 const mf::MultiFunction &multi_function = multi_function_builder.function();
116
117 /* Get the variables of the inputs of the node, creating inputs to the operation/procedure if
118 * needed. */
119 Vector<mf::Variable *> input_variables = this->get_input_variables(node, multi_function);
120
121 /* Call the node multi-function, getting the variables for its outputs. */
122 Vector<mf::Variable *> output_variables = procedure_builder_.add_call(multi_function,
123 input_variables);
124
125 /* Assign the output variables to the node's respective outputs, creating outputs for the
126 * operation/procedure if needed. */
127 this->assign_output_variables(node, output_variables);
128 }
129
130 /* Add destructor calls for the variables. */
131 for (const auto &item : output_to_variable_map_.items()) {
132 /* Variables that are used by the outputs should not be destructed. */
133 if (!output_variables_.contains(item.value)) {
134 procedure_builder_.add_destruct(*item.value);
135 }
136 }
137 for (mf::Variable *variable : implicit_variables_) {
138 /* Variables that are used by the outputs should not be destructed. */
139 if (!output_variables_.contains(variable)) {
140 procedure_builder_.add_destruct(*variable);
141 }
142 }
143 for (mf::Variable *variable : implicit_input_to_variable_map_.values()) {
144 procedure_builder_.add_destruct(*variable);
145 }
146
147 mf::ReturnInstruction &return_instruction = procedure_builder_.add_return();
148 mf::procedure_optimization::move_destructs_up(procedure_, return_instruction);
149 BLI_assert(procedure_.validate());
150}
151
152Vector<mf::Variable *> MultiFunctionProcedureOperation::get_input_variables(
153 DNode node, const mf::MultiFunction &multi_function)
154{
155 int available_inputs_index = 0;
156 Vector<mf::Variable *> input_variables;
157 for (int i = 0; i < node->input_sockets().size(); i++) {
158 const DInputSocket input{node.context(), node->input_sockets()[i]};
159
160 if (!is_socket_available(input.bsocket())) {
161 continue;
162 }
163
164 /* The origin socket is an input, that means the input is unlinked. */
165 const DSocket origin = get_input_origin_socket(input);
166 if (origin->is_input()) {
167 const InputDescriptor origin_descriptor = input_descriptor_from_input_socket(
168 origin.bsocket());
169
170 if (origin_descriptor.implicit_input == ImplicitInput::None) {
171 /* No implicit input, so get a constant variable that holds the socket value. */
172 input_variables.append(this->get_constant_input_variable(DInputSocket(origin)));
173 }
174 else {
175 input_variables.append(this->get_implicit_input_variable(input, DInputSocket(origin)));
176 }
177 }
178 else {
179 /* Otherwise, the origin socket is an output, which means it is linked. */
180 const DOutputSocket output = DOutputSocket(origin);
181
182 /* If the origin node is part of the multi-function procedure operation, then the output has
183 * an existing variable for it. */
184 if (compile_unit_.contains(output.node())) {
185 input_variables.append(output_to_variable_map_.lookup(output));
186 }
187 else {
188 /* Otherwise, the origin node is not part of the multi-function procedure operation, and a
189 * variable that represents an input to the multi-function procedure operation is used. */
190 input_variables.append(this->get_multi_function_input_variable(input, output));
191 }
192 }
193
194 /* We allow multi-functions to use float4 as opposed to ColorSceneLinear4f in their signature
195 * for easier development. Float4 and ColorSceneLinear4f will be implicitly converted to one
196 * another without information loss, so this flexibility is fine. However, float4 conversion to
197 * other types is different than ColorSceneLinear4f conversion to other types, and vice versa.
198 * So we need to manually convert to ColorSceneLinear4f if either the input or the origin are
199 * color sockets for proper implicit conversion later on. */
200 if (get_node_socket_result_type(origin.bsocket()) == ResultType::Color ||
202 {
203 const mf::DataType expected_type = mf::DataType::ForSingle(
204 CPPType::get<ColorSceneLinear4f<eAlpha::Premultiplied>>());
205 input_variables.last() = this->convert_variable(input_variables.last(), expected_type);
206 }
207
208 /* Implicitly convert the variable type to the expected parameter type if needed. */
209 const mf::ParamType parameter_type = multi_function.param_type(available_inputs_index);
210 input_variables.last() = this->convert_variable(input_variables.last(),
211 parameter_type.data_type());
212
213 available_inputs_index++;
214 }
215
216 return input_variables;
217}
218
219mf::Variable *MultiFunctionProcedureOperation::get_constant_input_variable(DInputSocket input)
220{
221 const mf::MultiFunction *constant_function = nullptr;
222 switch (input->type) {
223 case SOCK_FLOAT: {
224 const float value = input->default_value_typed<bNodeSocketValueFloat>()->value;
225 constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float>>(value);
226 break;
227 }
228 case SOCK_INT: {
229 const int value = input->default_value_typed<bNodeSocketValueInt>()->value;
230 constant_function = &procedure_.construct_function<mf::CustomMF_Constant<int32_t>>(value);
231 break;
232 }
233 case SOCK_BOOLEAN: {
234 const bool value = input->default_value_typed<bNodeSocketValueBoolean>()->value;
235 constant_function = &procedure_.construct_function<mf::CustomMF_Constant<bool>>(value);
236 break;
237 }
238 case SOCK_VECTOR: {
239 switch (input->default_value_typed<bNodeSocketValueVector>()->dimensions) {
240 case 2: {
241 const float2 value = float2(input->default_value_typed<bNodeSocketValueVector>()->value);
242 constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float2>>(value);
243 break;
244 }
245 case 3: {
246 const float3 value = float3(input->default_value_typed<bNodeSocketValueVector>()->value);
247 constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float3>>(value);
248 break;
249 }
250 case 4: {
251 const float4 value = float4(input->default_value_typed<bNodeSocketValueVector>()->value);
252 constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float4>>(value);
253 break;
254 }
255 default:
257 break;
258 }
259 break;
260 }
261 case SOCK_RGBA: {
262 const float4 value = float4(input->default_value_typed<bNodeSocketValueRGBA>()->value);
263 constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float4>>(value);
264 break;
265 }
266 case SOCK_MENU: {
267 const int32_t value = input->default_value_typed<bNodeSocketValueMenu>()->value;
268 constant_function = &procedure_.construct_function<mf::CustomMF_Constant<nodes::MenuValue>>(
269 value);
270 break;
271 }
272 case SOCK_STRING: {
273 const std::string value = input->default_value_typed<bNodeSocketValueString>()->value;
274 constant_function = &procedure_.construct_function<mf::CustomMF_Constant<std::string>>(
275 value);
276 break;
277 }
278 default:
280 break;
281 }
282
283 mf::Variable *constant_variable = procedure_builder_.add_call<1>(*constant_function)[0];
284 implicit_variables_.append(constant_variable);
285 return constant_variable;
286}
287
288mf::Variable *MultiFunctionProcedureOperation::get_implicit_input_variable(
289 const DInputSocket input, const DInputSocket origin)
290{
291 const InputDescriptor origin_descriptor = input_descriptor_from_input_socket(origin.bsocket());
292 const ImplicitInput implicit_input = origin_descriptor.implicit_input;
293
294 /* Inherit the type and implicit input of the origin input since doing implicit conversion inside
295 * the multi-function operation is much cheaper. */
296 InputDescriptor input_descriptor = input_descriptor_from_input_socket(input.bsocket());
297 input_descriptor.type = origin_descriptor.type;
298 input_descriptor.implicit_input = implicit_input;
299
300 /* An input was already declared for that implicit input, so no need to declare it again and we
301 * just return its variable. */
302 if (implicit_input_to_variable_map_.contains(implicit_input)) {
303 /* But first we update the domain priority of the input descriptor to be the higher priority of
304 * the existing descriptor and the descriptor of the new input socket. That's because the same
305 * implicit input might be used in inputs inside the multi-function procedure operation which
306 * have different priorities. */
307 InputDescriptor &existing_input_descriptor = this->get_input_descriptor(
308 implicit_inputs_to_input_identifiers_map_.lookup(implicit_input));
309 existing_input_descriptor.domain_priority = math::min(
310 existing_input_descriptor.domain_priority, input_descriptor.domain_priority);
311
312 return implicit_input_to_variable_map_.lookup(implicit_input);
313 }
314
315 const int implicit_input_index = implicit_inputs_to_input_identifiers_map_.size();
316 const std::string input_identifier = "implicit_input" + std::to_string(implicit_input_index);
317 declare_input_descriptor(input_identifier, input_descriptor);
318
319 /* Map the implicit input to the identifier of the operation input that was declared for it. */
320 implicit_inputs_to_input_identifiers_map_.add_new(implicit_input, input_identifier);
321
322 mf::Variable &variable = procedure_builder_.add_input_parameter(
323 mf::DataType::ForSingle(Result::cpp_type(input_descriptor.type)), input_identifier);
324 parameter_identifiers_.append(input_identifier);
325
326 /* Map the implicit input to the variable that was created for it. */
327 implicit_input_to_variable_map_.add(implicit_input, &variable);
328
329 return &variable;
330}
331
332mf::Variable *MultiFunctionProcedureOperation::get_multi_function_input_variable(
333 DInputSocket input_socket, DOutputSocket output_socket)
334{
335 /* An input was already declared for that same output socket, so no need to declare it again and
336 * we just return its variable. */
337 if (output_to_variable_map_.contains(output_socket)) {
338 /* But first we update the domain priority of the input descriptor to be the higher priority of
339 * the existing descriptor and the descriptor of the new input socket. That's because the same
340 * output might be connected to multiple inputs inside the multi-function procedure operation
341 * which have different priorities. */
342 const std::string input_identifier = outputs_to_declared_inputs_map_.lookup(output_socket);
343 InputDescriptor &input_descriptor = this->get_input_descriptor(input_identifier);
344 input_descriptor.domain_priority = math::min(
345 input_descriptor.domain_priority,
347
348 /* Increment the input's reference count. */
349 inputs_to_reference_counts_map_.lookup(input_identifier)++;
350
351 return output_to_variable_map_.lookup(output_socket);
352 }
353
354 const int input_index = inputs_to_linked_outputs_map_.size();
355 const std::string input_identifier = "input" + std::to_string(input_index);
356
357 /* Declare the input descriptor for this input and prefer to declare its type to be the same as
358 * the type of the output socket because doing type conversion in the multi-function procedure is
359 * cheaper. */
360 InputDescriptor input_descriptor = input_descriptor_from_input_socket(input_socket.bsocket());
361 input_descriptor.type = get_node_socket_result_type(output_socket.bsocket());
362 declare_input_descriptor(input_identifier, input_descriptor);
363
364 mf::Variable &variable = procedure_builder_.add_input_parameter(
365 mf::DataType::ForSingle(Result::cpp_type(input_descriptor.type)), input_identifier);
366 parameter_identifiers_.append(input_identifier);
367
368 /* Map the output socket to the variable that was created for it. */
369 output_to_variable_map_.add(output_socket, &variable);
370
371 /* Map the identifier of the operation input to the output socket it is linked to. */
372 inputs_to_linked_outputs_map_.add_new(input_identifier, output_socket);
373
374 /* Map the output socket to the identifier of the operation input that was declared for it. */
375 outputs_to_declared_inputs_map_.add_new(output_socket, input_identifier);
376
377 /* Map the identifier of the operation input to a reference count of 1, this will later be
378 * incremented if that same output was referenced again. */
379 inputs_to_reference_counts_map_.add_new(input_identifier, 1);
380
381 return &variable;
382}
383
384void MultiFunctionProcedureOperation::assign_output_variables(DNode node,
385 Vector<mf::Variable *> &variables)
386{
387 const DOutputSocket preview_output = find_preview_output_socket(node);
388
389 int available_outputs_index = 0;
390 for (int i = 0; i < node->output_sockets().size(); i++) {
391 const DOutputSocket output{node.context(), node->output_sockets()[i]};
392
393 if (!is_socket_available(output.bsocket())) {
394 continue;
395 }
396
397 mf::Variable *output_variable = variables[available_outputs_index];
398 output_to_variable_map_.add_new(output, output_variable);
399
400 /* If any of the nodes linked to the output are not part of the multi-function procedure
401 * operation but are part of the execution schedule, then an output result needs to be
402 * populated for it. */
403 const bool is_operation_output = is_output_linked_to_node_conditioned(output, [&](DNode node) {
404 return schedule_.contains(node) && !compile_unit_.contains(node);
405 });
406
407 /* If the output is used as the node preview, then an output result needs to be populated for
408 * it, and we additionally keep track of that output to later compute the previews from. */
409 const bool is_preview_output = output == preview_output;
410 if (is_preview_output) {
412 }
413
414 if (is_operation_output || is_preview_output) {
415 this->populate_operation_result(output, output_variable);
416 }
417
418 available_outputs_index++;
419 }
420}
421
422void MultiFunctionProcedureOperation::populate_operation_result(DOutputSocket output_socket,
423 mf::Variable *variable)
424{
425 const uint output_id = output_sockets_to_output_identifiers_map_.size();
426 const std::string output_identifier = "output" + std::to_string(output_id);
427
428 const ResultType result_type = get_node_socket_result_type(output_socket.bsocket());
429 const Result result = context().create_result(result_type);
430 populate_result(output_identifier, result);
431
432 /* Map the output socket to the identifier of the newly populated result. */
433 output_sockets_to_output_identifiers_map_.add_new(output_socket, output_identifier);
434
435 /* Implicitly convert the variable type to the expected result type if needed. */
436 const mf::DataType expected_type = mf::DataType::ForSingle(result.get_cpp_type());
437 mf::Variable *converted_variable = this->convert_variable(variable, expected_type);
438
439 procedure_builder_.add_output_parameter(*converted_variable);
440 output_variables_.add_new(converted_variable);
441 parameter_identifiers_.append(output_identifier);
442}
443
444mf::Variable *MultiFunctionProcedureOperation::convert_variable(mf::Variable *variable,
445 const mf::DataType expected_type)
446{
447 /* Conversion not needed. */
448 const mf::DataType variable_type = variable->data_type();
449 if (variable_type == expected_type) {
450 return variable;
451 }
452
453 const bke::DataTypeConversions &conversion_table = bke::get_implicit_type_conversions();
454 const mf::MultiFunction *function = conversion_table.get_conversion_multi_function(
455 variable_type, expected_type);
456
457 /* Conversion is not possible, return a default variable instead. */
458 if (!function) {
459 const mf::MultiFunction &constant_function =
460 procedure_.construct_function<mf::CustomMF_GenericConstant>(
461 expected_type.single_type(), expected_type.single_type().default_value(), false);
462 mf::Variable *constant_variable = procedure_builder_.add_call<1>(constant_function)[0];
463 implicit_variables_.append(constant_variable);
464 return constant_variable;
465 }
466
467 mf::Variable *converted_variable = procedure_builder_.add_call<1>(*function, {variable})[0];
468 implicit_variables_.append(converted_variable);
469 return converted_variable;
470}
471
472bool MultiFunctionProcedureOperation::is_single_value_operation()
473{
474 /* Return true if all inputs are single values. */
475 for (int i = 0; i < procedure_.params().size(); i++) {
476 if (procedure_.params()[i].type == mf::ParamType::InterfaceType::Input) {
477 Result &input = this->get_input(parameter_identifiers_[i]);
478 if (!input.is_single_value()) {
479 return false;
480 }
481 }
482 }
483 return true;
484}
485
486} // namespace blender::compositor
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
unsigned int uint
struct bNodeSocketValueFloat bNodeSocketValueFloat
struct bNodeSocketValueMenu bNodeSocketValueMenu
struct bNodeSocketValueInt bNodeSocketValueInt
struct bNodeSocketValueRGBA bNodeSocketValueRGBA
struct bNodeSocketValueVector bNodeSocketValueVector
struct bNodeSocketValueString bNodeSocketValueString
@ SOCK_INT
@ SOCK_VECTOR
@ SOCK_BOOLEAN
@ SOCK_FLOAT
@ SOCK_STRING
@ SOCK_RGBA
@ SOCK_MENU
struct bNodeSocketValueBoolean bNodeSocketValueBoolean
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
ItemIterator items() const &
Definition BLI_map.hh:902
bool contains(const Key &key) const
Definition BLI_set.hh:310
void append(const T &value)
const T & last(const int64_t n=0) const
static const CPPType & get()
Result create_result(ResultType type, ResultPrecision precision)
MultiFunctionProcedureOperation(Context &context, PixelCompileUnit &compile_unit, const Schedule &schedule)
Result & get_result(StringRef identifier)
Definition operation.cc:39
void populate_result(StringRef identifier, Result result)
Definition operation.cc:148
Result & get_input(StringRef identifier) const
Definition operation.cc:138
virtual Domain compute_domain()
Definition operation.cc:56
InputDescriptor & get_input_descriptor(StringRef identifier)
Definition operation.cc:158
void declare_input_descriptor(StringRef identifier, InputDescriptor descriptor)
Definition operation.cc:153
Map< DOutputSocket, std::string > outputs_to_declared_inputs_map_
Map< std::string, DOutputSocket > inputs_to_linked_outputs_map_
Map< std::string, int > inputs_to_reference_counts_map_
VectorSet< DOutputSocket > preview_outputs_
Map< DOutputSocket, std::string > output_sockets_to_output_identifiers_map_
PixelOperation(Context &context, PixelCompileUnit &compile_unit, const Schedule &schedule)
Map< ImplicitInput, std::string > implicit_inputs_to_input_identifiers_map_
static const CPPType & cpp_type(const ResultType type)
Definition result.cc:311
#define input
#define output
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
const DataTypeConversions & get_implicit_type_conversions()
bool is_output_linked_to_node_conditioned(DOutputSocket output, FunctionRef< bool(DNode)> condition)
Definition utilities.cc:113
DSocket get_input_origin_socket(DInputSocket input)
Definition utilities.cc:32
VectorSet< DNode > Schedule
VectorSet< DNode > PixelCompileUnit
DOutputSocket find_preview_output_socket(const DNode &node)
Definition utilities.cc:226
ResultType get_node_socket_result_type(const bNodeSocket *socket)
Definition utilities.cc:102
InputDescriptor input_descriptor_from_input_socket(const bNodeSocket *socket)
Definition utilities.cc:169
bool is_socket_available(const bNodeSocket *socket)
Definition utilities.cc:27
T min(const T &a, const T &b)
VecBase< float, 4 > float4
VecBase< float, 2 > float2
VecBase< float, 3 > float3
i
Definition text_draw.cc:230