Blender V4.3
COM_NodeOperationBuilder.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2013 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <set>
6
8
9#include "BKE_node_runtime.hh"
10
11#include "COM_Converter.h"
12#include "COM_Debug.h"
13
18#include "COM_ViewerOperation.h"
19
20#include "COM_ConstantFolder.h"
21#include "COM_NodeOperationBuilder.h" /* own include */
22
23namespace blender::compositor {
24
26 bNodeTree *b_nodetree,
27 ExecutionSystem *system)
28 : context_(context), exec_system_(system), current_node_(nullptr), active_viewer_(nullptr)
29{
30 graph_.from_bNodeTree(*context, b_nodetree);
31}
32
34{
35 /* interface handle for nodes */
36 NodeConverter converter(this);
37
38 for (Node *node : graph_.nodes()) {
39 current_node_ = node;
40
42 node->convert_to_operations(converter, *context_);
43 }
44
45 current_node_ = nullptr;
46
47 /* The input map constructed by nodes maps operation inputs to node inputs.
48 * Inverting yields a map of node inputs to all connected operation inputs,
49 * so multiple operations can use the same node input.
50 */
52 for (MutableMapItem<NodeOperationInput *, NodeInput *> item : input_map_.items()) {
53 inverse_input_map.add(item.value, item.key);
54 }
55
56 for (const NodeGraph::Link &link : graph_.links()) {
57 NodeOutput *from = link.from;
58 NodeInput *to = link.to;
59
60 NodeOperationOutput *op_from = output_map_.lookup_default(from, nullptr);
61
62 const blender::Span<NodeOperationInput *> op_to_list = inverse_input_map.lookup(to);
63 if (!op_from || op_to_list.is_empty()) {
64 /* XXX allow this? error/debug message? */
65 // BLI_assert(false);
66 /* XXX NOTE: this can happen with certain nodes (e.g. OutputFile)
67 * which only generate operations in certain circumstances (rendering)
68 * just let this pass silently for now ...
69 */
70 continue;
71 }
72
73 for (NodeOperationInput *op_to : op_to_list) {
74 add_link(op_from, op_to);
75 }
76 }
77
79
81
83
84 save_graphviz("compositor_prior_folding");
85 ConstantFolder folder(*this);
86 folder.fold_operations();
87
89
90 save_graphviz("compositor_prior_merging");
91 merge_equal_operations();
92
93 /* links not available from here on */
94 /* XXX make links_ a local variable to avoid confusion! */
95 links_.clear();
96
98
99 /* ensure topological (link-based) order of nodes */
100 // sort_operations(); /* not needed yet. */
101
102 /* transfer resulting operations to the system */
103 system->set_operations(operations_);
104}
105
107{
108 operation->set_id(operations_.size());
109 operations_.append(operation);
110 if (current_node_) {
111 operation->set_name(current_node_->get_bnode()->name);
112 operation->set_node_instance_key(current_node_->get_instance_key());
113 }
114 operation->set_execution_system(exec_system_);
115}
116
118 ConstantOperation *constant_operation)
119{
120 BLI_assert(constant_operation->get_number_of_input_sockets() == 0);
121 unlink_inputs_and_relink_outputs(operation, constant_operation);
122 add_operation(constant_operation);
123}
124
125void NodeOperationBuilder::unlink_inputs_and_relink_outputs(NodeOperation *unlinked_op,
126 NodeOperation *linked_op)
127{
128 int i = 0;
129 while (i < links_.size()) {
130 Link &link = links_[i];
131 if (&link.to()->get_operation() == unlinked_op) {
132 link.to()->set_link(nullptr);
133 links_.remove(i);
134 continue;
135 }
136
137 if (&link.from()->get_operation() == unlinked_op) {
138 link.to()->set_link(linked_op->get_output_socket());
139 links_[i] = Link(linked_op->get_output_socket(), link.to());
140 }
141 i++;
142 }
143}
144
146 NodeOperationInput *operation_socket)
147{
148 BLI_assert(current_node_);
149 BLI_assert(node_socket->get_node() == current_node_);
150
151 /* NOTE: this maps operation sockets to node sockets.
152 * for resolving links the map will be inverted first in convert_to_operations,
153 * to get a list of links for each node input socket.
154 */
155 input_map_.add_new(operation_socket, node_socket);
156}
157
159 NodeOperationOutput *operation_socket)
160{
161 BLI_assert(current_node_);
162 BLI_assert(node_socket->get_node() == current_node_);
163
164 output_map_.add_new(node_socket, operation_socket);
165}
166
168{
169 if (to->is_connected()) {
170 return;
171 }
172
173 links_.append(Link(from, to));
174
175 /* register with the input */
176 to->set_link(from);
177}
178
180{
181 int index = 0;
182 for (Link &link : links_) {
183 if (link.to() == to) {
184 /* unregister with the input */
185 to->set_link(nullptr);
186
187 links_.remove(index);
188 return;
189 }
190 index++;
191 }
192}
193
194PreviewOperation *NodeOperationBuilder::make_preview_operation() const
195{
196 BLI_assert(current_node_);
197
198 if (!(current_node_->get_bnode()->flag & NODE_PREVIEW)) {
199 return nullptr;
200 }
201 /* previews only in the active group */
202 if (!current_node_->is_in_active_group()) {
203 return nullptr;
204 }
205 /* do not calculate previews of hidden nodes */
206 if (current_node_->get_bnode()->flag & NODE_HIDDEN) {
207 return nullptr;
208 }
209
210 bke::bNodeInstanceHash *previews = context_->get_preview_hash();
211 if (previews) {
212 Scene *scene = context_->get_scene();
213 PreviewOperation *operation = new PreviewOperation(
214 &scene->view_settings,
215 &scene->display_settings,
216 current_node_->get_bnode()->runtime->preview_xsize,
217 current_node_->get_bnode()->runtime->preview_ysize);
218 operation->set_bnodetree(context_->get_bnodetree());
219 operation->verify_preview(previews, current_node_->get_instance_key());
220 return operation;
221 }
222
223 return nullptr;
224}
225
227{
228 PreviewOperation *operation = make_preview_operation();
229 if (operation) {
230 add_operation(operation);
231
232 add_link(output, operation->get_input_socket(0));
233 }
234}
235
237{
238 PreviewOperation *operation = make_preview_operation();
239 if (operation) {
240 add_operation(operation);
241
242 map_input_socket(input, operation->get_input_socket(0));
243 }
244}
245
247{
248 if (!active_viewer_) {
249 active_viewer_ = viewer;
250 viewer->set_active(true);
251 return;
252 }
253
254 /* A viewer is already registered, so we active this viewer but only if it is in the active node
255 * tree, since it takes precedence over viewer nodes in other trees. So deactivate existing
256 * viewer and set this viewer as active. */
257 if (current_node_->is_in_active_group()) {
258 active_viewer_->set_active(false);
259
260 active_viewer_ = viewer;
261 viewer->set_active(true);
262 }
263}
264
265/****************************
266 **** Optimization Steps ****
267 ****************************/
268
270{
271 Vector<Link> convert_links;
272 for (const Link &link : links_) {
273 /* proxy operations can skip data type conversion */
274 NodeOperation *from_op = &link.from()->get_operation();
275 NodeOperation *to_op = &link.to()->get_operation();
276 if (!(from_op->get_flags().use_datatype_conversion ||
278 {
279 continue;
280 }
281
282 if (link.from()->get_data_type() != link.to()->get_data_type()) {
283 convert_links.append(link);
284 }
285 }
286 for (const Link &link : convert_links) {
287 NodeOperation *converter = COM_convert_data_type(*link.from(), *link.to());
288 if (converter) {
289 add_operation(converter);
290
291 remove_input_link(link.to());
292 add_link(link.from(), converter->get_input_socket(0));
293 add_link(converter->get_output_socket(0), link.to());
294 }
295 }
296}
297
299{
300 /* NOTE: unconnected inputs cached first to avoid modifying
301 * operations_ while iterating over it
302 */
303 Vector<NodeOperationInput *> pending_inputs;
304 for (NodeOperation *op : operations_) {
305 for (int k = 0; k < op->get_number_of_input_sockets(); ++k) {
306 NodeOperationInput *input = op->get_input_socket(k);
307 if (!input->is_connected()) {
308 pending_inputs.append(input);
309 }
310 }
311 }
312 for (NodeOperationInput *input : pending_inputs) {
313 add_input_constant_value(input, input_map_.lookup_default(input, nullptr));
314 }
315}
316
318 const NodeInput *node_input)
319{
320 switch (input->get_data_type()) {
321 case DataType::Value: {
322 float value;
323 if (node_input && node_input->get_bnode_socket()) {
324 value = node_input->get_editor_value_float();
325 }
326 else {
327 value = 0.0f;
328 }
329
331 op->set_value(value);
332 add_operation(op);
333 add_link(op->get_output_socket(), input);
334 break;
335 }
336 case DataType::Color: {
337 float value[4];
338 if (node_input && node_input->get_bnode_socket()) {
339 node_input->get_editor_value_color(value);
340 }
341 else {
342 zero_v4(value);
343 }
344
346 op->set_channels(value);
347 add_operation(op);
348 add_link(op->get_output_socket(), input);
349 break;
350 }
351 case DataType::Vector: {
352 float value[3];
353 if (node_input && node_input->get_bnode_socket()) {
354 node_input->get_editor_value_vector(value);
355 }
356 else {
357 zero_v3(value);
358 }
359
361 op->set_vector(value);
362 add_operation(op);
363 add_link(op->get_output_socket(), input);
364 break;
365 }
366 case DataType::Float2:
367 /* An internal type that needn't be handled. */
369 break;
370 }
371}
372
374{
375 Vector<Link> proxy_links;
376 for (const Link &link : links_) {
377 /* don't replace links from proxy to proxy, since we may need them for replacing others! */
378 if (link.from()->get_operation().get_flags().is_proxy_operation &&
379 !link.to()->get_operation().get_flags().is_proxy_operation)
380 {
381 proxy_links.append(link);
382 }
383 }
384
385 for (const Link &link : proxy_links) {
386 NodeOperationInput *to = link.to();
387 NodeOperationOutput *from = link.from();
388 do {
389 /* walk upstream bypassing the proxy operation */
390 from = from->get_operation().get_input_socket(0)->get_link();
391 } while (from && from->get_operation().get_flags().is_proxy_operation);
392
394 /* we may not have a final proxy input link,
395 * in that case it just gets dropped
396 */
397 if (from) {
398 add_link(from, to);
399 }
400 }
401}
402
404{
405 /* Determine all canvas areas of the operations. */
406 const rcti &preferred_area = COM_AREA_NONE;
407 for (NodeOperation *op : operations_) {
408 if (op->is_output_operation(context_->is_rendering()) && !op->get_flags().is_preview_operation)
409 {
410 rcti canvas = COM_AREA_NONE;
411 op->determine_canvas(preferred_area, canvas);
412 op->set_canvas(canvas);
413 }
414 }
415
416 for (NodeOperation *op : operations_) {
417 if (op->is_output_operation(context_->is_rendering()) && op->get_flags().is_preview_operation)
418 {
419 rcti canvas = COM_AREA_NONE;
420 op->determine_canvas(preferred_area, canvas);
421 op->set_canvas(canvas);
422 }
423 }
424
425 /* Convert operation canvases when needed. */
426 {
427 Vector<Link> convert_links;
428 for (const Link &link : links_) {
429 if (link.to()->get_resize_mode() != ResizeMode::None) {
430 const rcti &from_canvas = link.from()->get_operation().get_canvas();
431 const rcti &to_canvas = link.to()->get_operation().get_canvas();
432
433 bool needs_conversion;
434 if (link.to()->get_resize_mode() == ResizeMode::Align) {
435 needs_conversion = from_canvas.xmin != to_canvas.xmin ||
436 from_canvas.ymin != to_canvas.ymin;
437 }
438 else {
439 needs_conversion = !BLI_rcti_compare(&from_canvas, &to_canvas);
440 }
441
442 if (needs_conversion) {
443 convert_links.append(link);
444 }
445 }
446 }
447 for (const Link &link : convert_links) {
448 COM_convert_canvas(*this, link.from(), link.to());
449 }
450 }
451}
452
454{
456 for (NodeOperation *op : operations) {
457 std::optional<NodeOperationHash> hash = op->generate_hash();
458 if (hash) {
459 hashes.append(std::move(*hash));
460 }
461 }
462 return hashes;
463}
464
465void NodeOperationBuilder::merge_equal_operations()
466{
467 bool check_for_next_merge = true;
468 while (check_for_next_merge) {
469 /* Re-generate hashes with any change. */
470 Vector<NodeOperationHash> hashes = generate_hashes(operations_);
471
472 /* Make hashes be consecutive when they are equal. */
473 std::sort(hashes.begin(), hashes.end());
474
475 bool any_merged = false;
476 const NodeOperationHash *prev_hash = nullptr;
477 for (const NodeOperationHash &hash : hashes) {
478 if (prev_hash && *prev_hash == hash) {
479 merge_equal_operations(prev_hash->get_operation(), hash.get_operation());
480 any_merged = true;
481 }
482 prev_hash = &hash;
483 }
484
485 check_for_next_merge = any_merged;
486 }
487}
488
489void NodeOperationBuilder::merge_equal_operations(NodeOperation *from, NodeOperation *into)
490{
491 unlink_inputs_and_relink_outputs(from, into);
492 operations_.remove_first_occurrence_and_reorder(from);
493 delete from;
494}
495
497 NodeOperationOutput *output) const
498{
500 for (const Link &link : links_) {
501 if (link.from() == output) {
502 inputs.append(link.to());
503 }
504 }
505 return inputs;
506}
507
508using Tags = std::set<NodeOperation *>;
509
511{
512 if (reachable.find(op) != reachable.end()) {
513 return;
514 }
515 reachable.insert(op);
516
517 for (int i = 0; i < op->get_number_of_input_sockets(); i++) {
518 NodeOperationInput *input = op->get_input_socket(i);
519 if (input->is_connected()) {
520 find_reachable_operations_recursive(reachable, &input->get_link()->get_operation());
521 }
522 }
523}
524
526{
527 Tags reachable;
528 for (NodeOperation *op : operations_) {
529 /* output operations are primary executed operations */
530 if (op->is_output_operation(context_->is_rendering())) {
532 }
533 }
534
535 /* delete unreachable operations */
536 Vector<NodeOperation *> reachable_ops;
537 for (NodeOperation *op : operations_) {
538 if (reachable.find(op) != reachable.end()) {
539 reachable_ops.append(op);
540 }
541 else {
542 delete op;
543 }
544 }
545 /* finally replace the operations list with the pruned list */
546 operations_ = reachable_ops;
547}
548
549/* topological (depth-first) sorting of operations */
551 Tags &visited,
552 NodeOperation *op)
553{
554 if (visited.find(op) != visited.end()) {
555 return;
556 }
557 visited.insert(op);
558
559 for (int i = 0; i < op->get_number_of_input_sockets(); i++) {
560 NodeOperationInput *input = op->get_input_socket(i);
561 if (input->is_connected()) {
562 sort_operations_recursive(sorted, visited, &input->get_link()->get_operation());
563 }
564 }
565
566 sorted.append(op);
567}
568
570{
572 sorted.reserve(operations_.size());
574
575 for (NodeOperation *operation : operations_) {
576 sort_operations_recursive(sorted, visited, operation);
577 }
578
579 operations_ = sorted;
580}
581
582void NodeOperationBuilder::save_graphviz(StringRefNull name)
583{
585 exec_system_->set_operations(operations_);
586 DebugInfo::graphviz(exec_system_, name);
587 }
588}
589
590std::ostream &operator<<(std::ostream &os, const NodeOperationBuilder &builder)
591{
592 os << "# Builder start\n";
593 os << "digraph G {\n";
594 os << " rankdir=LR;\n";
595 os << " node [shape=box];\n";
596 for (const NodeOperation *operation : builder.get_operations()) {
597 os << " op" << operation->get_id() << " [label=\"" << *operation << "\"];\n";
598 }
599
600 os << "\n";
601 for (const NodeOperationBuilder::Link &link : builder.get_links()) {
602 os << " op" << link.from()->get_operation().get_id() << " -> op"
603 << link.to()->get_operation().get_id() << ";\n";
604 }
605
606 os << "}\n";
607 os << "# Builder end\n";
608 return os;
609}
610
611std::ostream &operator<<(std::ostream &os, const NodeOperationBuilder::Link &link)
612{
613 os << link.from()->get_operation().get_id() << " -> " << link.to()->get_operation().get_id();
614 return os;
615}
616
617} // namespace blender::compositor
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
#define BLI_assert(a)
Definition BLI_assert.h:50
MINLINE void zero_v4(float r[4])
MINLINE void zero_v3(float r[3])
bool BLI_rcti_compare(const struct rcti *rect_a, const struct rcti *rect_b)
struct Link Link
@ NODE_HIDDEN
@ NODE_PREVIEW
#define output
Span< Value > lookup(const Key &key) const
void add(const Key &key, const Value &value)
constexpr bool is_empty() const
Definition BLI_span.hh:261
void append(const T &value)
void insert(const int64_t insert_index, const T &value)
void reserve(const int64_t min_capacity)
Overall context of the compositor.
const bNodeTree * get_bnodetree() const
get the bnodetree of the context
bool is_rendering() const
get the rendering field of the context
bke::bNodeInstanceHash * get_preview_hash() const
get the preview image hash table
static void node_to_operations(const Node *node)
Definition COM_Debug.h:72
static void graphviz(const ExecutionSystem *system, StringRefNull name="")
Definition COM_Debug.cc:340
the ExecutionSystem contains the whole compositor tree.
void set_operations(Span< NodeOperation * > operations)
void from_bNodeTree(const CompositorContext &context, bNodeTree *tree)
Span< Link > links() const
Span< Node * > nodes() const
NodeInput are sockets that can receive data/input.
Definition COM_Node.h:191
void get_editor_value_color(float *value) const
Definition COM_Node.cc:143
bNodeSocket * get_bnode_socket() const
Definition COM_Node.h:215
float get_editor_value_float() const
Definition COM_Node.cc:136
void get_editor_value_vector(float *value) const
Definition COM_Node.cc:150
void map_input_socket(NodeInput *node_socket, NodeOperationInput *operation_socket)
void map_output_socket(NodeOutput *node_socket, NodeOperationOutput *operation_socket)
void replace_operation_with_constant(NodeOperation *operation, ConstantOperation *constant_operation)
void add_input_constant_value(NodeOperationInput *input, const NodeInput *node_input)
Vector< NodeOperationInput * > cache_output_links(NodeOperationOutput *output) const
NodeOperationBuilder(const CompositorContext *context, bNodeTree *b_nodetree, ExecutionSystem *system)
void add_link(NodeOperationOutput *from, NodeOperationInput *to)
NodeOperation contains calculation logic.
const void set_node_instance_key(const bNodeInstanceKey &node_instance_key)
void set_name(const std::string name)
const NodeOperationFlags get_flags() const
unsigned int get_number_of_input_sockets() const
NodeOperationOutput * get_output_socket(unsigned int index=0)
NodeOperationInput * get_input_socket(unsigned int index)
void set_execution_system(ExecutionSystem *system)
NodeOutput are sockets that can send data/input.
Definition COM_Node.h:239
bNodeInstanceKey get_instance_key() const
Definition COM_Node.h:161
bool is_in_active_group() const
Is this node part of the active group the active group is the group that is currently being edited....
Definition COM_Node.h:141
const bNode * get_bnode() const
get the reference to the SDNA bNode struct
Definition COM_Node.h:65
OperationNode * node
StackEntry * from
Set< ComponentNode * > visited
@ Vector
Vector data type.
@ Float2
Float2 data type.
static void find_reachable_operations_recursive(Tags &reachable, NodeOperation *op)
NodeOperation * COM_convert_data_type(const NodeOperationOutput &from, const NodeOperationInput &to)
This function will add a date-type conversion rule when the to-socket does not support the from-socke...
static constexpr bool COM_EXPORT_GRAPHVIZ
Definition COM_Debug.h:18
std::set< NodeOperation * > Tags
void COM_convert_canvas(NodeOperationBuilder &builder, NodeOperationOutput *from_socket, NodeOperationInput *to_socket)
This function will add a resolution rule based on the settings of the NodeInput.
static Vector< NodeOperationHash > generate_hashes(Span< NodeOperation * > operations)
constexpr rcti COM_AREA_NONE
Definition COM_defines.h:89
static void sort_operations_recursive(Vector< NodeOperation * > &sorted, Tags &visited, NodeOperation *op)
std::ostream & operator<<(std::ostream &os, const eCompositorPriority &priority)
Definition COM_Enums.cc:27
static blender::bke::bNodeSocketTemplate inputs[]
#define hash
Definition noise.c:154
char name[64]
bNodeRuntimeHandle * runtime
int ymin
int xmin