Blender V5.0
geometry_nodes_repeat_zone.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
6
8#include "BKE_node_runtime.hh"
10
12
13#include "BLT_translation.hh"
14
15#include "BLI_array_utils.hh"
16
18
20
21namespace blender::nodes {
22
23using bke::SocketValueVariant;
24
32 public:
33 const bNode *repeat_output_bnode_ = nullptr;
35
38 const lf::Context &context) const override
39 {
40 GeoNodesUserData &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
41 const int iteration = lf_body_nodes_->index_of_try(const_cast<lf::FunctionNode *>(&node));
42 const LazyFunction &fn = node.function();
43 if (iteration == -1) {
44 /* The node is not a loop body node, just execute it normally. */
45 fn.execute(params, context);
46 return;
47 }
48
49 /* Setup context for the loop body evaluation. */
50 bke::RepeatZoneComputeContext body_compute_context{
51 user_data.compute_context, *repeat_output_bnode_, iteration};
52 GeoNodesUserData body_user_data = user_data;
53 body_user_data.compute_context = &body_compute_context;
55 user_data, body_compute_context.hash());
56
57 GeoNodesLocalUserData body_local_user_data{body_user_data};
58 lf::Context body_context{context.storage, &body_user_data, &body_local_user_data};
59 fn.execute(params, body_context);
60 }
61};
62
67 public:
68 const bNode *repeat_output_bnode_ = nullptr;
70
72 const lf::Context &context) const override
73 {
74 GeoNodesUserData &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
75 const GeoNodesCallData &call_data = *user_data.call_data;
76 if (!call_data.side_effect_nodes) {
77 return {};
78 }
79 const ComputeContextHash &context_hash = user_data.compute_context->hash();
80 const Span<int> iterations_with_side_effects =
82 {context_hash, repeat_output_bnode_->identifier});
83
85 for (const int i : iterations_with_side_effects) {
86 if (i >= 0 && i < lf_body_nodes_.size()) {
87 lf_nodes.append(lf_body_nodes_[i]);
88 }
89 }
90 return lf_nodes;
91 }
92};
93
98 std::optional<LazyFunctionForLogicalOr> or_function;
99 std::optional<RepeatZoneSideEffectProvider> side_effect_provider;
100 std::optional<RepeatBodyNodeExecuteWrapper> body_execute_wrapper;
101 std::optional<lf::GraphExecutor> graph_executor;
103 void *graph_executor_storage = nullptr;
107};
108
110 private:
111 const bNodeTree &btree_;
112 const bke::bNodeTreeZone &zone_;
113 const bNode &repeat_output_bnode_;
114 const ZoneBuildInfo &zone_info_;
115 const ZoneBodyFunction &body_fn_;
116
117 public:
119 const bke::bNodeTreeZone &zone,
120 ZoneBuildInfo &zone_info,
121 const ZoneBodyFunction &body_fn)
122 : btree_(btree),
123 zone_(zone),
124 repeat_output_bnode_(*zone.output_node()),
125 zone_info_(zone_info),
126 body_fn_(body_fn)
127 {
128 debug_name_ = "Repeat Zone";
129
130 initialize_zone_wrapper(zone, zone_info, body_fn, true, inputs_, outputs_);
131 /* Iterations input is always used. */
132 inputs_[zone_info.indices.inputs.main[0]].usage = lf::ValueUsage::Used;
133 }
134
135 void *init_storage(LinearAllocator<> &allocator) const override
136 {
137 return allocator.construct<RepeatEvalStorage>().release();
138 }
139
140 void destruct_storage(void *storage) const override
141 {
142 RepeatEvalStorage *s = static_cast<RepeatEvalStorage *>(storage);
143 if (s->graph_executor_storage) {
144 s->graph_executor->destruct_storage(s->graph_executor_storage);
145 }
146 std::destroy_at(s);
147 }
148
149 void execute_impl(lf::Params &params, const lf::Context &context) const override
150 {
151 const ScopedNodeTimer node_timer{context, repeat_output_bnode_};
152
153 auto &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
154 auto &local_user_data = *static_cast<GeoNodesLocalUserData *>(context.local_user_data);
155
156 const NodeGeometryRepeatOutput &node_storage = *static_cast<const NodeGeometryRepeatOutput *>(
157 repeat_output_bnode_.storage);
158 RepeatEvalStorage &eval_storage = *static_cast<RepeatEvalStorage *>(context.storage);
159
160 const int iterations_usage_index = zone_info_.indices.outputs.input_usages[0];
161 if (!params.output_was_set(iterations_usage_index)) {
162 /* The iterations input is always used. */
163 params.set_output(iterations_usage_index, true);
164 }
165
166 if (!eval_storage.graph_executor) {
167 /* Create the execution graph in the first evaluation. */
169 params, eval_storage, node_storage, user_data, local_user_data);
170 }
171
172 /* Execute the graph for the repeat zone. */
173 lf::RemappedParams eval_graph_params{*eval_storage.graph_executor,
174 params,
175 eval_storage.input_index_map,
176 eval_storage.output_index_map,
177 eval_storage.multi_threading_enabled};
178 lf::Context eval_graph_context{
179 eval_storage.graph_executor_storage, context.user_data, context.local_user_data};
180 eval_storage.graph_executor->execute(eval_graph_params, eval_graph_context);
181 }
182
192 RepeatEvalStorage &eval_storage,
193 const NodeGeometryRepeatOutput &node_storage,
194 GeoNodesUserData &user_data,
195 GeoNodesLocalUserData &local_user_data) const
196 {
197 const int num_repeat_items = node_storage.items_num;
198 const int num_border_links = body_fn_.indices.inputs.border_links.size();
199
200 /* Number of iterations to evaluate. */
201 const int iterations = std::max<int>(
202 0, params.get_input<SocketValueVariant>(zone_info_.indices.inputs.main[0]).get<int>());
203
204 if (iterations >= 10) {
205 /* Constructing and running the repeat zone has some overhead so that it's probably worth
206 * trying to do something else in the meantime already. */
208 }
209
210 /* Show a warning when the inspection index is out of range. */
211 if (node_storage.inspection_index > 0) {
212 if (node_storage.inspection_index >= iterations) {
213 if (geo_eval_log::GeoTreeLogger *tree_logger = local_user_data.try_get_tree_logger(
214 user_data))
215 {
216 tree_logger->node_warnings.append(
217 *tree_logger->allocator,
218 {repeat_output_bnode_.identifier,
219 {NodeWarningType::Info, N_("Inspection index is out of range")}});
220 }
221 }
222 }
223
224 /* Take iterations input into account. */
225 const int main_inputs_offset = 1;
226 const int body_inputs_offset = 1;
227
228 lf::Graph &lf_graph = eval_storage.graph;
229
232
233 for (const int i : inputs_.index_range()) {
234 const lf::Input &input = inputs_[i];
235 lf_inputs.append(&lf_graph.add_input(*input.type, this->input_name(i)));
236 }
237 for (const int i : outputs_.index_range()) {
238 const lf::Output &output = outputs_[i];
239 lf_outputs.append(&lf_graph.add_output(*output.type, this->output_name(i)));
240 }
241
242 /* Create body nodes. */
243 VectorSet<lf::FunctionNode *> &lf_body_nodes = eval_storage.lf_body_nodes;
244 for ([[maybe_unused]] const int i : IndexRange(iterations)) {
245 lf::FunctionNode &lf_node = lf_graph.add_function(*body_fn_.function);
246 lf_body_nodes.add_new(&lf_node);
247 }
248
249 /* Create nodes for combining border link usages. A border link is used when any of the loop
250 * bodies uses the border link, so an "or" node is necessary. */
251 Array<lf::FunctionNode *> lf_border_link_usage_or_nodes(num_border_links);
252 eval_storage.or_function.emplace(iterations);
253 for (const int i : IndexRange(num_border_links)) {
254 lf::FunctionNode &lf_node = lf_graph.add_function(*eval_storage.or_function);
255 lf_border_link_usage_or_nodes[i] = &lf_node;
256 }
257
258 const bool use_index_values = zone_.input_node()->output_socket(0).is_directly_linked();
259
260 if (use_index_values) {
261 eval_storage.index_values.reinitialize(iterations);
262 threading::parallel_for(IndexRange(iterations), 1024, [&](const IndexRange range) {
263 for (const int i : range) {
264 eval_storage.index_values[i].set(i);
265 }
266 });
267 }
268
269 /* Handle body nodes one by one. */
270 static const SocketValueVariant static_unused_index{-1};
271 for (const int iter_i : lf_body_nodes.index_range()) {
272 lf::FunctionNode &lf_node = *lf_body_nodes[iter_i];
273 const SocketValueVariant *index_value = use_index_values ?
274 &eval_storage.index_values[iter_i] :
275 &static_unused_index;
276 lf_node.input(body_fn_.indices.inputs.main[0]).set_default_value(index_value);
277 for (const int i : IndexRange(num_border_links)) {
278 lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.border_links[i]],
279 lf_node.input(body_fn_.indices.inputs.border_links[i]));
280 lf_graph.add_link(lf_node.output(body_fn_.indices.outputs.border_link_usages[i]),
281 lf_border_link_usage_or_nodes[i]->input(iter_i));
282 }
283
284 /* Handle reference sets. */
285 for (const auto &item : body_fn_.indices.inputs.reference_sets.items()) {
286 lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.reference_sets.lookup(item.key)],
287 lf_node.input(item.value));
288 }
289 }
290
291 static bool static_true = true;
292
293 /* Handle body nodes pair-wise. */
294 for (const int iter_i : lf_body_nodes.index_range().drop_back(1)) {
295 lf::FunctionNode &lf_node = *lf_body_nodes[iter_i];
296 lf::FunctionNode &lf_next_node = *lf_body_nodes[iter_i + 1];
297 for (const int i : IndexRange(num_repeat_items)) {
298 lf_graph.add_link(
299 lf_node.output(body_fn_.indices.outputs.main[i]),
300 lf_next_node.input(body_fn_.indices.inputs.main[i + body_inputs_offset]));
301 /* TODO: Add back-link after being able to check for cyclic dependencies. */
302 // lf_graph.add_link(lf_next_node.output(body_fn_.indices.outputs.input_usages[i]),
303 // lf_node.input(body_fn_.indices.inputs.output_usages[i]));
304 lf_node.input(body_fn_.indices.inputs.output_usages[i]).set_default_value(&static_true);
305 }
306 }
307
308 /* Handle border link usage outputs. */
309 for (const int i : IndexRange(num_border_links)) {
310 lf_graph.add_link(lf_border_link_usage_or_nodes[i]->output(0),
311 *lf_outputs[zone_info_.indices.outputs.border_link_usages[i]]);
312 }
313
314 if (iterations > 0) {
315 {
316 /* Link first body node to input/output nodes. */
317 lf::FunctionNode &lf_first_body_node = *lf_body_nodes[0];
318 for (const int i : IndexRange(num_repeat_items)) {
319 lf_graph.add_link(
320 *lf_inputs[zone_info_.indices.inputs.main[i + main_inputs_offset]],
321 lf_first_body_node.input(body_fn_.indices.inputs.main[i + body_inputs_offset]));
322 lf_graph.add_link(
323 lf_first_body_node.output(
324 body_fn_.indices.outputs.input_usages[i + body_inputs_offset]),
325 *lf_outputs[zone_info_.indices.outputs.input_usages[i + main_inputs_offset]]);
326 }
327 }
328 {
329 /* Link last body node to input/output nodes. */
330 lf::FunctionNode &lf_last_body_node = *lf_body_nodes.as_span().last();
331 for (const int i : IndexRange(num_repeat_items)) {
332 lf_graph.add_link(lf_last_body_node.output(body_fn_.indices.outputs.main[i]),
333 *lf_outputs[zone_info_.indices.outputs.main[i]]);
334 lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.output_usages[i]],
335 lf_last_body_node.input(body_fn_.indices.inputs.output_usages[i]));
336 }
337 }
338 }
339 else {
340 /* There are no iterations, just link the input directly to the output. */
341 for (const int i : IndexRange(num_repeat_items)) {
342 lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.main[i + main_inputs_offset]],
343 *lf_outputs[zone_info_.indices.outputs.main[i]]);
344 lf_graph.add_link(
345 *lf_inputs[zone_info_.indices.inputs.output_usages[i]],
346 *lf_outputs[zone_info_.indices.outputs.input_usages[i + main_inputs_offset]]);
347 }
348 for (const int i : IndexRange(num_border_links)) {
349 static bool static_false = false;
350 lf_outputs[zone_info_.indices.outputs.border_link_usages[i]]->set_default_value(
351 &static_false);
352 }
353 }
354
355 lf_outputs[zone_info_.indices.outputs.input_usages[0]]->set_default_value(&static_true);
356
357 /* The graph is ready, update the node indices which are required by the executor. */
358 lf_graph.update_node_indices();
359
360 // std::cout << "\n\n" << lf_graph.to_dot() << "\n\n";
361
362 /* Create a mapping from parameter indices inside of this graph to parameters of the repeat
363 * zone. The main complexity below stems from the fact that the iterations input is handled
364 * outside of this graph. */
365 eval_storage.output_index_map.reinitialize(outputs_.size() - 1);
366 eval_storage.input_index_map.resize(inputs_.size() - 1);
367 array_utils::fill_index_range<int>(eval_storage.input_index_map, 1);
368
369 Vector<const lf::GraphInputSocket *> lf_graph_inputs = lf_inputs.as_span().drop_front(1);
370
371 const int iteration_usage_index = zone_info_.indices.outputs.input_usages[0];
373 eval_storage.output_index_map.as_mutable_span().take_front(iteration_usage_index));
375 eval_storage.output_index_map.as_mutable_span().drop_front(iteration_usage_index),
376 iteration_usage_index + 1);
377
378 Vector<const lf::GraphOutputSocket *> lf_graph_outputs = lf_outputs.as_span().take_front(
379 iteration_usage_index);
380 lf_graph_outputs.extend(lf_outputs.as_span().drop_front(iteration_usage_index + 1));
381
382 eval_storage.body_execute_wrapper.emplace();
383 eval_storage.body_execute_wrapper->repeat_output_bnode_ = &repeat_output_bnode_;
384 eval_storage.body_execute_wrapper->lf_body_nodes_ = &lf_body_nodes;
385 eval_storage.side_effect_provider.emplace();
386 eval_storage.side_effect_provider->repeat_output_bnode_ = &repeat_output_bnode_;
387 eval_storage.side_effect_provider->lf_body_nodes_ = lf_body_nodes;
388
389 eval_storage.graph_executor.emplace(lf_graph,
390 std::move(lf_graph_inputs),
391 std::move(lf_graph_outputs),
392 nullptr,
393 &*eval_storage.side_effect_provider,
394 &*eval_storage.body_execute_wrapper);
395 eval_storage.graph_executor_storage = eval_storage.graph_executor->init_storage(
396 eval_storage.allocator);
397
398 /* Log graph for debugging purposes. */
399 const bNodeTree &btree_orig = *DEG_get_original(&btree_);
400 if (btree_orig.runtime->logged_zone_graphs) {
401 std::lock_guard lock{btree_orig.runtime->logged_zone_graphs->mutex};
402 btree_orig.runtime->logged_zone_graphs->graph_by_zone_id.lookup_or_add_cb(
403 repeat_output_bnode_.identifier, [&]() { return lf_graph.to_dot(); });
404 }
405 }
406
407 std::string input_name(const int i) const override
408 {
409 return zone_wrapper_input_name(zone_info_, zone_, inputs_, i);
410 }
411
412 std::string output_name(const int i) const override
413 {
414 return zone_wrapper_output_name(zone_info_, zone_, outputs_, i);
415 }
416};
417
419 const bNodeTree &btree,
420 const bke::bNodeTreeZone &zone,
421 ZoneBuildInfo &zone_info,
422 const ZoneBodyFunction &body_fn)
423{
424 return scope.construct<LazyFunctionForRepeatZone>(btree, zone, zone_info, body_fn);
425}
426
427} // namespace blender::nodes
T * DEG_get_original(T *id)
struct bNodeTree bNodeTree
volatile int lock
constexpr IndexRange drop_back(int64_t n) const
IndexRange index_range() const
Span< Key > as_span() const
void add_new(const Key &key)
void extend(Span< T > array)
const ComputeContextHash & hash() const
destruct_ptr< T > construct(Args &&...args)
T & construct(Args &&...args)
void append(const T &value)
Span< T > as_span() const
FunctionNode & add_function(const LazyFunction &fn)
void add_link(OutputSocket &from, InputSocket &to)
GraphOutputSocket & add_output(const CPPType &type, std::string name="")
GraphInputSocket & add_input(const CPPType &type, std::string name="")
const InputSocket & input(int index) const
const OutputSocket & output(int index) const
std::string output_name(const int i) const override
void * init_storage(LinearAllocator<> &allocator) const override
void initialize_execution_graph(lf::Params &params, RepeatEvalStorage &eval_storage, const NodeGeometryRepeatOutput &node_storage, GeoNodesUserData &user_data, GeoNodesLocalUserData &local_user_data) const
LazyFunctionForRepeatZone(const bNodeTree &btree, const bke::bNodeTreeZone &zone, ZoneBuildInfo &zone_info, const ZoneBodyFunction &body_fn)
std::string input_name(const int i) const override
void destruct_storage(void *storage) const override
void execute_impl(lf::Params &params, const lf::Context &context) const override
void execute_node(const lf::FunctionNode &node, lf::Params &params, const lf::Context &context) const override
Vector< const lf::FunctionNode * > get_nodes_with_side_effects(const lf::Context &context) const override
#define input
#define output
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void fill_index_range(MutableSpan< T > span, const T start=0)
void initialize_zone_wrapper(const bNodeTreeZone &zone, ZoneBuildInfo &zone_info, const ZoneBodyFunction &body_fn, const bool expose_all_reference_sets, Vector< lf::Input > &r_inputs, Vector< lf::Output > &r_outputs)
bool should_log_socket_values_for_context(const GeoNodesUserData &user_data, const ComputeContextHash hash)
LazyFunction & build_repeat_zone_lazy_function(ResourceScope &scope, const bNodeTree &btree, const bke::bNodeTreeZone &zone, ZoneBuildInfo &zone_info, const ZoneBodyFunction &body_fn)
std::string zone_wrapper_output_name(const ZoneBuildInfo &zone_info, const bNodeTreeZone &zone, const Span< lf::Output > outputs, const int lf_socket_i)
std::string zone_wrapper_input_name(const ZoneBuildInfo &zone_info, const bNodeTreeZone &zone, const Span< lf::Input > inputs, const int lf_socket_i)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
bNodeTreeRuntimeHandle * runtime
const GeoNodesSideEffectNodes * side_effect_nodes
geo_eval_log::GeoTreeLogger * try_get_tree_logger(const GeoNodesUserData &user_data) const
MultiValueMap< std::pair< ComputeContextHash, int32_t >, int > iterations_by_iteration_zone
std::optional< LazyFunctionForLogicalOr > or_function
VectorSet< lf::FunctionNode * > lf_body_nodes
std::optional< lf::GraphExecutor > graph_executor
std::optional< RepeatBodyNodeExecuteWrapper > body_execute_wrapper
std::optional< RepeatZoneSideEffectProvider > side_effect_provider
struct blender::nodes::ZoneFunctionIndices::@273312016272125124266321211240073263106341134120 inputs
i
Definition text_draw.cc:230