diff --git a/source/blender/blenkernel/BKE_node_runtime.hh b/source/blender/blenkernel/BKE_node_runtime.hh index 6b6d8d0ae7c..7a05be67a7c 100644 --- a/source/blender/blenkernel/BKE_node_runtime.hh +++ b/source/blender/blenkernel/BKE_node_runtime.hh @@ -161,6 +161,14 @@ class bNodeTreeRuntime : NonCopyable, NonMovable { std::unique_ptr reference_lifetimes_info; std::unique_ptr gizmo_propagation; + /** + * A bool for each input socket (indexed by `index_in_all_inputs()`) that indicates whether this + * socket is used by the node it belongs to. Sockets for which this is false may e.g. be grayed + * out. + */ + blender::Array inferenced_input_socket_usage; + CacheMutex inferenced_input_socket_usage_mutex; + /** * For geometry nodes, a lazy function graph with some additional info is cached. This is used to * evaluate the node group. Caching it here allows us to reuse the preprocessed node tree in case @@ -714,6 +722,28 @@ inline blender::IndexRange bNode::output_socket_indices_in_tree() const return blender::IndexRange::from_begin_size(this->output_socket(0).index_in_tree(), num_outputs); } +inline blender::IndexRange bNode::input_socket_indices_in_all_inputs() const +{ + BLI_assert(blender::bke::node_tree_runtime::topology_cache_is_available(*this)); + const int num_inputs = this->runtime->inputs.size(); + if (num_inputs == 0) { + return {}; + } + return blender::IndexRange::from_begin_size(this->input_socket(0).index_in_all_inputs(), + num_inputs); +} + +inline blender::IndexRange bNode::output_socket_indices_in_all_outputs() const +{ + BLI_assert(blender::bke::node_tree_runtime::topology_cache_is_available(*this)); + const int num_outputs = this->runtime->outputs.size(); + if (num_outputs == 0) { + return {}; + } + return blender::IndexRange::from_begin_size(this->output_socket(0).index_in_all_outputs(), + num_outputs); +} + inline bNodeSocket &bNode::input_socket(int index) { BLI_assert(blender::bke::node_tree_runtime::topology_cache_is_available(*this)); diff --git a/source/blender/blenkernel/intern/node_runtime.cc b/source/blender/blenkernel/intern/node_runtime.cc index aedf11e62a2..d27de7c5a34 100644 --- a/source/blender/blenkernel/intern/node_runtime.cc +++ b/source/blender/blenkernel/intern/node_runtime.cc @@ -13,6 +13,7 @@ #include "NOD_geometry_nodes_lazy_function.hh" #include "NOD_node_declaration.hh" +#include "NOD_socket_usage_inference.hh" namespace blender::bke::node_tree_runtime { @@ -662,3 +663,17 @@ bNodeSocket &bNode::socket_by_decl(const blender::nodes::SocketDeclaration &decl { return decl.in_out == SOCK_IN ? this->input_socket(decl.index) : this->output_socket(decl.index); } + +bool bNodeSocket::affects_node_output() const +{ + BLI_assert(this->is_input()); + BLI_assert(blender::bke::node_tree_runtime::topology_cache_is_available(*this)); + const bNodeTree &tree = this->owner_tree(); + + tree.runtime->inferenced_input_socket_usage_mutex.ensure([&]() { + tree.runtime->inferenced_input_socket_usage = + blender::nodes::socket_usage_inference::infer_all_input_sockets_usage(tree); + }); + + return tree.runtime->inferenced_input_socket_usage[this->index_in_all_inputs()]; +} diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index 345b432d7d5..20b21c0e1cc 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -67,6 +67,7 @@ static void add_tree_tag(bNodeTree *ntree, const eNodeTreeChangedFlag flag) ntree->runtime->changed_flag |= flag; ntree->runtime->topology_cache_mutex.tag_dirty(); ntree->runtime->tree_zones_cache_mutex.tag_dirty(); + ntree->runtime->inferenced_input_socket_usage_mutex.tag_dirty(); } static void add_node_tag(bNodeTree *ntree, bNode *node, const eNodeTreeChangedFlag flag) diff --git a/source/blender/blenlib/BLI_multi_value_map.hh b/source/blender/blenlib/BLI_multi_value_map.hh index 259c9b5158e..b66af542db8 100644 --- a/source/blender/blenlib/BLI_multi_value_map.hh +++ b/source/blender/blenlib/BLI_multi_value_map.hh @@ -125,6 +125,15 @@ template class MultiValueMap { return map_.size(); } + /** + * Returns true if there are no keys in the map. + * NOTE: There may be keys without values. In this case the map is not empty. + */ + bool is_empty() const + { + return map_.is_empty(); + } + /** * NOTE: This signature will change when the implementation changes. */ diff --git a/source/blender/editors/geometry/node_group_operator.cc b/source/blender/editors/geometry/node_group_operator.cc index 430de37ccf2..9afa6aa30b2 100644 --- a/source/blender/editors/geometry/node_group_operator.cc +++ b/source/blender/editors/geometry/node_group_operator.cc @@ -66,6 +66,7 @@ #include "NOD_geometry_nodes_dependencies.hh" #include "NOD_geometry_nodes_execute.hh" #include "NOD_geometry_nodes_lazy_function.hh" +#include "NOD_socket_usage_inference.hh" #include "AS_asset_catalog.hh" #include "AS_asset_catalog_path.hh" @@ -715,7 +716,8 @@ static void draw_property_for_socket(const bNodeTree &node_tree, PointerRNA *bmain_ptr, PointerRNA *op_ptr, const bNodeTreeInterfaceSocket &socket, - const int socket_index) + const int socket_index, + const bool affects_output) { bke::bNodeSocketType *typeinfo = bke::node_socket_type_find(socket.socket_type); const eNodeSocketDatatype socket_type = eNodeSocketDatatype(typeinfo->type); @@ -736,6 +738,7 @@ static void draw_property_for_socket(const bNodeTree &node_tree, SNPRINTF(rna_path, "[\"%s\"]", socket_id_esc); uiLayout *row = uiLayoutRow(layout, true); + uiLayoutSetActive(row, affects_output); uiLayoutSetPropDecorate(row, false); /* Use #uiItemPointerR to draw pointer properties because #uiItemR would not have enough @@ -786,10 +789,21 @@ static void run_node_group_ui(bContext *C, wmOperator *op) } node_tree->ensure_interface_cache(); + + Array input_usages(node_tree->interface_inputs().size()); + nodes::socket_usage_inference::infer_group_interface_inputs_usage( + *node_tree, op->properties, input_usages); + int input_index = 0; for (const bNodeTreeInterfaceSocket *io_socket : node_tree->interface_inputs()) { - draw_property_for_socket( - *node_tree, layout, op->properties, &bmain_ptr, op->ptr, *io_socket, input_index); + draw_property_for_socket(*node_tree, + layout, + op->properties, + &bmain_ptr, + op->ptr, + *io_socket, + input_index, + input_usages[input_index]); ++input_index; } } diff --git a/source/blender/editors/interface/templates/interface_template_node_inputs.cc b/source/blender/editors/interface/templates/interface_template_node_inputs.cc index 245aa579cd1..eb99b778464 100644 --- a/source/blender/editors/interface/templates/interface_template_node_inputs.cc +++ b/source/blender/editors/interface/templates/interface_template_node_inputs.cc @@ -73,6 +73,28 @@ static void draw_node_input(bContext *C, socket.typeinfo->draw(C, row, &socket_ptr, node_ptr, text); } +static bool panel_has_input_affecting_node_output( + const bNode &node, const blender::nodes::PanelDeclaration &panel_decl) +{ + for (const blender::nodes::ItemDeclaration *item_decl : panel_decl.items) { + if (const auto *socket_decl = dynamic_cast(item_decl)) { + if (socket_decl->in_out == SOCK_OUT) { + continue; + } + const bNodeSocket &socket = node.socket_by_decl(*socket_decl); + if (socket.affects_node_output()) { + return true; + } + } + else if (const auto *sub_panel_decl = dynamic_cast(item_decl)) { + if (panel_has_input_affecting_node_output(node, *sub_panel_decl)) { + return true; + } + } + } + return false; +} + static void draw_node_inputs_recursive(bContext *C, uiLayout *layout, bNode &node, @@ -82,6 +104,8 @@ static void draw_node_inputs_recursive(bContext *C, /* TODO: Use flag on the panel state instead which is better for dynamic panel amounts. */ const std::string panel_idname = "NodePanel" + std::to_string(panel_decl.identifier); PanelLayout panel = uiLayoutPanel(C, layout, panel_idname.c_str(), panel_decl.default_collapsed); + const bool has_used_inputs = panel_has_input_affecting_node_output(node, panel_decl); + uiLayoutSetActive(panel.header, has_used_inputs); uiItemL(panel.header, IFACE_(panel_decl.name.c_str()), ICON_NONE); if (!panel.body) { return; diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 6095fbf7585..24aa1322c6c 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -1284,6 +1284,10 @@ static void std_node_socket_draw( int type = sock->typeinfo->type; // int subtype = sock->typeinfo->subtype; + if (sock->is_input() && !sock->affects_node_output()) { + uiLayoutSetActive(layout, false); + } + /* XXX not nice, eventually give this node its own socket type ... */ if (node->type_legacy == CMP_NODE_OUTPUT_FILE) { node_file_output_socket_draw(C, layout, ptr, node_ptr); diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 69f922b3bb3..662095a94b2 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -2445,6 +2445,34 @@ static void node_draw_panels_background(const bNode &node) } } +/** + * Note that this is different from #panel_has_input_affecting_node_output in how it treats output + * sockets. Within the node UI, the panel should not be grayed out if it has an output socket. + * However, the sidebar only shows inputs, so output sockets should be ignored. + */ +static bool panel_has_only_inactive_inputs(const bNode &node, + const nodes::PanelDeclaration &panel_decl) +{ + for (const nodes::ItemDeclaration *item_decl : panel_decl.items) { + if (const auto *socket_decl = dynamic_cast(item_decl)) { + if (socket_decl->in_out == SOCK_OUT) { + return false; + } + const bNodeSocket &socket = node.socket_by_decl(*socket_decl); + if (socket.affects_node_output()) { + return false; + } + } + else if (const auto *sub_panel_decl = dynamic_cast(item_decl)) + { + if (!panel_has_only_inactive_inputs(node, *sub_panel_decl)) { + return false; + } + } + } + return true; +} + static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block) { BLI_assert(is_node_panels_supported(node)); @@ -2494,7 +2522,9 @@ static void node_draw_panels(bNodeTree &ntree, const bNode &node, uiBlock &block 0, 0, ""); - if (node.is_muted()) { + + const bool only_inactive_inputs = panel_has_only_inactive_inputs(node, panel_decl); + if (node.is_muted() || only_inactive_inputs) { UI_but_flag_enable(label_but, UI_BUT_INACTIVE); } diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index d660b0d1f75..89bb7176dac 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -207,6 +207,11 @@ typedef struct bNodeSocket { bool is_input() const; bool is_output() const; + /** + * False when this input socket definitely does not affect the output. + */ + bool affects_node_output() const; + /** Utility to access the value of the socket. */ template T *default_value_typed(); template const T *default_value_typed() const; @@ -501,10 +506,12 @@ typedef struct bNode { blender::Span input_sockets(); blender::Span input_sockets() const; blender::IndexRange input_socket_indices_in_tree() const; + blender::IndexRange input_socket_indices_in_all_inputs() const; /** A span containing all output sockets of the node (including unavailable sockets). */ blender::Span output_sockets(); blender::Span output_sockets() const; blender::IndexRange output_socket_indices_in_tree() const; + blender::IndexRange output_socket_indices_in_all_outputs() const; /** Utility to get an input socket by its index. */ bNodeSocket &input_socket(int index); const bNodeSocket &input_socket(int index) const; diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 80f8df5eba3..3f4daed5949 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -97,6 +97,7 @@ #include "NOD_geometry_nodes_gizmos.hh" #include "NOD_geometry_nodes_lazy_function.hh" #include "NOD_node_declaration.hh" +#include "NOD_socket_usage_inference.hh" #include "FN_field.hh" #include "FN_lazy_function_execute.hh" @@ -2014,6 +2015,7 @@ struct DrawGroupInputsContext { NodesModifierData &nmd; PointerRNA *md_ptr; PointerRNA *bmain_ptr; + Array input_usages; }; static void add_attribute_search_button(DrawGroupInputsContext &ctx, @@ -2159,10 +2161,11 @@ static void draw_property_for_socket(DrawGroupInputsContext &ctx, char rna_path[sizeof(socket_id_esc) + 4]; SNPRINTF(rna_path, "[\"%s\"]", socket_id_esc); + const int input_index = ctx.nmd.node_group->interface_input_index(socket); + uiLayout *row = uiLayoutRow(layout, true); uiLayoutSetPropDecorate(row, true); - - const int input_index = ctx.nmd.node_group->interface_input_index(socket); + uiLayoutSetActive(row, ctx.input_usages[input_index]); /* Use #uiItemPointerR to draw pointer properties because #uiItemR would not have enough * information about what type of ID to select for editing the values. This is because @@ -2263,6 +2266,27 @@ static bool interface_panel_has_socket(const bNodeTreeInterfacePanel &interface_ return false; } +static bool interface_panel_affects_output(DrawGroupInputsContext &ctx, + const bNodeTreeInterfacePanel &panel) +{ + for (const bNodeTreeInterfaceItem *item : panel.items()) { + if (item->item_type == NODE_INTERFACE_SOCKET) { + const auto &socket = *reinterpret_cast(item); + const int input_index = ctx.nmd.node_group->interface_input_index(socket); + if (ctx.input_usages[input_index]) { + return true; + } + } + else if (item->item_type == NODE_INTERFACE_PANEL) { + const auto &sub_interface_panel = *reinterpret_cast(item); + if (interface_panel_affects_output(ctx, sub_interface_panel)) { + return true; + } + } + } + return false; +} + static void draw_interface_panel_content(DrawGroupInputsContext &ctx, uiLayout *layout, const bNodeTreeInterfacePanel &interface_panel) @@ -2278,6 +2302,9 @@ static void draw_interface_panel_content(DrawGroupInputsContext &ctx, ctx.md_ptr->owner_id, &RNA_NodesModifierPanel, panel); PanelLayout panel_layout = uiLayoutPanelProp(&ctx.C, layout, &panel_ptr, "is_open"); uiItemL(panel_layout.header, IFACE_(sub_interface_panel.name), ICON_NONE); + if (!interface_panel_affects_output(ctx, sub_interface_panel)) { + uiLayoutSetActive(panel_layout.header, false); + } uiLayoutSetTooltipFunc( panel_layout.header, [](bContext * /*C*/, void *panel_arg, const char * /*tip*/) -> std::string { @@ -2500,6 +2527,9 @@ static void panel_draw(const bContext *C, Panel *panel) if (nmd->node_group != nullptr && nmd->settings.properties != nullptr) { nmd->node_group->ensure_interface_cache(); + ctx.input_usages.reinitialize(nmd->node_group->interface_inputs().size()); + nodes::socket_usage_inference::infer_group_interface_inputs_usage( + *nmd->node_group, nmd->settings.properties, ctx.input_usages); draw_interface_panel_content(ctx, layout, nmd->node_group->tree_interface.root_panel); } diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 0492a905375..a157067c6b3 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -89,6 +89,7 @@ set(SRC intern/node_util.cc intern/partial_eval.cc intern/socket_search_link.cc + intern/socket_usage_inference.cc intern/value_elem.cc NOD_common.hh @@ -121,6 +122,7 @@ set(SRC NOD_socket_items_ops.hh NOD_socket_items_ui.hh NOD_socket_search_link.hh + NOD_socket_usage_inference.hh NOD_texture.h NOD_value_elem.hh NOD_value_elem_eval.hh diff --git a/source/blender/nodes/NOD_geometry_nodes_execute.hh b/source/blender/nodes/NOD_geometry_nodes_execute.hh index adc7ce837c0..d3f58e2852d 100644 --- a/source/blender/nodes/NOD_geometry_nodes_execute.hh +++ b/source/blender/nodes/NOD_geometry_nodes_execute.hh @@ -6,7 +6,9 @@ #include "BLI_compute_context.hh" #include "BLI_function_ref.hh" +#include "BLI_generic_pointer.hh" #include "BLI_multi_value_map.hh" +#include "BLI_resource_scope.hh" #include "BLI_set.hh" #include "BKE_idprop.hh" @@ -66,4 +68,15 @@ void update_output_properties_from_node_tree(const bNodeTree &tree, const IDProperty *old_properties, IDProperty &properties); +/** + * Get the "base" input values that are passed into geometry nodes. In this context, "base" means + * that the retrieved input types are #bNodeSocketType::base_cpp_type (e.g. `float` for float + * sockets). If the input value can't be represented as base value, null is returned instead (e.g. + * for attribute inputs). + */ +void get_geometry_nodes_input_base_values(const bNodeTree &btree, + const IDProperty *properties, + ResourceScope &scope, + MutableSpan r_values); + } // namespace blender::nodes diff --git a/source/blender/nodes/NOD_socket_usage_inference.hh b/source/blender/nodes/NOD_socket_usage_inference.hh new file mode 100644 index 00000000000..05fbc5e16a7 --- /dev/null +++ b/source/blender/nodes/NOD_socket_usage_inference.hh @@ -0,0 +1,52 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_array.hh" +#include "BLI_generic_pointer.hh" + +struct bNodeTree; +struct bNodeSocket; +struct IDProperty; + +namespace blender::nodes::socket_usage_inference { + +/** + * Get a boolean value for each input socket in the given tree that indicates whether that input is + * used. It is assumed that all output sockets in the tree are used. + */ +Array infer_all_input_sockets_usage(const bNodeTree &tree); + +/** + * Get a boolean value for each node group input that indicates whether that input is used by the + * outputs. The result can be used to e.g. gray out or hide individual inputs that are unused. + * + * \param group: The node group that is called. + * \param group_input_values: An optional input value for each node group input. The type is + * expected to be `bNodeSocketType::base_cpp_type`. If the input value for a socket is not known + * or can't be represented as base type, null has to be passed instead. + * \param r_input_usages: The destination array where the inferred usages are written. + */ +void infer_group_interface_inputs_usage(const bNodeTree &group, + Span group_input_values, + MutableSpan r_input_usages); + +/** + * Same as above, but automatically retrieves the input values from the given sockets.. + * This is used for group nodes. + */ +void infer_group_interface_inputs_usage(const bNodeTree &group, + Span input_sockets, + MutableSpan r_input_usages); + +/** + * Same as above, but automatically retrieves the input values from the given properties. + * This is used with the geometry nodes modifier and node tools. + */ +void infer_group_interface_inputs_usage(const bNodeTree &group, + const IDProperty *properties, + MutableSpan r_input_usages); + +} // namespace blender::nodes::socket_usage_inference diff --git a/source/blender/nodes/intern/geometry_nodes_execute.cc b/source/blender/nodes/intern/geometry_nodes_execute.cc index be2d21d04c5..24d9b99e6d2 100644 --- a/source/blender/nodes/intern/geometry_nodes_execute.cc +++ b/source/blender/nodes/intern/geometry_nodes_execute.cc @@ -1041,4 +1041,69 @@ void update_output_properties_from_node_tree(const bNodeTree &tree, } } +void get_geometry_nodes_input_base_values(const bNodeTree &btree, + const IDProperty *properties, + ResourceScope &scope, + MutableSpan r_values) +{ + if (!properties) { + return; + } + + /* Assume that all inputs have unknown values by default. */ + r_values.fill(nullptr); + + btree.ensure_interface_cache(); + for (const int input_i : btree.interface_inputs().index_range()) { + const bNodeTreeInterfaceSocket &io_input = *btree.interface_inputs()[input_i]; + const bke::bNodeSocketType *stype = io_input.socket_typeinfo(); + if (!stype) { + continue; + } + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(stype->type); + if (!stype->base_cpp_type || !stype->geometry_nodes_cpp_type) { + continue; + } + const IDProperty *property = IDP_GetPropertyFromGroup(properties, io_input.identifier); + if (!property) { + continue; + } + if (!id_property_type_matches_socket(io_input, *property)) { + continue; + } + if (input_attribute_name_get(*properties, io_input).has_value()) { + /* Attributes don't have a single base value, so ignore them here.*/ + continue; + } + if (is_layer_selection_field(io_input)) { + /* Can't get a single value for layer selections. */ + continue; + } + + void *value_buffer = scope.linear_allocator().allocate( + stype->geometry_nodes_cpp_type->size(), stype->geometry_nodes_cpp_type->alignment()); + init_socket_cpp_value_from_property(*property, socket_type, value_buffer); + if (!stype->geometry_nodes_cpp_type->is_trivially_destructible()) { + scope.add_destruct_call([type = stype->geometry_nodes_cpp_type, value_buffer]() { + type->destruct(value_buffer); + }); + } + if (stype->geometry_nodes_cpp_type == stype->base_cpp_type) { + r_values[input_i] = {stype->base_cpp_type, value_buffer}; + continue; + } + if (stype->geometry_nodes_cpp_type == &CPPType::get()) { + const bke::SocketValueVariant &socket_value = *static_cast( + value_buffer); + if (!socket_value.is_single()) { + continue; + } + const GPointer single_value = socket_value.get_single_ptr(); + BLI_assert(single_value.type() == stype->base_cpp_type); + r_values[input_i] = single_value; + continue; + } + } +} + } // namespace blender::nodes diff --git a/source/blender/nodes/intern/socket_usage_inference.cc b/source/blender/nodes/intern/socket_usage_inference.cc new file mode 100644 index 00000000000..2df53eae238 --- /dev/null +++ b/source/blender/nodes/intern/socket_usage_inference.cc @@ -0,0 +1,907 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "NOD_geometry_nodes_execute.hh" +#include "NOD_multi_function.hh" +#include "NOD_node_in_compute_context.hh" +#include "NOD_socket_usage_inference.hh" + +#include "DNA_anim_types.h" +#include "DNA_node_types.h" + +#include "BKE_compute_contexts.hh" +#include "BKE_node_legacy_types.hh" +#include "BKE_node_runtime.hh" +#include "BKE_type_conversions.hh" + +#include "ANIM_action.hh" +#include "ANIM_action_iterators.hh" + +#include "BLI_stack.hh" + +namespace blender::nodes::socket_usage_inference { + +/** Utility class to simplify passing global state into all the functions during inferencing. */ +struct SocketUsageInferencer { + private: + /** Owns e.g. intermediate evaluated values. */ + ResourceScope scope_; + + /** Root node tree. */ + const bNodeTree &root_tree_; + + /** + * Stack of tasks that allows depth-first (partial) evaluation of the tree. + */ + Stack usage_tasks_; + Stack value_tasks_; + + /** + * If the usage of a socket is known, it is added to this map. Sockets not in this map are not + * known yet. + */ + Map all_socket_usages_; + + /** + * If the value of a socket is known, it is added to this map. The value may be null, which means + * that the value can be anything. Sockets not in this map have not been evaluated yet. + */ + Map all_socket_values_; + + /** + * All sockets that have animation data and thus their value is not fixed statically. This can + * contain sockets from multiple different trees. + */ + Set animated_sockets_; + Set trees_with_handled_animation_data_; + + /** Some inline storage to reduce the number of allocations. */ + AlignedBuffer<1024, 8> scope_buffer_; + + public: + SocketUsageInferencer(const bNodeTree &tree, + const std::optional> tree_input_values) + : root_tree_(tree) + { + scope_.linear_allocator().provide_buffer(scope_buffer_); + root_tree_.ensure_topology_cache(); + root_tree_.ensure_interface_cache(); + this->ensure_animation_data_processed(root_tree_); + + for (const bNode *node : root_tree_.group_input_nodes()) { + for (const int i : root_tree_.interface_inputs().index_range()) { + const bNodeSocket &socket = node->output_socket(i); + const void *input_value = nullptr; + if (tree_input_values.has_value()) { + input_value = (*tree_input_values)[i].get(); + } + all_socket_values_.add_new({nullptr, &socket}, input_value); + } + } + } + + void mark_top_level_node_outputs_as_used() + { + for (const bNodeSocket *socket : root_tree_.all_output_sockets()) { + all_socket_usages_.add_new({nullptr, socket}, true); + } + } + + bool is_socket_used(const SocketInContext &socket) + { + const std::optional is_used = all_socket_usages_.lookup_try(socket); + if (is_used.has_value()) { + return *is_used; + } + if (socket->owner_tree().has_available_link_cycle()) { + return false; + } + + BLI_assert(usage_tasks_.is_empty()); + usage_tasks_.push(socket); + + while (!usage_tasks_.is_empty()) { + const SocketInContext &socket = usage_tasks_.peek(); + this->usage_task(socket); + if (&socket == &usage_tasks_.peek()) { + /* The task is finished if it hasn't added any new task it depends on.*/ + usage_tasks_.pop(); + } + } + + return all_socket_usages_.lookup(socket); + } + + const void *get_socket_value(const SocketInContext &socket) + { + const std::optional value = all_socket_values_.lookup_try(socket); + if (value.has_value()) { + return *value; + } + if (socket->owner_tree().has_available_link_cycle()) { + return nullptr; + } + + BLI_assert(value_tasks_.is_empty()); + value_tasks_.push(socket); + + while (!value_tasks_.is_empty()) { + const SocketInContext &socket = value_tasks_.peek(); + this->value_task(socket); + if (&socket == &value_tasks_.peek()) { + /* The task is finished if it hasn't added any new task it depends on.*/ + value_tasks_.pop(); + } + } + + return all_socket_values_.lookup(socket); + } + + private: + void usage_task(const SocketInContext &socket) + { + if (all_socket_usages_.contains(socket)) { + return; + } + if (socket->is_input()) { + this->usage_task__input(socket); + } + else { + this->usage_task__output(socket); + } + } + + void usage_task__input(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + switch (node->type_legacy) { + case NODE_GROUP: + case NODE_CUSTOM_GROUP: { + this->usage_task__input__group_node(socket); + break; + } + case NODE_GROUP_OUTPUT: { + this->usage_task__input__group_output_node(socket); + break; + } + case GEO_NODE_SWITCH: { + this->usage_task__input__generic_switch(socket, switch__is_socket_selected); + break; + } + case GEO_NODE_INDEX_SWITCH: { + this->usage_task__input__generic_switch(socket, index_switch__is_socket_selected); + break; + } + case GEO_NODE_MENU_SWITCH: { + this->usage_task__input__generic_switch(socket, menu_switch__is_socket_selected); + break; + } + case GEO_NODE_SIMULATION_INPUT: { + this->usage_task__input__simulation_input_node(socket); + break; + } + case GEO_NODE_REPEAT_INPUT: { + this->usage_task__input__repeat_input_node(socket); + break; + } + case GEO_NODE_FOREACH_GEOMETRY_ELEMENT_INPUT: { + this->usage_task__input__foreach_element_input_node(socket); + break; + } + case GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT: { + this->usage_task__input__foreach_element_output_node(socket); + break; + } + case GEO_NODE_CAPTURE_ATTRIBUTE: { + this->usage_task__input__capture_attribute_node(socket); + break; + } + default: { + this->usage_task__input__fallback(socket); + break; + } + } + } + + /** + * Assumes that the first input is a condition that selects one of the remaining inputs which is + * then output. If necessary, this can trigger a value task for the condition socket. + */ + void usage_task__input__generic_switch( + const SocketInContext &socket, + const FunctionRef + is_selected_socket) + { + const NodeInContext node = socket.owner_node(); + BLI_assert(node->input_sockets().size() >= 1); + BLI_assert(node->output_sockets().size() == 1); + + if (socket->type == SOCK_CUSTOM && STREQ(socket->idname, "NodeSocketVirtual")) { + all_socket_usages_.add_new(socket, false); + return; + } + const SocketInContext output_socket = node.output_socket(0); + const std::optional output_is_used = all_socket_usages_.lookup_try(output_socket); + if (!output_is_used.has_value()) { + this->push_usage_task(output_socket); + return; + } + if (!*output_is_used) { + all_socket_usages_.add_new(socket, false); + return; + } + const SocketInContext condition_socket = node.input_socket(0); + if (socket == condition_socket) { + all_socket_usages_.add_new(socket, true); + return; + } + const void *condition_value = this->get_socket_value(condition_socket); + if (condition_value == nullptr) { + /* The exact condition value is unknown, so any input may be used. */ + all_socket_usages_.add_new(socket, true); + return; + } + const bool is_used = is_selected_socket(socket, condition_value); + all_socket_usages_.add_new(socket, is_used); + } + + void usage_task__input__group_node(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const bNodeTree *group = reinterpret_cast(node->id); + if (!group || ID_MISSING(&group->id)) { + all_socket_usages_.add_new(socket, false); + return; + } + group->ensure_topology_cache(); + if (group->has_available_link_cycle()) { + all_socket_usages_.add_new(socket, false); + return; + } + this->ensure_animation_data_processed(*group); + + /* The group node input is used iff any of the matching group inputs within the group is + * used. */ + const ComputeContext &group_context = scope_.construct( + socket.context, *node, node->owner_tree()); + Vector dependent_sockets; + for (const bNode *group_input_node : group->group_input_nodes()) { + dependent_sockets.append(&group_input_node->output_socket(socket->index())); + } + this->usage_task__with_dependent_sockets(socket, dependent_sockets, &group_context); + } + + void usage_task__input__group_output_node(const SocketInContext &socket) + { + const int output_i = socket->index(); + if (socket.context == nullptr) { + /* This is a final output which is always used. */ + all_socket_usages_.add_new(socket, true); + return; + } + /* The group output node is used iff the matching output of the parent group node is used. */ + const bke::GroupNodeComputeContext &group_context = + *static_cast(socket.context); + const bNodeSocket &group_node_output = group_context.caller_group_node()->output_socket( + output_i); + this->usage_task__with_dependent_sockets(socket, {&group_node_output}, group_context.parent()); + } + + void usage_task__output(const SocketInContext &socket) + { + /* An output socket is used if any of the sockets it is connected to is used. */ + Vector dependent_sockets; + for (const bNodeLink *link : socket->directly_linked_links()) { + if (link->is_used()) { + dependent_sockets.append(link->tosock); + } + } + this->usage_task__with_dependent_sockets(socket, dependent_sockets, socket.context); + } + + void usage_task__input__simulation_input_node(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const bNodeTree &tree = socket->owner_tree(); + + const NodeGeometrySimulationInput &storage = *static_cast( + node->storage); + const bNode *sim_output_node = tree.node_by_id(storage.output_node_id); + if (!sim_output_node) { + all_socket_usages_.add_new(socket, false); + return; + } + /* Simulation inputs are also used when any of the simulation outputs are used. */ + Vector dependent_sockets; + dependent_sockets.extend(node->output_sockets()); + dependent_sockets.extend(sim_output_node->output_sockets()); + this->usage_task__with_dependent_sockets(socket, dependent_sockets, socket.context); + } + + void usage_task__input__repeat_input_node(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const bNodeTree &tree = socket->owner_tree(); + + const NodeGeometryRepeatInput &storage = *static_cast( + node->storage); + const bNode *repeat_output_node = tree.node_by_id(storage.output_node_id); + if (!repeat_output_node) { + all_socket_usages_.add_new(socket, false); + return; + } + /* Assume that all repeat inputs are used when any of the outputs are used. This check could + * become more precise in the future if necessary. */ + Vector dependent_sockets; + dependent_sockets.extend(node->output_sockets()); + dependent_sockets.extend(repeat_output_node->output_sockets()); + this->usage_task__with_dependent_sockets(socket, dependent_sockets, socket.context); + } + + void usage_task__input__foreach_element_output_node(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + this->usage_task__with_dependent_sockets( + socket, {&node->output_by_identifier(socket->identifier)}, socket.context); + } + + void usage_task__input__capture_attribute_node(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + this->usage_task__with_dependent_sockets( + socket, {&node->output_socket(socket->index())}, socket.context); + } + + void usage_task__input__fallback(const SocketInContext &socket) + { + this->usage_task__with_dependent_sockets( + socket, socket->owner_node().output_sockets(), socket.context); + } + + void usage_task__input__foreach_element_input_node(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const bNodeTree &tree = socket->owner_tree(); + + const NodeGeometryForeachGeometryElementInput &storage = + *static_cast(node->storage); + const bNode *foreach_output_node = tree.node_by_id(storage.output_node_id); + if (!foreach_output_node) { + all_socket_usages_.add_new(socket, false); + return; + } + Vector dependent_sockets; + if (StringRef(socket->identifier).startswith("Input_")) { + dependent_sockets.append(&node->output_by_identifier(socket->identifier)); + } + else { + /* The geometry and selection inputs are used whenever any of the zone outputs is used. */ + dependent_sockets.extend(node->output_sockets()); + dependent_sockets.extend(foreach_output_node->output_sockets()); + } + this->usage_task__with_dependent_sockets(socket, dependent_sockets, socket.context); + } + + /** + * Utility that handles simple cases where a socket is used if any of its dependent sockets is + * used. + */ + void usage_task__with_dependent_sockets(const SocketInContext &socket, + const Span dependent_sockets, + const ComputeContext *dependent_socket_context) + { + /* Check if any of the dependent sockets is used. */ + SocketInContext next_unknown_socket; + for (const bNodeSocket *dependent_socket_ptr : dependent_sockets) { + const SocketInContext dependent_socket{dependent_socket_context, dependent_socket_ptr}; + const std::optional is_used = all_socket_usages_.lookup_try(dependent_socket); + if (!is_used.has_value() && !next_unknown_socket) { + next_unknown_socket = dependent_socket; + continue; + } + if (is_used.value_or(false)) { + all_socket_usages_.add_new(socket, true); + return; + } + } + if (next_unknown_socket) { + /* Create a task that checks if the next dependent socket is used. Intentionally only create + * a task for the very next one and not for all, because that could potentially trigger a lot + * of unnecessary evaluations. */ + this->push_usage_task(next_unknown_socket); + return; + } + /* None of the dependent sockets is used, so the current socket is not used either. */ + all_socket_usages_.add_new(socket, false); + } + + void value_task(const SocketInContext &socket) + { + if (all_socket_values_.contains(socket)) { + /* Task is done already. */ + return; + } + const CPPType *base_type = socket->typeinfo->base_cpp_type; + if (!base_type) { + /* The socket type is unknown for some reason (maybe a socket type from the future?).*/ + all_socket_values_.add_new(socket, nullptr); + return; + } + if (socket->is_input()) { + this->value_task__input(socket); + } + else { + this->value_task__output(socket); + } + } + + void value_task__output(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + if (node->is_muted()) { + this->value_task__output__muted_node(socket); + return; + } + switch (node->type_legacy) { + case NODE_GROUP: + case NODE_CUSTOM_GROUP: { + this->value_task__output__group_node(socket); + return; + } + case NODE_GROUP_INPUT: { + this->value_task__output__group_input_node(socket); + return; + } + case GEO_NODE_SWITCH: { + this->value_task__output__generic_switch(socket, switch__is_socket_selected); + return; + } + case GEO_NODE_INDEX_SWITCH: { + this->value_task__output__generic_switch(socket, index_switch__is_socket_selected); + return; + } + case GEO_NODE_MENU_SWITCH: { + this->value_task__output__generic_switch(socket, menu_switch__is_socket_selected); + return; + } + default: { + if (node->typeinfo->build_multi_function) { + this->value_task__output__multi_function_node(socket); + return; + } + break; + } + } + /* If none of the above cases work, the socket value is set to null which means that it is + * unknown/dynamic. */ + all_socket_values_.add_new(socket, nullptr); + } + + void value_task__output__group_node(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const bNodeTree *group = reinterpret_cast(node->id); + if (!group || ID_MISSING(&group->id)) { + all_socket_values_.add_new(socket, nullptr); + return; + } + if (group->has_available_link_cycle()) { + all_socket_values_.add_new(socket, nullptr); + return; + } + this->ensure_animation_data_processed(*group); + const bNode *group_output_node = group->group_output_node(); + if (!group_output_node) { + /* Can't compute the value if the group does not have an output node. */ + all_socket_values_.add_new(socket, nullptr); + return; + } + const ComputeContext &group_context = scope_.construct( + socket.context, *node, node->owner_tree()); + const SocketInContext socket_in_group{&group_context, + &group_output_node->input_socket(socket->index())}; + const std::optional value = all_socket_values_.lookup_try(socket_in_group); + if (!value.has_value()) { + this->push_value_task(socket_in_group); + return; + } + all_socket_values_.add_new(socket, *value); + } + + void value_task__output__group_input_node(const SocketInContext &socket) + { + /* Group inputs for the root context should be initialized already. */ + BLI_assert(socket.context != nullptr); + + const bke::GroupNodeComputeContext &group_context = + *static_cast(socket.context); + const SocketInContext group_node_input{ + group_context.parent(), &group_context.caller_group_node()->input_socket(socket->index())}; + const std::optional value = all_socket_values_.lookup_try(group_node_input); + if (!value.has_value()) { + this->push_value_task(group_node_input); + return; + } + all_socket_values_.add_new(socket, *value); + } + + /** + * Assumes that the first input is a condition that selects one of the remaining inputs which is + * then output. If necessary, this can trigger a value task for the condition socket. + */ + void value_task__output__generic_switch( + const SocketInContext &socket, + const FunctionRef + is_selected_socket) + { + const NodeInContext node = socket.owner_node(); + BLI_assert(node->input_sockets().size() >= 1); + BLI_assert(node->output_sockets().size() == 1); + + const SocketInContext condition_socket = node.input_socket(0); + const std::optional condition_value = all_socket_values_.lookup_try( + condition_socket); + if (!condition_value.has_value()) { + this->push_value_task(condition_socket); + return; + } + if (!*condition_value) { + /* The condition value is not a simple static value, so the output is unknown. */ + all_socket_values_.add_new(socket, nullptr); + return; + } + for (const int input_i : node->input_sockets().index_range().drop_front(1)) { + const SocketInContext input_socket = node.input_socket(input_i); + if (input_socket->type == SOCK_CUSTOM && STREQ(input_socket->idname, "NodeSocketVirtual")) { + continue; + } + const bool is_selected = is_selected_socket(input_socket, *condition_value); + if (!is_selected) { + continue; + } + const std::optional input_value = all_socket_values_.lookup_try(input_socket); + if (!input_value.has_value()) { + this->push_value_task(input_socket); + return; + } + all_socket_values_.add_new(socket, *input_value); + return; + } + /* The condition did not match any of the inputs, so the output is unknown. */ + all_socket_values_.add_new(socket, nullptr); + } + + void value_task__output__multi_function_node(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + const int inputs_num = node->input_sockets().size(); + + /* Gather all input values are return early if any of them is not known.*/ + Vector input_values(inputs_num); + for (const int input_i : IndexRange(inputs_num)) { + const SocketInContext input_socket = node.input_socket(input_i); + const std::optional input_value = all_socket_values_.lookup_try(input_socket); + if (!input_value.has_value()) { + this->push_value_task(input_socket); + return; + } + if (*input_value == nullptr) { + all_socket_values_.add_new(socket, nullptr); + return; + } + input_values[input_i] = *input_value; + } + + /* Get the multi-function for the node. */ + NodeMultiFunctionBuilder builder{*node.node, node->owner_tree()}; + node->typeinfo->build_multi_function(builder); + const mf::MultiFunction &fn = builder.function(); + + /* We only evaluate the node for a single value here. */ + const IndexMask mask(1); + + /* Prepare parameters for the multi-function evaluation. */ + mf::ParamsBuilder params{fn, &mask}; + for (const int input_i : IndexRange(inputs_num)) { + const SocketInContext input_socket = node.input_socket(input_i); + if (!input_socket->is_available()) { + continue; + } + params.add_readonly_single_input( + GPointer(input_socket->typeinfo->base_cpp_type, input_values[input_i])); + } + for (const int output_i : node->output_sockets().index_range()) { + const SocketInContext output_socket = node.output_socket(output_i); + if (!output_socket->is_available()) { + continue; + } + /* Allocate memory for the output value. */ + const CPPType &base_type = *output_socket->typeinfo->base_cpp_type; + void *value = scope_.linear_allocator().allocate(base_type.size(), base_type.alignment()); + params.add_uninitialized_single_output(GMutableSpan(base_type, value, 1)); + all_socket_values_.add_new(output_socket, value); + if (!base_type.is_trivially_destructible()) { + scope_.add_destruct_call( + [type = &base_type, value]() { type->destruct(const_cast(value)); }); + } + } + mf::ContextBuilder context; + /* Actually evaluate the multi-function. The outputs will be written into the memory allocated + * earlier, which has been added to #all_socket_values_ already. */ + fn.call(mask, params, context); + } + + void value_task__output__muted_node(const SocketInContext &socket) + { + const NodeInContext node = socket.owner_node(); + + SocketInContext input_socket; + for (const bNodeLink &internal_link : node->internal_links()) { + if (internal_link.tosock == socket.socket) { + input_socket = SocketInContext{socket.context, internal_link.fromsock}; + break; + } + } + if (!input_socket) { + /* The output does not have an internal link to an input. */ + all_socket_values_.add_new(socket, nullptr); + return; + } + const std::optional input_value = all_socket_values_.lookup_try(input_socket); + if (!input_value.has_value()) { + this->push_value_task(input_socket); + return; + } + const void *converted_value = this->convert_type_if_necessary( + *input_value, *input_socket.socket, *socket.socket); + all_socket_values_.add_new(socket, converted_value); + } + + void value_task__input(const SocketInContext &socket) + { + if (socket->is_multi_input()) { + /* Can't know the single value of a multi-input. */ + all_socket_values_.add_new(socket, nullptr); + return; + } + const bNodeLink *source_link = nullptr; + const Span connected_links = socket->directly_linked_links(); + for (const bNodeLink *link : connected_links) { + if (!link->is_used()) { + continue; + } + if (link->fromnode->is_dangling_reroute()) { + continue; + } + source_link = link; + break; + } + if (!source_link) { + this->value_task__input__unlinked(socket); + return; + } + this->value_task__input__linked({socket.context, source_link->fromsock}, socket); + } + + void value_task__input__unlinked(const SocketInContext &socket) + { + if (animated_sockets_.contains(socket.socket)) { + /* The value of animated sockets is not known statically. */ + all_socket_values_.add_new(socket, nullptr); + return; + } + + const CPPType &base_type = *socket->typeinfo->base_cpp_type; + void *value_buffer = scope_.linear_allocator().allocate(base_type.size(), + base_type.alignment()); + socket->typeinfo->get_base_cpp_value(socket->default_value, value_buffer); + all_socket_values_.add_new(socket, value_buffer); + if (!base_type.is_trivially_destructible()) { + scope_.add_destruct_call( + [type = &base_type, value_buffer]() { type->destruct(value_buffer); }); + } + } + + void value_task__input__linked(const SocketInContext &from_socket, + const SocketInContext &to_socket) + { + const std::optional from_value = all_socket_values_.lookup_try(from_socket); + if (!from_value.has_value()) { + this->push_value_task(from_socket); + return; + } + const void *converted_value = this->convert_type_if_necessary( + *from_value, *from_socket.socket, *to_socket.socket); + all_socket_values_.add_new(to_socket, converted_value); + } + + const void *convert_type_if_necessary(const void *src, + const bNodeSocket &from_socket, + const bNodeSocket &to_socket) + { + if (!src) { + return nullptr; + } + const CPPType *from_type = from_socket.typeinfo->base_cpp_type; + const CPPType *to_type = to_socket.typeinfo->base_cpp_type; + if (from_type == to_type) { + return src; + } + if (!to_type) { + return nullptr; + } + const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); + if (!conversions.is_convertible(*from_type, *to_type)) { + return nullptr; + } + void *dst = scope_.linear_allocator().allocate(to_type->size(), to_type->alignment()); + conversions.convert_to_uninitialized(*from_type, *to_type, src, dst); + if (!to_type->is_trivially_destructible()) { + scope_.add_destruct_call([to_type, dst]() { to_type->destruct(dst); }); + } + return dst; + } + + static bool switch__is_socket_selected(const SocketInContext &socket, const void *condition) + { + const bool is_true = *static_cast(condition); + const int selected_index = is_true ? 2 : 1; + return socket->index() == selected_index; + } + + static bool index_switch__is_socket_selected(const SocketInContext &socket, + const void *condition) + { + const int index = *static_cast(condition); + return socket->index() == index + 1; + } + + static bool menu_switch__is_socket_selected(const SocketInContext &socket, const void *condition) + { + const NodeMenuSwitch &storage = *static_cast( + socket->owner_node().storage); + const int menu_value = *static_cast(condition); + const NodeEnumItem &item = storage.enum_definition.items_array[socket->index() - 1]; + return menu_value == item.identifier; + } + + void push_usage_task(const SocketInContext &socket) + { + usage_tasks_.push(socket); + } + + void push_value_task(const SocketInContext &socket) + { + value_tasks_.push(socket); + } + + void ensure_animation_data_processed(const bNodeTree &tree) + { + if (!trees_with_handled_animation_data_.add(&tree)) { + return; + } + if (!tree.adt) { + return; + } + + static std::regex pattern(R"#(nodes\["(.*)"\].inputs\[(\d+)\].default_value)#"); + MultiValueMap animated_inputs_by_node_name; + auto handle_rna_path = [&](const char *rna_path) { + std::cmatch match; + if (!std::regex_match(rna_path, match, pattern)) { + return; + } + const StringRef node_name{match[1].first, match[1].second - match[1].first}; + const int socket_index = std::stoi(match[2]); + animated_inputs_by_node_name.add(node_name, socket_index); + }; + + /* Gather all inputs controlled by fcurves. */ + if (tree.adt->action) { + animrig::foreach_fcurve_in_action_slot( + tree.adt->action->wrap(), tree.adt->slot_handle, [&](const FCurve &fcurve) { + handle_rna_path(fcurve.rna_path); + }); + } + /* Gather all inputs controlled by drivers. */ + LISTBASE_FOREACH (const FCurve *, driver, &tree.adt->drivers) { + handle_rna_path(driver->rna_path); + } + + /* Actually find the #bNodeSocket for each controlled input. */ + if (!animated_inputs_by_node_name.is_empty()) { + for (const bNode *node : tree.all_nodes()) { + const Span animated_inputs = animated_inputs_by_node_name.lookup(node->name); + for (const int socket_index : animated_inputs) { + const bNodeSocket &socket = node->input_socket(socket_index); + animated_sockets_.add(&socket); + } + } + } + } +}; + +Array infer_all_input_sockets_usage(const bNodeTree &tree) +{ + tree.ensure_topology_cache(); + const Span all_input_sockets = tree.all_input_sockets(); + Array all_usages(all_input_sockets.size()); + + SocketUsageInferencer inferencer{tree, std::nullopt}; + inferencer.mark_top_level_node_outputs_as_used(); + + for (const int i : all_input_sockets.index_range()) { + const bNodeSocket &socket = *all_input_sockets[i]; + all_usages[i] = inferencer.is_socket_used({nullptr, &socket}); + } + + return all_usages; +} + +void infer_group_interface_inputs_usage(const bNodeTree &group, + const Span group_input_values, + const MutableSpan r_input_usages) +{ + SocketUsageInferencer inferencer{group, group_input_values}; + + r_input_usages.fill(false); + for (const bNode *node : group.group_input_nodes()) { + for (const int i : group.interface_inputs().index_range()) { + const bNodeSocket &socket = node->output_socket(i); + r_input_usages[i] |= inferencer.is_socket_used({nullptr, &socket}); + } + } +} + +void infer_group_interface_inputs_usage(const bNodeTree &group, + Span input_sockets, + MutableSpan r_input_usages) +{ + BLI_assert(group.interface_inputs().size() == input_sockets.size()); + + AlignedBuffer<1024, 8> allocator_buffer; + LinearAllocator<> allocator; + allocator.provide_buffer(allocator_buffer); + + Array input_values(input_sockets.size()); + for (const int i : input_sockets.index_range()) { + const bNodeSocket &socket = *input_sockets[i]; + if (socket.is_directly_linked()) { + continue; + } + + const bke::bNodeSocketType &stype = *socket.typeinfo; + const CPPType *base_type = stype.base_cpp_type; + if (base_type == nullptr) { + continue; + } + void *value = allocator.allocate(base_type->size(), base_type->alignment()); + stype.get_base_cpp_value(socket.default_value, value); + input_values[i] = GPointer(base_type, value); + } + + infer_group_interface_inputs_usage(group, input_values, r_input_usages); + + for (GPointer &value : input_values) { + if (const void *data = value.get()) { + value.type()->destruct(const_cast(data)); + } + } +} + +void infer_group_interface_inputs_usage(const bNodeTree &group, + const IDProperty *properties, + MutableSpan r_input_usages) +{ + const int inputs_num = group.interface_inputs().size(); + Array input_values(inputs_num); + ResourceScope scope; + nodes::get_geometry_nodes_input_base_values(group, properties, scope, input_values); + nodes::socket_usage_inference::infer_group_interface_inputs_usage( + group, input_values, r_input_usages); +} + +} // namespace blender::nodes::socket_usage_inference