Blender V4.5
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 const float3 value = float3(input->default_value_typed<bNodeSocketValueVector>()->value);
240 constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float3>>(value);
241 break;
242 }
243 case SOCK_RGBA: {
244 const float4 value = float4(input->default_value_typed<bNodeSocketValueRGBA>()->value);
245 constant_function = &procedure_.construct_function<mf::CustomMF_Constant<float4>>(value);
246 break;
247 }
248 default:
250 break;
251 }
252
253 mf::Variable *constant_variable = procedure_builder_.add_call<1>(*constant_function)[0];
254 implicit_variables_.append(constant_variable);
255 return constant_variable;
256}
257
258mf::Variable *MultiFunctionProcedureOperation::get_implicit_input_variable(
259 const DInputSocket input, const DInputSocket origin)
260{
261 const InputDescriptor origin_descriptor = input_descriptor_from_input_socket(origin.bsocket());
262 const ImplicitInput implicit_input = origin_descriptor.implicit_input;
263
264 /* Inherit the type and implicit input of the origin input since doing implicit conversion inside
265 * the multi-function operation is much cheaper. */
266 InputDescriptor input_descriptor = input_descriptor_from_input_socket(input.bsocket());
267 input_descriptor.type = origin_descriptor.type;
268 input_descriptor.implicit_input = implicit_input;
269
270 /* An input was already declared for that implicit input, so no need to declare it again and we
271 * just return its variable. */
272 if (implicit_input_to_variable_map_.contains(implicit_input)) {
273 /* But first we update the domain priority of the input descriptor to be the higher priority of
274 * the existing descriptor and the descriptor of the new input socket. That's because the same
275 * implicit input might be used in inputs inside the multi-function procedure operation which
276 * have different priorities. */
277 InputDescriptor &existing_input_descriptor = this->get_input_descriptor(
278 implicit_inputs_to_input_identifiers_map_.lookup(implicit_input));
279 existing_input_descriptor.domain_priority = math::min(
280 existing_input_descriptor.domain_priority, input_descriptor.domain_priority);
281
282 return implicit_input_to_variable_map_.lookup(implicit_input);
283 }
284
285 const int implicit_input_index = implicit_inputs_to_input_identifiers_map_.size();
286 const std::string input_identifier = "implicit_input" + std::to_string(implicit_input_index);
287 declare_input_descriptor(input_identifier, input_descriptor);
288
289 /* Map the implicit input to the identifier of the operation input that was declared for it. */
290 implicit_inputs_to_input_identifiers_map_.add_new(implicit_input, input_identifier);
291
292 mf::Variable &variable = procedure_builder_.add_input_parameter(
293 mf::DataType::ForSingle(Result::cpp_type(input_descriptor.type)), input_identifier);
294 parameter_identifiers_.append(input_identifier);
295
296 /* Map the implicit input to the variable that was created for it. */
297 implicit_input_to_variable_map_.add(implicit_input, &variable);
298
299 return &variable;
300}
301
302mf::Variable *MultiFunctionProcedureOperation::get_multi_function_input_variable(
303 DInputSocket input_socket, DOutputSocket output_socket)
304{
305 /* An input was already declared for that same output socket, so no need to declare it again and
306 * we just return its variable. */
307 if (output_to_variable_map_.contains(output_socket)) {
308 /* But first we update the domain priority of the input descriptor to be the higher priority of
309 * the existing descriptor and the descriptor of the new input socket. That's because the same
310 * output might be connected to multiple inputs inside the multi-function procedure operation
311 * which have different priorities. */
312 const std::string input_identifier = outputs_to_declared_inputs_map_.lookup(output_socket);
313 InputDescriptor &input_descriptor = this->get_input_descriptor(input_identifier);
314 input_descriptor.domain_priority = math::min(
315 input_descriptor.domain_priority,
317
318 /* Increment the input's reference count. */
319 inputs_to_reference_counts_map_.lookup(input_identifier)++;
320
321 return output_to_variable_map_.lookup(output_socket);
322 }
323
324 const int input_index = inputs_to_linked_outputs_map_.size();
325 const std::string input_identifier = "input" + std::to_string(input_index);
326
327 /* Declare the input descriptor for this input and prefer to declare its type to be the same as
328 * the type of the output socket because doing type conversion in the multi-function procedure is
329 * cheaper. */
330 InputDescriptor input_descriptor = input_descriptor_from_input_socket(input_socket.bsocket());
331 input_descriptor.type = get_node_socket_result_type(output_socket.bsocket());
332 declare_input_descriptor(input_identifier, input_descriptor);
333
334 mf::Variable &variable = procedure_builder_.add_input_parameter(
335 mf::DataType::ForSingle(Result::cpp_type(input_descriptor.type)), input_identifier);
336 parameter_identifiers_.append(input_identifier);
337
338 /* Map the output socket to the variable that was created for it. */
339 output_to_variable_map_.add(output_socket, &variable);
340
341 /* Map the identifier of the operation input to the output socket it is linked to. */
342 inputs_to_linked_outputs_map_.add_new(input_identifier, output_socket);
343
344 /* Map the output socket to the identifier of the operation input that was declared for it. */
345 outputs_to_declared_inputs_map_.add_new(output_socket, input_identifier);
346
347 /* Map the identifier of the operation input to a reference count of 1, this will later be
348 * incremented if that same output was referenced again. */
349 inputs_to_reference_counts_map_.add_new(input_identifier, 1);
350
351 return &variable;
352}
353
354void MultiFunctionProcedureOperation::assign_output_variables(DNode node,
355 Vector<mf::Variable *> &variables)
356{
357 const DOutputSocket preview_output = find_preview_output_socket(node);
358
359 int available_outputs_index = 0;
360 for (int i = 0; i < node->output_sockets().size(); i++) {
361 const DOutputSocket output{node.context(), node->output_sockets()[i]};
362
363 if (!is_socket_available(output.bsocket())) {
364 continue;
365 }
366
367 mf::Variable *output_variable = variables[available_outputs_index];
368 output_to_variable_map_.add_new(output, output_variable);
369
370 /* If any of the nodes linked to the output are not part of the multi-function procedure
371 * operation but are part of the execution schedule, then an output result needs to be
372 * populated for it. */
373 const bool is_operation_output = is_output_linked_to_node_conditioned(output, [&](DNode node) {
374 return schedule_.contains(node) && !compile_unit_.contains(node);
375 });
376
377 /* If the output is used as the node preview, then an output result needs to be populated for
378 * it, and we additionally keep track of that output to later compute the previews from. */
379 const bool is_preview_output = output == preview_output;
380 if (is_preview_output) {
382 }
383
384 if (is_operation_output || is_preview_output) {
385 this->populate_operation_result(output, output_variable);
386 }
387
388 available_outputs_index++;
389 }
390}
391
392void MultiFunctionProcedureOperation::populate_operation_result(DOutputSocket output_socket,
393 mf::Variable *variable)
394{
395 const uint output_id = output_sockets_to_output_identifiers_map_.size();
396 const std::string output_identifier = "output" + std::to_string(output_id);
397
398 const ResultType result_type = get_node_socket_result_type(output_socket.bsocket());
399 const Result result = context().create_result(result_type);
400 populate_result(output_identifier, result);
401
402 /* Map the output socket to the identifier of the newly populated result. */
403 output_sockets_to_output_identifiers_map_.add_new(output_socket, output_identifier);
404
405 /* Implicitly convert the variable type to the expected result type if needed. */
406 const mf::DataType expected_type = mf::DataType::ForSingle(result.get_cpp_type());
407 mf::Variable *converted_variable = this->convert_variable(variable, expected_type);
408
409 procedure_builder_.add_output_parameter(*converted_variable);
410 output_variables_.add_new(converted_variable);
411 parameter_identifiers_.append(output_identifier);
412}
413
414mf::Variable *MultiFunctionProcedureOperation::convert_variable(mf::Variable *variable,
415 const mf::DataType expected_type)
416{
417 const mf::DataType variable_type = variable->data_type();
418 if (variable_type == expected_type) {
419 return variable;
420 }
421
422 const bke::DataTypeConversions &conversion_table = bke::get_implicit_type_conversions();
423 const mf::MultiFunction *function = conversion_table.get_conversion_multi_function(
424 variable_type, expected_type);
425
426 mf::Variable *converted_variable = procedure_builder_.add_call<1>(*function, {variable})[0];
427 implicit_variables_.append(converted_variable);
428 return converted_variable;
429}
430
431bool MultiFunctionProcedureOperation::is_single_value_operation()
432{
433 /* Return true if all inputs are single values. */
434 for (int i = 0; i < procedure_.params().size(); i++) {
435 if (procedure_.params()[i].type == mf::ParamType::InterfaceType::Input) {
436 Result &input = this->get_input(parameter_identifiers_[i]);
437 if (!input.is_single_value()) {
438 return false;
439 }
440 }
441 }
442 return true;
443}
444
445} // 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 bNodeSocketValueInt bNodeSocketValueInt
struct bNodeSocketValueRGBA bNodeSocketValueRGBA
struct bNodeSocketValueVector bNodeSocketValueVector
@ SOCK_INT
@ SOCK_VECTOR
@ SOCK_BOOLEAN
@ SOCK_FLOAT
@ SOCK_RGBA
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:244
#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:88
DSocket get_input_origin_socket(DInputSocket input)
Definition utilities.cc:30
VectorSet< DNode > Schedule
VectorSet< DNode > PixelCompileUnit
DOutputSocket find_preview_output_socket(const DNode &node)
Definition utilities.cc:200
ResultType get_node_socket_result_type(const bNodeSocket *socket)
Definition utilities.cc:66
InputDescriptor input_descriptor_from_input_socket(const bNodeSocket *socket)
Definition utilities.cc:144
bool is_socket_available(const bNodeSocket *socket)
Definition utilities.cc:25
T min(const T &a, const T &b)
VecBase< float, 4 > float4
VecBase< float, 3 > float3
i
Definition text_draw.cc:230