It should be possible to use this type without having to include the entire logging system.
428 lines
18 KiB
C++
428 lines
18 KiB
C++
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "NOD_geometry_nodes_lazy_function.hh"
|
|
|
|
#include "BKE_compute_contexts.hh"
|
|
#include "BKE_node_runtime.hh"
|
|
#include "BKE_node_socket_value.hh"
|
|
|
|
#include "FN_lazy_function_execute.hh"
|
|
|
|
#include "BLT_translation.hh"
|
|
|
|
#include "BLI_array_utils.hh"
|
|
|
|
#include "DEG_depsgraph_query.hh"
|
|
|
|
#include "FN_lazy_function_graph_executor.hh"
|
|
|
|
namespace blender::nodes {
|
|
|
|
using bke::SocketValueVariant;
|
|
|
|
/**
|
|
* Wraps the execution of a repeat loop body. The purpose is to setup the correct #ComputeContext
|
|
* inside of the loop body. This is necessary to support correct logging inside of a repeat zone.
|
|
* An alternative would be to use a separate `LazyFunction` for every iteration, but that would
|
|
* have higher overhead.
|
|
*/
|
|
class RepeatBodyNodeExecuteWrapper : public lf::GraphExecutorNodeExecuteWrapper {
|
|
public:
|
|
const bNode *repeat_output_bnode_ = nullptr;
|
|
VectorSet<lf::FunctionNode *> *lf_body_nodes_ = nullptr;
|
|
|
|
void execute_node(const lf::FunctionNode &node,
|
|
lf::Params ¶ms,
|
|
const lf::Context &context) const override
|
|
{
|
|
GeoNodesUserData &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
|
|
const int iteration = lf_body_nodes_->index_of_try(const_cast<lf::FunctionNode *>(&node));
|
|
const LazyFunction &fn = node.function();
|
|
if (iteration == -1) {
|
|
/* The node is not a loop body node, just execute it normally. */
|
|
fn.execute(params, context);
|
|
return;
|
|
}
|
|
|
|
/* Setup context for the loop body evaluation. */
|
|
bke::RepeatZoneComputeContext body_compute_context{
|
|
user_data.compute_context, *repeat_output_bnode_, iteration};
|
|
GeoNodesUserData body_user_data = user_data;
|
|
body_user_data.compute_context = &body_compute_context;
|
|
body_user_data.log_socket_values = should_log_socket_values_for_context(
|
|
user_data, body_compute_context.hash());
|
|
|
|
GeoNodesLocalUserData body_local_user_data{body_user_data};
|
|
lf::Context body_context{context.storage, &body_user_data, &body_local_user_data};
|
|
fn.execute(params, body_context);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Knows which iterations of the loop evaluation have side effects.
|
|
*/
|
|
class RepeatZoneSideEffectProvider : public lf::GraphExecutorSideEffectProvider {
|
|
public:
|
|
const bNode *repeat_output_bnode_ = nullptr;
|
|
Span<lf::FunctionNode *> lf_body_nodes_;
|
|
|
|
Vector<const lf::FunctionNode *> get_nodes_with_side_effects(
|
|
const lf::Context &context) const override
|
|
{
|
|
GeoNodesUserData &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
|
|
const GeoNodesCallData &call_data = *user_data.call_data;
|
|
if (!call_data.side_effect_nodes) {
|
|
return {};
|
|
}
|
|
const ComputeContextHash &context_hash = user_data.compute_context->hash();
|
|
const Span<int> iterations_with_side_effects =
|
|
call_data.side_effect_nodes->iterations_by_iteration_zone.lookup(
|
|
{context_hash, repeat_output_bnode_->identifier});
|
|
|
|
Vector<const lf::FunctionNode *> lf_nodes;
|
|
for (const int i : iterations_with_side_effects) {
|
|
if (i >= 0 && i < lf_body_nodes_.size()) {
|
|
lf_nodes.append(lf_body_nodes_[i]);
|
|
}
|
|
}
|
|
return lf_nodes;
|
|
}
|
|
};
|
|
|
|
struct RepeatEvalStorage {
|
|
LinearAllocator<> allocator;
|
|
VectorSet<lf::FunctionNode *> lf_body_nodes;
|
|
lf::Graph graph;
|
|
std::optional<LazyFunctionForLogicalOr> or_function;
|
|
std::optional<RepeatZoneSideEffectProvider> side_effect_provider;
|
|
std::optional<RepeatBodyNodeExecuteWrapper> body_execute_wrapper;
|
|
std::optional<lf::GraphExecutor> graph_executor;
|
|
Array<SocketValueVariant> index_values;
|
|
void *graph_executor_storage = nullptr;
|
|
bool multi_threading_enabled = false;
|
|
Vector<int> input_index_map;
|
|
Vector<int> output_index_map;
|
|
};
|
|
|
|
class LazyFunctionForRepeatZone : public LazyFunction {
|
|
private:
|
|
const bNodeTree &btree_;
|
|
const bke::bNodeTreeZone &zone_;
|
|
const bNode &repeat_output_bnode_;
|
|
const ZoneBuildInfo &zone_info_;
|
|
const ZoneBodyFunction &body_fn_;
|
|
|
|
public:
|
|
LazyFunctionForRepeatZone(const bNodeTree &btree,
|
|
const bke::bNodeTreeZone &zone,
|
|
ZoneBuildInfo &zone_info,
|
|
const ZoneBodyFunction &body_fn)
|
|
: btree_(btree),
|
|
zone_(zone),
|
|
repeat_output_bnode_(*zone.output_node()),
|
|
zone_info_(zone_info),
|
|
body_fn_(body_fn)
|
|
{
|
|
debug_name_ = "Repeat Zone";
|
|
|
|
initialize_zone_wrapper(zone, zone_info, body_fn, true, inputs_, outputs_);
|
|
/* Iterations input is always used. */
|
|
inputs_[zone_info.indices.inputs.main[0]].usage = lf::ValueUsage::Used;
|
|
}
|
|
|
|
void *init_storage(LinearAllocator<> &allocator) const override
|
|
{
|
|
return allocator.construct<RepeatEvalStorage>().release();
|
|
}
|
|
|
|
void destruct_storage(void *storage) const override
|
|
{
|
|
RepeatEvalStorage *s = static_cast<RepeatEvalStorage *>(storage);
|
|
if (s->graph_executor_storage) {
|
|
s->graph_executor->destruct_storage(s->graph_executor_storage);
|
|
}
|
|
std::destroy_at(s);
|
|
}
|
|
|
|
void execute_impl(lf::Params ¶ms, const lf::Context &context) const override
|
|
{
|
|
const ScopedNodeTimer node_timer{context, repeat_output_bnode_};
|
|
|
|
auto &user_data = *static_cast<GeoNodesUserData *>(context.user_data);
|
|
auto &local_user_data = *static_cast<GeoNodesLocalUserData *>(context.local_user_data);
|
|
|
|
const NodeGeometryRepeatOutput &node_storage = *static_cast<const NodeGeometryRepeatOutput *>(
|
|
repeat_output_bnode_.storage);
|
|
RepeatEvalStorage &eval_storage = *static_cast<RepeatEvalStorage *>(context.storage);
|
|
|
|
const int iterations_usage_index = zone_info_.indices.outputs.input_usages[0];
|
|
if (!params.output_was_set(iterations_usage_index)) {
|
|
/* The iterations input is always used. */
|
|
params.set_output(iterations_usage_index, true);
|
|
}
|
|
|
|
if (!eval_storage.graph_executor) {
|
|
/* Create the execution graph in the first evaluation. */
|
|
this->initialize_execution_graph(
|
|
params, eval_storage, node_storage, user_data, local_user_data);
|
|
}
|
|
|
|
/* Execute the graph for the repeat zone. */
|
|
lf::RemappedParams eval_graph_params{*eval_storage.graph_executor,
|
|
params,
|
|
eval_storage.input_index_map,
|
|
eval_storage.output_index_map,
|
|
eval_storage.multi_threading_enabled};
|
|
lf::Context eval_graph_context{
|
|
eval_storage.graph_executor_storage, context.user_data, context.local_user_data};
|
|
eval_storage.graph_executor->execute(eval_graph_params, eval_graph_context);
|
|
}
|
|
|
|
/**
|
|
* Generate a lazy-function graph that contains the loop body (`body_fn_`) as many times
|
|
* as there are iterations. Since this graph depends on the number of iterations, it can't be
|
|
* reused in general. We could consider caching a version of this graph per number of iterations,
|
|
* but right now that doesn't seem worth it. In practice, it takes much less time to create the
|
|
* graph than to execute it (for intended use cases of this generic implementation, more special
|
|
* case repeat loop evaluations could be implemented separately).
|
|
*/
|
|
void initialize_execution_graph(lf::Params ¶ms,
|
|
RepeatEvalStorage &eval_storage,
|
|
const NodeGeometryRepeatOutput &node_storage,
|
|
GeoNodesUserData &user_data,
|
|
GeoNodesLocalUserData &local_user_data) const
|
|
{
|
|
const int num_repeat_items = node_storage.items_num;
|
|
const int num_border_links = body_fn_.indices.inputs.border_links.size();
|
|
|
|
/* Number of iterations to evaluate. */
|
|
const int iterations = std::max<int>(
|
|
0, params.get_input<SocketValueVariant>(zone_info_.indices.inputs.main[0]).get<int>());
|
|
|
|
if (iterations >= 10) {
|
|
/* Constructing and running the repeat zone has some overhead so that it's probably worth
|
|
* trying to do something else in the meantime already. */
|
|
lazy_threading::send_hint();
|
|
}
|
|
|
|
/* Show a warning when the inspection index is out of range. */
|
|
if (node_storage.inspection_index > 0) {
|
|
if (node_storage.inspection_index >= iterations) {
|
|
if (geo_eval_log::GeoTreeLogger *tree_logger = local_user_data.try_get_tree_logger(
|
|
user_data))
|
|
{
|
|
tree_logger->node_warnings.append(
|
|
*tree_logger->allocator,
|
|
{repeat_output_bnode_.identifier,
|
|
{NodeWarningType::Info, N_("Inspection index is out of range")}});
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Take iterations input into account. */
|
|
const int main_inputs_offset = 1;
|
|
const int body_inputs_offset = 1;
|
|
|
|
lf::Graph &lf_graph = eval_storage.graph;
|
|
|
|
Vector<lf::GraphInputSocket *> lf_inputs;
|
|
Vector<lf::GraphOutputSocket *> lf_outputs;
|
|
|
|
for (const int i : inputs_.index_range()) {
|
|
const lf::Input &input = inputs_[i];
|
|
lf_inputs.append(&lf_graph.add_input(*input.type, this->input_name(i)));
|
|
}
|
|
for (const int i : outputs_.index_range()) {
|
|
const lf::Output &output = outputs_[i];
|
|
lf_outputs.append(&lf_graph.add_output(*output.type, this->output_name(i)));
|
|
}
|
|
|
|
/* Create body nodes. */
|
|
VectorSet<lf::FunctionNode *> &lf_body_nodes = eval_storage.lf_body_nodes;
|
|
for ([[maybe_unused]] const int i : IndexRange(iterations)) {
|
|
lf::FunctionNode &lf_node = lf_graph.add_function(*body_fn_.function);
|
|
lf_body_nodes.add_new(&lf_node);
|
|
}
|
|
|
|
/* Create nodes for combining border link usages. A border link is used when any of the loop
|
|
* bodies uses the border link, so an "or" node is necessary. */
|
|
Array<lf::FunctionNode *> lf_border_link_usage_or_nodes(num_border_links);
|
|
eval_storage.or_function.emplace(iterations);
|
|
for (const int i : IndexRange(num_border_links)) {
|
|
lf::FunctionNode &lf_node = lf_graph.add_function(*eval_storage.or_function);
|
|
lf_border_link_usage_or_nodes[i] = &lf_node;
|
|
}
|
|
|
|
const bool use_index_values = zone_.input_node()->output_socket(0).is_directly_linked();
|
|
|
|
if (use_index_values) {
|
|
eval_storage.index_values.reinitialize(iterations);
|
|
threading::parallel_for(IndexRange(iterations), 1024, [&](const IndexRange range) {
|
|
for (const int i : range) {
|
|
eval_storage.index_values[i].set(i);
|
|
}
|
|
});
|
|
}
|
|
|
|
/* Handle body nodes one by one. */
|
|
static const SocketValueVariant static_unused_index{-1};
|
|
for (const int iter_i : lf_body_nodes.index_range()) {
|
|
lf::FunctionNode &lf_node = *lf_body_nodes[iter_i];
|
|
const SocketValueVariant *index_value = use_index_values ?
|
|
&eval_storage.index_values[iter_i] :
|
|
&static_unused_index;
|
|
lf_node.input(body_fn_.indices.inputs.main[0]).set_default_value(index_value);
|
|
for (const int i : IndexRange(num_border_links)) {
|
|
lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.border_links[i]],
|
|
lf_node.input(body_fn_.indices.inputs.border_links[i]));
|
|
lf_graph.add_link(lf_node.output(body_fn_.indices.outputs.border_link_usages[i]),
|
|
lf_border_link_usage_or_nodes[i]->input(iter_i));
|
|
}
|
|
|
|
/* Handle reference sets. */
|
|
for (const auto &item : body_fn_.indices.inputs.reference_sets.items()) {
|
|
lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.reference_sets.lookup(item.key)],
|
|
lf_node.input(item.value));
|
|
}
|
|
}
|
|
|
|
static bool static_true = true;
|
|
|
|
/* Handle body nodes pair-wise. */
|
|
for (const int iter_i : lf_body_nodes.index_range().drop_back(1)) {
|
|
lf::FunctionNode &lf_node = *lf_body_nodes[iter_i];
|
|
lf::FunctionNode &lf_next_node = *lf_body_nodes[iter_i + 1];
|
|
for (const int i : IndexRange(num_repeat_items)) {
|
|
lf_graph.add_link(
|
|
lf_node.output(body_fn_.indices.outputs.main[i]),
|
|
lf_next_node.input(body_fn_.indices.inputs.main[i + body_inputs_offset]));
|
|
/* TODO: Add back-link after being able to check for cyclic dependencies. */
|
|
// lf_graph.add_link(lf_next_node.output(body_fn_.indices.outputs.input_usages[i]),
|
|
// lf_node.input(body_fn_.indices.inputs.output_usages[i]));
|
|
lf_node.input(body_fn_.indices.inputs.output_usages[i]).set_default_value(&static_true);
|
|
}
|
|
}
|
|
|
|
/* Handle border link usage outputs. */
|
|
for (const int i : IndexRange(num_border_links)) {
|
|
lf_graph.add_link(lf_border_link_usage_or_nodes[i]->output(0),
|
|
*lf_outputs[zone_info_.indices.outputs.border_link_usages[i]]);
|
|
}
|
|
|
|
if (iterations > 0) {
|
|
{
|
|
/* Link first body node to input/output nodes. */
|
|
lf::FunctionNode &lf_first_body_node = *lf_body_nodes[0];
|
|
for (const int i : IndexRange(num_repeat_items)) {
|
|
lf_graph.add_link(
|
|
*lf_inputs[zone_info_.indices.inputs.main[i + main_inputs_offset]],
|
|
lf_first_body_node.input(body_fn_.indices.inputs.main[i + body_inputs_offset]));
|
|
lf_graph.add_link(
|
|
lf_first_body_node.output(
|
|
body_fn_.indices.outputs.input_usages[i + body_inputs_offset]),
|
|
*lf_outputs[zone_info_.indices.outputs.input_usages[i + main_inputs_offset]]);
|
|
}
|
|
}
|
|
{
|
|
/* Link last body node to input/output nodes. */
|
|
lf::FunctionNode &lf_last_body_node = *lf_body_nodes.as_span().last();
|
|
for (const int i : IndexRange(num_repeat_items)) {
|
|
lf_graph.add_link(lf_last_body_node.output(body_fn_.indices.outputs.main[i]),
|
|
*lf_outputs[zone_info_.indices.outputs.main[i]]);
|
|
lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.output_usages[i]],
|
|
lf_last_body_node.input(body_fn_.indices.inputs.output_usages[i]));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* There are no iterations, just link the input directly to the output. */
|
|
for (const int i : IndexRange(num_repeat_items)) {
|
|
lf_graph.add_link(*lf_inputs[zone_info_.indices.inputs.main[i + main_inputs_offset]],
|
|
*lf_outputs[zone_info_.indices.outputs.main[i]]);
|
|
lf_graph.add_link(
|
|
*lf_inputs[zone_info_.indices.inputs.output_usages[i]],
|
|
*lf_outputs[zone_info_.indices.outputs.input_usages[i + main_inputs_offset]]);
|
|
}
|
|
for (const int i : IndexRange(num_border_links)) {
|
|
static bool static_false = false;
|
|
lf_outputs[zone_info_.indices.outputs.border_link_usages[i]]->set_default_value(
|
|
&static_false);
|
|
}
|
|
}
|
|
|
|
lf_outputs[zone_info_.indices.outputs.input_usages[0]]->set_default_value(&static_true);
|
|
|
|
/* The graph is ready, update the node indices which are required by the executor. */
|
|
lf_graph.update_node_indices();
|
|
|
|
// std::cout << "\n\n" << lf_graph.to_dot() << "\n\n";
|
|
|
|
/* Create a mapping from parameter indices inside of this graph to parameters of the repeat
|
|
* zone. The main complexity below stems from the fact that the iterations input is handled
|
|
* outside of this graph. */
|
|
eval_storage.output_index_map.reinitialize(outputs_.size() - 1);
|
|
eval_storage.input_index_map.resize(inputs_.size() - 1);
|
|
array_utils::fill_index_range<int>(eval_storage.input_index_map, 1);
|
|
|
|
Vector<const lf::GraphInputSocket *> lf_graph_inputs = lf_inputs.as_span().drop_front(1);
|
|
|
|
const int iteration_usage_index = zone_info_.indices.outputs.input_usages[0];
|
|
array_utils::fill_index_range<int>(
|
|
eval_storage.output_index_map.as_mutable_span().take_front(iteration_usage_index));
|
|
array_utils::fill_index_range<int>(
|
|
eval_storage.output_index_map.as_mutable_span().drop_front(iteration_usage_index),
|
|
iteration_usage_index + 1);
|
|
|
|
Vector<const lf::GraphOutputSocket *> lf_graph_outputs = lf_outputs.as_span().take_front(
|
|
iteration_usage_index);
|
|
lf_graph_outputs.extend(lf_outputs.as_span().drop_front(iteration_usage_index + 1));
|
|
|
|
eval_storage.body_execute_wrapper.emplace();
|
|
eval_storage.body_execute_wrapper->repeat_output_bnode_ = &repeat_output_bnode_;
|
|
eval_storage.body_execute_wrapper->lf_body_nodes_ = &lf_body_nodes;
|
|
eval_storage.side_effect_provider.emplace();
|
|
eval_storage.side_effect_provider->repeat_output_bnode_ = &repeat_output_bnode_;
|
|
eval_storage.side_effect_provider->lf_body_nodes_ = lf_body_nodes;
|
|
|
|
eval_storage.graph_executor.emplace(lf_graph,
|
|
std::move(lf_graph_inputs),
|
|
std::move(lf_graph_outputs),
|
|
nullptr,
|
|
&*eval_storage.side_effect_provider,
|
|
&*eval_storage.body_execute_wrapper);
|
|
eval_storage.graph_executor_storage = eval_storage.graph_executor->init_storage(
|
|
eval_storage.allocator);
|
|
|
|
/* Log graph for debugging purposes. */
|
|
const bNodeTree &btree_orig = *DEG_get_original(&btree_);
|
|
if (btree_orig.runtime->logged_zone_graphs) {
|
|
std::lock_guard lock{btree_orig.runtime->logged_zone_graphs->mutex};
|
|
btree_orig.runtime->logged_zone_graphs->graph_by_zone_id.lookup_or_add_cb(
|
|
repeat_output_bnode_.identifier, [&]() { return lf_graph.to_dot(); });
|
|
}
|
|
}
|
|
|
|
std::string input_name(const int i) const override
|
|
{
|
|
return zone_wrapper_input_name(zone_info_, zone_, inputs_, i);
|
|
}
|
|
|
|
std::string output_name(const int i) const override
|
|
{
|
|
return zone_wrapper_output_name(zone_info_, zone_, outputs_, i);
|
|
}
|
|
};
|
|
|
|
LazyFunction &build_repeat_zone_lazy_function(ResourceScope &scope,
|
|
const bNodeTree &btree,
|
|
const bke::bNodeTreeZone &zone,
|
|
ZoneBuildInfo &zone_info,
|
|
const ZoneBodyFunction &body_fn)
|
|
{
|
|
return scope.construct<LazyFunctionForRepeatZone>(btree, zone, zone_info, body_fn);
|
|
}
|
|
|
|
} // namespace blender::nodes
|