Blender V4.3
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 <string>
6
7#include "DNA_node_types.h"
8
10
11#include "COM_compile_state.hh"
12#include "COM_context.hh"
13#include "COM_evaluator.hh"
15#include "COM_node_operation.hh"
16#include "COM_operation.hh"
17#include "COM_result.hh"
18#include "COM_scheduler.hh"
20#include "COM_utilities.hh"
21
23
24using namespace nodes::derived_node_tree_types;
25
26Evaluator::Evaluator(Context &context) : context_(context) {}
27
29{
30 context_.reset();
31
32 if (!is_compiled_) {
33 compile_and_evaluate();
34 }
35 else {
36 for (const std::unique_ptr<Operation> &operation : operations_stream_) {
37 if (context_.is_canceled()) {
38 context_.cache_manager().skip_next_reset();
39 break;
40 }
41 operation->evaluate();
42 }
43 }
44
45 if (context_.profiler()) {
46 context_.profiler()->finalize(context_.get_node_tree());
47 }
48}
49
51{
52 operations_stream_.clear();
53 derived_node_tree_.reset();
54
55 is_compiled_ = false;
56}
57
58bool Evaluator::validate_node_tree()
59{
60 if (derived_node_tree_->has_link_cycles()) {
61 context_.set_info_message("Compositor node tree has cyclic links!");
62 return false;
63 }
64
65 if (derived_node_tree_->has_undefined_nodes_or_sockets()) {
66 context_.set_info_message("Compositor node tree has undefined nodes or sockets!");
67 return false;
68 }
69
70 return true;
71}
72
73void Evaluator::compile_and_evaluate()
74{
75 derived_node_tree_ = std::make_unique<DerivedNodeTree>(context_.get_node_tree());
76
77 if (!validate_node_tree()) {
78 return;
79 }
80
81 if (context_.is_canceled()) {
82 context_.cache_manager().skip_next_reset();
83 reset();
84 return;
85 }
86
87 const Schedule schedule = compute_schedule(context_, *derived_node_tree_);
88
89 CompileState compile_state(schedule);
90
91 for (const DNode &node : schedule) {
92 if (context_.is_canceled()) {
93 context_.cache_manager().skip_next_reset();
94 reset();
95 return;
96 }
97
98 if (compile_state.should_compile_pixel_compile_unit(node)) {
99 compile_and_evaluate_pixel_compile_unit(compile_state);
100 }
101
102 if (is_pixel_node(node)) {
103 compile_state.add_node_to_pixel_compile_unit(node);
104 }
105 else {
106 compile_and_evaluate_node(node, compile_state);
107 }
108 }
109
110 is_compiled_ = true;
111}
112
113void Evaluator::compile_and_evaluate_node(DNode node, CompileState &compile_state)
114{
115 NodeOperation *operation = node->typeinfo->get_compositor_operation(context_, node);
116
117 compile_state.map_node_to_node_operation(node, operation);
118
119 map_node_operation_inputs_to_their_results(node, operation, compile_state);
120
121 /* This has to be done after input mapping because the method may add Input Single Value
122 * Operations to the operations stream, which needs to be evaluated before the operation itself
123 * is evaluated. */
124 operations_stream_.append(std::unique_ptr<Operation>(operation));
125
126 operation->compute_results_reference_counts(compile_state.get_schedule());
127
128 operation->evaluate();
129}
130
131void Evaluator::map_node_operation_inputs_to_their_results(DNode node,
132 NodeOperation *operation,
133 CompileState &compile_state)
134{
135 for (const bNodeSocket *input : node->input_sockets()) {
136 const DInputSocket dinput{node.context(), input};
137
138 DSocket dorigin = get_input_origin_socket(dinput);
139
140 /* The origin socket is an output, which means the input is linked. So map the input to the
141 * result we get from the output. */
142 if (dorigin->is_output()) {
143 Result &result = compile_state.get_result_from_output_socket(DOutputSocket(dorigin));
144 operation->map_input_to_result(input->identifier, &result);
145 continue;
146 }
147
148 /* Otherwise, the origin socket is an input, which either means the input is unlinked and the
149 * origin is the input socket itself or the input is connected to an unlinked input of a group
150 * input node and the origin is the input of the group input node. So map the input to the
151 * result of a newly created Input Single Value Operation. */
152 InputSingleValueOperation *input_operation = new InputSingleValueOperation(
153 context_, DInputSocket(dorigin));
154 operation->map_input_to_result(input->identifier, &input_operation->get_result());
155
156 operations_stream_.append(std::unique_ptr<InputSingleValueOperation>(input_operation));
157
158 input_operation->evaluate();
159 }
160}
161
162void Evaluator::compile_and_evaluate_pixel_compile_unit(CompileState &compile_state)
163{
164 PixelCompileUnit &compile_unit = compile_state.get_pixel_compile_unit();
165
166 /* GPUs have hardware limitations on the number of output images shaders can have, so we might
167 * have to split the compile unit into smaller units to workaround this limitation. In practice,
168 * splitting will almost always never happen due to the scheduling strategy we use, so the base
169 * case remains fast. */
170 int number_of_outputs = 0;
171 for (int i : compile_unit.index_range()) {
172 const DNode node = compile_unit[i];
173 number_of_outputs += compile_state.compute_pixel_node_operation_outputs_count(node);
174
175 /* The GPU module currently only supports up to 8 output images in shaders, but once this
176 * limitation is lifted, we can replace that with GPU_max_images(). */
177 if (number_of_outputs <= 8) {
178 continue;
179 }
180
181 /* The number of outputs surpassed the limit, so we split the compile unit into two equal parts
182 * and recursively call this method on each of them. It might seem unexpected that we split in
183 * half as opposed to split at the node that surpassed the limit, but that is because the act
184 * of splitting might actually introduce new outputs, since links that were previously internal
185 * to the compile unit might now be external. So we can't precisely split and guarantee correct
186 * units, and we just rely or recursive splitting until units are small enough. Further, half
187 * splitting helps balancing the shaders, where we don't want to have one gigantic shader and
188 * a tiny one. */
189 const int split_index = compile_unit.size() / 2;
190 const PixelCompileUnit start_compile_unit(compile_unit.as_span().take_front(split_index));
191 const PixelCompileUnit end_compile_unit(compile_unit.as_span().drop_front(split_index));
192
193 compile_state.get_pixel_compile_unit() = start_compile_unit;
194 this->compile_and_evaluate_pixel_compile_unit(compile_state);
195
196 compile_state.get_pixel_compile_unit() = end_compile_unit;
197 this->compile_and_evaluate_pixel_compile_unit(compile_state);
198
199 /* No need to continue, the above recursive calls will eventually exist the loop and do the
200 * actual compilation. */
201 return;
202 }
203
204 const Schedule &schedule = compile_state.get_schedule();
205 PixelOperation *operation = new ShaderOperation(context_, compile_unit, schedule);
206
207 for (DNode node : compile_unit) {
208 compile_state.map_node_to_pixel_operation(node, operation);
209 }
210
211 map_pixel_operation_inputs_to_their_results(operation, compile_state);
212
213 operations_stream_.append(std::unique_ptr<Operation>(operation));
214
215 operation->compute_results_reference_counts(compile_state.get_schedule());
216
217 operation->evaluate();
218
219 compile_state.reset_pixel_compile_unit();
220}
221
222void Evaluator::map_pixel_operation_inputs_to_their_results(PixelOperation *operation,
223 CompileState &compile_state)
224{
225 for (const auto item : operation->get_inputs_to_linked_outputs_map().items()) {
226 Result &result = compile_state.get_result_from_output_socket(item.value);
227 operation->map_input_to_result(item.key, &result);
228 }
229}
230
231} // namespace blender::realtime_compositor
int64_t size() const
virtual const bNodeTree & get_node_tree() const =0
virtual void set_info_message(StringRef message) const =0
void finalize(const bNodeTree &node_tree)
Definition profiler.cc:66
bool is_pixel_node(DNode node)
Definition utilities.cc:104
VectorSet< DNode > Schedule
Schedule compute_schedule(const Context &context, const DerivedNodeTree &tree)
Definition scheduler.cc:279
DSocket get_input_origin_socket(DInputSocket input)
Definition utilities.cc:34