/* SPDX-License-Identifier: GPL-2.0-or-later */ /** * This file mainly converts a #bNodeTree into a lazy-function graph. This generally works by * creating a lazy-function for every node, which is then put into the lazy-function graph. Then * the nodes in the new graph are linked based on links in the original #bNodeTree. Some additional * nodes are inserted for things like type conversions and multi-input sockets. * * Currently, lazy-functions are even created for nodes that don't strictly require it, like * reroutes or muted nodes. In the future we could avoid that at the cost of additional code * complexity. So far, this does not seem to be a performance issue. */ #include "NOD_geometry_exec.hh" #include "NOD_geometry_nodes_lazy_function.hh" #include "NOD_multi_function.hh" #include "NOD_node_declaration.hh" #include "BLI_cpp_types.hh" #include "BLI_dot_export.hh" #include "BLI_hash.h" #include "BLI_lazy_threading.hh" #include "BLI_map.hh" #include "DNA_ID.h" #include "BKE_compute_contexts.hh" #include "BKE_geometry_set.hh" #include "BKE_type_conversions.hh" #include "FN_field_cpp_type.hh" #include "FN_lazy_function_graph_executor.hh" #include "DEG_depsgraph_query.h" namespace blender::nodes { using fn::ValueOrField; using fn::ValueOrFieldCPPType; static const CPPType *get_socket_cpp_type(const bNodeSocketType &typeinfo) { const CPPType *type = typeinfo.geometry_nodes_cpp_type; if (type == nullptr) { return nullptr; } BLI_assert(type->has_special_member_functions()); return type; } static const CPPType *get_socket_cpp_type(const bNodeSocket &socket) { return get_socket_cpp_type(*socket.typeinfo); } static const CPPType *get_vector_type(const CPPType &type) { const VectorCPPType *vector_type = VectorCPPType::get_from_value(type); if (vector_type == nullptr) { return nullptr; } return &vector_type->self; } /** * Checks which sockets of the node are available and creates corresponding inputs/outputs on the * lazy-function. */ static void lazy_function_interface_from_node(const bNode &node, Vector &r_used_inputs, Vector &r_used_outputs, Vector &r_inputs, Vector &r_outputs) { const bool is_muted = node.is_muted(); const bool supports_laziness = node.typeinfo->geometry_node_execute_supports_laziness || node.is_group(); const lf::ValueUsage input_usage = supports_laziness ? lf::ValueUsage::Maybe : lf::ValueUsage::Used; for (const bNodeSocket *socket : node.input_sockets()) { if (!socket->is_available()) { continue; } const CPPType *type = get_socket_cpp_type(*socket); if (type == nullptr) { continue; } if (socket->is_multi_input() && !is_muted) { type = get_vector_type(*type); } r_inputs.append({socket->identifier, *type, input_usage}); r_used_inputs.append(socket); } for (const bNodeSocket *socket : node.output_sockets()) { if (!socket->is_available()) { continue; } const CPPType *type = get_socket_cpp_type(*socket); if (type == nullptr) { continue; } r_outputs.append({socket->identifier, *type}); r_used_outputs.append(socket); } } /** * Used for most normal geometry nodes like Subdivision Surface and Set Position. */ class LazyFunctionForGeometryNode : public LazyFunction { private: const bNode &node_; public: /** * Index of a boolean input that indicates whether the output socket is used. */ Map lf_input_for_output_bsocket_usage_; /** * Index of an attribute set input that indicates which anonymous attributes should be * propagated to the output. */ Map lf_input_for_attribute_propagation_to_output_; LazyFunctionForGeometryNode(const bNode &node, Vector &r_used_inputs, Vector &r_used_outputs) : node_(node) { BLI_assert(node.typeinfo->geometry_node_execute != nullptr); debug_name_ = node.name; lazy_function_interface_from_node(node, r_used_inputs, r_used_outputs, inputs_, outputs_); const NodeDeclaration &node_decl = *node.declaration(); const aal::RelationsInNode *relations = node_decl.anonymous_attribute_relations(); if (relations == nullptr) { return; } Vector handled_field_outputs; for (const aal::AvailableRelation &relation : relations->available_relations) { const bNodeSocket &output_bsocket = node.output_socket(relation.field_output); if (output_bsocket.is_available() && !handled_field_outputs.contains(&output_bsocket)) { handled_field_outputs.append(&output_bsocket); const int lf_index = inputs_.append_and_get_index_as("Output Used", CPPType::get()); lf_input_for_output_bsocket_usage_.add(output_bsocket.identifier, lf_index); } } Vector handled_geometry_outputs; for (const aal::PropagateRelation &relation : relations->propagate_relations) { const bNodeSocket &output_bsocket = node.output_socket(relation.to_geometry_output); if (output_bsocket.is_available() && !handled_geometry_outputs.contains(&output_bsocket)) { handled_geometry_outputs.append(&output_bsocket); const int lf_index = inputs_.append_and_get_index_as( "Propagate to Output", CPPType::get()); lf_input_for_attribute_propagation_to_output_.add(output_bsocket.identifier, lf_index); } } } void execute_impl(lf::Params ¶ms, const lf::Context &context) const override { GeoNodesLFUserData *user_data = dynamic_cast(context.user_data); BLI_assert(user_data != nullptr); GeoNodeExecParams geo_params{node_, params, context, lf_input_for_output_bsocket_usage_, lf_input_for_attribute_propagation_to_output_}; geo_eval_log::TimePoint start_time = geo_eval_log::Clock::now(); node_.typeinfo->geometry_node_execute(geo_params); geo_eval_log::TimePoint end_time = geo_eval_log::Clock::now(); if (geo_eval_log::GeoModifierLog *modifier_log = user_data->modifier_data->eval_log) { geo_eval_log::GeoTreeLogger &tree_logger = modifier_log->get_local_tree_logger( *user_data->compute_context); tree_logger.node_execution_times.append({node_.identifier, start_time, end_time}); } } std::string input_name(const int index) const override { for (const auto [identifier, lf_index] : lf_input_for_output_bsocket_usage_.items()) { if (index == lf_index) { return "Use Output '" + identifier + "'"; } } for (const auto [identifier, lf_index] : lf_input_for_attribute_propagation_to_output_.items()) { if (index == lf_index) { return "Propagate to '" + identifier + "'"; } } return inputs_[index].debug_name; } std::string output_name(const int index) const override { return outputs_[index].debug_name; } }; /** * Used to gather all inputs of a multi-input socket. A separate node is necessary because * multi-inputs are not supported in lazy-function graphs. */ class LazyFunctionForMultiInput : public LazyFunction { private: const CPPType *base_type_; public: LazyFunctionForMultiInput(const bNodeSocket &socket) { debug_name_ = "Multi Input"; base_type_ = get_socket_cpp_type(socket); BLI_assert(base_type_ != nullptr); BLI_assert(socket.is_multi_input()); const bNodeTree &btree = socket.owner_tree(); for (const bNodeLink *link : socket.directly_linked_links()) { if (link->is_muted() || !link->fromsock->is_available() || nodeIsDanglingReroute(&btree, link->fromnode)) { continue; } inputs_.append({"Input", *base_type_}); } const CPPType *vector_type = get_vector_type(*base_type_); BLI_assert(vector_type != nullptr); outputs_.append({"Output", *vector_type}); } void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override { /* Currently we only have multi-inputs for geometry and string sockets. This could be * generalized in the future. */ base_type_->to_static_type_tag>([&](auto type_tag) { using T = typename decltype(type_tag)::type; if constexpr (std::is_void_v) { /* This type is not supported in this node for now. */ BLI_assert_unreachable(); } else { void *output_ptr = params.get_output_data_ptr(0); Vector &values = *new (output_ptr) Vector(); for (const int i : inputs_.index_range()) { values.append(params.extract_input(i)); } params.output_set(0); } }); } }; /** * Simple lazy-function that just forwards the input. */ class LazyFunctionForRerouteNode : public LazyFunction { public: LazyFunctionForRerouteNode(const CPPType &type) { debug_name_ = "Reroute"; inputs_.append({"Input", type}); outputs_.append({"Output", type}); } void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override { void *input_value = params.try_get_input_data_ptr(0); void *output_value = params.get_output_data_ptr(0); BLI_assert(input_value != nullptr); BLI_assert(output_value != nullptr); const CPPType &type = *inputs_[0].type; type.move_construct(input_value, output_value); params.output_set(0); } }; /** * Lazy functions for nodes whose type cannot be found. An undefined function just outputs default * values. It's useful to have so other parts of the conversion don't have to care about undefined * nodes. */ class LazyFunctionForUndefinedNode : public LazyFunction { public: LazyFunctionForUndefinedNode(const bNode &node, Vector &r_used_outputs) { debug_name_ = "Undefined"; Vector dummy_used_inputs; Vector dummy_inputs; lazy_function_interface_from_node( node, dummy_used_inputs, r_used_outputs, dummy_inputs, outputs_); } void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override { params.set_default_remaining_outputs(); } }; /** * Executes a multi-function. If all inputs are single values, the results will also be single * values. If any input is a field, the outputs will also be fields. */ static void execute_multi_function_on_value_or_field( const MultiFunction &fn, const std::shared_ptr &owned_fn, const Span input_types, const Span output_types, const Span input_values, const Span output_values) { BLI_assert(fn.param_amount() == input_types.size() + output_types.size()); BLI_assert(input_types.size() == input_values.size()); BLI_assert(output_types.size() == output_values.size()); /* Check if any input is a field. */ bool any_input_is_field = false; for (const int i : input_types.index_range()) { const ValueOrFieldCPPType &type = *input_types[i]; const void *value_or_field = input_values[i]; if (type.is_field(value_or_field)) { any_input_is_field = true; break; } } if (any_input_is_field) { /* Convert all inputs into fields, so that they can be used as input in the new field. */ Vector input_fields; for (const int i : input_types.index_range()) { const ValueOrFieldCPPType &type = *input_types[i]; const void *value_or_field = input_values[i]; input_fields.append(type.as_field(value_or_field)); } /* Construct the new field node. */ std::shared_ptr operation; if (owned_fn) { operation = std::make_shared(owned_fn, std::move(input_fields)); } else { operation = std::make_shared(fn, std::move(input_fields)); } /* Store the new fields in the output. */ for (const int i : output_types.index_range()) { const ValueOrFieldCPPType &type = *output_types[i]; void *value_or_field = output_values[i]; type.construct_from_field(value_or_field, GField{operation, i}); } } else { /* In this case, the multi-function is evaluated directly. */ mf::ParamsBuilder params{fn, 1}; mf::ContextBuilder context; for (const int i : input_types.index_range()) { const ValueOrFieldCPPType &type = *input_types[i]; const void *value_or_field = input_values[i]; const void *value = type.get_value_ptr(value_or_field); params.add_readonly_single_input(GPointer{type.value, value}); } for (const int i : output_types.index_range()) { const ValueOrFieldCPPType &type = *output_types[i]; void *value_or_field = output_values[i]; type.self.default_construct(value_or_field); void *value = type.get_value_ptr(value_or_field); type.value.destruct(value); params.add_uninitialized_single_output(GMutableSpan{type.value, value, 1}); } fn.call(IndexRange(1), params, context); } } /** * Behavior of muted nodes: * - Some inputs are forwarded to outputs without changes. * - Some inputs are converted to a different type which becomes the output. * - Some outputs are value initialized because they don't have a corresponding input. */ class LazyFunctionForMutedNode : public LazyFunction { private: Array input_by_output_index_; public: LazyFunctionForMutedNode(const bNode &node, Vector &r_used_inputs, Vector &r_used_outputs) { debug_name_ = "Muted"; lazy_function_interface_from_node(node, r_used_inputs, r_used_outputs, inputs_, outputs_); for (lf::Input &fn_input : inputs_) { fn_input.usage = lf::ValueUsage::Maybe; } for (lf::Input &fn_input : inputs_) { fn_input.usage = lf::ValueUsage::Unused; } input_by_output_index_.reinitialize(outputs_.size()); input_by_output_index_.fill(-1); for (const bNodeLink *internal_link : node.internal_links()) { const int input_i = r_used_inputs.first_index_of_try(internal_link->fromsock); const int output_i = r_used_outputs.first_index_of_try(internal_link->tosock); if (ELEM(-1, input_i, output_i)) { continue; } input_by_output_index_[output_i] = input_i; inputs_[input_i].usage = lf::ValueUsage::Maybe; } } void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override { for (const int output_i : outputs_.index_range()) { if (params.output_was_set(output_i)) { continue; } const CPPType &output_type = *outputs_[output_i].type; void *output_value = params.get_output_data_ptr(output_i); const int input_i = input_by_output_index_[output_i]; if (input_i == -1) { /* The output does not have a corresponding input. */ output_type.value_initialize(output_value); params.output_set(output_i); continue; } const void *input_value = params.try_get_input_data_ptr_or_request(input_i); if (input_value == nullptr) { continue; } const CPPType &input_type = *inputs_[input_i].type; if (input_type == output_type) { /* Forward the value as is. */ input_type.copy_construct(input_value, output_value); params.output_set(output_i); continue; } /* Perform a type conversion and then format the value. */ const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); const auto *from_type = ValueOrFieldCPPType::get_from_self(input_type); const auto *to_type = ValueOrFieldCPPType::get_from_self(output_type); if (from_type != nullptr && to_type != nullptr) { if (conversions.is_convertible(from_type->value, to_type->value)) { const MultiFunction &multi_fn = *conversions.get_conversion_multi_function( mf::DataType::ForSingle(from_type->value), mf::DataType::ForSingle(to_type->value)); execute_multi_function_on_value_or_field( multi_fn, {}, {from_type}, {to_type}, {input_value}, {output_value}); } params.output_set(output_i); continue; } /* Use a value initialization if the conversion does not work. */ output_type.value_initialize(output_value); params.output_set(output_i); } } }; /** * Type conversions are generally implemented as multi-functions. This node checks if the input is * a field or single value and outputs a field or single value respectively. */ class LazyFunctionForMultiFunctionConversion : public LazyFunction { private: const MultiFunction &fn_; const ValueOrFieldCPPType &from_type_; const ValueOrFieldCPPType &to_type_; public: LazyFunctionForMultiFunctionConversion(const MultiFunction &fn, const ValueOrFieldCPPType &from, const ValueOrFieldCPPType &to) : fn_(fn), from_type_(from), to_type_(to) { debug_name_ = "Convert"; inputs_.append({"From", from.self}); outputs_.append({"To", to.self}); } void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override { const void *from_value = params.try_get_input_data_ptr(0); void *to_value = params.get_output_data_ptr(0); BLI_assert(from_value != nullptr); BLI_assert(to_value != nullptr); execute_multi_function_on_value_or_field( fn_, {}, {&from_type_}, {&to_type_}, {from_value}, {to_value}); params.output_set(0); } }; /** * This lazy-function wraps nodes that are implemented as multi-function (mostly math nodes). */ class LazyFunctionForMultiFunctionNode : public LazyFunction { private: const NodeMultiFunctions::Item fn_item_; Vector input_types_; Vector output_types_; public: LazyFunctionForMultiFunctionNode(const bNode &node, NodeMultiFunctions::Item fn_item, Vector &r_used_inputs, Vector &r_used_outputs) : fn_item_(std::move(fn_item)) { BLI_assert(fn_item_.fn != nullptr); debug_name_ = node.name; lazy_function_interface_from_node(node, r_used_inputs, r_used_outputs, inputs_, outputs_); for (const lf::Input &fn_input : inputs_) { input_types_.append(ValueOrFieldCPPType::get_from_self(*fn_input.type)); } for (const lf::Output &fn_output : outputs_) { output_types_.append(ValueOrFieldCPPType::get_from_self(*fn_output.type)); } } void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override { Vector input_values(inputs_.size()); Vector output_values(outputs_.size()); for (const int i : inputs_.index_range()) { input_values[i] = params.try_get_input_data_ptr(i); } for (const int i : outputs_.index_range()) { output_values[i] = params.get_output_data_ptr(i); } execute_multi_function_on_value_or_field( *fn_item_.fn, fn_item_.owned_fn, input_types_, output_types_, input_values, output_values); for (const int i : outputs_.index_range()) { params.output_set(i); } } }; /** * Some sockets have non-trivial implicit inputs (e.g. the Position input of the Set Position * node). Those are implemented as a separate node that outputs the value. */ class LazyFunctionForImplicitInput : public LazyFunction { private: /** * The function that generates the implicit input. The passed in memory is uninitialized. */ std::function init_fn_; public: LazyFunctionForImplicitInput(const CPPType &type, std::function init_fn) : init_fn_(std::move(init_fn)) { debug_name_ = "Input"; outputs_.append({"Output", type}); } void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override { void *value = params.get_output_data_ptr(0); init_fn_(value); params.output_set(0); } }; /** * The viewer node does not have outputs. Instead it is executed because the executor knows that it * has side effects. The side effect is that the inputs to the viewer are logged. */ class LazyFunctionForViewerNode : public LazyFunction { private: const bNode &bnode_; /** The field is only logged when it is linked. */ bool use_field_input_ = true; public: LazyFunctionForViewerNode(const bNode &bnode, Vector &r_used_inputs) : bnode_(bnode) { debug_name_ = "Viewer"; Vector dummy_used_outputs; lazy_function_interface_from_node(bnode, r_used_inputs, dummy_used_outputs, inputs_, outputs_); const Span links = r_used_inputs[1]->directly_linked_links(); if (links.is_empty() || nodeIsDanglingReroute(&bnode.owner_tree(), links.first()->fromnode)) { use_field_input_ = false; r_used_inputs.pop_last(); inputs_.pop_last(); } } void execute_impl(lf::Params ¶ms, const lf::Context &context) const override { GeoNodesLFUserData *user_data = dynamic_cast(context.user_data); BLI_assert(user_data != nullptr); if (user_data->modifier_data == nullptr) { return; } if (user_data->modifier_data->eval_log == nullptr) { return; } GeometrySet geometry = params.extract_input(0); const NodeGeometryViewer *storage = static_cast(bnode_.storage); if (use_field_input_) { const void *value_or_field = params.try_get_input_data_ptr(1); BLI_assert(value_or_field != nullptr); const auto &value_or_field_type = *ValueOrFieldCPPType::get_from_self(*inputs_[1].type); GField field = value_or_field_type.as_field(value_or_field); const eAttrDomain domain = eAttrDomain(storage->domain); const StringRefNull viewer_attribute_name = ".viewer"; if (domain == ATTR_DOMAIN_INSTANCE) { if (geometry.has_instances()) { GeometryComponent &component = geometry.get_component_for_write( GEO_COMPONENT_TYPE_INSTANCES); bke::try_capture_field_on_geometry( component, viewer_attribute_name, ATTR_DOMAIN_INSTANCE, field); } } else { geometry.modify_geometry_sets([&](GeometrySet &geometry) { for (const GeometryComponentType type : {GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}) { if (geometry.has(type)) { GeometryComponent &component = geometry.get_component_for_write(type); eAttrDomain used_domain = domain; if (used_domain == ATTR_DOMAIN_AUTO) { if (const std::optional detected_domain = bke::try_detect_field_domain(component, field)) { used_domain = *detected_domain; } else { used_domain = ATTR_DOMAIN_POINT; } } bke::try_capture_field_on_geometry( component, viewer_attribute_name, used_domain, field); } } }); } } geo_eval_log::GeoTreeLogger &tree_logger = user_data->modifier_data->eval_log->get_local_tree_logger(*user_data->compute_context); tree_logger.log_viewer_node(bnode_, std::move(geometry)); } }; /** * Outputs true when a specific viewer node is used in the current context and false otherwise. */ class LazyFunctionForViewerInputUsage : public LazyFunction { private: const lf::FunctionNode &lf_viewer_node_; public: LazyFunctionForViewerInputUsage(const lf::FunctionNode &lf_viewer_node) : lf_viewer_node_(lf_viewer_node) { debug_name_ = "Viewer Input Usage"; outputs_.append_as("Viewer is Used", CPPType::get()); } void execute_impl(lf::Params ¶ms, const lf::Context &context) const override { GeoNodesLFUserData *user_data = dynamic_cast(context.user_data); BLI_assert(user_data != nullptr); const ComputeContextHash &context_hash = user_data->compute_context->hash(); const GeoNodesModifierData &modifier_data = *user_data->modifier_data; const Span nodes_with_side_effects = modifier_data.side_effect_nodes->lookup(context_hash); const bool viewer_is_used = nodes_with_side_effects.contains(&lf_viewer_node_); params.set_output(0, viewer_is_used); } }; /** * This lazy-function wraps a group node. Internally it just executes the lazy-function graph of * the referenced group. */ class LazyFunctionForGroupNode : public LazyFunction { private: const bNode &group_node_; bool has_many_nodes_ = false; std::optional lf_logger_; std::optional lf_side_effect_provider_; std::optional graph_executor_; struct Storage { void *graph_executor_storage = nullptr; /* To avoid computing the hash more than once. */ std::optional context_hash_cache; }; public: /** * For every input bsocket there is a corresponding boolean output that indicates whether that * input is used. */ Map lf_output_for_input_bsocket_usage_; /** * For every output bsocket there is a corresponding boolean input that indicates whether the * output is used. */ Map lf_input_for_output_bsocket_usage_; /** * For every geometry output that can propagate attributes from an input, there is an attribute * set input. It indicates which attributes should be propagated to the output. */ Map lf_input_for_attribute_propagation_to_output_; LazyFunctionForGroupNode(const bNode &group_node, const GeometryNodesLazyFunctionGraphInfo &lf_graph_info) : group_node_(group_node) { debug_name_ = group_node.name; allow_missing_requested_inputs_ = true; Vector tmp_inputs; Vector tmp_outputs; lazy_function_interface_from_node(group_node, tmp_inputs, tmp_outputs, inputs_, outputs_); has_many_nodes_ = lf_graph_info.num_inline_nodes_approximate > 1000; Vector graph_inputs; /* Add inputs that also exist on the bnode. */ graph_inputs.extend(lf_graph_info.mapping.group_input_sockets); /* Add a boolean input for every output bsocket that indicates whether that socket is used. */ for (const int i : group_node.output_sockets().index_range()) { lf_input_for_output_bsocket_usage_.add_new( i, graph_inputs.append_and_get_index(lf_graph_info.mapping.group_output_used_sockets[i])); inputs_.append_as("Output is Used", CPPType::get(), lf::ValueUsage::Maybe); } graph_inputs.extend(lf_graph_info.mapping.group_output_used_sockets); /* Add an attribute set input for every output geometry socket that can propagate attributes * from inputs. */ for (auto [output_index, lf_socket] : lf_graph_info.mapping.attribute_set_by_geometry_output.items()) { const int lf_index = inputs_.append_and_get_index_as( "Attribute Set", CPPType::get(), lf::ValueUsage::Maybe); graph_inputs.append(lf_socket); lf_input_for_attribute_propagation_to_output_.add(output_index, lf_index); } Vector graph_outputs; /* Add outputs that also exist on the bnode. */ graph_outputs.extend(lf_graph_info.mapping.standard_group_output_sockets); /* Add a boolean output for every input bsocket that indicates whether that socket is used. */ for (const int i : group_node.input_sockets().index_range()) { const InputUsageHint &input_usage_hint = lf_graph_info.mapping.group_input_usage_hints[i]; if (input_usage_hint.type == InputUsageHintType::DynamicSocket) { const lf::InputSocket *lf_socket = lf_graph_info.mapping.group_input_usage_sockets[i]; lf_output_for_input_bsocket_usage_.add_new(i, graph_outputs.append_and_get_index(lf_socket)); outputs_.append_as("Input is Used", CPPType::get()); } } lf_logger_.emplace(lf_graph_info); lf_side_effect_provider_.emplace(); graph_executor_.emplace(lf_graph_info.graph, std::move(graph_inputs), std::move(graph_outputs), &*lf_logger_, &*lf_side_effect_provider_); } void execute_impl(lf::Params ¶ms, const lf::Context &context) const override { GeoNodesLFUserData *user_data = dynamic_cast(context.user_data); BLI_assert(user_data != nullptr); if (has_many_nodes_) { /* If the called node group has many nodes, it's likely that executing it takes a while even * if every individual node is very small. */ lazy_threading::send_hint(); } Storage *storage = static_cast(context.storage); /* The compute context changes when entering a node group. */ bke::NodeGroupComputeContext compute_context{ user_data->compute_context, group_node_.identifier, storage->context_hash_cache}; storage->context_hash_cache = compute_context.hash(); GeoNodesLFUserData group_user_data = *user_data; group_user_data.compute_context = &compute_context; if (user_data->modifier_data->socket_log_contexts) { group_user_data.log_socket_values = user_data->modifier_data->socket_log_contexts->contains( compute_context.hash()); } lf::Context group_context = context; group_context.user_data = &group_user_data; group_context.storage = storage->graph_executor_storage; graph_executor_->execute(params, group_context); } void *init_storage(LinearAllocator<> &allocator) const override { Storage *s = allocator.construct().release(); s->graph_executor_storage = graph_executor_->init_storage(allocator); return s; } void destruct_storage(void *storage) const override { Storage *s = static_cast(storage); graph_executor_->destruct_storage(s->graph_executor_storage); std::destroy_at(s); } std::string name() const override { std::stringstream ss; ss << "Group '" << (group_node_.id->name + 2) << "' (" << group_node_.name << ")"; return ss.str(); } std::string input_name(const int i) const override { if (i < group_node_.input_sockets().size()) { return group_node_.input_socket(i).name; } for (const auto [bsocket_index, lf_socket_index] : lf_input_for_output_bsocket_usage_.items()) { if (i == lf_socket_index) { std::stringstream ss; ss << "'" << group_node_.output_socket(bsocket_index).name << "' output is used"; return ss.str(); } } for (const auto [bsocket_index, lf_index] : lf_input_for_attribute_propagation_to_output_.items()) { if (i == lf_index) { std::stringstream ss; ss << "Propagate to '" << group_node_.output_socket(bsocket_index).name << "'"; return ss.str(); } } return inputs_[i].debug_name; } std::string output_name(const int i) const override { if (i < group_node_.output_sockets().size()) { return group_node_.output_socket(i).name; } for (const auto [bsocket_index, lf_socket_index] : lf_output_for_input_bsocket_usage_.items()) { if (i == lf_socket_index) { std::stringstream ss; ss << "'" << group_node_.input_socket(bsocket_index).name << "' input is used"; return ss.str(); } } return outputs_[i].debug_name; } }; static GMutablePointer get_socket_default_value(LinearAllocator<> &allocator, const bNodeSocket &bsocket) { const bNodeSocketType &typeinfo = *bsocket.typeinfo; const CPPType *type = get_socket_cpp_type(typeinfo); if (type == nullptr) { return {}; } void *buffer = allocator.allocate(type->size(), type->alignment()); typeinfo.get_geometry_nodes_cpp_value(bsocket, buffer); return {type, buffer}; } class GroupInputDebugInfo : public lf::DummyDebugInfo { public: Vector socket_names; std::string node_name() const override { return "Group Input"; } std::string output_name(const int i) const override { return this->socket_names[i]; } }; class GroupOutputDebugInfo : public lf::DummyDebugInfo { public: Vector socket_names; std::string node_name() const override { return "Group Output"; } std::string input_name(const int i) const override { return this->socket_names[i]; } }; /** * Computes the logical or of the inputs and supports short-circuit evaluation (i.e. if the first * input is true already, the other inputs are not checked). */ class LazyFunctionForLogicalOr : public lf::LazyFunction { public: LazyFunctionForLogicalOr(const int inputs_num) { debug_name_ = "Logical Or"; for ([[maybe_unused]] const int i : IndexRange(inputs_num)) { inputs_.append_as("Input", CPPType::get(), lf::ValueUsage::Maybe); } outputs_.append_as("Output", CPPType::get()); } void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override { int first_unavailable_input = -1; for (const int i : inputs_.index_range()) { if (const bool *value = params.try_get_input_data_ptr(i)) { if (*value) { params.set_output(0, true); return; } } else { first_unavailable_input = i; } } if (first_unavailable_input == -1) { params.set_output(0, false); return; } params.try_get_input_data_ptr_or_request(first_unavailable_input); } }; /** * Outputs booleans that indicate which inputs of a switch node are used. Note that it's possible * that both inputs are used when the condition is a field. */ class LazyFunctionForSwitchSocketUsage : public lf::LazyFunction { public: LazyFunctionForSwitchSocketUsage() { debug_name_ = "Switch Socket Usage"; inputs_.append_as("Condition", CPPType::get>()); outputs_.append_as("False", CPPType::get()); outputs_.append_as("True", CPPType::get()); } void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override { const ValueOrField &condition = params.get_input>(0); if (condition.is_field()) { params.set_output(0, true); params.set_output(1, true); } else { const bool value = condition.as_value(); params.set_output(0, !value); params.set_output(1, value); } } }; /** * Takes a field as input and extracts the set of anonymous attributes that it references. */ class LazyFunctionForAnonymousAttributeSetExtract : public lf::LazyFunction { private: const ValueOrFieldCPPType &type_; public: LazyFunctionForAnonymousAttributeSetExtract(const ValueOrFieldCPPType &type) : type_(type) { debug_name_ = "Extract Attribute Set"; inputs_.append_as("Field", type.self); outputs_.append_as("Attributes", CPPType::get()); } void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override { const void *value_or_field = params.try_get_input_data_ptr(0); bke::AnonymousAttributeSet attributes; if (type_.is_field(value_or_field)) { const GField &field = *type_.get_field_ptr(value_or_field); field.node().for_each_field_input_recursive([&](const FieldInput &field_input) { if (const auto *attr_field_input = dynamic_cast( &field_input)) { if (!attributes.names) { attributes.names = std::make_shared>(); } attributes.names->add_as(attr_field_input->anonymous_id()->name()); } }); } params.set_output(0, std::move(attributes)); } }; /** * Conditionally joins multiple attribute sets. Each input attribute set can be disabled with a * corresponding boolean input. */ class LazyFunctionForAnonymousAttributeSetJoin : public lf::LazyFunction { const int amount_; public: LazyFunctionForAnonymousAttributeSetJoin(const int amount) : amount_(amount) { debug_name_ = "Join Attribute Sets"; for ([[maybe_unused]] const int i : IndexRange(amount)) { inputs_.append_as("Use", CPPType::get()); inputs_.append_as( "Attribute Set", CPPType::get(), lf::ValueUsage::Maybe); } outputs_.append_as("Attribute Set", CPPType::get()); } void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override { Vector sets; bool set_is_missing = false; for (const int i : IndexRange(amount_)) { if (params.get_input(this->get_use_input(i))) { if (bke::AnonymousAttributeSet *set = params.try_get_input_data_ptr_or_request( this->get_attribute_set_input(i))) { sets.append(set); } else { set_is_missing = true; } } } if (set_is_missing) { return; } bke::AnonymousAttributeSet joined_set; if (sets.is_empty()) { /* Nothing to do. */ } else if (sets.size() == 1) { joined_set.names = std::move(sets[0]->names); } else { joined_set.names = std::make_shared>(); for (const bke::AnonymousAttributeSet *set : sets) { if (set->names) { for (const std::string &name : *set->names) { joined_set.names->add(name); } } } } params.set_output(0, std::move(joined_set)); } int get_use_input(const int i) const { return 2 * i; } int get_attribute_set_input(const int i) const { return 2 * i + 1; } }; enum class AttributeReferenceKeyType { /** Attribute referenced by a field passed into the group. */ InputField, /** Attributes referenced on the output geometry outside of the current group. */ OutputGeometry, /** Attribute referenced by a field created within the current group. */ Socket, }; /** * Identifier for something that can reference anonymous attributes that should be propagated. */ struct AttributeReferenceKey { AttributeReferenceKeyType type; /* Used when type is InputField or OutputGeometry. */ int index = 0; /* Used when type is Socket. */ const bNodeSocket *bsocket = nullptr; uint64_t hash() const { return get_default_hash_3(this->type, this->bsocket, this->index); } friend bool operator==(const AttributeReferenceKey &a, const AttributeReferenceKey &b) { return a.type == b.type && a.bsocket == b.bsocket && a.index == b.index; } friend std::ostream &operator<<(std::ostream &stream, const AttributeReferenceKey &value) { if (value.type == AttributeReferenceKeyType::InputField) { stream << "Input Field: " << value.index; } else if (value.type == AttributeReferenceKeyType::OutputGeometry) { stream << "Output Geometry: " << value.index; } else { stream << "Socket: " << value.bsocket->owner_node().name << " -> " << value.bsocket->name; } return stream; } }; /** * Additional information that corresponds to an #AttributeReferenceKey. */ struct AttributeReferenceInfo { /** Output socket that contains an attribute set containing the referenced attributes. */ lf::OutputSocket *lf_attribute_set_socket = nullptr; /** Geometry sockets that contain the referenced attributes. */ Vector initial_geometry_sockets; }; /** * Utility class to build a lazy-function graph based on a geometry nodes tree. * This is mainly a separate class because it makes it easier to have variables that can be * accessed by many functions. */ struct GeometryNodesLazyFunctionGraphBuilder { private: const bNodeTree &btree_; GeometryNodesLazyFunctionGraphInfo *lf_graph_info_; lf::Graph *lf_graph_; GeometryNodeLazyFunctionGraphMapping *mapping_; MultiValueMap input_socket_map_; Map output_socket_map_; Map multi_input_socket_nodes_; const bke::DataTypeConversions *conversions_; /** * Maps bsockets to boolean sockets in the graph whereby each boolean socket indicates whether * the bsocket is used. Sockets not contained in this map are not used. * This is indexed by `bNodeSocket::index_in_tree()`. */ Array socket_is_used_map_; /** * Some built-in nodes get additional boolean inputs that indicate whether certain outputs are * used (field output sockets that contain new anonymous attribute references). */ Vector> output_used_sockets_for_builtin_nodes_; /** * Maps from output geometry sockets to corresponding attribute set inputs. */ Map attribute_set_propagation_map_; /** * Boolean inputs that tell a node if some socket (of the same or another node) is used. If this * socket is in a link-cycle, its input can become a constant true. */ Set socket_usage_inputs_; /** * All group input nodes are combined into one dummy node in the lazy-function graph. */ lf::DummyNode *group_input_lf_node_; friend class UsedSocketVisualizeOptions; public: GeometryNodesLazyFunctionGraphBuilder(const bNodeTree &btree, GeometryNodesLazyFunctionGraphInfo &lf_graph_info) : btree_(btree), lf_graph_info_(&lf_graph_info) { } void build() { btree_.ensure_topology_cache(); lf_graph_ = &lf_graph_info_->graph; mapping_ = &lf_graph_info_->mapping; conversions_ = &bke::get_implicit_type_conversions(); socket_is_used_map_.reinitialize(btree_.all_sockets().size()); socket_is_used_map_.fill(nullptr); this->prepare_node_multi_functions(); this->build_group_input_node(); if (btree_.group_output_node() == nullptr) { this->build_fallback_output_node(); } this->handle_nodes(); this->handle_links(); this->add_default_inputs(); this->build_attribute_propagation_input_node(); this->build_output_usage_input_node(); this->build_input_usage_output_node(); this->build_socket_usages(); this->build_attribute_propagation_sets(); this->fix_link_cycles(); // this->print_graph(); lf_graph_->update_node_indices(); lf_graph_info_->num_inline_nodes_approximate += lf_graph_->nodes().size(); } private: void prepare_node_multi_functions() { lf_graph_info_->node_multi_functions = std::make_unique(btree_); } void build_group_input_node() { Vector input_cpp_types; const Span interface_inputs = btree_.interface_inputs(); for (const bNodeSocket *interface_input : interface_inputs) { input_cpp_types.append(interface_input->typeinfo->geometry_nodes_cpp_type); } /* Create a dummy node for the group inputs. */ auto debug_info = std::make_unique(); group_input_lf_node_ = &lf_graph_->add_dummy({}, input_cpp_types, debug_info.get()); for (const int i : interface_inputs.index_range()) { mapping_->group_input_sockets.append(&group_input_lf_node_->output(i)); debug_info->socket_names.append(interface_inputs[i]->name); } lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); } /** * Build an output node that just outputs default values in the case when there is no Group * Output node in the tree. */ void build_fallback_output_node() { Vector output_cpp_types; auto debug_info = std::make_unique(); for (const bNodeSocket *interface_output : btree_.interface_outputs()) { output_cpp_types.append(interface_output->typeinfo->geometry_nodes_cpp_type); debug_info->socket_names.append(interface_output->name); } lf::Node &lf_node = lf_graph_->add_dummy(output_cpp_types, {}, debug_info.get()); for (lf::InputSocket *lf_socket : lf_node.inputs()) { const CPPType &type = lf_socket->type(); lf_socket->set_default_value(type.default_value()); } mapping_->standard_group_output_sockets = lf_node.inputs(); lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); } void handle_nodes() { /* Insert all nodes into the lazy function graph. */ for (const bNode *bnode : btree_.all_nodes()) { const bNodeType *node_type = bnode->typeinfo; if (node_type == nullptr) { continue; } if (bnode->is_muted()) { this->handle_muted_node(*bnode); continue; } switch (node_type->type) { case NODE_FRAME: { /* Ignored. */ break; } case NODE_REROUTE: { this->handle_reroute_node(*bnode); break; } case NODE_GROUP_INPUT: { this->handle_group_input_node(*bnode); break; } case NODE_GROUP_OUTPUT: { this->handle_group_output_node(*bnode); break; } case NODE_CUSTOM_GROUP: case NODE_GROUP: { this->handle_group_node(*bnode); break; } case GEO_NODE_VIEWER: { this->handle_viewer_node(*bnode); break; } default: { if (node_type->geometry_node_execute) { this->handle_geometry_node(*bnode); break; } const NodeMultiFunctions::Item &fn_item = lf_graph_info_->node_multi_functions->try_get( *bnode); if (fn_item.fn != nullptr) { this->handle_multi_function_node(*bnode, fn_item); break; } if (node_type == &NodeTypeUndefined) { this->handle_undefined_node(*bnode); break; } /* Nodes that don't match any of the criteria above are just ignored. */ break; } } } } void handle_muted_node(const bNode &bnode) { Vector used_inputs; Vector used_outputs; auto lazy_function = std::make_unique( bnode, used_inputs, used_outputs); lf::Node &lf_node = lf_graph_->add_function(*lazy_function); lf_graph_info_->functions.append(std::move(lazy_function)); for (const int i : used_inputs.index_range()) { const bNodeSocket &bsocket = *used_inputs[i]; lf::InputSocket &lf_socket = lf_node.input(i); input_socket_map_.add(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } for (const int i : used_outputs.index_range()) { const bNodeSocket &bsocket = *used_outputs[i]; lf::OutputSocket &lf_socket = lf_node.output(i); output_socket_map_.add_new(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } } void handle_reroute_node(const bNode &bnode) { const bNodeSocket &input_bsocket = bnode.input_socket(0); const bNodeSocket &output_bsocket = bnode.output_socket(0); const CPPType *type = get_socket_cpp_type(input_bsocket); if (type == nullptr) { return; } auto lazy_function = std::make_unique(*type); lf::Node &lf_node = lf_graph_->add_function(*lazy_function); lf_graph_info_->functions.append(std::move(lazy_function)); lf::InputSocket &lf_input = lf_node.input(0); lf::OutputSocket &lf_output = lf_node.output(0); input_socket_map_.add(&input_bsocket, &lf_input); output_socket_map_.add_new(&output_bsocket, &lf_output); mapping_->bsockets_by_lf_socket_map.add(&lf_input, &input_bsocket); mapping_->bsockets_by_lf_socket_map.add(&lf_output, &output_bsocket); } void handle_group_input_node(const bNode &bnode) { for (const int i : btree_.interface_inputs().index_range()) { const bNodeSocket &bsocket = bnode.output_socket(i); lf::OutputSocket &lf_socket = group_input_lf_node_->output(i); output_socket_map_.add_new(&bsocket, &lf_socket); mapping_->dummy_socket_map.add_new(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } } void handle_group_output_node(const bNode &bnode) { Vector output_cpp_types; auto debug_info = std::make_unique(); for (const bNodeSocket *interface_input : btree_.interface_outputs()) { output_cpp_types.append(interface_input->typeinfo->geometry_nodes_cpp_type); debug_info->socket_names.append(interface_input->name); } lf::DummyNode &group_output_lf_node = lf_graph_->add_dummy( output_cpp_types, {}, debug_info.get()); for (const int i : group_output_lf_node.inputs().index_range()) { const bNodeSocket &bsocket = bnode.input_socket(i); lf::InputSocket &lf_socket = group_output_lf_node.input(i); input_socket_map_.add(&bsocket, &lf_socket); mapping_->dummy_socket_map.add(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } if (&bnode == btree_.group_output_node()) { mapping_->standard_group_output_sockets = group_output_lf_node.inputs(); } lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); } void handle_group_node(const bNode &bnode) { const bNodeTree *group_btree = reinterpret_cast(bnode.id); if (group_btree == nullptr) { return; } const GeometryNodesLazyFunctionGraphInfo *group_lf_graph_info = ensure_geometry_nodes_lazy_function_graph(*group_btree); if (group_lf_graph_info == nullptr) { return; } auto lazy_function = std::make_unique(bnode, *group_lf_graph_info); lf::FunctionNode &lf_node = lf_graph_->add_function(*lazy_function); for (const int i : bnode.input_sockets().index_range()) { const bNodeSocket &bsocket = bnode.input_socket(i); BLI_assert(!bsocket.is_multi_input()); lf::InputSocket &lf_socket = lf_node.input(i); input_socket_map_.add(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } for (const int i : bnode.output_sockets().index_range()) { const bNodeSocket &bsocket = bnode.output_socket(i); lf::OutputSocket &lf_socket = lf_node.output(i); output_socket_map_.add_new(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } mapping_->group_node_map.add(&bnode, &lf_node); lf_graph_info_->num_inline_nodes_approximate += group_lf_graph_info->num_inline_nodes_approximate; static const bool static_false = false; for (const int i : lazy_function->lf_input_for_output_bsocket_usage_.values()) { lf_node.input(i).set_default_value(&static_false); socket_usage_inputs_.add(&lf_node.input(i)); } /* Keep track of attribute set inputs that need to be populated later. */ for (const auto [output_index, lf_input_index] : lazy_function->lf_input_for_attribute_propagation_to_output_.items()) { attribute_set_propagation_map_.add(&bnode.output_socket(output_index), &lf_node.input(lf_input_index)); } lf_graph_info_->functions.append(std::move(lazy_function)); } void handle_geometry_node(const bNode &bnode) { Vector used_inputs; Vector used_outputs; auto lazy_function = std::make_unique( bnode, used_inputs, used_outputs); lf::Node &lf_node = lf_graph_->add_function(*lazy_function); for (const int i : used_inputs.index_range()) { const bNodeSocket &bsocket = *used_inputs[i]; lf::InputSocket &lf_socket = lf_node.input(i); if (bsocket.is_multi_input()) { auto multi_input_lazy_function = std::make_unique(bsocket); lf::Node &lf_multi_input_node = lf_graph_->add_function(*multi_input_lazy_function); lf_graph_info_->functions.append(std::move(multi_input_lazy_function)); lf_graph_->add_link(lf_multi_input_node.output(0), lf_socket); multi_input_socket_nodes_.add_new(&bsocket, &lf_multi_input_node); for (lf::InputSocket *lf_multi_input_socket : lf_multi_input_node.inputs()) { mapping_->bsockets_by_lf_socket_map.add(lf_multi_input_socket, &bsocket); } } else { input_socket_map_.add(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } } for (const int i : used_outputs.index_range()) { const bNodeSocket &bsocket = *used_outputs[i]; lf::OutputSocket &lf_socket = lf_node.output(i); output_socket_map_.add_new(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } for (const auto [identifier, lf_input_index] : lazy_function->lf_input_for_output_bsocket_usage_.items()) { output_used_sockets_for_builtin_nodes_.append_as(&bnode.output_by_identifier(identifier), &lf_node.input(lf_input_index)); socket_usage_inputs_.add_new(&lf_node.input(lf_input_index)); } /* Keep track of attribute set inputs that need to be populated later. */ for (const auto [identifier, lf_input_index] : lazy_function->lf_input_for_attribute_propagation_to_output_.items()) { attribute_set_propagation_map_.add(&bnode.output_by_identifier(identifier), &lf_node.input(lf_input_index)); } lf_graph_info_->functions.append(std::move(lazy_function)); } void handle_multi_function_node(const bNode &bnode, const NodeMultiFunctions::Item &fn_item) { Vector used_inputs; Vector used_outputs; auto lazy_function = std::make_unique( bnode, fn_item, used_inputs, used_outputs); lf::Node &lf_node = lf_graph_->add_function(*lazy_function); lf_graph_info_->functions.append(std::move(lazy_function)); for (const int i : used_inputs.index_range()) { const bNodeSocket &bsocket = *used_inputs[i]; BLI_assert(!bsocket.is_multi_input()); lf::InputSocket &lf_socket = lf_node.input(i); input_socket_map_.add(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } for (const int i : used_outputs.index_range()) { const bNodeSocket &bsocket = *used_outputs[i]; lf::OutputSocket &lf_socket = lf_node.output(i); output_socket_map_.add(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } } void handle_viewer_node(const bNode &bnode) { Vector used_inputs; auto lazy_function = std::make_unique(bnode, used_inputs); lf::FunctionNode &lf_node = lf_graph_->add_function(*lazy_function); lf_graph_info_->functions.append(std::move(lazy_function)); for (const int i : used_inputs.index_range()) { const bNodeSocket &bsocket = *used_inputs[i]; lf::InputSocket &lf_socket = lf_node.input(i); input_socket_map_.add(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } mapping_->viewer_node_map.add(&bnode, &lf_node); } void handle_undefined_node(const bNode &bnode) { Vector used_outputs; auto lazy_function = std::make_unique(bnode, used_outputs); lf::FunctionNode &lf_node = lf_graph_->add_function(*lazy_function); lf_graph_info_->functions.append(std::move(lazy_function)); for (const int i : used_outputs.index_range()) { const bNodeSocket &bsocket = *used_outputs[i]; lf::OutputSocket &lf_socket = lf_node.output(i); output_socket_map_.add(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } } void handle_links() { for (const auto item : output_socket_map_.items()) { this->insert_links_from_socket(*item.key, *item.value); } } void insert_links_from_socket(const bNodeSocket &from_bsocket, lf::OutputSocket &from_lf_socket) { if (nodeIsDanglingReroute(&btree_, &from_bsocket.owner_node())) { return; } const Span links_from_bsocket = from_bsocket.directly_linked_links(); struct TypeWithLinks { const CPPType *type; Vector links; }; /* Group available target sockets by type so that they can be handled together. */ Vector types_with_links; for (const bNodeLink *link : links_from_bsocket) { if (link->is_muted()) { continue; } if (!link->is_available()) { continue; } const bNodeSocket &to_bsocket = *link->tosock; const CPPType *to_type = get_socket_cpp_type(to_bsocket); if (to_type == nullptr) { continue; } bool inserted = false; for (TypeWithLinks &types_with_links : types_with_links) { if (types_with_links.type == to_type) { types_with_links.links.append(link); inserted = true; break; } } if (inserted) { continue; } types_with_links.append({to_type, {link}}); } for (const TypeWithLinks &type_with_links : types_with_links) { const CPPType &to_type = *type_with_links.type; const Span links = type_with_links.links; lf::OutputSocket *converted_from_lf_socket = this->insert_type_conversion_if_necessary( from_lf_socket, to_type); auto make_input_link_or_set_default = [&](lf::InputSocket &to_lf_socket) { if (converted_from_lf_socket == nullptr) { const void *default_value = to_type.default_value(); to_lf_socket.set_default_value(default_value); } else { lf_graph_->add_link(*converted_from_lf_socket, to_lf_socket); } }; for (const bNodeLink *link : links) { const bNodeSocket &to_bsocket = *link->tosock; if (to_bsocket.is_multi_input()) { /* TODO: Cache this index on the link. */ int link_index = 0; for (const bNodeLink *multi_input_link : to_bsocket.directly_linked_links()) { if (multi_input_link == link) { break; } if (multi_input_link->is_muted() || !multi_input_link->fromsock->is_available() || nodeIsDanglingReroute(&btree_, multi_input_link->fromnode)) { continue; } link_index++; } if (to_bsocket.owner_node().is_muted()) { if (link_index == 0) { for (lf::InputSocket *to_lf_socket : input_socket_map_.lookup(&to_bsocket)) { make_input_link_or_set_default(*to_lf_socket); } } } else { lf::Node *multi_input_lf_node = multi_input_socket_nodes_.lookup_default(&to_bsocket, nullptr); if (multi_input_lf_node == nullptr) { continue; } make_input_link_or_set_default(multi_input_lf_node->input(link_index)); } } else { for (lf::InputSocket *to_lf_socket : input_socket_map_.lookup(&to_bsocket)) { make_input_link_or_set_default(*to_lf_socket); } } } } } lf::OutputSocket *insert_type_conversion_if_necessary(lf::OutputSocket &from_socket, const CPPType &to_type) { const CPPType &from_type = from_socket.type(); if (from_type == to_type) { return &from_socket; } const auto *from_field_type = ValueOrFieldCPPType::get_from_self(from_type); const auto *to_field_type = ValueOrFieldCPPType::get_from_self(to_type); if (from_field_type != nullptr && to_field_type != nullptr) { if (conversions_->is_convertible(from_field_type->value, to_field_type->value)) { const MultiFunction &multi_fn = *conversions_->get_conversion_multi_function( mf::DataType::ForSingle(from_field_type->value), mf::DataType::ForSingle(to_field_type->value)); auto fn = std::make_unique( multi_fn, *from_field_type, *to_field_type); lf::Node &conversion_node = lf_graph_->add_function(*fn); lf_graph_info_->functions.append(std::move(fn)); lf_graph_->add_link(from_socket, conversion_node.input(0)); return &conversion_node.output(0); } } return nullptr; } void add_default_inputs() { for (auto item : input_socket_map_.items()) { const bNodeSocket &bsocket = *item.key; const Span lf_sockets = item.value; for (lf::InputSocket *lf_socket : lf_sockets) { if (lf_socket->origin() != nullptr) { /* Is linked already. */ continue; } this->add_default_input(bsocket, *lf_socket); } } } void add_default_input(const bNodeSocket &input_bsocket, lf::InputSocket &input_lf_socket) { if (this->try_add_implicit_input(input_bsocket, input_lf_socket)) { return; } GMutablePointer value = get_socket_default_value(lf_graph_info_->allocator, input_bsocket); if (value.get() == nullptr) { /* Not possible to add a default value. */ return; } input_lf_socket.set_default_value(value.get()); if (!value.type()->is_trivially_destructible()) { lf_graph_info_->values_to_destruct.append(value); } } bool try_add_implicit_input(const bNodeSocket &input_bsocket, lf::InputSocket &input_lf_socket) { const bNode &bnode = input_bsocket.owner_node(); const SocketDeclaration *socket_decl = input_bsocket.runtime->declaration; if (socket_decl == nullptr) { return false; } if (socket_decl->input_field_type != InputSocketFieldType::Implicit) { return false; } const ImplicitInputValueFn *implicit_input_fn = socket_decl->implicit_input_fn(); if (implicit_input_fn == nullptr) { return false; } std::function init_fn = [&bnode, implicit_input_fn](void *r_value) { (*implicit_input_fn)(bnode, r_value); }; const CPPType &type = input_lf_socket.type(); auto lazy_function = std::make_unique(type, std::move(init_fn)); lf::Node &lf_node = lf_graph_->add_function(*lazy_function); lf_graph_info_->functions.append(std::move(lazy_function)); lf_graph_->add_link(lf_node.output(0), input_lf_socket); return true; } /** * Every output geometry socket that may propagate attributes has to know which attributes should * be propagated. Therefore, every one of these outputs gets a corresponding attribute set input. */ void build_attribute_propagation_input_node() { const aal::RelationsInNode &tree_relations = *btree_.runtime->anonymous_attribute_relations; Vector output_indices; for (const aal::PropagateRelation &relation : tree_relations.propagate_relations) { output_indices.append_non_duplicates(relation.to_geometry_output); } Vector cpp_types; auto debug_info = std::make_unique(); debug_info->name = "Attributes to Propagate to Output"; cpp_types.append_n_times(&CPPType::get(), output_indices.size()); lf::Node &lf_node = lf_graph_->add_dummy({}, cpp_types, debug_info.get()); for (const int i : output_indices.index_range()) { const int output_index = output_indices[i]; mapping_->attribute_set_by_geometry_output.add(output_index, &lf_node.output(i)); debug_info->output_names.append(btree_.interface_outputs()[output_index]->name); } lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); } /** * Build new boolean group inputs that indicate which group outputs are used. */ void build_output_usage_input_node() { const Span interface_outputs = btree_.interface_outputs(); Vector cpp_types; cpp_types.append_n_times(&CPPType::get(), interface_outputs.size()); auto debug_info = std::make_unique(); debug_info->name = "Output Socket Usage"; lf::Node &lf_node = lf_graph_->add_dummy({}, cpp_types, debug_info.get()); for (const int i : interface_outputs.index_range()) { mapping_->group_output_used_sockets.append(&lf_node.output(i)); debug_info->output_names.append(interface_outputs[i]->name); } lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); } /** * Build new boolean group outputs that indicate which group inputs are used depending on other * group inputs. */ void build_input_usage_output_node() { const Span interface_inputs = btree_.interface_inputs(); Vector cpp_types; cpp_types.append_n_times(&CPPType::get(), interface_inputs.size()); auto debug_info = std::make_unique(); debug_info->name = "Input Socket Usage"; lf::Node &lf_node = lf_graph_->add_dummy(cpp_types, {}, debug_info.get()); for (const int i : interface_inputs.index_range()) { mapping_->group_input_usage_sockets.append(&lf_node.input(i)); debug_info->input_names.append(interface_inputs[i]->name); } lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); } /** * For every socket we want to determine if it will be used depending on the inputs of the node * group (just static analysis is not enough when there are e.g. Switch nodes). This function * populates #socket_is_used_map_ with that information. */ void build_socket_usages() { OrSocketUsagesCache or_socket_usages_cache; if (const bNode *group_output_bnode = btree_.group_output_node()) { /* Whether a group output is used is determined by a group input that has been created * exactly for this purpose. */ for (const bNodeSocket *bsocket : group_output_bnode->input_sockets().drop_back(1)) { const int index = bsocket->index(); socket_is_used_map_[bsocket->index_in_tree()] = const_cast( mapping_->group_output_used_sockets[index]); } } /* Iterate over all nodes from right to left to determine when which sockets are used. */ for (const bNode *bnode : btree_.toposort_right_to_left()) { const bNodeType *node_type = bnode->typeinfo; if (node_type == nullptr) { /* Ignore. */ continue; } this->build_output_socket_usages(*bnode, or_socket_usages_cache); if (bnode->is_muted()) { this->build_muted_node_usages(*bnode, or_socket_usages_cache); continue; } switch (node_type->type) { case NODE_GROUP_OUTPUT: { /* Handled before this loop already. */ break; } case NODE_GROUP_INPUT: { /* Handled after this loop. */ break; } case NODE_FRAME: { /* Ignored. */ break; } case NODE_REROUTE: { /* The input is used exactly when the output is used. */ socket_is_used_map_[bnode->input_socket(0).index_in_tree()] = socket_is_used_map_[bnode->output_socket(0).index_in_tree()]; break; } case GEO_NODE_SWITCH: { this->build_switch_node_socket_usage(*bnode); break; } case GEO_NODE_VIEWER: { this->build_viewer_node_socket_usage(*bnode); break; } case NODE_GROUP: case NODE_CUSTOM_GROUP: { this->build_group_node_socket_usage(*bnode, or_socket_usages_cache); break; } default: { this->build_standard_node_input_socket_usage(*bnode, or_socket_usages_cache); break; } } } this->build_group_input_usages(or_socket_usages_cache); this->link_output_used_sockets_for_builtin_nodes(); } using OrSocketUsagesCache = Map, lf::OutputSocket *>; /** * Combine multiple socket usages with a logical or. Inserts a new node for that purpose if * necessary. */ lf::OutputSocket *or_socket_usages(MutableSpan usages, OrSocketUsagesCache &cache) { if (usages.is_empty()) { return nullptr; } if (usages.size() == 1) { return usages[0]; } std::sort(usages.begin(), usages.end()); return cache.lookup_or_add_cb_as(usages, [&]() { auto logical_or_fn = std::make_unique(usages.size()); lf::Node &logical_or_node = lf_graph_->add_function(*logical_or_fn); lf_graph_info_->functions.append(std::move(logical_or_fn)); for (const int i : usages.index_range()) { lf_graph_->add_link(*usages[i], logical_or_node.input(i)); } return &logical_or_node.output(0); }); } void build_output_socket_usages(const bNode &bnode, OrSocketUsagesCache &or_socket_usages_cache) { /* Output sockets are used when any of their linked inputs are used. */ for (const bNodeSocket *socket : bnode.output_sockets()) { if (!socket->is_available()) { continue; } /* Determine when linked target sockets are used. */ Vector target_usages; for (const bNodeLink *link : socket->directly_linked_links()) { if (!link->is_used()) { continue; } const bNodeSocket &target_socket = *link->tosock; if (lf::OutputSocket *is_used_socket = socket_is_used_map_[target_socket.index_in_tree()]) { target_usages.append_non_duplicates(is_used_socket); } } /* Combine target socket usages into the usage of the current socket. */ socket_is_used_map_[socket->index_in_tree()] = this->or_socket_usages( target_usages, or_socket_usages_cache); } } /** * An input of a muted node is used when any of its internally linked outputs is used. */ void build_muted_node_usages(const bNode &bnode, OrSocketUsagesCache &or_socket_usages_cache) { /* Find all outputs that use a specific input. */ MultiValueMap outputs_by_input; for (const bNodeLink *blink : bnode.internal_links()) { outputs_by_input.add(blink->fromsock, blink->tosock); } for (const auto item : outputs_by_input.items()) { const bNodeSocket &input_bsocket = *item.key; const Span output_bsockets = item.value; /* The input is used if any of the internally linked outputs is used. */ Vector lf_socket_usages; for (const bNodeSocket *output_bsocket : output_bsockets) { if (lf::OutputSocket *lf_socket = socket_is_used_map_[output_bsocket->index_in_tree()]) { lf_socket_usages.append(lf_socket); } } socket_is_used_map_[input_bsocket.index_in_tree()] = this->or_socket_usages( lf_socket_usages, or_socket_usages_cache); } } void build_switch_node_socket_usage(const bNode &bnode) { const bNodeSocket *switch_input_bsocket = nullptr; const bNodeSocket *false_input_bsocket = nullptr; const bNodeSocket *true_input_bsocket = nullptr; const bNodeSocket *output_bsocket = nullptr; for (const bNodeSocket *socket : bnode.input_sockets()) { if (!socket->is_available()) { continue; } if (socket->name == StringRef("Switch")) { switch_input_bsocket = socket; } else if (socket->name == StringRef("False")) { false_input_bsocket = socket; } else if (socket->name == StringRef("True")) { true_input_bsocket = socket; } } for (const bNodeSocket *socket : bnode.output_sockets()) { if (socket->is_available()) { output_bsocket = socket; break; } } lf::OutputSocket *output_is_used_socket = socket_is_used_map_[output_bsocket->index_in_tree()]; if (output_is_used_socket == nullptr) { return; } socket_is_used_map_[switch_input_bsocket->index_in_tree()] = output_is_used_socket; lf::InputSocket *lf_switch_input = input_socket_map_.lookup(switch_input_bsocket)[0]; if (lf::OutputSocket *lf_switch_origin = lf_switch_input->origin()) { /* The condition input is dynamic, so the usage of the other inputs is as well. */ static const LazyFunctionForSwitchSocketUsage switch_socket_usage_fn; lf::Node &lf_node = lf_graph_->add_function(switch_socket_usage_fn); lf_graph_->add_link(*lf_switch_origin, lf_node.input(0)); socket_is_used_map_[false_input_bsocket->index_in_tree()] = &lf_node.output(0); socket_is_used_map_[true_input_bsocket->index_in_tree()] = &lf_node.output(1); } else { if (switch_input_bsocket->default_value_typed()->value) { socket_is_used_map_[true_input_bsocket->index_in_tree()] = output_is_used_socket; } else { socket_is_used_map_[false_input_bsocket->index_in_tree()] = output_is_used_socket; } } } void build_viewer_node_socket_usage(const bNode &bnode) { const lf::FunctionNode &lf_viewer_node = *mapping_->viewer_node_map.lookup(&bnode); auto lazy_function = std::make_unique(lf_viewer_node); lf::Node &lf_node = lf_graph_->add_function(*lazy_function); lf_graph_info_->functions.append(std::move(lazy_function)); for (const bNodeSocket *bsocket : bnode.input_sockets()) { if (bsocket->is_available()) { socket_is_used_map_[bsocket->index_in_tree()] = &lf_node.output(0); } } } void build_group_node_socket_usage(const bNode &bnode, OrSocketUsagesCache &or_socket_usages_cache) { const bNodeTree *bgroup = reinterpret_cast(bnode.id); if (bgroup == nullptr) { return; } const GeometryNodesLazyFunctionGraphInfo *group_lf_graph_info = ensure_geometry_nodes_lazy_function_graph(*bgroup); if (group_lf_graph_info == nullptr) { return; } lf::FunctionNode &lf_group_node = const_cast( *mapping_->group_node_map.lookup(&bnode)); const auto &fn = static_cast(lf_group_node.function()); for (const bNodeSocket *input_bsocket : bnode.input_sockets()) { const int input_index = input_bsocket->index(); const InputUsageHint &input_usage_hint = group_lf_graph_info->mapping.group_input_usage_hints[input_index]; switch (input_usage_hint.type) { case InputUsageHintType::Never: { /* Nothing to do. */ break; } case InputUsageHintType::DependsOnOutput: { Vector output_usages; for (const int i : input_usage_hint.output_dependencies) { if (lf::OutputSocket *lf_socket = socket_is_used_map_[bnode.output_socket(i).index_in_tree()]) { output_usages.append(lf_socket); } } socket_is_used_map_[input_bsocket->index_in_tree()] = this->or_socket_usages( output_usages, or_socket_usages_cache); break; } case InputUsageHintType::DynamicSocket: { socket_is_used_map_[input_bsocket->index_in_tree()] = &const_cast( lf_group_node.output(fn.lf_output_for_input_bsocket_usage_.lookup(input_index))); break; } } } for (const bNodeSocket *output_bsocket : bnode.output_sockets()) { const int output_index = output_bsocket->index(); const int lf_input_index = fn.lf_input_for_output_bsocket_usage_.lookup(output_index); lf::InputSocket &lf_socket = lf_group_node.input(lf_input_index); if (lf::OutputSocket *lf_output_is_used = socket_is_used_map_[output_bsocket->index_in_tree()]) { lf_graph_->add_link(*lf_output_is_used, lf_socket); } else { static const bool static_false = false; lf_socket.set_default_value(&static_false); } } } void build_standard_node_input_socket_usage(const bNode &bnode, OrSocketUsagesCache &or_socket_usages_cache) { if (bnode.input_sockets().is_empty()) { return; } Vector output_usages; for (const bNodeSocket *output_socket : bnode.output_sockets()) { if (!output_socket->is_available()) { continue; } if (lf::OutputSocket *is_used_socket = socket_is_used_map_[output_socket->index_in_tree()]) { output_usages.append_non_duplicates(is_used_socket); } } /* Assume every input is used when any output is used. */ lf::OutputSocket *lf_usage = this->or_socket_usages(output_usages, or_socket_usages_cache); if (lf_usage == nullptr) { return; } for (const bNodeSocket *input_socket : bnode.input_sockets()) { if (input_socket->is_available()) { socket_is_used_map_[input_socket->index_in_tree()] = lf_usage; } } } void build_group_input_usages(OrSocketUsagesCache &or_socket_usages_cache) { const Span group_input_nodes = btree_.group_input_nodes(); for (const int i : btree_.interface_inputs().index_range()) { Vector target_usages; for (const bNode *group_input_node : group_input_nodes) { if (lf::OutputSocket *lf_socket = socket_is_used_map_[group_input_node->output_socket(i).index_in_tree()]) { target_usages.append_non_duplicates(lf_socket); } } lf::OutputSocket *lf_socket = this->or_socket_usages(target_usages, or_socket_usages_cache); lf::InputSocket *lf_group_output = const_cast( mapping_->group_input_usage_sockets[i]); InputUsageHint input_usage_hint; if (lf_socket == nullptr) { static const bool static_false = false; lf_group_output->set_default_value(&static_false); input_usage_hint.type = InputUsageHintType::Never; } else { lf_graph_->add_link(*lf_socket, *lf_group_output); if (lf_socket->node().is_dummy()) { /* Can support slightly more complex cases where it depends on more than one output in * the future. */ input_usage_hint.type = InputUsageHintType::DependsOnOutput; input_usage_hint.output_dependencies = { mapping_->group_output_used_sockets.first_index_of(lf_socket)}; } else { input_usage_hint.type = InputUsageHintType::DynamicSocket; } } lf_graph_info_->mapping.group_input_usage_hints.append(std::move(input_usage_hint)); } } void link_output_used_sockets_for_builtin_nodes() { for (const auto &[output_bsocket, lf_input] : output_used_sockets_for_builtin_nodes_) { if (lf::OutputSocket *lf_is_used = socket_is_used_map_[output_bsocket->index_in_tree()]) { lf_graph_->add_link(*lf_is_used, *lf_input); } else { static const bool static_false = false; lf_input->set_default_value(&static_false); } } } void build_attribute_propagation_sets() { ResourceScope scope; const Array relations_by_node = bke::anonymous_attribute_inferencing::get_relations_by_node(btree_, scope); VectorSet attribute_reference_keys; /* Indexed by reference key index. */ Vector attribute_reference_infos; this->build_attribute_references( relations_by_node, attribute_reference_keys, attribute_reference_infos); MultiValueMap referenced_by_field_socket; MultiValueMap propagated_to_geometry_socket; this->gather_referenced_and_potentially_propagated_data(relations_by_node, attribute_reference_keys, attribute_reference_infos, referenced_by_field_socket, propagated_to_geometry_socket); MultiValueMap required_propagated_to_geometry_socket; this->gather_required_propagated_data(relations_by_node, attribute_reference_keys, referenced_by_field_socket, propagated_to_geometry_socket, required_propagated_to_geometry_socket); this->build_attribute_sets_to_propagate(attribute_reference_keys, attribute_reference_infos, required_propagated_to_geometry_socket); } void build_attribute_references(const Span relations_by_node, VectorSet &r_attribute_reference_keys, Vector &r_attribute_reference_infos) { auto add_get_attributes_node = [&](lf::OutputSocket &lf_field_socket) -> lf::OutputSocket & { const ValueOrFieldCPPType &type = *ValueOrFieldCPPType::get_from_self( lf_field_socket.type()); auto lazy_function = std::make_unique(type); lf::Node &lf_node = lf_graph_->add_function(*lazy_function); lf_graph_->add_link(lf_field_socket, lf_node.input(0)); lf_graph_info_->functions.append(std::move(lazy_function)); return lf_node.output(0); }; /* Find nodes that create new anonymous attributes. */ for (const bNode *node : btree_.all_nodes()) { const aal::RelationsInNode &relations = *relations_by_node[node->index()]; for (const aal::AvailableRelation &relation : relations.available_relations) { const bNodeSocket &geometry_bsocket = node->output_socket(relation.geometry_output); const bNodeSocket &field_bsocket = node->output_socket(relation.field_output); if (!field_bsocket.is_available()) { continue; } if (!field_bsocket.is_directly_linked()) { continue; } AttributeReferenceKey key; key.type = AttributeReferenceKeyType::Socket; key.bsocket = &field_bsocket; const int key_index = r_attribute_reference_keys.index_of_or_add(key); if (key_index >= r_attribute_reference_infos.size()) { AttributeReferenceInfo info; lf::OutputSocket &lf_field_socket = *output_socket_map_.lookup(&field_bsocket); info.lf_attribute_set_socket = &add_get_attributes_node(lf_field_socket); r_attribute_reference_infos.append(info); } AttributeReferenceInfo &info = r_attribute_reference_infos[key_index]; if (geometry_bsocket.is_available()) { info.initial_geometry_sockets.append(&geometry_bsocket); } } } /* Find field group inputs that are evaluated within this node tree. */ const aal::RelationsInNode &tree_relations = *btree_.runtime->anonymous_attribute_relations; for (const aal::EvalRelation &relation : tree_relations.eval_relations) { AttributeReferenceKey key; key.type = AttributeReferenceKeyType::InputField; key.index = relation.field_input; r_attribute_reference_keys.add_new(key); AttributeReferenceInfo info; lf::OutputSocket &lf_field_socket = *const_cast( mapping_->group_input_sockets[relation.field_input]); info.lf_attribute_set_socket = &add_get_attributes_node(lf_field_socket); for (const bNode *bnode : btree_.group_input_nodes()) { info.initial_geometry_sockets.append(&bnode->output_socket(relation.geometry_input)); } r_attribute_reference_infos.append(std::move(info)); } /* Find group outputs that attributes need to be propagated to. */ for (const aal::PropagateRelation &relation : tree_relations.propagate_relations) { AttributeReferenceKey key; key.type = AttributeReferenceKeyType::OutputGeometry; key.index = relation.to_geometry_output; const int key_index = r_attribute_reference_keys.index_of_or_add(key); if (key_index >= r_attribute_reference_infos.size()) { AttributeReferenceInfo info; info.lf_attribute_set_socket = const_cast( mapping_->attribute_set_by_geometry_output.lookup(relation.to_geometry_output)); r_attribute_reference_infos.append(info); } AttributeReferenceInfo &info = r_attribute_reference_infos[key_index]; for (const bNode *bnode : btree_.group_input_nodes()) { info.initial_geometry_sockets.append(&bnode->output_socket(relation.from_geometry_input)); } } } /** * For every field socket, figure out which anonymous attributes it may reference. * For every geometry socket, figure out which anonymous attributes may be propagated to it. */ void gather_referenced_and_potentially_propagated_data( const Span relations_by_node, const Span attribute_reference_keys, const Span attribute_reference_infos, MultiValueMap &r_referenced_by_field_socket, MultiValueMap &r_propagated_to_geometry_socket) { /* Initialize maps. */ for (const int key_index : attribute_reference_keys.index_range()) { const AttributeReferenceKey &key = attribute_reference_keys[key_index]; const AttributeReferenceInfo &info = attribute_reference_infos[key_index]; switch (key.type) { case AttributeReferenceKeyType::InputField: { for (const bNode *bnode : btree_.group_input_nodes()) { const bNodeSocket &bsocket = bnode->output_socket(key.index); r_referenced_by_field_socket.add(&bsocket, key_index); } break; } case AttributeReferenceKeyType::OutputGeometry: { break; } case AttributeReferenceKeyType::Socket: { r_referenced_by_field_socket.add(key.bsocket, key_index); break; } } for (const bNodeSocket *geometry_bsocket : info.initial_geometry_sockets) { r_propagated_to_geometry_socket.add(geometry_bsocket, key_index); } } /* Propagate attribute usages from left to right. */ for (const bNode *bnode : btree_.toposort_left_to_right()) { for (const bNodeSocket *bsocket : bnode->input_sockets()) { if (bsocket->is_available()) { Vector referenced_keys; Vector propagated_keys; for (const bNodeLink *blink : bsocket->directly_linked_links()) { if (blink->is_used()) { referenced_keys.extend_non_duplicates( r_referenced_by_field_socket.lookup(blink->fromsock)); propagated_keys.extend_non_duplicates( r_propagated_to_geometry_socket.lookup(blink->fromsock)); } } if (!referenced_keys.is_empty()) { r_referenced_by_field_socket.add_multiple(bsocket, referenced_keys); } if (!propagated_keys.is_empty()) { r_propagated_to_geometry_socket.add_multiple(bsocket, propagated_keys); } } } const aal::RelationsInNode &relations = *relations_by_node[bnode->index()]; for (const aal::ReferenceRelation &relation : relations.reference_relations) { const bNodeSocket &input_bsocket = bnode->input_socket(relation.from_field_input); const bNodeSocket &output_bsocket = bnode->output_socket(relation.to_field_output); if (!input_bsocket.is_available() || !output_bsocket.is_available()) { continue; } r_referenced_by_field_socket.add_multiple( &output_bsocket, Vector(r_referenced_by_field_socket.lookup(&input_bsocket))); } for (const aal::PropagateRelation &relation : relations.propagate_relations) { const bNodeSocket &input_bsocket = bnode->input_socket(relation.from_geometry_input); const bNodeSocket &output_bsocket = bnode->output_socket(relation.to_geometry_output); if (!input_bsocket.is_available() || !output_bsocket.is_available()) { continue; } r_propagated_to_geometry_socket.add_multiple( &output_bsocket, Vector(r_propagated_to_geometry_socket.lookup(&input_bsocket))); } } } /** * Determines which anonymous attributes should be propagated to which geometry sockets. */ void gather_required_propagated_data( const Span relations_by_node, const VectorSet &attribute_reference_keys, const MultiValueMap &referenced_by_field_socket, const MultiValueMap &propagated_to_geometry_socket, MultiValueMap &r_required_propagated_to_geometry_socket) { const aal::RelationsInNode &tree_relations = *btree_.runtime->anonymous_attribute_relations; MultiValueMap required_by_geometry_socket; /* Initialize required attributes at group output. */ if (const bNode *group_output_bnode = btree_.group_output_node()) { for (const aal::PropagateRelation &relation : tree_relations.propagate_relations) { AttributeReferenceKey key; key.type = AttributeReferenceKeyType::OutputGeometry; key.index = relation.to_geometry_output; const int key_index = attribute_reference_keys.index_of(key); required_by_geometry_socket.add( &group_output_bnode->input_socket(relation.to_geometry_output), key_index); } for (const aal::AvailableRelation &relation : tree_relations.available_relations) { const bNodeSocket &geometry_bsocket = group_output_bnode->input_socket( relation.geometry_output); const bNodeSocket &field_bsocket = group_output_bnode->input_socket(relation.field_output); required_by_geometry_socket.add_multiple( &geometry_bsocket, referenced_by_field_socket.lookup(&field_bsocket)); } } /* Propagate attribute usages from right to left. */ for (const bNode *bnode : btree_.toposort_right_to_left()) { const aal::RelationsInNode &relations = *relations_by_node[bnode->index()]; for (const bNodeSocket *bsocket : bnode->output_sockets()) { if (!bsocket->is_available()) { continue; } Vector required_attributes; for (const bNodeLink *blink : bsocket->directly_linked_links()) { if (blink->is_used()) { const bNodeSocket &to_socket = *blink->tosock; required_attributes.extend_non_duplicates( required_by_geometry_socket.lookup(&to_socket)); } } const Span available_attributes = propagated_to_geometry_socket.lookup(bsocket); for (const int key_index : required_attributes) { if (available_attributes.contains(key_index)) { required_by_geometry_socket.add(bsocket, key_index); const AttributeReferenceKey &key = attribute_reference_keys[key_index]; if (key.type != AttributeReferenceKeyType::Socket || &key.bsocket->owner_node() != bnode) { r_required_propagated_to_geometry_socket.add(bsocket, key_index); } } } } for (const bNodeSocket *bsocket : bnode->input_sockets()) { if (!bsocket->is_available()) { continue; } Vector required_attributes; for (const aal::PropagateRelation &relation : relations.propagate_relations) { if (relation.from_geometry_input == bsocket->index()) { const bNodeSocket &output_bsocket = bnode->output_socket(relation.to_geometry_output); required_attributes.extend_non_duplicates( required_by_geometry_socket.lookup(&output_bsocket)); } } for (const aal::EvalRelation &relation : relations.eval_relations) { if (relation.geometry_input == bsocket->index()) { const bNodeSocket &field_bsocket = bnode->input_socket(relation.field_input); if (field_bsocket.is_available()) { required_attributes.extend_non_duplicates( referenced_by_field_socket.lookup(&field_bsocket)); } } } const Span available_attributes = propagated_to_geometry_socket.lookup(bsocket); for (const int key_index : required_attributes) { if (available_attributes.contains(key_index)) { required_by_geometry_socket.add(bsocket, key_index); } } } } } /** * For every node that propagates attributes, prepare an attribute set containing information * about which attributes should be propagated. */ void build_attribute_sets_to_propagate( const Span attribute_reference_keys, const Span attribute_reference_infos, const MultiValueMap &required_propagated_to_geometry_socket) { JoinAttibuteSetsCache join_attribute_sets_cache; for (const auto [geometry_output_bsocket, lf_attribute_set_input] : attribute_set_propagation_map_.items()) { const Span required = required_propagated_to_geometry_socket.lookup( geometry_output_bsocket); Vector attribute_set_sockets; Vector used_sockets; for (const int i : required.index_range()) { const int key_index = required[i]; const AttributeReferenceKey &key = attribute_reference_keys[key_index]; const AttributeReferenceInfo &info = attribute_reference_infos[key_index]; lf::OutputSocket *lf_socket_usage = nullptr; switch (key.type) { case AttributeReferenceKeyType::InputField: { lf_socket_usage = const_cast( mapping_->group_input_usage_sockets[key.index]) ->origin(); break; } case AttributeReferenceKeyType::OutputGeometry: { lf_socket_usage = const_cast( mapping_->group_output_used_sockets[key.index]); break; } case AttributeReferenceKeyType::Socket: { lf_socket_usage = socket_is_used_map_[key.bsocket->index_in_tree()]; break; } } if (lf_socket_usage) { attribute_set_sockets.append(info.lf_attribute_set_socket); used_sockets.append(lf_socket_usage); } } if (lf::OutputSocket *joined_attribute_set = this->join_attribute_sets( attribute_set_sockets, used_sockets, join_attribute_sets_cache)) { lf_graph_->add_link(*joined_attribute_set, *lf_attribute_set_input); } else { static const bke::AnonymousAttributeSet empty_set; lf_attribute_set_input->set_default_value(&empty_set); } } } using JoinAttibuteSetsCache = Map, lf::OutputSocket *>; /** * Join multiple attributes set into a single attribute set that can be passed into a node. */ lf::OutputSocket *join_attribute_sets(const Span attribute_set_sockets, const Span used_sockets, JoinAttibuteSetsCache &cache) { BLI_assert(attribute_set_sockets.size() == used_sockets.size()); if (attribute_set_sockets.is_empty()) { return nullptr; } if (attribute_set_sockets.size() == 1) { return attribute_set_sockets[0]; } Vector key; key.extend(attribute_set_sockets); key.extend(used_sockets); std::sort(key.begin(), key.end()); return cache.lookup_or_add_cb(key, [&]() { auto lazy_function = std::make_unique( attribute_set_sockets.size()); lf::Node &lf_node = lf_graph_->add_function(*lazy_function); for (const int i : attribute_set_sockets.index_range()) { lf::InputSocket &lf_use_input = lf_node.input(lazy_function->get_use_input(i)); socket_usage_inputs_.add(&lf_use_input); lf::InputSocket &lf_attributes_input = lf_node.input( lazy_function->get_attribute_set_input(i)); lf_graph_->add_link(*used_sockets[i], lf_use_input); lf_graph_->add_link(*attribute_set_sockets[i], lf_attributes_input); } lf_graph_info_->functions.append(std::move(lazy_function)); return &lf_node.output(0); }); } /** * By depending on "the future" (whether a specific socket is used in the future), it is possible * to introduce cycles in the graph. This function finds those cycles and breaks them by removing * specific links. * * Example for a cycle: There is a `Distribute Points on Faces` node and its `Normal` output is * only used when the number of generated points is larger than 1000 because of some switch node * later in the tree. In this case, to know whether the `Normal` output is needed, one first has * to compute the points, but for that one has to know whether the normal information has to be * added to the points. The fix is to always add the normal information in this case. */ void fix_link_cycles() { lf_graph_->update_socket_indices(); const int sockets_num = lf_graph_->socket_num(); struct SocketState { bool done = false; bool in_stack = false; }; Array socket_states(sockets_num); Stack lf_sockets_to_check; for (lf::Node *lf_node : lf_graph_->nodes()) { if (lf_node->is_function()) { for (lf::OutputSocket *lf_socket : lf_node->outputs()) { if (lf_socket->targets().is_empty()) { lf_sockets_to_check.push(lf_socket); } } } if (lf_node->outputs().is_empty()) { for (lf::InputSocket *lf_socket : lf_node->inputs()) { lf_sockets_to_check.push(lf_socket); } } } Vector lf_socket_stack; while (!lf_sockets_to_check.is_empty()) { lf::Socket *lf_inout_socket = lf_sockets_to_check.peek(); lf::Node &lf_node = lf_inout_socket->node(); SocketState &state = socket_states[lf_inout_socket->index_in_graph()]; lf_socket_stack.append(lf_inout_socket); state.in_stack = true; Vector lf_origin_sockets; if (lf_inout_socket->is_input()) { lf::InputSocket &lf_input_socket = lf_inout_socket->as_input(); if (lf::OutputSocket *lf_origin_socket = lf_input_socket.origin()) { lf_origin_sockets.append(lf_origin_socket); } } else { lf::OutputSocket &lf_output_socket = lf_inout_socket->as_output(); if (lf_node.is_function()) { lf::FunctionNode &lf_function_node = static_cast(lf_node); const lf::LazyFunction &fn = lf_function_node.function(); fn.possible_output_dependencies( lf_output_socket.index(), [&](const Span input_indices) { for (const int input_index : input_indices) { lf_origin_sockets.append(&lf_node.input(input_index)); } }); } } bool pushed_socket = false; for (lf::Socket *lf_origin_socket : lf_origin_sockets) { if (socket_states[lf_origin_socket->index_in_graph()].in_stack) { const Span cycle = lf_socket_stack.as_span().drop_front( lf_socket_stack.first_index_of(lf_origin_socket)); bool broke_cycle = false; for (lf::Socket *lf_cycle_socket : cycle) { if (lf_cycle_socket->is_input() && socket_usage_inputs_.contains(&lf_cycle_socket->as_input())) { lf::InputSocket &lf_cycle_input_socket = lf_cycle_socket->as_input(); lf_graph_->clear_origin(lf_cycle_input_socket); static const bool static_true = true; lf_cycle_input_socket.set_default_value(&static_true); broke_cycle = true; } } if (!broke_cycle) { BLI_assert_unreachable(); } } else if (!socket_states[lf_origin_socket->index_in_graph()].done) { lf_sockets_to_check.push(lf_origin_socket); pushed_socket = true; } } if (pushed_socket) { continue; } state.done = true; state.in_stack = false; lf_sockets_to_check.pop(); lf_socket_stack.pop_last(); } } void print_graph(); }; class UsedSocketVisualizeOptions : public lf::Graph::ToDotOptions { private: const GeometryNodesLazyFunctionGraphBuilder &builder_; Map socket_font_colors_; Map socket_name_suffixes_; public: UsedSocketVisualizeOptions(const GeometryNodesLazyFunctionGraphBuilder &builder) : builder_(builder) { VectorSet found; for (const int bsocket_index : builder_.socket_is_used_map_.index_range()) { const bNodeSocket *bsocket = builder_.btree_.all_sockets()[bsocket_index]; lf::OutputSocket *lf_used_socket = builder_.socket_is_used_map_[bsocket_index]; if (lf_used_socket == nullptr) { continue; } const float hue = BLI_hash_int_01(uintptr_t(lf_used_socket)); std::stringstream ss; ss.precision(3); ss << hue << " 0.9 0.5"; const std::string color_str = ss.str(); const std::string suffix = " (" + std::to_string(found.index_of_or_add(lf_used_socket)) + ")"; socket_font_colors_.add(lf_used_socket, color_str); socket_name_suffixes_.add(lf_used_socket, suffix); if (bsocket->is_input()) { for (const lf::InputSocket *lf_socket : builder_.input_socket_map_.lookup(bsocket)) { socket_font_colors_.add(lf_socket, color_str); socket_name_suffixes_.add(lf_socket, suffix); } } else if (lf::OutputSocket *lf_socket = builder_.output_socket_map_.lookup(bsocket)) { socket_font_colors_.add(lf_socket, color_str); socket_name_suffixes_.add(lf_socket, suffix); } } } std::optional socket_font_color(const lf::Socket &socket) const override { if (const std::string *color = socket_font_colors_.lookup_ptr(&socket)) { return *color; } return std::nullopt; } std::string socket_name(const lf::Socket &socket) const override { return socket.name() + socket_name_suffixes_.lookup_default(&socket, ""); } void add_edge_attributes(const lf::OutputSocket & /*from*/, const lf::InputSocket &to, dot::DirectedEdge &dot_edge) const override { if (builder_.socket_usage_inputs_.contains_as(&to)) { // dot_edge.attributes.set("constraint", "false"); dot_edge.attributes.set("color", "#00000055"); } } }; void GeometryNodesLazyFunctionGraphBuilder::print_graph() { UsedSocketVisualizeOptions options{*this}; std::cout << "\n\n" << lf_graph_->to_dot(options) << "\n\n"; } const GeometryNodesLazyFunctionGraphInfo *ensure_geometry_nodes_lazy_function_graph( const bNodeTree &btree) { btree.ensure_topology_cache(); if (btree.has_available_link_cycle()) { return nullptr; } if (const ID *id_orig = DEG_get_original_id(const_cast(&btree.id))) { if (id_orig->tag & LIB_TAG_MISSING) { return nullptr; } } for (const bNodeSocket *interface_bsocket : btree.interface_inputs()) { if (interface_bsocket->typeinfo->geometry_nodes_cpp_type == nullptr) { return nullptr; } } for (const bNodeSocket *interface_bsocket : btree.interface_outputs()) { if (interface_bsocket->typeinfo->geometry_nodes_cpp_type == nullptr) { return nullptr; } } std::unique_ptr &lf_graph_info_ptr = btree.runtime->geometry_nodes_lazy_function_graph_info; if (lf_graph_info_ptr) { return lf_graph_info_ptr.get(); } std::lock_guard lock{btree.runtime->geometry_nodes_lazy_function_graph_info_mutex}; if (lf_graph_info_ptr) { return lf_graph_info_ptr.get(); } auto lf_graph_info = std::make_unique(); GeometryNodesLazyFunctionGraphBuilder builder{btree, *lf_graph_info}; builder.build(); lf_graph_info_ptr = std::move(lf_graph_info); return lf_graph_info_ptr.get(); } GeometryNodesLazyFunctionLogger::GeometryNodesLazyFunctionLogger( const GeometryNodesLazyFunctionGraphInfo &lf_graph_info) : lf_graph_info_(lf_graph_info) { } void GeometryNodesLazyFunctionLogger::log_socket_value( const fn::lazy_function::Socket &lf_socket, const GPointer value, const fn::lazy_function::Context &context) const { /* In this context we expect only a single kind of user data, so use `static_cast`. */ GeoNodesLFUserData *user_data = static_cast(context.user_data); BLI_assert(dynamic_cast(context.user_data) != nullptr); if (!user_data->log_socket_values) { return; } if (user_data->modifier_data->eval_log == nullptr) { return; } const Span bsockets = lf_graph_info_.mapping.bsockets_by_lf_socket_map.lookup(&lf_socket); if (bsockets.is_empty()) { return; } geo_eval_log::GeoTreeLogger &tree_logger = user_data->modifier_data->eval_log->get_local_tree_logger(*user_data->compute_context); for (const bNodeSocket *bsocket : bsockets) { /* Avoid logging to some sockets when the same value will also be logged to a linked socket. * This reduces the number of logged values without losing information. */ if (bsocket->is_input() && bsocket->is_directly_linked()) { continue; } const bNode &bnode = bsocket->owner_node(); if (bnode.is_reroute()) { continue; } tree_logger.log_value(bsocket->owner_node(), *bsocket, value); } } static std::mutex dump_error_context_mutex; void GeometryNodesLazyFunctionLogger::dump_when_outputs_are_missing( const lf::FunctionNode &node, Span missing_sockets, const lf::Context &context) const { std::lock_guard lock{dump_error_context_mutex}; GeoNodesLFUserData *user_data = dynamic_cast(context.user_data); BLI_assert(user_data != nullptr); user_data->compute_context->print_stack(std::cout, node.name()); std::cout << "Missing outputs:\n"; for (const lf::OutputSocket *socket : missing_sockets) { std::cout << " " << socket->name() << "\n"; } } void GeometryNodesLazyFunctionLogger::dump_when_input_is_set_twice( const lf::InputSocket &target_socket, const lf::OutputSocket &from_socket, const lf::Context &context) const { std::lock_guard lock{dump_error_context_mutex}; std::stringstream ss; ss << from_socket.node().name() << ":" << from_socket.name() << " -> " << target_socket.node().name() << ":" << target_socket.name(); GeoNodesLFUserData *user_data = dynamic_cast(context.user_data); BLI_assert(user_data != nullptr); user_data->compute_context->print_stack(std::cout, ss.str()); } Vector GeometryNodesLazyFunctionSideEffectProvider:: get_nodes_with_side_effects(const lf::Context &context) const { GeoNodesLFUserData *user_data = dynamic_cast(context.user_data); BLI_assert(user_data != nullptr); const ComputeContextHash &context_hash = user_data->compute_context->hash(); const GeoNodesModifierData &modifier_data = *user_data->modifier_data; return modifier_data.side_effect_nodes->lookup(context_hash); } GeometryNodesLazyFunctionGraphInfo::GeometryNodesLazyFunctionGraphInfo() = default; GeometryNodesLazyFunctionGraphInfo::~GeometryNodesLazyFunctionGraphInfo() { for (GMutablePointer &p : this->values_to_destruct) { p.destruct(); } } [[maybe_unused]] static void add_thread_id_debug_message( const GeometryNodesLazyFunctionGraphInfo &lf_graph_info, const lf::FunctionNode &node, const lf::Context &context) { static std::atomic thread_id_source = 0; static thread_local const int thread_id = thread_id_source.fetch_add(1); static thread_local const std::string thread_id_str = "Thread: " + std::to_string(thread_id); GeoNodesLFUserData *user_data = dynamic_cast(context.user_data); BLI_assert(user_data != nullptr); if (user_data->modifier_data->eval_log == nullptr) { return; } geo_eval_log::GeoTreeLogger &tree_logger = user_data->modifier_data->eval_log->get_local_tree_logger(*user_data->compute_context); /* Find corresponding node based on the socket mapping. */ auto check_sockets = [&](const Span lf_sockets) { for (const lf::Socket *lf_socket : lf_sockets) { const Span bsockets = lf_graph_info.mapping.bsockets_by_lf_socket_map.lookup(lf_socket); if (!bsockets.is_empty()) { const bNodeSocket &bsocket = *bsockets[0]; const bNode &bnode = bsocket.owner_node(); tree_logger.debug_messages.append({bnode.identifier, thread_id_str}); return true; } } return false; }; if (check_sockets(node.inputs().cast())) { return; } check_sockets(node.outputs().cast()); } void GeometryNodesLazyFunctionLogger::log_before_node_execute(const lf::FunctionNode &node, const lf::Params & /*params*/, const lf::Context &context) const { /* Enable this to see the threads that invoked a node. */ if constexpr (false) { add_thread_id_debug_message(lf_graph_info_, node, context); } } } // namespace blender::nodes