Blender V5.0
evaluator.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BLI_memory_utils.hh"
6#include "BLI_string.h"
7
8#include "DNA_node_types.h"
9
11
12#include "COM_compile_state.hh"
13#include "COM_context.hh"
14#include "COM_evaluator.hh"
18#include "COM_node_operation.hh"
19#include "COM_operation.hh"
20#include "COM_result.hh"
21#include "COM_scheduler.hh"
23#include "COM_utilities.hh"
24
25namespace blender::compositor {
26
27using namespace nodes::derived_node_tree_types;
28
29Evaluator::Evaluator(Context &context) : context_(context) {}
30
32{
33 context_.reset();
34
35 BLI_SCOPED_DEFER([&]() {
36 if (context_.profiler()) {
37 context_.profiler()->finalize(context_.get_node_tree());
38 }
39 });
40
41 derived_node_tree_ = std::make_unique<DerivedNodeTree>(context_.get_node_tree());
42
43 if (!this->validate_node_tree()) {
44 return;
45 }
46
47 if (context_.is_canceled()) {
48 this->cancel_evaluation();
49 return;
50 }
51
52 const Schedule schedule = compute_schedule(context_, *derived_node_tree_);
53
54 CompileState compile_state(context_, schedule);
55
56 for (const DNode &node : schedule) {
57 if (context_.is_canceled()) {
58 this->cancel_evaluation();
59 return;
60 }
61
62 if (compile_state.should_compile_pixel_compile_unit(node)) {
63 this->evaluate_pixel_compile_unit(compile_state);
64 }
65
66 if (is_pixel_node(node)) {
67 compile_state.add_node_to_pixel_compile_unit(node);
68 }
69 else {
70 this->evaluate_node(node, compile_state);
71 }
72 }
73}
74
75bool Evaluator::validate_node_tree()
76{
77 if (derived_node_tree_->has_link_cycles()) {
78 context_.set_info_message("Compositor node tree has cyclic links!");
79 return false;
80 }
81
82 if (derived_node_tree_->has_undefined_nodes_or_sockets()) {
83 context_.set_info_message("Compositor node tree has undefined nodes or sockets!");
84 return false;
85 }
86
87 for (const bNodeTree *node_tree : derived_node_tree_->used_btrees()) {
88 for (const bNode *node : node_tree->all_nodes()) {
89 /* The poll method of those two nodes perform raw pointer comparisons of node trees, so they
90 * can wrongly fail since the compositor localizes the node tree, changing its pointer value
91 * than the one in the main database. So handle those two nodes. */
92 if (STR_ELEM(node->idname, "CompositorNodeRLayers", "CompositorNodeCryptomatteV2")) {
93 continue;
94 }
95
96 const char *disabled_hint = nullptr;
97 if (!node->typeinfo->poll(node->typeinfo, node_tree, &disabled_hint)) {
98 context_.set_info_message("Compositor node tree has unsupported nodes.");
99 return false;
100 }
101 }
102 }
103
104 return true;
105}
106
107void Evaluator::evaluate_node(DNode node, CompileState &compile_state)
108{
109 NodeOperation *operation = node->typeinfo->get_compositor_operation(context_, node);
110
111 compile_state.map_node_to_node_operation(node, operation);
112
113 map_node_operation_inputs_to_their_results(node, operation, compile_state);
114
115 /* This has to be done after input mapping because the method may add Input Single Value
116 * Operations to the operations stream, which needs to be evaluated before the operation itself
117 * is evaluated. */
118 operations_stream_.append(std::unique_ptr<Operation>(operation));
119
120 operation->compute_results_reference_counts(compile_state.get_schedule());
121
122 operation->evaluate();
123}
124
125void Evaluator::map_node_operation_inputs_to_their_results(DNode node,
126 NodeOperation *operation,
127 CompileState &compile_state)
128{
129 for (const bNodeSocket *input : node->input_sockets()) {
130 const DInputSocket dinput{node.context(), input};
131
133 continue;
134 }
135
136 DSocket dorigin = get_input_origin_socket(dinput);
137
138 /* The origin socket is an output, which means the input is linked. So map the input to the
139 * result we get from the output. */
140 if (dorigin->is_output()) {
141 Result &result = compile_state.get_result_from_output_socket(DOutputSocket(dorigin));
142 operation->map_input_to_result(input->identifier, &result);
143 continue;
144 }
145
146 /* Otherwise, the origin socket is an input, which either means the input is unlinked and the
147 * origin is the input socket itself or the input is connected to an unlinked input of a group
148 * input node and the origin is the input of the group input node. So map the input to the
149 * result of a newly created Input Single Value Operation. */
150 InputSingleValueOperation *input_operation = new InputSingleValueOperation(
151 context_, DInputSocket(dorigin));
152 operation->map_input_to_result(input->identifier, &input_operation->get_result());
153
154 operations_stream_.append(std::unique_ptr<InputSingleValueOperation>(input_operation));
155
156 input_operation->evaluate();
157 }
158}
159
160/* Create one of the concrete subclasses of the PixelOperation based on the context and compile
161 * state. Deleting the operation is the caller's responsibility. */
163{
164 const Schedule &schedule = compile_state.get_schedule();
165 PixelCompileUnit &compile_unit = compile_state.get_pixel_compile_unit();
166
167 /* Use multi-function procedure to execute the pixel compile unit for CPU contexts or if the
168 * compile unit is single value and would thus be more efficient to execute on the CPU. */
169 if (!context.use_gpu() || compile_state.is_pixel_compile_unit_single_value()) {
170 return new MultiFunctionProcedureOperation(context, compile_unit, schedule);
171 }
172
173 return new ShaderOperation(context, compile_unit, schedule);
174}
175
176void Evaluator::evaluate_pixel_compile_unit(CompileState &compile_state)
177{
178 PixelCompileUnit &compile_unit = compile_state.get_pixel_compile_unit();
179
180 /* Pixel operations might have limitations on the number of outputs they can have, so we might
181 * have to split the compile unit into smaller units to workaround this limitation. In practice,
182 * splitting will almost always never happen due to the scheduling strategy we use, so the base
183 * case remains fast. */
184 int number_of_outputs = 0;
185 for (int i : compile_unit.index_range()) {
186 const DNode node = compile_unit[i];
187 number_of_outputs += compile_state.compute_pixel_node_operation_outputs_count(node);
188
189 if (number_of_outputs <= PixelOperation::maximum_number_of_outputs(context_)) {
190 continue;
191 }
192
193 /* The number of outputs surpassed the limit, so we split the compile unit into two equal parts
194 * and recursively call this method on each of them. It might seem unexpected that we split in
195 * half as opposed to split at the node that surpassed the limit, but that is because the act
196 * of splitting might actually introduce new outputs, since links that were previously internal
197 * to the compile unit might now be external. So we can't precisely split and guarantee correct
198 * units, and we just rely or recursive splitting until units are small enough. Further, half
199 * splitting helps balancing the shaders, where we don't want to have one gigantic shader and
200 * a tiny one. */
201 const int split_index = compile_unit.size() / 2;
202 const PixelCompileUnit start_compile_unit(compile_unit.as_span().take_front(split_index));
203 const PixelCompileUnit end_compile_unit(compile_unit.as_span().drop_front(split_index));
204
205 compile_state.get_pixel_compile_unit() = start_compile_unit;
206 this->evaluate_pixel_compile_unit(compile_state);
207
208 compile_state.get_pixel_compile_unit() = end_compile_unit;
209 this->evaluate_pixel_compile_unit(compile_state);
210
211 /* No need to continue, the above recursive calls will eventually exist the loop and do the
212 * actual compilation. */
213 return;
214 }
215
216 PixelOperation *operation = create_pixel_operation(context_, compile_state);
217
218 for (DNode node : compile_unit) {
219 compile_state.map_node_to_pixel_operation(node, operation);
220 }
221
222 map_pixel_operation_inputs_to_their_results(operation, compile_state);
223
224 operations_stream_.append(std::unique_ptr<Operation>(operation));
225
226 operation->compute_results_reference_counts(compile_state.get_schedule());
227
228 operation->evaluate();
229
230 compile_state.reset_pixel_compile_unit();
231}
232
233void Evaluator::map_pixel_operation_inputs_to_their_results(PixelOperation *operation,
234 CompileState &compile_state)
235{
236 for (const auto item : operation->get_inputs_to_linked_outputs_map().items()) {
237 Result &result = compile_state.get_result_from_output_socket(item.value);
238 operation->map_input_to_result(item.key, &result);
239
240 /* Correct the reference count of the result in case multiple of the result's outgoing links
241 * corresponds to a single input in the pixel operation. See the description of the member
242 * inputs_to_reference_counts_map_ variable for more information. */
243 const int internal_reference_count = operation->get_internal_input_reference_count(item.key);
244 result.decrement_reference_count(internal_reference_count - 1);
245 }
246
247 for (const auto item : operation->get_implicit_inputs_to_input_identifiers_map().items()) {
248 ImplicitInputOperation *input_operation = new ImplicitInputOperation(context_, item.key);
249 operation->map_input_to_result(item.value, &input_operation->get_result());
250
251 operations_stream_.append(std::unique_ptr<ImplicitInputOperation>(input_operation));
252
253 input_operation->evaluate();
254 }
255}
256
257void Evaluator::cancel_evaluation()
258{
259 context_.cache_manager().skip_next_reset();
260 for (const std::unique_ptr<Operation> &operation : operations_stream_) {
261 operation->free_results();
262 }
263}
264
265} // namespace blender::compositor
#define BLI_SCOPED_DEFER(function_to_defer)
#define STR_ELEM(...)
Definition BLI_string.h:661
struct bNodeSocket bNodeSocket
PixelCompileUnit & get_pixel_compile_unit()
void add_node_to_pixel_compile_unit(DNode node)
bool should_compile_pixel_compile_unit(DNode node)
virtual void set_info_message(StringRef message) const
Evaluator(Context &context)
Definition evaluator.cc:29
static int maximum_number_of_outputs(Context &context)
#define input
DSocket get_input_origin_socket(DInputSocket input)
Definition utilities.cc:32
VectorSet< DNode > Schedule
VectorSet< DNode > PixelCompileUnit
static PixelOperation * create_pixel_operation(Context &context, CompileState &compile_state)
Definition evaluator.cc:162
Schedule compute_schedule(const Context &context, const DerivedNodeTree &tree)
Definition scheduler.cc:315
bool is_pixel_node(DNode node)
Definition utilities.cc:143
bool is_socket_available(const bNodeSocket *socket)
Definition utilities.cc:27
i
Definition text_draw.cc:230