From 8ec9c62d3ea8d8f00a375c01e7533a080f7476c5 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 3 Apr 2025 15:44:06 +0200 Subject: [PATCH] Geometry Nodes: add Closures and Bundles behind experimental feature flag This implements bundles and closures which are described in more detail in this blog post: https://code.blender.org/2024/11/geometry-nodes-workshop-october-2024/ tl;dr: * Bundles are containers that allow storing multiple socket values in a single value. Each value in the bundle is identified by a name. Bundles can be nested. * Closures are functions that are created with the Closure Zone and can be evaluated with the Evaluate Closure node. To use the patch, the `Bundle and Closure Nodes` experimental feature has to be enabled. This is necessary, because these features are not fully done yet and still need iterations to improve the workflow before they can be officially released. These iterations are easier to do in `main` than in a separate branch though. That's because this patch is quite large and somewhat prone to merge conflicts. Also other work we want to do, depends on this. This adds the following new nodes: * Combine Bundle: can pack multiple values into one. * Separate Bundle: extracts values from a bundle. * Closure Zone: outputs a closure zone for use in the `Evaluate Closure` node. * Evaluate Closure: evaluates the passed in closure. Things that will be added soon after this lands: * Fields in bundles and closures. The way this is done changes with #134811, so I rather implement this once both are in `main`. * UI features for keeping sockets in sync (right now there are warnings only). One bigger issue is the limited support for lazyness. For example, all inputs of a Combine Bundle node will be evaluated, even if they are not all needed. The same is true for all captured values of a closure. This is a deeper limitation that needs to be resolved at some point. This will likely be done after an initial version of this patch is done. Pull Request: https://projects.blender.org/blender/blender/pulls/128340 --- .../datafiles/userdef/userdef_default_theme.c | 1 + scripts/startup/bl_operators/node.py | 12 + scripts/startup/bl_ui/node_add_menu.py | 7 + .../startup/bl_ui/node_add_menu_geometry.py | 6 + scripts/startup/bl_ui/space_userpref.py | 1 + source/blender/blenkernel/BKE_bake_items.hh | 18 + .../blenkernel/BKE_compute_contexts.hh | 28 + .../blenkernel/BKE_node_legacy_types.hh | 5 + .../blenkernel/BKE_node_tree_interface.hh | 4 + .../BKE_node_tree_reference_lifetimes.hh | 14 +- .../blenkernel/intern/bake_items_serialize.cc | 52 ++ .../blenkernel/intern/bake_items_socket.cc | 123 ++- .../blenkernel/intern/compute_contexts.cc | 30 + source/blender/blenkernel/intern/cpp_types.cc | 7 + .../intern/geometry_nodes_bundle.cc | 146 ++++ source/blender/blenkernel/intern/node.cc | 82 +- .../blenkernel/intern/node_socket_value.cc | 26 + .../intern/node_tree_reference_lifetimes.cc | 216 +++++- .../blenloader/intern/versioning_userdef.cc | 4 + source/blender/depsgraph/CMakeLists.txt | 1 + .../editors/geometry/node_group_operator.cc | 2 + source/blender/editors/include/UI_icons.hh | 2 + .../blender/editors/include/UI_resources.hh | 1 + .../editors/interface/interface_icons.cc | 4 + source/blender/editors/interface/resources.cc | 3 + .../interface_template_node_inputs.cc | 2 +- source/blender/editors/object/CMakeLists.txt | 1 + .../editors/space_action/CMakeLists.txt | 1 + source/blender/editors/space_node/drawnode.cc | 31 +- .../blender/editors/space_node/node_draw.cc | 55 +- .../editors/space_node/node_relationships.cc | 2 + source/blender/editors/util/ed_viewer_path.cc | 9 + .../FN_lazy_function_graph_executor.hh | 5 + .../intern/lazy_function_graph_executor.cc | 13 + source/blender/makesdna/DNA_node_types.h | 111 +++ source/blender/makesdna/DNA_userdef_types.h | 5 +- .../makesrna/intern/rna_node_socket.cc | 58 ++ .../blender/makesrna/intern/rna_nodetree.cc | 269 +++++++ source/blender/makesrna/intern/rna_userdef.cc | 10 + source/blender/modifiers/intern/MOD_nodes.cc | 9 +- source/blender/nodes/CMakeLists.txt | 8 + source/blender/nodes/NOD_geometry_exec.hh | 14 +- .../nodes/NOD_geometry_nodes_bundle.hh | 66 ++ .../nodes/NOD_geometry_nodes_bundle_fwd.hh | 14 + .../nodes/NOD_geometry_nodes_closure.hh | 119 +++ .../nodes/NOD_geometry_nodes_closure_eval.hh | 38 + .../nodes/NOD_geometry_nodes_closure_fwd.hh | 14 + .../nodes/NOD_geometry_nodes_lazy_function.hh | 46 ++ .../blender/nodes/NOD_geometry_nodes_log.hh | 27 + .../blender/nodes/NOD_socket_declarations.hh | 36 + .../blender/nodes/NOD_socket_interface_key.hh | 32 + source/blender/nodes/NOD_socket_items_ui.hh | 2 + source/blender/nodes/geometry/CMakeLists.txt | 4 + .../nodes/geometry/include/NOD_geo_bundle.hh | 181 +++++ .../nodes/geometry/include/NOD_geo_closure.hh | 339 +++++++++ .../nodes/geometry/include/NOD_geo_repeat.hh | 4 +- .../geometry/include/NOD_geo_simulation.hh | 4 +- .../nodes/geometry/node_geometry_tree.cc | 34 +- .../nodes/geometry/nodes/node_geo_bake.cc | 2 +- .../nodes/geometry/nodes/node_geo_closure.cc | 237 ++++++ .../geometry/nodes/node_geo_combine_bundle.cc | 157 ++++ .../nodes/node_geo_evaluate_closure.cc | 163 ++++ .../geometry/nodes/node_geo_index_switch.cc | 4 +- .../geometry/nodes/node_geo_menu_switch.cc | 4 +- .../nodes/node_geo_separate_bundle.cc | 174 +++++ .../geometry/nodes/node_geo_simulation.cc | 4 +- .../nodes/geometry/nodes/node_geo_switch.cc | 4 +- .../nodes/intern/geometry_nodes_bundle.cc | 149 ++++ .../nodes/intern/geometry_nodes_closure.cc | 31 + .../intern/geometry_nodes_closure_zone.cc | 717 ++++++++++++++++++ .../nodes/intern/geometry_nodes_execute.cc | 9 +- ...try_nodes_foreach_geometry_element_zone.cc | 2 +- .../intern/geometry_nodes_lazy_function.cc | 253 ++++-- .../nodes/intern/geometry_nodes_log.cc | 38 + .../intern/geometry_nodes_repeat_zone.cc | 2 +- source/blender/nodes/intern/node_common.cc | 8 + .../blender/nodes/intern/node_declaration.cc | 8 +- source/blender/nodes/intern/node_register.cc | 20 + source/blender/nodes/intern/node_socket.cc | 41 + .../nodes/intern/node_socket_declarations.cc | 96 +++ 80 files changed, 4317 insertions(+), 164 deletions(-) create mode 100644 source/blender/blenkernel/intern/geometry_nodes_bundle.cc create mode 100644 source/blender/nodes/NOD_geometry_nodes_bundle.hh create mode 100644 source/blender/nodes/NOD_geometry_nodes_bundle_fwd.hh create mode 100644 source/blender/nodes/NOD_geometry_nodes_closure.hh create mode 100644 source/blender/nodes/NOD_geometry_nodes_closure_eval.hh create mode 100644 source/blender/nodes/NOD_geometry_nodes_closure_fwd.hh create mode 100644 source/blender/nodes/NOD_socket_interface_key.hh create mode 100644 source/blender/nodes/geometry/include/NOD_geo_bundle.hh create mode 100644 source/blender/nodes/geometry/include/NOD_geo_closure.hh create mode 100644 source/blender/nodes/geometry/nodes/node_geo_closure.cc create mode 100644 source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc create mode 100644 source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc create mode 100644 source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc create mode 100644 source/blender/nodes/intern/geometry_nodes_bundle.cc create mode 100644 source/blender/nodes/intern/geometry_nodes_closure.cc create mode 100644 source/blender/nodes/intern/geometry_nodes_closure_zone.cc diff --git a/release/datafiles/userdef/userdef_default_theme.c b/release/datafiles/userdef/userdef_default_theme.c index f8a05d2f74c..4d0fa75ff37 100644 --- a/release/datafiles/userdef/userdef_default_theme.c +++ b/release/datafiles/userdef/userdef_default_theme.c @@ -901,6 +901,7 @@ const bTheme U_theme_default = { .node_zone_simulation = RGBA(0x66416233), .node_zone_repeat = RGBA(0x76512f33), .node_zone_foreach_geometry_element = RGBA(0x33527f33), + .node_zone_closure = RGBA(0x527F3333), .movie = RGBA(0x0f0f0fcc), .gp_vertex_size = 3, .gp_vertex = RGBA(0x97979700), diff --git a/scripts/startup/bl_operators/node.py b/scripts/startup/bl_operators/node.py index 512ac312c67..5bb5bba8940 100644 --- a/scripts/startup/bl_operators/node.py +++ b/scripts/startup/bl_operators/node.py @@ -233,6 +233,17 @@ class NODE_OT_add_foreach_geometry_element_zone(NodeAddZoneOperator, Operator): add_default_geometry_link = False +class NODE_OT_add_closure_zone(NodeAddZoneOperator, Operator): + """Add a Closure zone""" + bl_idname = "node.add_closure_zone" + bl_label = "Add Closure Zone" + bl_options = {'REGISTER', 'UNDO'} + + input_node_type = "GeometryNodeClosureInput" + output_node_type = "GeometryNodeClosureOutput" + add_default_geometry_link = False + + class NODE_OT_collapse_hide_unused_toggle(Operator): """Toggle collapsed nodes and hide unused sockets""" bl_idname = "node.collapse_hide_unused_toggle" @@ -672,6 +683,7 @@ classes = ( NODE_OT_add_simulation_zone, NODE_OT_add_repeat_zone, NODE_OT_add_foreach_geometry_element_zone, + NODE_OT_add_closure_zone, NODE_OT_collapse_hide_unused_toggle, NODE_OT_interface_item_new, NODE_OT_interface_item_duplicate, diff --git a/scripts/startup/bl_ui/node_add_menu.py b/scripts/startup/bl_ui/node_add_menu.py index 06b04aea6bd..286ab0af242 100644 --- a/scripts/startup/bl_ui/node_add_menu.py +++ b/scripts/startup/bl_ui/node_add_menu.py @@ -88,6 +88,13 @@ def add_foreach_geometry_element_zone(layout, label): return props +def add_closure_zone(layout, label): + props = layout.operator( + "node.add_closure_zone", text=label, text_ctxt=i18n_contexts.default) + props.use_transform = True + return props + + class NODE_MT_category_layout(Menu): bl_idname = "NODE_MT_category_layout" bl_label = "Layout" diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index 32738d33532..3cbf487eb22 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -629,12 +629,18 @@ class NODE_MT_category_GEO_UTILITIES(Menu): layout.menu("NODE_MT_category_GEO_UTILITIES_ROTATION") layout.menu("NODE_MT_category_GEO_UTILITIES_DEPRECATED") layout.separator() + if context.preferences.experimental.use_bundle_and_closure_nodes: + node_add_menu.add_closure_zone(layout, label="Closure") + node_add_menu.add_node_type(layout, "GeometryNodeEvaluateClosure") node_add_menu.add_foreach_geometry_element_zone(layout, label="For Each Element") node_add_menu.add_node_type(layout, "GeometryNodeIndexSwitch") node_add_menu.add_node_type(layout, "GeometryNodeMenuSwitch") node_add_menu.add_node_type(layout, "FunctionNodeRandomValue") node_add_menu.add_repeat_zone(layout, label="Repeat") node_add_menu.add_node_type(layout, "GeometryNodeSwitch") + if context.preferences.experimental.use_bundle_and_closure_nodes: + node_add_menu.add_node_type(layout, "GeometryNodeCombineBundle") + node_add_menu.add_node_type(layout, "GeometryNodeSeparateBundle") node_add_menu.draw_assets_for_catalog(layout, self.bl_label) diff --git a/scripts/startup/bl_ui/space_userpref.py b/scripts/startup/bl_ui/space_userpref.py index 08380c02a0e..691447e3e45 100644 --- a/scripts/startup/bl_ui/space_userpref.py +++ b/scripts/startup/bl_ui/space_userpref.py @@ -2851,6 +2851,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel): ("blender/blender/projects/10", "Pipeline, Assets & IO Project Page")), ({"property": "use_new_volume_nodes"}, ("blender/blender/issues/103248", "#103248")), ({"property": "use_shader_node_previews"}, ("blender/blender/issues/110353", "#110353")), + ({"property": "use_bundle_and_closure_nodes"}, ("blender/blender/issues/134029", "#134029")), ), ) diff --git a/source/blender/blenkernel/BKE_bake_items.hh b/source/blender/blenkernel/BKE_bake_items.hh index a289bfc45c4..faad866aeb4 100644 --- a/source/blender/blenkernel/BKE_bake_items.hh +++ b/source/blender/blenkernel/BKE_bake_items.hh @@ -14,6 +14,8 @@ #include "BKE_geometry_set.hh" #include "BKE_volume_grid_fwd.hh" +#include "NOD_socket_interface_key.hh" + namespace blender::bke::bake { /** @@ -143,4 +145,20 @@ class StringBakeItem : public BakeItem { void count_memory(MemoryCounter &memory) const override; }; +/** + * \note It's not possible to use #PrimitiveBakeItem for bundles in general, because the items in + * the bundle also have to be converted to their bakeable form. This is especially important when + * serializing the bake. + */ +class BundleBakeItem : public BakeItem { + public: + struct Item { + nodes::SocketInterfaceKey key; + std::string socket_idname; + std::unique_ptr value; + }; + + Vector items; +}; + } // namespace blender::bke::bake diff --git a/source/blender/blenkernel/BKE_compute_contexts.hh b/source/blender/blenkernel/BKE_compute_contexts.hh index d6a33a2e9f0..58305e84963 100644 --- a/source/blender/blenkernel/BKE_compute_contexts.hh +++ b/source/blender/blenkernel/BKE_compute_contexts.hh @@ -156,6 +156,34 @@ class ForeachGeometryElementZoneComputeContext : public ComputeContext { void print_current_in_line(std::ostream &stream) const override; }; +class EvaluateClosureComputeContext : public ComputeContext { + private: + static constexpr const char *s_static_type = "CLOSURE"; + + int32_t node_id_; + + /** + * Extra information that might not always be available. + */ + const bNode *evaluate_node_ = nullptr; + + public: + EvaluateClosureComputeContext(const ComputeContext *parent, int32_t node_id); + EvaluateClosureComputeContext(const ComputeContext *parent, const bNode &evaluate_node); + + int32_t node_id() const + { + return node_id_; + } + const bNode *evaluate_node() const + { + return evaluate_node_; + } + + private: + void print_current_in_line(std::ostream &stream) const override; +}; + class OperatorComputeContext : public ComputeContext { private: static constexpr const char *s_static_type = "OPERATOR"; diff --git a/source/blender/blenkernel/BKE_node_legacy_types.hh b/source/blender/blenkernel/BKE_node_legacy_types.hh index 6529d3411d2..600c722852c 100644 --- a/source/blender/blenkernel/BKE_node_legacy_types.hh +++ b/source/blender/blenkernel/BKE_node_legacy_types.hh @@ -517,6 +517,11 @@ #define GEO_NODE_MERGE_LAYERS 2150 #define GEO_NODE_INPUT_COLLECTION 2151 #define GEO_NODE_INPUT_OBJECT 2152 +#define GEO_NODE_COMBINE_BUNDLE 2153 +#define GEO_NODE_SEPARATE_BUNDLE 2154 +#define GEO_NODE_CLOSURE_OUTPUT 2155 +#define GEO_NODE_EVALUATE_CLOSURE 2156 +#define GEO_NODE_CLOSURE_INPUT 2157 /** \} */ diff --git a/source/blender/blenkernel/BKE_node_tree_interface.hh b/source/blender/blenkernel/BKE_node_tree_interface.hh index 21f2a4a57cc..8ab97bf99dc 100644 --- a/source/blender/blenkernel/BKE_node_tree_interface.hh +++ b/source/blender/blenkernel/BKE_node_tree_interface.hh @@ -179,6 +179,8 @@ static const bNodeSocketStaticTypeInfo node_socket_subtypes[] = { {"NodeSocketTexture", "NodeTreeInterfaceSocketTexture", SOCK_TEXTURE, PROP_NONE}, {"NodeSocketMaterial", "NodeTreeInterfaceSocketMaterial", SOCK_MATERIAL, PROP_NONE}, {"NodeSocketMenu", "NodeTreeInterfaceSocketMenu", SOCK_MENU, PROP_NONE}, + {"NodeSocketBundle", "NodeTreeInterfaceSocketBundle", SOCK_BUNDLE, PROP_NONE}, + {"NodeSocketClosure", "NodeTreeInterfaceSocketClosure", SOCK_CLOSURE, PROP_NONE}, }; template bool socket_data_to_static_type(const eNodeSocketDatatype type, const Fn &fn) @@ -228,6 +230,8 @@ template bool socket_data_to_static_type(const eNodeSocketDatatype case SOCK_SHADER: case SOCK_MATRIX: case SOCK_GEOMETRY: + case SOCK_BUNDLE: + case SOCK_CLOSURE: return true; } return false; diff --git a/source/blender/blenkernel/BKE_node_tree_reference_lifetimes.hh b/source/blender/blenkernel/BKE_node_tree_reference_lifetimes.hh index a72995ac0c3..4d6e326a424 100644 --- a/source/blender/blenkernel/BKE_node_tree_reference_lifetimes.hh +++ b/source/blender/blenkernel/BKE_node_tree_reference_lifetimes.hh @@ -44,11 +44,13 @@ enum class ReferenceSetType { * input. In such cases, the caller may provide a set of attributes that should be propagated. */ GroupOutputData, + ClosureOutputData, /** * Field inputs may require attributes that need to be propagated from other geometry inputs to * the node that evaluates the field. */ GroupInputReferenceSet, + ClosureInputReferenceSet, /** * Locally created anonymous attributes (like with the Capture Attribute node) need to be * propagated to the nodes that use them or even to the group output. @@ -61,7 +63,7 @@ struct ReferenceSetInfo { union { /** Used for group interface sockets. */ int index; - /** Used for local sockets. */ + /** Used for local and closure sockets. */ const bNodeSocket *socket; }; @@ -79,7 +81,10 @@ struct ReferenceSetInfo { ReferenceSetInfo(ReferenceSetType type, const bNodeSocket *socket) : type(type), socket(socket) { - BLI_assert(ELEM(type, ReferenceSetType::LocalReferenceSet)); + BLI_assert(ELEM(type, + ReferenceSetType::LocalReferenceSet, + ReferenceSetType::ClosureInputReferenceSet, + ReferenceSetType::ClosureOutputData)); } friend std::ostream &operator<<(std::ostream &stream, const ReferenceSetInfo &info); @@ -102,4 +107,9 @@ struct ReferenceLifetimesInfo { bool analyse_reference_lifetimes(bNodeTree &tree); +/** The socket type allows storing references to data stored elsewhere. */ +bool can_contain_reference(eNodeSocketDatatype socket_type); +/** The socket type allows storing data that may be referenced elsewhere. */ +bool can_contain_referenced_data(eNodeSocketDatatype socket_type); + } // namespace blender::bke::node_tree_reference_lifetimes diff --git a/source/blender/blenkernel/intern/bake_items_serialize.cc b/source/blender/blenkernel/intern/bake_items_serialize.cc index 0fad4fe4179..d51491db5fe 100644 --- a/source/blender/blenkernel/intern/bake_items_serialize.cc +++ b/source/blender/blenkernel/intern/bake_items_serialize.cc @@ -1507,6 +1507,20 @@ static void serialize_bake_item(const BakeItem &item, auto io_data = serialize_primitive_value(data_type, primitive_state_item->value()); r_io_item.append("data", std::move(io_data)); } + else if (const auto *bundle_state_item = dynamic_cast(&item)) { + r_io_item.append_str("type", "BUNDLE"); + ArrayValue &io_items = *r_io_item.append_array("items"); + for (const BundleBakeItem::Item &item : bundle_state_item->items) { + DictionaryValue &io_bundle_item = *io_items.append_dict(); + ArrayValue &io_key = *io_bundle_item.append_array("key"); + for (const std::string &identifier : item.key.identifiers()) { + io_key.append_str(identifier); + } + io_bundle_item.append_str("socket_idname", item.socket_idname); + io::serialize::DictionaryValue &io_bundle_item_value = *io_bundle_item.append_dict("value"); + serialize_bake_item(*item.value, blob_writer, blob_sharing, io_bundle_item_value); + } + } } static std::unique_ptr deserialize_bake_item(const DictionaryValue &io_item, @@ -1592,6 +1606,44 @@ static std::unique_ptr deserialize_bake_item(const DictionaryValue &io return std::make_unique(std::move(str)); } } + if (*state_item_type == StringRef("BUNDLE")) { + const ArrayValue *io_items = io_item.lookup_array("items"); + if (!io_items) { + return {}; + } + auto bundle = std::make_unique(); + for (const auto &io_item_ : io_items->elements()) { + const DictionaryValue *io_item = io_item_->as_dictionary_value(); + if (!io_item) { + return {}; + } + const ArrayValue *io_key = io_item->lookup_array("key"); + if (!io_key) { + return {}; + } + Vector key; + for (const auto &io_key_value : io_key->elements()) { + const StringValue *io_key_string = io_key_value->as_string_value(); + if (!io_key_string) { + return {}; + } + key.append(io_key_string->value()); + } + const std::optional socket_idname = io_item->lookup_str("socket_idname"); + if (!socket_idname) { + return {}; + } + const DictionaryValue *io_item_value = io_item->lookup_dict("value"); + std::unique_ptr value = deserialize_bake_item( + *io_item_value, blob_reader, blob_sharing); + if (!value) { + return {}; + } + bundle->items.append(BundleBakeItem::Item{ + nodes::SocketInterfaceKey{std::move(key)}, *socket_idname, std::move(value)}); + } + return bundle; + } const std::shared_ptr *io_data = io_item.lookup("data"); if (!io_data) { return {}; diff --git a/source/blender/blenkernel/intern/bake_items_socket.cc b/source/blender/blenkernel/intern/bake_items_socket.cc index db9d7934c31..5b4454859b1 100644 --- a/source/blender/blenkernel/intern/bake_items_socket.cc +++ b/source/blender/blenkernel/intern/bake_items_socket.cc @@ -8,6 +8,8 @@ #include "BKE_node_socket_value.hh" #include "BKE_volume_grid.hh" +#include "NOD_geometry_nodes_bundle.hh" + namespace blender::bke::bake { static void capture_field_on_geometry_components(GeometrySet &geometry, @@ -37,6 +39,72 @@ static void capture_field_on_geometry_components(GeometrySet &geometry, } } +static std::unique_ptr move_common_socket_value_to_bake_item( + const bNodeSocketType &stype, + void *socket_value, + std::optional name, + Vector &r_geometry_bake_items) +{ + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(stype.type); + switch (socket_type) { + case SOCK_GEOMETRY: { + GeometrySet &geometry = *static_cast(socket_value); + auto item = std::make_unique(std::move(geometry)); + r_geometry_bake_items.append(item.get()); + return item; + } + case SOCK_STRING: { + auto &value_variant = *static_cast(socket_value); + return std::make_unique(value_variant.extract()); + } + case SOCK_FLOAT: + case SOCK_VECTOR: + case SOCK_INT: + case SOCK_BOOLEAN: + case SOCK_ROTATION: + case SOCK_MATRIX: + case SOCK_RGBA: { + auto &value_variant = *static_cast(socket_value); + if (value_variant.is_context_dependent_field()) { + /* Not supported here because it's not known which geometry this field belongs to. */ + return {}; + } +#ifdef WITH_OPENVDB + if (value_variant.is_volume_grid()) { + bke::GVolumeGrid grid = value_variant.get(); + if (name) { + grid.get_for_write().set_name(*name); + } + return std::make_unique( + std::make_unique(std::move(grid))); + } +#endif + value_variant.convert_to_single(); + GPointer value = value_variant.get_single_ptr(); + return std::make_unique(*value.type(), value.get()); + } + case SOCK_BUNDLE: { + auto &value_variant = *static_cast(socket_value); + nodes::BundlePtr bundle_ptr = value_variant.extract(); + auto bundle_bake_item = std::make_unique(); + if (bundle_ptr) { + const nodes::Bundle &bundle = *bundle_ptr; + for (const nodes::Bundle::StoredItem &bundle_item : bundle.items()) { + if (std::unique_ptr bake_item = move_common_socket_value_to_bake_item( + *bundle_item.type, bundle_item.value, std::nullopt, r_geometry_bake_items)) + { + bundle_bake_item->items.append(BundleBakeItem::Item{ + bundle_item.key, bundle_item.type->idname, std::move(bake_item)}); + } + } + } + return bundle_bake_item; + } + default: + return {}; + } +} + Array> move_socket_values_to_bake_items(const Span socket_values, const BakeSocketConfig &config, BakeDataBlockMap *data_block_map) @@ -63,6 +131,7 @@ Array> move_socket_values_to_bake_items(const Span> move_socket_values_to_bake_items(const Span(socket_value); - bake_items[i] = std::make_unique(value_variant.extract()); + bake_items[i] = move_common_socket_value_to_bake_item( + stype, socket_value, config.names[i], geometry_bake_items); break; } case SOCK_FLOAT: @@ -95,21 +164,17 @@ Array> move_socket_values_to_bake_items(const Span(attribute_name); } -#ifdef WITH_OPENVDB - else if (value_variant.is_volume_grid()) { - bke::GVolumeGrid grid = value_variant.get(); - grid.get_for_write().set_name(config.names[i]); - bake_items[i] = std::make_unique( - std::make_unique(std::move(grid))); - } -#endif else { - value_variant.convert_to_single(); - GPointer value = value_variant.get_single_ptr(); - bake_items[i] = std::make_unique(*value.type(), value.get()); + bake_items[i] = move_common_socket_value_to_bake_item( + stype, socket_value, config.names[i], geometry_bake_items); } break; } + case SOCK_BUNDLE: { + bake_items[i] = move_common_socket_value_to_bake_item( + stype, socket_value, config.names[i], geometry_bake_items); + break; + } default: break; } @@ -129,6 +194,9 @@ Array> move_socket_values_to_bake_items(const Span> move_socket_values_to_bake_items(const Span(&bake_item)) { + if (!make_attribute_field) { + return false; + } std::shared_ptr attribute_field = make_attribute_field(base_type); r_attribute_map.add(item->name(), attribute_field->attribute_name()); fn::GField field{attribute_field}; @@ -193,6 +264,32 @@ Array> move_socket_values_to_bake_items(const Span(&bake_item)) { + nodes::BundlePtr bundle_ptr = nodes::Bundle::create(); + nodes::Bundle &bundle = const_cast(*bundle_ptr); + for (const BundleBakeItem::Item &item : item->items) { + const bNodeSocketType *stype = node_socket_type_find(item.socket_idname); + if (!stype) { + return false; + } + if (!stype->geometry_nodes_cpp_type) { + return false; + } + BUFFER_FOR_CPP_TYPE_VALUE(*stype->geometry_nodes_cpp_type, buffer); + if (!copy_bake_item_to_socket_value( + *item.value, eNodeSocketDatatype(stype->type), {}, r_attribute_map, buffer)) + { + return false; + } + bundle.add(item.key, *stype, buffer); + stype->geometry_nodes_cpp_type->destruct(buffer); + } + new (r_value) SocketValueVariant(std::move(bundle_ptr)); + return true; + } + return false; + } default: return false; } diff --git a/source/blender/blenkernel/intern/compute_contexts.cc b/source/blender/blenkernel/intern/compute_contexts.cc index 8ec54d32f15..d529b070887 100644 --- a/source/blender/blenkernel/intern/compute_contexts.cc +++ b/source/blender/blenkernel/intern/compute_contexts.cc @@ -144,6 +144,36 @@ void ForeachGeometryElementZoneComputeContext::print_current_in_line(std::ostrea stream << "Foreach Geometry Element Zone ID: " << output_node_id_; } +EvaluateClosureComputeContext::EvaluateClosureComputeContext(const ComputeContext *parent, + const int32_t node_id) + : ComputeContext(s_static_type, parent), node_id_(node_id) +{ + /* Mix static type and node id into a single buffer so that only a single call to #mix_in is + * necessary. */ + const int type_size = strlen(s_static_type); + const int buffer_size = type_size + 1 + sizeof(int32_t); + DynamicStackBuffer<64, 8> buffer_owner(buffer_size, 8); + char *buffer = static_cast(buffer_owner.buffer()); + memcpy(buffer, s_static_type, type_size + 1); + memcpy(buffer + type_size + 1, &node_id_, sizeof(int32_t)); + hash_.mix_in(buffer, buffer_size); +} + +EvaluateClosureComputeContext::EvaluateClosureComputeContext(const ComputeContext *parent, + const bNode &node) + : EvaluateClosureComputeContext(parent, node.identifier) +{ + evaluate_node_ = &node; +} + +void EvaluateClosureComputeContext::print_current_in_line(std::ostream &stream) const +{ + if (evaluate_node_ != nullptr) { + stream << "Evaluate Closure: " << evaluate_node_->name; + return; + } +} + OperatorComputeContext::OperatorComputeContext() : OperatorComputeContext(nullptr) {} OperatorComputeContext::OperatorComputeContext(const ComputeContext *parent) diff --git a/source/blender/blenkernel/intern/cpp_types.cc b/source/blender/blenkernel/intern/cpp_types.cc index c1e7d193878..6999191eb4d 100644 --- a/source/blender/blenkernel/intern/cpp_types.cc +++ b/source/blender/blenkernel/intern/cpp_types.cc @@ -11,6 +11,9 @@ #include "BKE_instances.hh" #include "BKE_node_socket_value.hh" +#include "NOD_geometry_nodes_bundle.hh" +#include "NOD_geometry_nodes_closure.hh" + #include "DNA_meshdata_types.h" struct Tex; @@ -29,6 +32,8 @@ BLI_CPP_TYPE_MAKE(Image *, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(Material *, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(MStringProperty, CPPTypeFlags::None); +BLI_CPP_TYPE_MAKE(blender::nodes::BundlePtr, CPPTypeFlags::None); +BLI_CPP_TYPE_MAKE(blender::nodes::ClosurePtr, CPPTypeFlags::None); BLI_CPP_TYPE_MAKE(blender::bke::GeometryNodesReferenceSet, CPPTypeFlags::None); BLI_CPP_TYPE_MAKE(blender::bke::SocketValueVariant, CPPTypeFlags::Printable); @@ -50,6 +55,8 @@ void BKE_cpp_types_init() BLI_CPP_TYPE_REGISTER(Material *); BLI_CPP_TYPE_REGISTER(MStringProperty); + BLI_CPP_TYPE_REGISTER(blender::nodes::BundlePtr); + BLI_CPP_TYPE_REGISTER(blender::nodes::ClosurePtr); BLI_CPP_TYPE_REGISTER(blender::bke::GeometryNodesReferenceSet); BLI_CPP_TYPE_REGISTER(blender::bke::SocketValueVariant); diff --git a/source/blender/blenkernel/intern/geometry_nodes_bundle.cc b/source/blender/blenkernel/intern/geometry_nodes_bundle.cc new file mode 100644 index 00000000000..9cb2dee2887 --- /dev/null +++ b/source/blender/blenkernel/intern/geometry_nodes_bundle.cc @@ -0,0 +1,146 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_type_conversions.hh" +#include "NOD_geometry_nodes_bundle.hh" + +namespace blender::bke { + +SocketInterfaceKey::SocketInterfaceKey(std::string identifier) +{ + identifiers_.append(std::move(identifier)); +} + +SocketInterfaceKey::SocketInterfaceKey(Vector identifiers) + : identifiers_(std::move(identifiers)) +{ + BLI_assert(!identifiers_.is_empty()); +} + +Span SocketInterfaceKey::identifiers() const +{ + return identifiers_; +} + +bool SocketInterfaceKey::matches(const SocketInterfaceKey &other) const +{ + for (const std::string &identifier : other.identifiers_) { + if (identifiers_.contains(identifier)) { + return true; + } + } + return false; +} + +Bundle::Bundle() = default; + +Bundle::~Bundle() +{ + for (StoredItem &item : items_) { + item.type->geometry_nodes_cpp_type->destruct(item.value); + } + for (void *buffer : buffers_) { + MEM_freeN(buffer); + } +} + +Bundle::Bundle(const Bundle &other) +{ + for (const StoredItem &item : other.items_) { + this->add_new(item.key, *item.type, item.value); + } +} + +Bundle::Bundle(Bundle &&other) noexcept + : items_(std::move(other.items_)), buffers_(std::move(other.buffers_)) +{ +} + +Bundle &Bundle::operator=(const Bundle &other) +{ + if (this == &other) { + return *this; + } + this->~Bundle(); + new (this) Bundle(other); + return *this; +} + +Bundle &Bundle::operator=(Bundle &&other) noexcept +{ + if (this == &other) { + return *this; + } + this->~Bundle(); + new (this) Bundle(std::move(other)); + return *this; +} + +void Bundle::add_new(SocketInterfaceKey key, const bNodeSocketType &type, const void *value) +{ + BLI_assert(!this->contains(key)); + BLI_assert(type.geometry_nodes_cpp_type); + const CPPType &cpp_type = *type.geometry_nodes_cpp_type; + void *buffer = MEM_mallocN_aligned(cpp_type.size(), cpp_type.alignment(), __func__); + cpp_type.copy_construct(value, buffer); + items_.append(StoredItem{std::move(key), &type, buffer}); + buffers_.append(buffer); +} + +bool Bundle::add(const SocketInterfaceKey &key, const bNodeSocketType &type, const void *value) +{ + if (this->contains(key)) { + return false; + } + this->add_new(key, type, value); + return true; +} + +bool Bundle::add(SocketInterfaceKey &&key, const bNodeSocketType &type, const void *value) +{ + if (this->contains(key)) { + return false; + } + this->add_new(std::move(key), type, value); + return true; +} + +std::optional Bundle::lookup(const SocketInterfaceKey &key) const +{ + for (const StoredItem &item : items_) { + if (item.key.matches(key)) { + return Item{item.type, item.value}; + } + } + return std::nullopt; +} + +bool Bundle::remove(const SocketInterfaceKey &key) +{ + const int removed_num = items_.remove_if([&key](StoredItem &item) { + if (item.key.matches(key)) { + item.type->geometry_nodes_cpp_type->destruct(item.value); + return true; + } + return false; + }); + return removed_num >= 1; +} + +bool Bundle::contains(const SocketInterfaceKey &key) const +{ + for (const StoredItem &item : items_) { + if (item.key.matches(key)) { + return true; + } + } + return false; +} + +void Bundle::delete_self() +{ + MEM_delete(this); +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 51fe2b25381..a4a9db6d976 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -68,6 +68,8 @@ #include "BKE_node_tree_update.hh" #include "BKE_preview_image.hh" #include "BKE_type_conversions.hh" +#include "NOD_geometry_nodes_bundle.hh" +#include "NOD_geometry_nodes_closure.hh" #include "RNA_access.hh" #include "RNA_define.hh" @@ -77,7 +79,9 @@ #include "NOD_common.hh" #include "NOD_composite.hh" #include "NOD_geo_bake.hh" +#include "NOD_geo_bundle.hh" #include "NOD_geo_capture_attribute.hh" +#include "NOD_geo_closure.hh" #include "NOD_geo_foreach_geometry_element.hh" #include "NOD_geo_index_switch.hh" #include "NOD_geo_menu_switch.hh" @@ -203,7 +207,11 @@ static void ntree_copy_data(Main * /*bmain*/, dst_runtime.reference_lifetimes_info = std::make_unique( *ntree_src->runtime->reference_lifetimes_info); for (ReferenceSetInfo &reference_set : dst_runtime.reference_lifetimes_info->reference_sets) { - if (ELEM(reference_set.type, ReferenceSetType::LocalReferenceSet)) { + if (ELEM(reference_set.type, + ReferenceSetType::LocalReferenceSet, + ReferenceSetType::ClosureInputReferenceSet, + ReferenceSetType::ClosureOutputData)) + { reference_set.socket = socket_map.lookup(reference_set.socket); } for (auto &socket : reference_set.potential_data_origins) { @@ -330,6 +338,8 @@ static void library_foreach_node_socket(bNodeSocket *sock, LibraryForeachIDData case SOCK_SHADER: case SOCK_GEOMETRY: case SOCK_MENU: + case SOCK_BUNDLE: + case SOCK_CLOSURE: break; } } @@ -712,6 +722,8 @@ static void write_node_socket_default_value(BlendWriter *writer, const bNodeSock break; case SOCK_SHADER: case SOCK_GEOMETRY: + case SOCK_BUNDLE: + case SOCK_CLOSURE: BLI_assert_unreachable(); break; } @@ -882,6 +894,20 @@ void node_tree_blend_write(BlendWriter *writer, bNodeTree *ntree) if (node->type_legacy == GEO_NODE_BAKE) { nodes::socket_items::blend_write(writer, *node); } + if (node->type_legacy == GEO_NODE_COMBINE_BUNDLE) { + nodes::socket_items::blend_write(writer, *node); + } + if (node->type_legacy == GEO_NODE_SEPARATE_BUNDLE) { + nodes::socket_items::blend_write(writer, *node); + } + if (node->type_legacy == GEO_NODE_CLOSURE_OUTPUT) { + nodes::socket_items::blend_write(writer, *node); + nodes::socket_items::blend_write(writer, *node); + } + if (node->type_legacy == GEO_NODE_EVALUATE_CLOSURE) { + nodes::socket_items::blend_write(writer, *node); + nodes::socket_items::blend_write(writer, *node); + } if (node->type_legacy == GEO_NODE_MENU_SWITCH) { nodes::socket_items::blend_write(writer, *node); } @@ -961,6 +987,8 @@ static bool is_node_socket_supported(const bNodeSocket *sock) case SOCK_ROTATION: case SOCK_MENU: case SOCK_MATRIX: + case SOCK_BUNDLE: + case SOCK_CLOSURE: return true; } return false; @@ -1180,6 +1208,26 @@ void node_tree_blend_read_data(BlendDataReader *reader, ID *owner_id, bNodeTree nodes::socket_items::blend_read_data(reader, *node); break; } + case GEO_NODE_COMBINE_BUNDLE: { + nodes::socket_items::blend_read_data(reader, *node); + break; + } + case GEO_NODE_CLOSURE_OUTPUT: { + nodes::socket_items::blend_read_data(reader, *node); + nodes::socket_items::blend_read_data(reader, *node); + break; + } + case GEO_NODE_EVALUATE_CLOSURE: { + nodes::socket_items::blend_read_data(reader, + *node); + nodes::socket_items::blend_read_data(reader, + *node); + break; + } + case GEO_NODE_SEPARATE_BUNDLE: { + nodes::socket_items::blend_read_data(reader, *node); + break; + } case GEO_NODE_MENU_SWITCH: { nodes::socket_items::blend_read_data(reader, *node); break; @@ -1998,6 +2046,8 @@ static void socket_id_user_increment(bNodeSocket *sock) case SOCK_CUSTOM: case SOCK_SHADER: case SOCK_GEOMETRY: + case SOCK_BUNDLE: + case SOCK_CLOSURE: break; } } @@ -2046,6 +2096,8 @@ static bool socket_id_user_decrement(bNodeSocket *sock) case SOCK_CUSTOM: case SOCK_SHADER: case SOCK_GEOMETRY: + case SOCK_BUNDLE: + case SOCK_CLOSURE: break; } return false; @@ -2104,6 +2156,8 @@ void node_modify_socket_type(bNodeTree &ntree, case SOCK_TEXTURE: case SOCK_MATERIAL: case SOCK_MENU: + case SOCK_BUNDLE: + case SOCK_CLOSURE: break; } } @@ -2247,6 +2301,10 @@ std::optional node_static_socket_type(const int type, const int s return "NodeSocketMaterial"; case SOCK_MENU: return "NodeSocketMenu"; + case SOCK_BUNDLE: + return "NodeSocketBundle"; + case SOCK_CLOSURE: + return "NodeSocketClosure"; case SOCK_CUSTOM: break; } @@ -2344,6 +2402,10 @@ std::optional node_static_socket_interface_type_new(const int typ return "NodeTreeInterfaceSocketMaterial"; case SOCK_MENU: return "NodeTreeInterfaceSocketMenu"; + case SOCK_BUNDLE: + return "NodeTreeInterfaceSocketBundle"; + case SOCK_CLOSURE: + return "NodeTreeInterfaceSocketClosure"; case SOCK_CUSTOM: break; } @@ -2385,6 +2447,10 @@ std::optional node_static_socket_label(const int type, const int return "Material"; case SOCK_MENU: return "Menu"; + case SOCK_BUNDLE: + return "Bundle"; + case SOCK_CLOSURE: + return "Closure"; case SOCK_CUSTOM: break; } @@ -2863,6 +2929,8 @@ static void *socket_value_storage(bNodeSocket &socket) case SOCK_CUSTOM: case SOCK_SHADER: case SOCK_GEOMETRY: + case SOCK_BUNDLE: + case SOCK_CLOSURE: /* Unmovable types. */ break; } @@ -4283,6 +4351,12 @@ const CPPType *socket_type_to_geo_nodes_base_cpp_type(const eNodeSocketDatatype case SOCK_MATRIX: cpp_type = &CPPType::get(); break; + case SOCK_BUNDLE: + cpp_type = &CPPType::get(); + break; + case SOCK_CLOSURE: + cpp_type = &CPPType::get(); + break; default: cpp_type = slow_socket_type_to_geo_nodes_base_cpp_type(type); break; @@ -4317,6 +4391,12 @@ std::optional geo_nodes_base_cpp_type_to_socket_type(const if (type.is()) { return SOCK_STRING; } + if (type.is()) { + return SOCK_BUNDLE; + } + if (type.is()) { + return SOCK_CLOSURE; + } return std::nullopt; } diff --git a/source/blender/blenkernel/intern/node_socket_value.cc b/source/blender/blenkernel/intern/node_socket_value.cc index 63266a68fa7..9ddb64efe5b 100644 --- a/source/blender/blenkernel/intern/node_socket_value.cc +++ b/source/blender/blenkernel/intern/node_socket_value.cc @@ -11,6 +11,8 @@ #include "BKE_node.hh" #include "BKE_node_socket_value.hh" #include "BKE_volume_grid.hh" +#include "NOD_geometry_nodes_bundle.hh" +#include "NOD_geometry_nodes_closure.hh" #include "BLI_color.hh" #include "BLI_math_rotation_types.hh" @@ -59,6 +61,12 @@ template static std::optional static_type_to_so if constexpr (is_same_any_v) { return SOCK_STRING; } + if constexpr (is_same_any_v) { + return SOCK_BUNDLE; + } + if constexpr (is_same_any_v) { + return SOCK_CLOSURE; + } return std::nullopt; } @@ -87,6 +95,10 @@ static bool static_type_is_base_socket_type(const eNodeSocketDatatype socket_typ return std::is_same_v; case SOCK_MENU: return std::is_same_v; + case SOCK_BUNDLE: + return std::is_same_v; + case SOCK_CLOSURE: + return std::is_same_v; case SOCK_CUSTOM: case SOCK_SHADER: case SOCK_OBJECT: @@ -251,6 +263,14 @@ void SocketValueVariant::store_single(const eNodeSocketDatatype socket_type, con value_.emplace(*static_cast(value)); break; } + case SOCK_BUNDLE: { + value_.emplace(*static_cast(value)); + break; + } + case SOCK_CLOSURE: { + value_.emplace(*static_cast(value)); + break; + } default: { BLI_assert_unreachable(); break; @@ -347,6 +367,10 @@ void *SocketValueVariant::allocate_single(const eNodeSocketDatatype socket_type) return value_.allocate(); case SOCK_MENU: return value_.allocate(); + case SOCK_BUNDLE: + return value_.allocate(); + case SOCK_CLOSURE: + return value_.allocate(); default: { BLI_assert_unreachable(); return nullptr; @@ -405,6 +429,8 @@ INSTANTIATE_SINGLE_AND_FIELD_AND_GRID(blender::math::Quaternion) INSTANTIATE(std::string) INSTANTIATE(fn::GField) +INSTANTIATE(blender::nodes::BundlePtr) +INSTANTIATE(blender::nodes::ClosurePtr) INSTANTIATE(float4x4) INSTANTIATE(fn::Field) diff --git a/source/blender/blenkernel/intern/node_tree_reference_lifetimes.cc b/source/blender/blenkernel/intern/node_tree_reference_lifetimes.cc index 16817acb416..0ff58064207 100644 --- a/source/blender/blenkernel/intern/node_tree_reference_lifetimes.cc +++ b/source/blender/blenkernel/intern/node_tree_reference_lifetimes.cc @@ -38,6 +38,12 @@ std::ostream &operator<<(std::ostream &stream, const ReferenceSetInfo &info) case ReferenceSetType::LocalReferenceSet: stream << "Local: " << info.socket->name; break; + case ReferenceSetType::ClosureInputReferenceSet: + stream << "Closure Input Reference: " << info.socket->name; + break; + case ReferenceSetType::ClosureOutputData: + stream << "Closure Output Data: " << info.socket->name; + break; } stream << " ("; for (const bNodeSocket *socket : info.potential_data_origins) { @@ -74,14 +80,15 @@ static bool or_into_each_other(MutableBoundedBitSpan a, MutableBoundedBitSpan b) return true; } -static bool can_contain_reference(const eNodeSocketDatatype socket_type) +bool can_contain_reference(const eNodeSocketDatatype socket_type) { - return nodes::socket_type_supports_fields(socket_type); + return nodes::socket_type_supports_fields(socket_type) || + ELEM(socket_type, SOCK_BUNDLE, SOCK_CLOSURE); } -static bool can_contain_referenced_data(const eNodeSocketDatatype socket_type) +bool can_contain_referenced_data(const eNodeSocketDatatype socket_type) { - return ELEM(socket_type, SOCK_GEOMETRY); + return ELEM(socket_type, SOCK_GEOMETRY, SOCK_BUNDLE, SOCK_CLOSURE); } static const bNodeTreeZone *get_zone_of_node_if_full(const bNodeTreeZones *zones, @@ -226,7 +233,8 @@ static Array prepare_relations_by_node(const bNode static Vector find_reference_sets( const bNodeTree &tree, const Span &relations_by_node, - Vector &r_group_output_reference_sets) + Vector &r_group_output_reference_sets, + MultiValueMap &r_output_set_sources_by_closure_zone) { Vector reference_sets; const Span interface_inputs = tree.interface_inputs(); @@ -265,6 +273,7 @@ static Vector find_reference_sets( } } } + /* Handle references created by nodes in the current tree. */ for (const bNode *node : tree.all_nodes()) { if (node->is_muted()) { continue; @@ -285,6 +294,45 @@ static Vector find_reference_sets( } } + const bNodeTreeZones *zones = tree.zones(); + if (!zones) { + return reference_sets; + } + for (const bNodeTreeZone *zone : zones->zones) { + if (zone->output_node->type_legacy != GEO_NODE_CLOSURE_OUTPUT) { + continue; + } + const auto &storage = *static_cast( + zone->output_node->storage); + const int old_reference_sets_count = reference_sets.size(); + /* Handle references coming from field inputs in the closure. */ + for (const int input_i : IndexRange(storage.input_items.items_num)) { + const bNodeSocket &socket = zone->input_node->output_socket(input_i); + if (can_contain_reference(eNodeSocketDatatype(socket.type))) { + reference_sets.append({ReferenceSetType::ClosureInputReferenceSet, &socket}); + } + } + /* Handle references required by output geometries in the closure. */ + for (const int output_i : IndexRange(storage.output_items.items_num)) { + const bNodeSocket &socket = zone->output_node->input_socket(output_i); + if (can_contain_referenced_data(eNodeSocketDatatype(socket.type))) { + r_output_set_sources_by_closure_zone.add( + zone, + reference_sets.append_and_get_index({ReferenceSetType::ClosureOutputData, &socket})); + } + } + /* All references referenced passed into this zone may exist on the geometry inputs. */ + MutableSpan new_reference_sets = reference_sets.as_mutable_span().drop_front( + old_reference_sets_count); + for (const int input_i : IndexRange(storage.input_items.items_num)) { + const bNodeSocket &socket = zone->input_node->output_socket(input_i); + if (can_contain_referenced_data(eNodeSocketDatatype(socket.type))) { + for (ReferenceSetInfo &source : new_reference_sets) { + source.potential_data_origins.append(&socket); + } + } + } + } return reference_sets; } @@ -299,7 +347,8 @@ static void set_initial_data_and_reference_bits(const bNodeTree &tree, r_potential_data_by_socket[socket->index_in_tree()][reference_set_i].set(); } switch (reference_set.type) { - case ReferenceSetType::LocalReferenceSet: { + case ReferenceSetType::LocalReferenceSet: + case ReferenceSetType::ClosureInputReferenceSet: { r_potential_reference_by_socket[reference_set.socket->index_in_tree()][reference_set_i] .set(); break; @@ -311,7 +360,8 @@ static void set_initial_data_and_reference_bits(const bNodeTree &tree, } break; } - case ReferenceSetType::GroupOutputData: { + case ReferenceSetType::GroupOutputData: + case ReferenceSetType::ClosureOutputData: { /* Nothing to do. */ break; } @@ -320,22 +370,22 @@ static void set_initial_data_and_reference_bits(const bNodeTree &tree, } static BitVector<> get_references_coming_from_outside_zone( - const bNodeTreeZone &zone, - const BitGroupVector<> &potential_data_by_socket, - const BitGroupVector<> &potential_reference_by_socket) + const bNodeTreeZone &zone, const Span *> sources) { - BitVector<> found(potential_data_by_socket.group_size(), false); + BitVector<> found(sources.first()->group_size(), false); /* Gather references that are passed into the zone from the outside, either through the input * node or border links. */ for (const bNodeSocket *socket : zone.input_node->input_sockets()) { const int src = socket->index_in_tree(); - found |= potential_data_by_socket[src]; - found |= potential_reference_by_socket[src]; + for (const BitGroupVector<> *source : sources) { + found |= (*source)[src]; + } } for (const bNodeLink *link : zone.border_links) { const int src = link->fromsock->index_in_tree(); - found |= potential_data_by_socket[src]; - found |= potential_reference_by_socket[src]; + for (const BitGroupVector<> *source : sources) { + found |= (*source)[src]; + } } return found; } @@ -427,7 +477,7 @@ static bool pass_left_to_right(const bNodeTree &tree, * references created in the zone stay local inside the zone and are not propagated to the * outside. Instead, the foreach-element output node creates new references. */ const BitVector<> outside_references = get_references_coming_from_outside_zone( - *zone, r_potential_data_by_socket, r_potential_reference_by_socket); + *zone, {&r_potential_data_by_socket, &r_potential_reference_by_socket}); for (const int item_i : IndexRange(storage->generation_items.items_num)) { const int src_index = node->input_socket(storage->main_items.items_num + item_i).index_in_tree(); @@ -442,6 +492,38 @@ static bool pass_left_to_right(const bNodeTree &tree, } break; } + case GEO_NODE_CLOSURE_INPUT: { + const bNodeTreeZone *zone = get_zone_of_node_if_full(zones, *node); + if (!zone) { + break; + } + /* Data referenced by border links may also be passed into the closure as input. */ + const BitVector<> outside_references = get_references_coming_from_outside_zone( + *zone, {&r_potential_data_by_socket, &r_potential_reference_by_socket}); + for (const int i : node->output_sockets().index_range()) { + const int dst_index = zone->input_node->output_socket(i).index_in_tree(); + r_potential_data_by_socket[dst_index] |= outside_references; + r_potential_reference_by_socket[dst_index] |= outside_references; + } + break; + } + case GEO_NODE_CLOSURE_OUTPUT: { + const bNodeTreeZone *zone = get_zone_of_node_if_full(zones, *node); + if (!zone) { + break; + } + /* References passed through border links are referenced by the closure. */ + const BitVector<> passed_in_references = get_references_coming_from_outside_zone( + *zone, {&r_potential_reference_by_socket}); + const int dst_index = zone->output_node->output_socket(0).index_in_tree(); + for (const int i : node->input_sockets().index_range()) { + const int src_index = zone->output_node->input_socket(i).index_in_tree(); + r_potential_data_by_socket[dst_index] |= r_potential_data_by_socket[src_index]; + r_potential_reference_by_socket[dst_index] |= r_potential_reference_by_socket[src_index]; + r_potential_reference_by_socket[dst_index] |= passed_in_references; + } + break; + } case GEO_NODE_REPEAT_OUTPUT: { const bNodeTreeZone *zone = get_zone_of_node_if_full(zones, *node); if (!zone) { @@ -460,7 +542,7 @@ static bool pass_left_to_right(const bNodeTree &tree, } const BitVector<> outside_references = get_references_coming_from_outside_zone( - *zone, r_potential_data_by_socket, r_potential_reference_by_socket); + *zone, {&r_potential_data_by_socket, &r_potential_reference_by_socket}); /* Propagate within output node. */ for (const int i : IndexRange(items_num)) { @@ -495,7 +577,7 @@ static bool pass_left_to_right(const bNodeTree &tree, return needs_extra_pass; } -static void prepare_required_data_for_outputs( +static void prepare_required_data_for_group_outputs( const bNodeTree &tree, const Span reference_sets, const Span group_output_set_sources, @@ -503,7 +585,6 @@ static void prepare_required_data_for_outputs( const BitGroupVector<> &potential_reference_by_socket, BitGroupVector<> &r_required_data_by_socket) { - /* Initialize required data for group output sockets. */ if (const bNode *group_output_node = tree.group_output_node()) { const Span sockets = group_output_node->input_sockets().drop_back(1); for (const int reference_set_i : group_output_set_sources) { @@ -529,6 +610,72 @@ static void prepare_required_data_for_outputs( } } +static void prepare_required_data_for_closure_outputs( + const bNodeTree &tree, + const Span reference_sets, + MultiValueMap &output_set_sources_by_closure_zone, + const BitGroupVector<> &potential_data_by_socket, + const BitGroupVector<> &potential_reference_by_socket, + BitGroupVector<> &r_required_data_by_socket) +{ + const bNodeTreeZones *zones = tree.zones(); + if (!zones) { + return; + } + for (const bNodeTreeZone *zone : zones->zones) { + if (!zone->input_node || !zone->output_node) { + continue; + } + if (zone->output_node->type_legacy != GEO_NODE_CLOSURE_OUTPUT) { + continue; + } + const Span closure_output_set_sources = output_set_sources_by_closure_zone.lookup(zone); + for (const int reference_set_i : closure_output_set_sources) { + const ReferenceSetInfo &reference_set = reference_sets[reference_set_i]; + BLI_assert(reference_set.type == ReferenceSetType::ClosureOutputData); + r_required_data_by_socket[reference_set.socket->index_in_tree()][reference_set_i].set(); + } + BitVector<> potential_output_references(reference_sets.size(), false); + const Span sockets = zone->output_node->input_sockets().drop_back(1); + for (const bNodeSocket *socket : sockets) { + potential_output_references |= potential_reference_by_socket[socket->index_in_tree()]; + } + for (const bNodeSocket *socket : sockets) { + if (!can_contain_referenced_data(eNodeSocketDatatype(socket->type))) { + continue; + } + const int index = socket->index_in_tree(); + r_required_data_by_socket[index] |= potential_output_references; + /* Make sure that only available data is also required. This is enforced in the end anyway, + * but may reduce some unnecessary work. */ + r_required_data_by_socket[index] &= potential_data_by_socket[index]; + } + } +} + +static void prepare_required_data_for_outputs( + const bNodeTree &tree, + const Span reference_sets, + const Span group_output_set_sources, + MultiValueMap &output_set_sources_by_closure_zone, + const BitGroupVector<> &potential_data_by_socket, + const BitGroupVector<> &potential_reference_by_socket, + BitGroupVector<> &r_required_data_by_socket) +{ + prepare_required_data_for_group_outputs(tree, + reference_sets, + group_output_set_sources, + potential_data_by_socket, + potential_reference_by_socket, + r_required_data_by_socket); + prepare_required_data_for_closure_outputs(tree, + reference_sets, + output_set_sources_by_closure_zone, + potential_data_by_socket, + potential_reference_by_socket, + r_required_data_by_socket); +} + static bool pass_right_to_left(const bNodeTree &tree, const Span &relations_by_node, const BitGroupVector<> &potential_reference_by_socket, @@ -649,6 +796,31 @@ static bool pass_right_to_left(const bNodeTree &tree, } break; } + case GEO_NODE_EVALUATE_CLOSURE: { + /* Data referenced by the closure is required on all the other inputs. */ + const bNodeSocket &closure_socket = node->input_socket(0); + const BoundedBitSpan required_references = + potential_reference_by_socket[closure_socket.index_in_tree()]; + for (const bNodeSocket *input_socket : node->input_sockets().drop_front(1)) { + const int dst_index = input_socket->index_in_tree(); + r_required_data_by_socket[dst_index] |= required_references; + } + break; + } + case GEO_NODE_CLOSURE_OUTPUT: { + const bNodeTreeZone *zone = get_zone_of_node_if_full(zones, *node); + if (!zone) { + break; + } + /* Data that's required on the closure is also required on all inputs of the closure. */ + const bNodeSocket &output_socket = node->output_socket(0); + const BoundedBitSpan required_data = + r_required_data_by_socket[output_socket.index_in_tree()]; + for (const bNodeSocket *input_socket : node->input_sockets()) { + r_required_data_by_socket[input_socket->index_in_tree()] |= required_data; + } + break; + } } } return needs_extra_pass; @@ -788,8 +960,9 @@ static std::unique_ptr make_reference_lifetimes_info(con Array relations_by_node = prepare_relations_by_node(tree, scope); Vector group_output_set_sources; + MultiValueMap output_set_sources_by_closure_zone; reference_lifetimes_info->reference_sets = find_reference_sets( - tree, relations_by_node, group_output_set_sources); + tree, relations_by_node, group_output_set_sources, output_set_sources_by_closure_zone); const Span reference_sets = reference_lifetimes_info->reference_sets; const int sockets_num = tree.all_sockets().size(); @@ -811,6 +984,7 @@ static std::unique_ptr make_reference_lifetimes_info(con prepare_required_data_for_outputs(tree, reference_sets, group_output_set_sources, + output_set_sources_by_closure_zone, potential_data_by_socket, potential_reference_by_socket, required_data_by_socket); @@ -828,7 +1002,7 @@ static std::unique_ptr make_reference_lifetimes_info(con std::cout << "\n\n" << node_tree_to_dot(tree, bNodeTreeBitGroupVectorOptions( - {required_data_by_socket, potential_reference_by_socket})) + {potential_reference_by_socket, required_data_by_socket})) << "\n\n"; #endif diff --git a/source/blender/blenloader/intern/versioning_userdef.cc b/source/blender/blenloader/intern/versioning_userdef.cc index c9945eb21ef..abb400179cb 100644 --- a/source/blender/blenloader/intern/versioning_userdef.cc +++ b/source/blender/blenloader/intern/versioning_userdef.cc @@ -231,6 +231,10 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme) FROM_DEFAULT_V4_UCHAR(tui.wcol_state.success); } + if (!USER_VERSION_ATLEAST(405, 14)) { + FROM_DEFAULT_V4_UCHAR(space_node.node_zone_closure); + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a USER_VERSION_ATLEAST check. diff --git a/source/blender/depsgraph/CMakeLists.txt b/source/blender/depsgraph/CMakeLists.txt index 57b3852d5ac..f845fbed7db 100644 --- a/source/blender/depsgraph/CMakeLists.txt +++ b/source/blender/depsgraph/CMakeLists.txt @@ -7,6 +7,7 @@ set(INC ../animrig ../makesrna ../modifiers + ../nodes # RNA_prototypes.hh ${CMAKE_BINARY_DIR}/source/blender/makesrna ) diff --git a/source/blender/editors/geometry/node_group_operator.cc b/source/blender/editors/geometry/node_group_operator.cc index 385304f8424..37e125f5917 100644 --- a/source/blender/editors/geometry/node_group_operator.cc +++ b/source/blender/editors/geometry/node_group_operator.cc @@ -381,6 +381,8 @@ static std::optional socket_type_to_id_type(const eNodeSocketDatatype s case SOCK_ROTATION: case SOCK_MENU: case SOCK_MATRIX: + case SOCK_BUNDLE: + case SOCK_CLOSURE: return std::nullopt; case SOCK_OBJECT: return ID_OB; diff --git a/source/blender/editors/include/UI_icons.hh b/source/blender/editors/include/UI_icons.hh index c2244d4f546..dba27c7315b 100644 --- a/source/blender/editors/include/UI_icons.hh +++ b/source/blender/editors/include/UI_icons.hh @@ -1124,6 +1124,8 @@ DEF_ICON_VECTOR(NODE_SOCKET_MATERIAL) DEF_ICON_VECTOR(NODE_SOCKET_ROTATION) DEF_ICON_VECTOR(NODE_SOCKET_MENU) DEF_ICON_VECTOR(NODE_SOCKET_MATRIX) +DEF_ICON_VECTOR(NODE_SOCKET_BUNDLE) +DEF_ICON_VECTOR(NODE_SOCKET_CLOSURE) /* add as needed. */ diff --git a/source/blender/editors/include/UI_resources.hh b/source/blender/editors/include/UI_resources.hh index ffe260c2919..f160bd6847f 100644 --- a/source/blender/editors/include/UI_resources.hh +++ b/source/blender/editors/include/UI_resources.hh @@ -202,6 +202,7 @@ enum ThemeColorID { TH_NODE_ZONE_SIMULATION, TH_NODE_ZONE_REPEAT, TH_NODE_ZONE_FOREACH_GEOMETRY_ELEMENT, + TH_NODE_ZONE_CLOSURE, TH_SIMULATED_FRAMES, TH_CONSOLE_OUTPUT, diff --git a/source/blender/editors/interface/interface_icons.cc b/source/blender/editors/interface/interface_icons.cc index 37d0c92b0e2..b12da46e343 100644 --- a/source/blender/editors/interface/interface_icons.cc +++ b/source/blender/editors/interface/interface_icons.cc @@ -633,6 +633,8 @@ DEF_ICON_NODE_SOCKET_DRAW(material, eNodeSocketDatatype::SOCK_MATERIAL) DEF_ICON_NODE_SOCKET_DRAW(rotation, eNodeSocketDatatype::SOCK_ROTATION) DEF_ICON_NODE_SOCKET_DRAW(menu, eNodeSocketDatatype::SOCK_MENU) DEF_ICON_NODE_SOCKET_DRAW(matrix, eNodeSocketDatatype::SOCK_MATRIX) +DEF_ICON_NODE_SOCKET_DRAW(bundle, eNodeSocketDatatype::SOCK_BUNDLE) +DEF_ICON_NODE_SOCKET_DRAW(closure, eNodeSocketDatatype::SOCK_CLOSURE) /* Dynamically render icon instead of rendering a plain color to a texture/buffer * This is not strictly a "vicon", as it needs access to icon->obj to get the color info, @@ -1025,6 +1027,8 @@ static void init_internal_icons() def_internal_vicon(ICON_NODE_SOCKET_ROTATION, icon_node_socket_draw_rotation); def_internal_vicon(ICON_NODE_SOCKET_MENU, icon_node_socket_draw_menu); def_internal_vicon(ICON_NODE_SOCKET_MATRIX, icon_node_socket_draw_matrix); + def_internal_vicon(ICON_NODE_SOCKET_BUNDLE, icon_node_socket_draw_bundle); + def_internal_vicon(ICON_NODE_SOCKET_CLOSURE, icon_node_socket_draw_closure); } #else diff --git a/source/blender/editors/interface/resources.cc b/source/blender/editors/interface/resources.cc index 5b4e16556bd..cee0b9002bc 100644 --- a/source/blender/editors/interface/resources.cc +++ b/source/blender/editors/interface/resources.cc @@ -694,6 +694,9 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) case TH_NODE_ZONE_FOREACH_GEOMETRY_ELEMENT: cp = ts->node_zone_foreach_geometry_element; break; + case TH_NODE_ZONE_CLOSURE: + cp = ts->node_zone_closure; + break; case TH_SIMULATED_FRAMES: cp = ts->simulated_frames; break; 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 6884562bbfc..b43a9cf0ced 100644 --- a/source/blender/editors/interface/templates/interface_template_node_inputs.cc +++ b/source/blender/editors/interface/templates/interface_template_node_inputs.cc @@ -56,7 +56,7 @@ static void draw_node_input(bContext *C, if (socket.typeinfo->draw == nullptr) { return; } - if (ELEM(socket.type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_SHADER)) { + if (ELEM(socket.type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_SHADER, SOCK_BUNDLE, SOCK_CLOSURE)) { return; } const bNode &node = *static_cast(node_ptr->data); diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt index 71f0f6a8f2e..5f1e7759304 100644 --- a/source/blender/editors/object/CMakeLists.txt +++ b/source/blender/editors/object/CMakeLists.txt @@ -7,6 +7,7 @@ set(INC ../../ikplugin ../../makesrna ../../modifiers + ../../nodes ../../python ../../shader_fx diff --git a/source/blender/editors/space_action/CMakeLists.txt b/source/blender/editors/space_action/CMakeLists.txt index fb236c93ee6..94626dfbc40 100644 --- a/source/blender/editors/space_action/CMakeLists.txt +++ b/source/blender/editors/space_action/CMakeLists.txt @@ -6,6 +6,7 @@ set(INC ../include ../../makesrna ../../modifiers + ../../nodes # RNA_prototypes.hh ${CMAKE_BINARY_DIR}/source/blender/makesrna diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index dbb47c3fa8e..0710af2e00f 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -1145,6 +1145,8 @@ static const float std_node_socket_colors[][4] = { {0.65, 0.39, 0.78, 1.0}, /* SOCK_ROTATION */ {0.40, 0.40, 0.40, 1.0}, /* SOCK_MENU */ {0.72, 0.20, 0.52, 1.0}, /* SOCK_MATRIX */ + {0.30, 0.50, 0.50, 1.0}, /* SOCK_BUNDLE */ + {0.50, 0.60, 0.40, 1.0}, /* SOCK_CLOSURE */ }; void std_node_socket_colors_get(int socket_type, float *r_color) @@ -1172,23 +1174,16 @@ static void std_node_socket_color_simple_fn(const bke::bNodeSocketType *type, fl using SocketColorFn = void (*)(bContext *C, PointerRNA *ptr, PointerRNA *node_ptr, float *r_color); /* Callbacks for all built-in socket types. */ static const SocketColorFn std_node_socket_color_funcs[] = { - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, - nullptr /* UNUSED. */, - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, - std_node_socket_color_fn, + std_node_socket_color_fn, std_node_socket_color_fn, + std_node_socket_color_fn, std_node_socket_color_fn, + std_node_socket_color_fn, nullptr /* UNUSED. */, + std_node_socket_color_fn, std_node_socket_color_fn, + std_node_socket_color_fn, std_node_socket_color_fn, + std_node_socket_color_fn, std_node_socket_color_fn, + std_node_socket_color_fn, std_node_socket_color_fn, + std_node_socket_color_fn, std_node_socket_color_fn, + std_node_socket_color_fn, std_node_socket_color_fn, + std_node_socket_color_fn, }; /* draw function for file output node sockets, @@ -1580,6 +1575,8 @@ static void std_node_socket_interface_draw(ID *id, case SOCK_SHADER: case SOCK_GEOMETRY: case SOCK_MATRIX: + case SOCK_BUNDLE: + case SOCK_CLOSURE: break; case SOCK_CUSTOM: diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 117e0667679..c2a632a135f 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -1818,6 +1818,48 @@ static void create_inspection_string_for_geometry_socket(fmt::memory_buffer &buf } } +static void create_inspection_string_for_bundle(const geo_log::BundleValueLog &value_log, + fmt::memory_buffer &buf) +{ + if (value_log.items.is_empty()) { + fmt::format_to(fmt::appender(buf), "{}", TIP_("Empty Bundle")); + return; + } + fmt::format_to(fmt::appender(buf), "{}", TIP_("Bundle values:\n")); + for (const geo_log::BundleValueLog::Item &item : value_log.items) { + fmt::format_to(fmt::appender(buf), + fmt::runtime("\u2022 \"{}\" ({})\n"), + item.key.identifiers().first(), + TIP_(item.type->label)); + } +} + +static void create_inspection_string_for_closure(const geo_log::ClosureValueLog &value_log, + fmt::memory_buffer &buf) +{ + if (value_log.inputs.is_empty() && value_log.outputs.is_empty()) { + fmt::format_to(fmt::appender(buf), "{}", TIP_("Empty Closure")); + } + if (!value_log.inputs.is_empty()) { + fmt::format_to(fmt::appender(buf), "{}:\n", TIP_("Inputs")); + for (const geo_log::ClosureValueLog::Item &item : value_log.inputs) { + fmt::format_to(fmt::appender(buf), + fmt::runtime("\u2022 {} ({})\n"), + item.key.identifiers().first(), + IFACE_(item.type->label)); + } + } + if (!value_log.outputs.is_empty()) { + fmt::format_to(fmt::appender(buf), "{}:\n", TIP_("Outputs")); + for (const geo_log::ClosureValueLog::Item &item : value_log.outputs) { + fmt::format_to(fmt::appender(buf), + fmt::runtime("\u2022 {} ({})\n"), + item.key.identifiers().first(), + IFACE_(item.type->label)); + } + } +} + static void create_inspection_string_for_default_socket_value(const bNodeSocket &socket, fmt::memory_buffer &buf) { @@ -1905,6 +1947,16 @@ static std::optional create_log_inspection_string(geo_log::GeoTreeL { create_inspection_string_for_geometry_info(*geo_value_log, buf); } + else if (const geo_log::BundleValueLog *bundle_value_log = + dynamic_cast(value_log)) + { + create_inspection_string_for_bundle(*bundle_value_log, buf); + } + else if (const geo_log::ClosureValueLog *closure_value_log = + dynamic_cast(value_log)) + { + create_inspection_string_for_closure(*closure_value_log, buf); + } std::string str = fmt::to_string(buf); if (str.empty()) { @@ -3117,7 +3169,8 @@ static Vector node_get_extra_info(const bContext &C, NODE_GROUP_OUTPUT, GEO_NODE_SIMULATION_OUTPUT, GEO_NODE_REPEAT_OUTPUT, - GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT))) + GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT, + GEO_NODE_EVALUATE_CLOSURE))) { std::optional row = node_get_execution_time_label_row( tree_draw_ctx, snode, node); diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index cc680ffc127..1d028224702 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -2525,6 +2525,8 @@ static int get_main_socket_priority(const bNodeSocket *socket) case SOCK_TEXTURE: case SOCK_MATERIAL: case SOCK_MENU: + case SOCK_BUNDLE: + case SOCK_CLOSURE: return 6; } return -1; diff --git a/source/blender/editors/util/ed_viewer_path.cc b/source/blender/editors/util/ed_viewer_path.cc index ae0008f5264..28139d07dfb 100644 --- a/source/blender/editors/util/ed_viewer_path.cc +++ b/source/blender/editors/util/ed_viewer_path.cc @@ -55,6 +55,9 @@ static ViewerPathElem *viewer_path_elem_for_zone(const bNodeTreeZone &zone) node_elem->index = storage.inspection_index; return &node_elem->base; } + case GEO_NODE_CLOSURE_OUTPUT: { + return nullptr; + } } BLI_assert_unreachable(); return nullptr; @@ -125,6 +128,9 @@ static void viewer_path_for_geometry_node(const SpaceNode &snode, node->identifier); for (const bNodeTreeZone *zone : zone_stack) { ViewerPathElem *zone_elem = viewer_path_elem_for_zone(*zone); + if (!zone_elem) { + return; + } BLI_addtail(&r_dst.path, zone_elem); } @@ -143,6 +149,9 @@ static void viewer_path_for_geometry_node(const SpaceNode &snode, node.identifier); for (const bNodeTreeZone *zone : zone_stack) { ViewerPathElem *zone_elem = viewer_path_elem_for_zone(*zone); + if (!zone_elem) { + return; + } BLI_addtail(&r_dst.path, zone_elem); } diff --git a/source/blender/functions/FN_lazy_function_graph_executor.hh b/source/blender/functions/FN_lazy_function_graph_executor.hh index 380fab9052f..1145ec69e53 100644 --- a/source/blender/functions/FN_lazy_function_graph_executor.hh +++ b/source/blender/functions/FN_lazy_function_graph_executor.hh @@ -121,6 +121,11 @@ class GraphExecutor : public LazyFunction { friend class Executor; public: + GraphExecutor(const Graph &graph, + const Logger *logger, + const SideEffectProvider *side_effect_provider, + const NodeExecuteWrapper *node_execute_wrapper); + GraphExecutor(const Graph &graph, Vector graph_inputs, Vector graph_outputs, diff --git a/source/blender/functions/intern/lazy_function_graph_executor.cc b/source/blender/functions/intern/lazy_function_graph_executor.cc index 99f34d70d53..47852b5fc24 100644 --- a/source/blender/functions/intern/lazy_function_graph_executor.cc +++ b/source/blender/functions/intern/lazy_function_graph_executor.cc @@ -1459,6 +1459,19 @@ inline void Executor::execute_node(const FunctionNode &node, } } +GraphExecutor::GraphExecutor(const Graph &graph, + const Logger *logger, + const SideEffectProvider *side_effect_provider, + const NodeExecuteWrapper *node_execute_wrapper) + : GraphExecutor(graph, + Vector(graph.graph_inputs()), + Vector(graph.graph_outputs()), + logger, + side_effect_provider, + node_execute_wrapper) +{ +} + GraphExecutor::GraphExecutor(const Graph &graph, Vector graph_inputs, Vector graph_outputs, diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index 89161030a35..50b623dff5f 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -273,6 +273,8 @@ typedef enum eNodeSocketDatatype { SOCK_ROTATION = 14, SOCK_MENU = 15, SOCK_MATRIX = 16, + SOCK_BUNDLE = 17, + SOCK_CLOSURE = 18, } eNodeSocketDatatype; /** Socket shape. */ @@ -2107,6 +2109,85 @@ typedef struct NodeGeometryForeachGeometryElementOutput { char _pad[3]; } NodeGeometryForeachGeometryElementOutput; +typedef struct NodeGeometryClosureInput { + /** bNode.identifier of the corresponding output node. */ + int32_t output_node_id; +} NodeGeometryClosureInput; + +typedef struct NodeGeometryClosureInputItem { + char *name; + /** #eNodeSocketDatatype. */ + short socket_type; + char _pad[2]; + int identifier; +} NodeGeometryClosureInputItem; + +typedef struct NodeGeometryClosureOutputItem { + char *name; + /** #eNodeSocketDatatype. */ + short socket_type; + char _pad[2]; + int identifier; +} NodeGeometryClosureOutputItem; + +typedef struct NodeGeometryClosureInputItems { + NodeGeometryClosureInputItem *items; + int items_num; + int active_index; + int next_identifier; + char _pad[4]; +} NodeGeometryClosureInputItems; + +typedef struct NodeGeometryClosureOutputItems { + NodeGeometryClosureOutputItem *items; + int items_num; + int active_index; + int next_identifier; + char _pad[4]; +} NodeGeometryClosureOutputItems; + +typedef struct NodeGeometryClosureOutput { + NodeGeometryClosureInputItems input_items; + NodeGeometryClosureOutputItems output_items; +} NodeGeometryClosureOutput; + +typedef struct NodeGeometryEvaluateClosureInputItem { + char *name; + /** #eNodeSocketDatatype */ + short socket_type; + char _pad[2]; + int identifier; +} NodeGeometryEvaluateClosureInputItem; + +typedef struct NodeGeometryEvaluateClosureOutputItem { + char *name; + /** #eNodeSocketDatatype */ + short socket_type; + char _pad[2]; + int identifier; +} NodeGeometryEvaluateClosureOutputItem; + +typedef struct NodeGeometryEvaluateClosureInputItems { + NodeGeometryEvaluateClosureInputItem *items; + int items_num; + int active_index; + int next_identifier; + char _pad[4]; +} NodeGeometryEvaluateClosureInputItems; + +typedef struct NodeGeometryEvaluateClosureOutputItems { + NodeGeometryEvaluateClosureOutputItem *items; + int items_num; + int active_index; + int next_identifier; + char _pad[4]; +} NodeGeometryEvaluateClosureOutputItems; + +typedef struct NodeGeometryEvaluateClosure { + NodeGeometryEvaluateClosureInputItems input_items; + NodeGeometryEvaluateClosureOutputItems output_items; +} NodeGeometryEvaluateClosure; + typedef struct IndexSwitchItem { /** Generated unique identifier which stays the same even when the item order or names change. */ int identifier; @@ -2221,6 +2302,36 @@ typedef struct NodeGeometryBake { char _pad[4]; } NodeGeometryBake; +typedef struct NodeGeometryCombineBundleItem { + char *name; + int identifier; + int16_t socket_type; + char _pad[2]; +} NodeGeometryCombineBundleItem; + +typedef struct NodeGeometryCombineBundle { + NodeGeometryCombineBundleItem *items; + int items_num; + int next_identifier; + int active_index; + char _pad[4]; +} NodeGeometryCombineBundle; + +typedef struct NodeGeometrySeparateBundleItem { + char *name; + int identifier; + int16_t socket_type; + char _pad[2]; +} NodeGeometrySeparateBundleItem; + +typedef struct NodeGeometrySeparateBundle { + NodeGeometrySeparateBundleItem *items; + int items_num; + int next_identifier; + int active_index; + char _pad[4]; +} NodeGeometrySeparateBundle; + /* script node mode */ enum { NODE_SCRIPT_INTERNAL = 0, diff --git a/source/blender/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h index 065fd8be938..1d8ea7c1431 100644 --- a/source/blender/makesdna/DNA_userdef_types.h +++ b/source/blender/makesdna/DNA_userdef_types.h @@ -359,7 +359,9 @@ typedef struct ThemeSpace { unsigned char node_zone_simulation[4]; unsigned char node_zone_repeat[4]; unsigned char node_zone_foreach_geometry_element[4]; + unsigned char node_zone_closure[4]; unsigned char simulated_frames[4]; + char _pad7[4]; /** For sequence editor. */ unsigned char movie[4], movieclip[4], mask[4], image[4], scene[4], audio[4]; @@ -766,7 +768,8 @@ typedef struct UserDef_Experimental { char use_sculpt_texture_paint; char use_new_volume_nodes; char use_shader_node_previews; - char _pad[6]; + char use_bundle_and_closure_nodes; + char _pad[5]; } UserDef_Experimental; #define USER_EXPERIMENTAL_TEST(userdef, member) \ diff --git a/source/blender/makesrna/intern/rna_node_socket.cc b/source/blender/makesrna/intern/rna_node_socket.cc index 2fb4bbcaebf..143d5460215 100644 --- a/source/blender/makesrna/intern/rna_node_socket.cc +++ b/source/blender/makesrna/intern/rna_node_socket.cc @@ -34,6 +34,8 @@ const EnumPropertyItem rna_enum_node_socket_type_items[] = { {SOCK_TEXTURE, "TEXTURE", ICON_NODE_SOCKET_TEXTURE, "Texture", ""}, {SOCK_MATERIAL, "MATERIAL", ICON_NODE_SOCKET_MATERIAL, "Material", ""}, {SOCK_MENU, "MENU", ICON_NODE_SOCKET_MENU, "Menu", ""}, + {SOCK_BUNDLE, "BUNDLE", ICON_NODE_SOCKET_BUNDLE, "Bundle", ""}, + {SOCK_CLOSURE, "CLOSURE", ICON_NODE_SOCKET_CLOSURE, "Closure", ""}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -1565,6 +1567,48 @@ static void rna_def_node_socket_interface_geometry(BlenderRNA *brna, const char rna_def_node_tree_interface_socket_builtin(srna); } +static void rna_def_node_socket_bundle(BlenderRNA *brna, const char *identifier) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, identifier, "NodeSocketStandard"); + RNA_def_struct_ui_text(srna, "Bundle Node Socket", "Bundle socket of a node"); + RNA_def_struct_ui_icon(srna, ICON_NODE_SOCKET_BUNDLE); + RNA_def_struct_sdna(srna, "bNodeSocket"); +} + +static void rna_def_node_socket_interface_bundle(BlenderRNA *brna, const char *identifier) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, identifier, "NodeTreeInterfaceSocket"); + RNA_def_struct_ui_text(srna, "Bundle Node Socket Interface", "Bundle socket of a node"); + RNA_def_struct_sdna(srna, "bNodeTreeInterfaceSocket"); + + rna_def_node_tree_interface_socket_builtin(srna); +} + +static void rna_def_node_socket_closure(BlenderRNA *brna, const char *identifier) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, identifier, "NodeSocketStandard"); + RNA_def_struct_ui_text(srna, "Closure Node Socket", "Closure socket of a node"); + RNA_def_struct_ui_icon(srna, ICON_NODE_SOCKET_CLOSURE); + RNA_def_struct_sdna(srna, "bNodeSocket"); +} + +static void rna_def_node_socket_interface_closure(BlenderRNA *brna, const char *identifier) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, identifier, "NodeTreeInterfaceSocket"); + RNA_def_struct_ui_text(srna, "Closure Node Socket Interface", "Closure socket of a node"); + RNA_def_struct_sdna(srna, "bNodeTreeInterfaceSocket"); + + rna_def_node_tree_interface_socket_builtin(srna); +} + static void rna_def_node_socket_collection(BlenderRNA *brna, const char *identifier) { StructRNA *srna; @@ -1787,6 +1831,8 @@ static const bNodeSocketStaticTypeInfo node_socket_subtypes[] = { {"NodeSocketTexture", "NodeTreeInterfaceSocketTexture", SOCK_TEXTURE, PROP_NONE}, {"NodeSocketMaterial", "NodeTreeInterfaceSocketMaterial", SOCK_MATERIAL, PROP_NONE}, {"NodeSocketMenu", "NodeTreeInterfaceSocketMenu", SOCK_MENU, PROP_NONE}, + {"NodeSocketBundle", "NodeTreeInterfaceSocketBundle", SOCK_BUNDLE, PROP_NONE}, + {"NodeSocketClosure", "NodeTreeInterfaceSocketClosure", SOCK_CLOSURE, PROP_NONE}, }; static void rna_def_node_socket_subtypes(BlenderRNA *brna) @@ -1843,6 +1889,12 @@ static void rna_def_node_socket_subtypes(BlenderRNA *brna) case SOCK_MENU: rna_def_node_socket_menu(brna, identifier); break; + case SOCK_BUNDLE: + rna_def_node_socket_bundle(brna, identifier); + break; + case SOCK_CLOSURE: + rna_def_node_socket_closure(brna, identifier); + break; case SOCK_CUSTOM: break; @@ -1909,6 +1961,12 @@ void rna_def_node_socket_interface_subtypes(BlenderRNA *brna) case SOCK_MATERIAL: rna_def_node_socket_interface_material(brna, identifier); break; + case SOCK_BUNDLE: + rna_def_node_socket_interface_bundle(brna, identifier); + break; + case SOCK_CLOSURE: + rna_def_node_socket_interface_closure(brna, identifier); + break; case SOCK_CUSTOM: break; diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 7b8cb36dd39..c769ff15416 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -57,6 +57,8 @@ const EnumPropertyItem rna_enum_node_socket_data_type_items[] = { {SOCK_COLLECTION, "COLLECTION", ICON_NODE_SOCKET_COLLECTION, "Collection", ""}, {SOCK_TEXTURE, "TEXTURE", ICON_NODE_SOCKET_TEXTURE, "Texture", ""}, {SOCK_MATERIAL, "MATERIAL", ICON_NODE_SOCKET_MATERIAL, "Material", ""}, + {SOCK_BUNDLE, "BUNDLE", ICON_NODE_SOCKET_BUNDLE, "Bundle", ""}, + {SOCK_CLOSURE, "CLOSURE", ICON_NODE_SOCKET_CLOSURE, "Closure", ""}, {0, nullptr, 0, nullptr, nullptr}, }; @@ -627,7 +629,9 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = { # include "NOD_common.hh" # include "NOD_composite.hh" # include "NOD_geo_bake.hh" +# include "NOD_geo_bundle.hh" # include "NOD_geo_capture_attribute.hh" +# include "NOD_geo_closure.hh" # include "NOD_geo_foreach_geometry_element.hh" # include "NOD_geo_index_switch.hh" # include "NOD_geo_menu_switch.hh" @@ -654,12 +658,18 @@ static const EnumPropertyItem node_cryptomatte_layer_name_items[] = { using blender::float2; using blender::nodes::BakeItemsAccessor; using blender::nodes::CaptureAttributeItemsAccessor; +using blender::nodes::ClosureInputItemsAccessor; +using blender::nodes::ClosureOutputItemsAccessor; +using blender::nodes::CombineBundleItemsAccessor; +using blender::nodes::EvaluateClosureInputItemsAccessor; +using blender::nodes::EvaluateClosureOutputItemsAccessor; using blender::nodes::ForeachGeometryElementGenerationItemsAccessor; using blender::nodes::ForeachGeometryElementInputItemsAccessor; using blender::nodes::ForeachGeometryElementMainItemsAccessor; using blender::nodes::IndexSwitchItemsAccessor; using blender::nodes::MenuSwitchItemsAccessor; using blender::nodes::RepeatItemsAccessor; +using blender::nodes::SeparateBundleItemsAccessor; using blender::nodes::SimulationItemsAccessor; extern FunctionRNA rna_NodeTree_poll_func; @@ -10022,6 +10032,13 @@ static void def_geo_foreach_geometry_element_input(BlenderRNA *brna, StructRNA * def_common_zone_input(brna, srna); } +static void def_geo_closure_input(BlenderRNA *brna, StructRNA *srna) +{ + RNA_def_struct_sdna_from(srna, "NodeGeometryClosureInput", "storage"); + + def_common_zone_input(brna, srna); +} + static void rna_def_node_item_array_socket_item_common(StructRNA *srna, const char *accessor, const bool add_socket_type) @@ -10404,6 +10421,79 @@ static void def_geo_foreach_geometry_element_output(BlenderRNA * /*brna*/, Struc RNA_def_property_update(prop, NC_NODE, "rna_Node_update"); } +static void rna_def_geo_closure_input_item(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometryClosureInputItem", nullptr); + RNA_def_struct_ui_text(srna, "Closure Input Item", ""); + RNA_def_struct_sdna(srna, "NodeGeometryClosureInputItem"); + + rna_def_node_item_array_socket_item_common(srna, "ClosureInputItemsAccessor", true); +} + +static void rna_def_geo_closure_input_items(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometryClosureInputItems", nullptr); + RNA_def_struct_ui_text(srna, "Closure Input Items", ""); + RNA_def_struct_sdna(srna, "bNode"); + + rna_def_node_item_array_new_with_socket_and_name( + srna, "NodeGeometryClosureInputItem", "ClosureInputItemsAccessor"); + rna_def_node_item_array_common_functions( + srna, "NodeGeometryClosureInputItem", "ClosureInputItemsAccessor"); +} + +static void rna_def_geo_closure_output_item(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometryClosureOutputItem", nullptr); + RNA_def_struct_ui_text(srna, "Closure Output Item", ""); + RNA_def_struct_sdna(srna, "NodeGeometryClosureOutputItem"); + + rna_def_node_item_array_socket_item_common(srna, "ClosureOutputItemsAccessor", true); +} + +static void rna_def_geo_closure_output_items(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometryClosureOutputItems", nullptr); + RNA_def_struct_ui_text(srna, "Closure Output Items", ""); + RNA_def_struct_sdna(srna, "bNode"); + + rna_def_node_item_array_new_with_socket_and_name( + srna, "NodeGeometryClosureOutputItem", "ClosureOutputItemsAccessor"); + rna_def_node_item_array_common_functions( + srna, "NodeGeometryClosureOutputItem", "ClosureOutputItemsAccessor"); +} + +static void def_geo_closure_output(BlenderRNA * /*brna*/, StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryClosureOutput", "storage"); + + prop = RNA_def_property(srna, "input_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, nullptr, "input_items.items", "input_items.items_num"); + RNA_def_property_struct_type(prop, "NodeGeometryClosureInputItem"); + RNA_def_property_srna(prop, "NodeGeometryClosureInputItems"); + + prop = RNA_def_property(srna, "output_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, nullptr, "output_items.items", "output_items.items_num"); + RNA_def_property_struct_type(prop, "NodeGeometryClosureOutputItem"); + RNA_def_property_srna(prop, "NodeGeometryClosureOutputItems"); + + prop = RNA_def_property(srna, "active_input_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, nullptr, "input_items.active_index"); + RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); + RNA_def_property_update(prop, NC_NODE, nullptr); + + prop = RNA_def_property(srna, "active_output_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, nullptr, "output_items.active_index"); + RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); + RNA_def_property_update(prop, NC_NODE, nullptr); +} + static void rna_def_geo_capture_attribute_item(BlenderRNA *brna) { StructRNA *srna = RNA_def_struct(brna, "NodeGeometryCaptureAttributeItem", nullptr); @@ -10471,6 +10561,87 @@ static void rna_def_geo_capture_attribute(BlenderRNA * /*brna*/, StructRNA *srna RNA_def_property_update(prop, NC_NODE, "rna_Node_update"); } +static void rna_def_geo_evaluate_closure_input_item(BlenderRNA *brna) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, "NodeGeometryEvaluateClosureInputItem", nullptr); + RNA_def_struct_ui_text(srna, "Input Item", ""); + + rna_def_node_item_array_socket_item_common(srna, "EvaluateClosureInputItemsAccessor", true); +} + +static void rna_def_geo_evaluate_closure_input_items(BlenderRNA *brna) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, "NodeGeometryEvaluateClosureInputItems", nullptr); + RNA_def_struct_ui_text(srna, "Input Items", ""); + RNA_def_struct_sdna(srna, "bNode"); + + rna_def_node_item_array_new_with_socket_and_name( + srna, "NodeGeometryEvaluateClosureInputItem", "EvaluateClosureInputItemsAccessor"); + rna_def_node_item_array_common_functions( + srna, "NodeGeometryEvaluateClosureInputItem", "EvaluateClosureInputItemsAccessor"); +} + +static void rna_def_geo_evaluate_closure_output_item(BlenderRNA *brna) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, "NodeGeometryEvaluateClosureOutputItem", nullptr); + RNA_def_struct_ui_text(srna, "Output Item", ""); + + rna_def_node_item_array_socket_item_common(srna, "EvaluateClosureOutputItemsAccessor", true); +} + +static void rna_def_geo_evaluate_closure_output_items(BlenderRNA *brna) +{ + StructRNA *srna; + + srna = RNA_def_struct(brna, "NodeGeometryEvaluateClosureOutputItems", nullptr); + RNA_def_struct_ui_text(srna, "Output Items", ""); + RNA_def_struct_sdna(srna, "bNode"); + + rna_def_node_item_array_new_with_socket_and_name( + srna, "NodeGeometryEvaluateClosureOutputItem", "EvaluateClosureOutputItemsAccessor"); + rna_def_node_item_array_common_functions( + srna, "NodeGeometryEvaluateClosureOutputItem", "EvaluateClosureOutputItemsAccessor"); +} + +static void def_geo_evaluate_closure(BlenderRNA * /*brna*/, StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryEvaluateClosure", "storage"); + + prop = RNA_def_property(srna, "input_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, nullptr, "input_items.items", "input_items.items_num"); + RNA_def_property_struct_type(prop, "NodeGeometryEvaluateClosureInputItem"); + RNA_def_property_ui_text(prop, "Input Items", ""); + RNA_def_property_srna(prop, "NodeGeometryEvaluateClosureInputItems"); + + prop = RNA_def_property(srna, "output_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, nullptr, "output_items.items", "output_items.items_num"); + RNA_def_property_struct_type(prop, "NodeGeometryEvaluateClosureOutputItem"); + RNA_def_property_ui_text(prop, "Output Items", ""); + RNA_def_property_srna(prop, "NodeGeometryEvaluateClosureOutputItems"); + + prop = RNA_def_property(srna, "active_input_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, nullptr, "input_items.active_index"); + RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); + RNA_def_property_update(prop, NC_NODE, nullptr); + + prop = RNA_def_property(srna, "active_output_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, nullptr, "output_items.active_index"); + RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); + RNA_def_property_update(prop, NC_NODE, nullptr); +} + static void rna_def_geo_bake_item(BlenderRNA *brna) { PropertyRNA *prop; @@ -10543,6 +10714,86 @@ static void rna_def_geo_bake(BlenderRNA * /*brna*/, StructRNA *srna) RNA_def_property_update(prop, NC_NODE, nullptr); } +static void rna_def_geo_combine_bundle_item(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometryCombineBundleItem", nullptr); + RNA_def_struct_ui_text(srna, "Combine Bundle Item", ""); + + rna_def_node_item_array_socket_item_common(srna, "CombineBundleItemsAccessor", true); +} + +static void rna_def_geo_combine_bundle_items(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometryCombineBundleItems", nullptr); + RNA_def_struct_sdna(srna, "bNode"); + RNA_def_struct_ui_text(srna, "Items", "Collection of combine bundle items"); + + rna_def_node_item_array_new_with_socket_and_name( + srna, "NodeGeometryCombineBundleItem", "CombineBundleItemsAccessor"); + rna_def_node_item_array_common_functions( + srna, "NodeGeometryCombineBundleItem", "CombineBundleItemsAccessor"); +} + +static void rna_def_geo_combine_bundle(BlenderRNA * /*brna*/, StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometryCombineBundle", "storage"); + + prop = RNA_def_property(srna, "bundle_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, nullptr, "items", "items_num"); + RNA_def_property_struct_type(prop, "NodeGeometryCombineBundleItem"); + RNA_def_property_ui_text(prop, "Items", ""); + RNA_def_property_srna(prop, "NodeGeometryCombineBundleItems"); + + prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, nullptr, "active_index"); + RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); + RNA_def_property_update(prop, NC_NODE, nullptr); +} + +static void rna_def_geo_separate_bundle_item(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometrySeparateBundleItem", nullptr); + RNA_def_struct_ui_text(srna, "Separate Bundle Item", ""); + + rna_def_node_item_array_socket_item_common(srna, "SeparateBundleItemsAccessor", true); +} + +static void rna_def_geo_separate_bundle_items(BlenderRNA *brna) +{ + StructRNA *srna = RNA_def_struct(brna, "NodeGeometrySeparateBundleItems", nullptr); + RNA_def_struct_sdna(srna, "bNode"); + RNA_def_struct_ui_text(srna, "Items", "Collection of separate bundle items"); + + rna_def_node_item_array_new_with_socket_and_name( + srna, "NodeGeometrySeparateBundleItem", "SeparateBundleItemsAccessor"); + rna_def_node_item_array_common_functions( + srna, "NodeGeometrySeparateBundleItem", "SeparateBundleItemsAccessor"); +} + +static void rna_def_geo_separate_bundle(BlenderRNA * /*brna*/, StructRNA *srna) +{ + PropertyRNA *prop; + + RNA_def_struct_sdna_from(srna, "NodeGeometrySeparateBundle", "storage"); + + prop = RNA_def_property(srna, "bundle_items", PROP_COLLECTION, PROP_NONE); + RNA_def_property_collection_sdna(prop, nullptr, "items", "items_num"); + RNA_def_property_struct_type(prop, "NodeGeometrySeparateBundleItem"); + RNA_def_property_ui_text(prop, "Items", ""); + RNA_def_property_srna(prop, "NodeGeometrySeparateBundleItems"); + + prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED); + RNA_def_property_int_sdna(prop, nullptr, "active_index"); + RNA_def_property_ui_text(prop, "Active Item Index", "Index of the active item"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); + RNA_def_property_update(prop, NC_NODE, nullptr); +} + static void rna_def_index_switch_item(BlenderRNA *brna) { PropertyRNA *prop; @@ -12650,6 +12901,12 @@ static void rna_def_nodes(BlenderRNA *brna) define("GeometryNode", "GeometryNodeVolumeCube"); define("GeometryNode", "GeometryNodeVolumeToMesh"); define("GeometryNode", "GeometryNodeWarning"); + define("GeometryNode", "GeometryNodeSeparateBundle", rna_def_geo_separate_bundle); + define("GeometryNode", "GeometryNodeCombineBundle", rna_def_geo_combine_bundle); + define("GeometryNode", "GeometryNodeClosureInput", def_geo_closure_input); + define("GeometryNode", "GeometryNodeClosureOutput", def_geo_closure_output); + define("GeometryNode", "GeometryNodeEvaluateClosure", def_geo_evaluate_closure); + /* Node group types are currently defined for each tree type individually. */ define("ShaderNode", "ShaderNodeGroup", def_group); @@ -12687,6 +12944,12 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_index_switch_item(brna); rna_def_menu_switch_item(brna); rna_def_geo_bake_item(brna); + rna_def_geo_combine_bundle_item(brna); + rna_def_geo_separate_bundle_item(brna); + rna_def_geo_closure_input_item(brna); + rna_def_geo_closure_output_item(brna); + rna_def_geo_evaluate_closure_input_item(brna); + rna_def_geo_evaluate_closure_output_item(brna); rna_def_geo_capture_attribute_item(brna); rna_def_nodes(brna); @@ -12727,6 +12990,12 @@ void RNA_def_nodetree(BlenderRNA *brna) rna_def_geo_index_switch_items(brna); rna_def_geo_menu_switch_items(brna); rna_def_bake_items(brna); + rna_def_geo_combine_bundle_items(brna); + rna_def_geo_separate_bundle_items(brna); + rna_def_geo_closure_input_items(brna); + rna_def_geo_closure_output_items(brna); + rna_def_geo_evaluate_closure_input_items(brna); + rna_def_geo_evaluate_closure_output_items(brna); rna_def_geo_capture_attribute_items(brna); rna_def_node_instance_hash(brna); diff --git a/source/blender/makesrna/intern/rna_userdef.cc b/source/blender/makesrna/intern/rna_userdef.cc index 28941ea3115..f43052a2fb2 100644 --- a/source/blender/makesrna/intern/rna_userdef.cc +++ b/source/blender/makesrna/intern/rna_userdef.cc @@ -3500,6 +3500,12 @@ static void rna_def_userdef_theme_space_node(BlenderRNA *brna) RNA_def_property_array(prop, 4); RNA_def_property_ui_text(prop, "For Each Geometry Element Zone", ""); RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); + + prop = RNA_def_property(srna, "closure_zone", PROP_FLOAT, PROP_COLOR_GAMMA); + RNA_def_property_float_sdna(prop, nullptr, "node_zone_closure"); + RNA_def_property_array(prop, 4); + RNA_def_property_ui_text(prop, "Closure Zone", ""); + RNA_def_property_update(prop, 0, "rna_userdef_theme_update"); } static void rna_def_userdef_theme_space_buts(BlenderRNA *brna) @@ -7615,6 +7621,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna) prop, "Shader Node Previews", "Enables previews in the shader node editor"); RNA_def_property_update(prop, 0, "rna_userdef_ui_update"); + prop = RNA_def_property(srna, "use_bundle_and_closure_nodes", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_ui_text( + prop, "Bundle and Closure Nodes", "Enables bundle and closure nodes in Geometry Nodes"); + prop = RNA_def_property(srna, "use_extensions_debug", PROP_BOOLEAN, PROP_NONE); RNA_def_property_ui_text( prop, diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index bbbfaf9fb3b..a49575c8a67 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -862,18 +862,17 @@ static void check_property_socket_sync(const Object *ob, const bNodeTreeInterfaceSocket *socket = nmd->node_group->interface_inputs()[i]; const bke::bNodeSocketType *typeinfo = socket->socket_typeinfo(); const eNodeSocketDatatype type = typeinfo ? eNodeSocketDatatype(typeinfo->type) : SOCK_CUSTOM; + if (type == SOCK_GEOMETRY) { + geometry_socket_count++; + } /* The first socket is the special geometry socket for the modifier object. */ if (i == 0 && type == SOCK_GEOMETRY) { - geometry_socket_count++; continue; } IDProperty *property = properties.lookup_key_default_as(socket->identifier, nullptr); if (property == nullptr) { - if (ELEM(type, SOCK_GEOMETRY, SOCK_MATRIX)) { - geometry_socket_count++; - } - else { + if (!ELEM(type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_BUNDLE, SOCK_CLOSURE)) { BKE_modifier_set_error( ob, md, "Missing property for input socket \"%s\"", socket->name ? socket->name : ""); } diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index a157067c6b3..9da864ce005 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -68,6 +68,9 @@ set(INC_SYS set(SRC intern/derived_node_tree.cc + intern/geometry_nodes_bundle.cc + intern/geometry_nodes_closure_zone.cc + intern/geometry_nodes_closure.cc intern/geometry_nodes_dependencies.cc intern/geometry_nodes_execute.cc intern/geometry_nodes_foreach_geometry_element_zone.cc @@ -97,6 +100,11 @@ set(SRC NOD_derived_node_tree.hh NOD_geometry.hh NOD_geometry_exec.hh + NOD_geometry_nodes_bundle_fwd.hh + NOD_geometry_nodes_bundle.hh + NOD_geometry_nodes_closure.hh + NOD_geometry_nodes_closure_eval.hh + NOD_geometry_nodes_closure_fwd.hh NOD_geometry_nodes_dependencies.hh NOD_geometry_nodes_execute.hh NOD_geometry_nodes_gizmos.hh diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 75bc330603c..8be3207bdef 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -17,6 +17,8 @@ #include "BKE_geometry_set.hh" #include "BKE_node_socket_value.hh" #include "BKE_volume_grid_fwd.hh" +#include "NOD_geometry_nodes_bundle_fwd.hh" +#include "NOD_geometry_nodes_closure_fwd.hh" #include "DNA_node_types.h" @@ -109,7 +111,7 @@ class GeoNodeExecParams { template static constexpr bool stored_as_SocketValueVariant_v = is_field_base_type_v || fn::is_field_v || bke::is_VolumeGrid_v || - is_same_any_v; + is_same_any_v; /** * Get the input value for the input socket with the given identifier. @@ -168,6 +170,16 @@ class GeoNodeExecParams { } } + /** + * Low level access to the parameters. Usually, it's better to use #get_input, #extract_input and + * #set_output instead because they are easier to use and more safe. Sometimes it can be + * beneficial to have more direct access to the raw values though and avoid the indirection. + */ + lf::Params &low_level_lazy_function_params() + { + return params_; + } + /** * Store the output value for the given socket identifier. */ diff --git a/source/blender/nodes/NOD_geometry_nodes_bundle.hh b/source/blender/nodes/NOD_geometry_nodes_bundle.hh new file mode 100644 index 00000000000..a6d6b8317a4 --- /dev/null +++ b/source/blender/nodes/NOD_geometry_nodes_bundle.hh @@ -0,0 +1,66 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BKE_node.hh" + +#include "NOD_geometry_nodes_bundle_fwd.hh" +#include "NOD_socket_interface_key.hh" + +#include "DNA_node_types.h" + +namespace blender::nodes { + +/** + * A bundle is a map containing keys and their corresponding values. Values are stored as the type + * they have in Geometry Nodes (#bNodeSocketType::geometry_nodes_cpp_type). + */ +class Bundle : public ImplicitSharingMixin { + public: + struct StoredItem { + SocketInterfaceKey key; + const bke::bNodeSocketType *type; + void *value; + }; + + private: + Vector items_; + Vector buffers_; + + public: + struct Item { + const bke::bNodeSocketType *type; + const void *value; + }; + + Bundle(); + Bundle(const Bundle &other); + Bundle(Bundle &&other) noexcept; + Bundle &operator=(const Bundle &other); + Bundle &operator=(Bundle &&other) noexcept; + ~Bundle(); + + static BundlePtr create() + { + return BundlePtr(MEM_new(__func__)); + } + + void add_new(SocketInterfaceKey key, const bke::bNodeSocketType &type, const void *value); + bool add(const SocketInterfaceKey &key, const bke::bNodeSocketType &type, const void *value); + bool add(SocketInterfaceKey &&key, const bke::bNodeSocketType &type, const void *value); + bool remove(const SocketInterfaceKey &key); + bool contains(const SocketInterfaceKey &key) const; + + std::optional lookup(const SocketInterfaceKey &key) const; + + Span items() const + { + return items_; + } + + void delete_self() override; +}; + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry_nodes_bundle_fwd.hh b/source/blender/nodes/NOD_geometry_nodes_bundle_fwd.hh new file mode 100644 index 00000000000..9668cf07845 --- /dev/null +++ b/source/blender/nodes/NOD_geometry_nodes_bundle_fwd.hh @@ -0,0 +1,14 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_implicit_sharing_ptr.hh" + +namespace blender::nodes { + +class Bundle; +using BundlePtr = ImplicitSharingPtr; + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry_nodes_closure.hh b/source/blender/nodes/NOD_geometry_nodes_closure.hh new file mode 100644 index 00000000000..cd1b528209d --- /dev/null +++ b/source/blender/nodes/NOD_geometry_nodes_closure.hh @@ -0,0 +1,119 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BKE_node.hh" + +#include "NOD_geometry_nodes_closure_fwd.hh" +#include "NOD_socket_interface_key.hh" + +#include "BLI_resource_scope.hh" + +#include "FN_lazy_function.hh" + +namespace blender::nodes { + +/** Describes the names and types of the inputs and outputs of a closure. */ +class ClosureSignature { + public: + struct Item { + SocketInterfaceKey key; + const bke::bNodeSocketType *type = nullptr; + }; + + Vector inputs; + Vector outputs; + + std::optional find_input_index(const SocketInterfaceKey &key) const; + std::optional find_output_index(const SocketInterfaceKey &key) const; +}; + +/** + * Describes the meaning of the various inputs and outputs of the lazy-function that's contained + * in the closure. + */ +struct ClosureFunctionIndices { + struct { + IndexRange main; + /** A boolean input for each output indicating whether that output is used. */ + IndexRange output_usages; + /** + * A #GeometryNodesReferenceSet input for a subset of the outputs. This is used to tell the + * closure which attributes it has to propagate to the outputs. + * + * Main output index -> input lf socket index. + */ + Map output_data_reference_sets; + } inputs; + struct { + IndexRange main; + /** A boolean output for each input indicating whether that input is used. */ + IndexRange input_usages; + } outputs; +}; + +/** + * A closure is like a node group that is passed around as a value. It's typically evaluated using + * the Evaluate Closure node. + * + * Internally, a closure is a lazy-function. So the inputs that are passed to the closure are + * requested lazily. It's *not* yet supported to request the potentially captured values from the + * Closure Zone lazily. + */ +class Closure : public ImplicitSharingMixin { + private: + std::shared_ptr signature_; + /** + * When building complex lazy-functions, e.g. from Geometry Nodes, one often has to allocate + * various additional resources (e.g. the lazy-functions for the individual nodes). Using + * #ResourceScope provides a simple way to pass ownership of all these additional resources to + * the Closure. + */ + std::unique_ptr scope_; + const fn::lazy_function::LazyFunction &function_; + ClosureFunctionIndices indices_; + Vector default_input_values_; + + public: + Closure(std::shared_ptr signature, + std::unique_ptr scope, + const fn::lazy_function::LazyFunction &function, + ClosureFunctionIndices indices, + Vector default_input_values) + : signature_(signature), + scope_(std::move(scope)), + function_(function), + indices_(indices), + default_input_values_(std::move(default_input_values)) + { + } + + const ClosureSignature &signature() const + { + return *signature_; + } + + const ClosureFunctionIndices &indices() const + { + return indices_; + } + + const fn::lazy_function::LazyFunction &function() const + { + return function_; + } + + const void *default_input_value(const int index) const + { + return default_input_values_[index]; + } + + void delete_self() override + { + MEM_delete(this); + } +}; + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry_nodes_closure_eval.hh b/source/blender/nodes/NOD_geometry_nodes_closure_eval.hh new file mode 100644 index 00000000000..1c84b26d296 --- /dev/null +++ b/source/blender/nodes/NOD_geometry_nodes_closure_eval.hh @@ -0,0 +1,38 @@ +/* SPDX-FileCopyrightText: 2025 Blender Foundation + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "NOD_geometry_nodes_closure.hh" + +#include "NOD_geometry_nodes_lazy_function.hh" + +namespace blender::nodes { + +struct ClosureEagerEvalParams { + struct InputItem { + SocketInterfaceKey key; + const bke::bNodeSocketType *type = nullptr; + /** + * The actual socket value of type bNodeSocketType::geometry_nodes_cpp_type. + * This is not const, because it may be moved from. + */ + void *value = nullptr; + }; + + struct OutputItem { + SocketInterfaceKey key; + const bke::bNodeSocketType *type = nullptr; + /** Where the output value should be stored. */ + void *value = nullptr; + }; + + Vector inputs; + Vector outputs; + GeoNodesLFUserData *user_data = nullptr; +}; + +void evaluate_closure_eagerly(const Closure &closure, ClosureEagerEvalParams ¶ms); + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry_nodes_closure_fwd.hh b/source/blender/nodes/NOD_geometry_nodes_closure_fwd.hh new file mode 100644 index 00000000000..7764695053b --- /dev/null +++ b/source/blender/nodes/NOD_geometry_nodes_closure_fwd.hh @@ -0,0 +1,14 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_implicit_sharing_ptr.hh" + +namespace blender::nodes { + +class Closure; +using ClosurePtr = ImplicitSharingPtr; + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index ce61f301a90..b7a4f72a390 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -454,6 +454,7 @@ void set_default_remaining_node_outputs(lf::Params ¶ms, const bNode &node); void set_default_value_for_output_socket(lf::Params ¶ms, const int lf_index, const bNodeSocket &bsocket); +void construct_socket_default_value(const bke::bNodeSocketType &stype, void *r_value); std::string make_anonymous_attribute_socket_inspection_string(const bNodeSocket &socket); std::string make_anonymous_attribute_socket_inspection_string(StringRef node_name, @@ -463,6 +464,7 @@ struct FoundNestedNodeID { int id; bool is_in_simulation = false; bool is_in_loop = false; + bool is_in_closure = false; }; std::optional find_nested_node_id(const GeoNodesLFUserData &user_data, @@ -589,9 +591,36 @@ LazyFunction &build_foreach_geometry_element_zone_lazy_function(ResourceScope &s ZoneBuildInfo &zone_info, const ZoneBodyFunction &body_fn); +LazyFunction &build_closure_zone_lazy_function(ResourceScope &scope, + const bNodeTree &btree, + const bke::bNodeTreeZone &zone, + ZoneBuildInfo &zone_info, + const ZoneBodyFunction &body_fn); + +struct EvaluateClosureFunctionIndices { + struct { + Vector main; + Vector output_usages; + Map reference_set_by_output; + } inputs; + struct { + Vector main; + Vector input_usages; + } outputs; +}; + +struct EvaluateClosureFunction { + const LazyFunction *lazy_function = nullptr; + EvaluateClosureFunctionIndices indices; +}; + +EvaluateClosureFunction build_evaluate_closure_node_lazy_function(ResourceScope &scope, + const bNode &bnode); + void initialize_zone_wrapper(const bke::bNodeTreeZone &zone, ZoneBuildInfo &zone_info, const ZoneBodyFunction &body_fn, + bool expose_all_reference_sets, Vector &r_inputs, Vector &r_outputs); @@ -605,4 +634,21 @@ std::string zone_wrapper_output_name(const ZoneBuildInfo &zone_info, const Span outputs, const int lf_socket_i); +/** + * Performs implicit conversion between socket types. Returns false if the conversion is not + * possible. In that case, r_to_value is left uninitialized. + */ +[[nodiscard]] bool implicitly_convert_socket_value(const bke::bNodeSocketType &from_type, + const void *from_value, + const bke::bNodeSocketType &to_type, + void *r_to_value); + +/** + * Builds a lazy-function that can convert between socket types. Returns null if the conversion is + * never possible. + */ +const LazyFunction *build_implicit_conversion_lazy_function(const bke::bNodeSocketType &from_type, + const bke::bNodeSocketType &to_type, + ResourceScope &scope); + } // namespace blender::nodes diff --git a/source/blender/nodes/NOD_geometry_nodes_log.hh b/source/blender/nodes/NOD_geometry_nodes_log.hh index fe8ddff32c9..3b3b2ba07e0 100644 --- a/source/blender/nodes/NOD_geometry_nodes_log.hh +++ b/source/blender/nodes/NOD_geometry_nodes_log.hh @@ -40,6 +40,8 @@ #include "BKE_node_tree_zones.hh" #include "BKE_volume_grid_fwd.hh" +#include "NOD_geometry_nodes_bundle.hh" + #include "FN_field.hh" #include "DNA_node_types.h" @@ -184,6 +186,31 @@ class GeometryInfoLog : public ValueLog { GeometryInfoLog(const bke::GVolumeGrid &grid); }; +class BundleValueLog : public ValueLog { + public: + struct Item { + SocketInterfaceKey key; + const bke::bNodeSocketType *type; + }; + + Vector items; + + BundleValueLog(Vector items); +}; + +class ClosureValueLog : public ValueLog { + public: + struct Item { + SocketInterfaceKey key; + const bke::bNodeSocketType *type; + }; + + Vector inputs; + Vector outputs; + + ClosureValueLog(Vector inputs, Vector outputs); +}; + /** * Data logged by a viewer node when it is executed. In this case, we do want to log the entire * geometry. diff --git a/source/blender/nodes/NOD_socket_declarations.hh b/source/blender/nodes/NOD_socket_declarations.hh index b0394ff786b..d2e13fcc66c 100644 --- a/source/blender/nodes/NOD_socket_declarations.hh +++ b/source/blender/nodes/NOD_socket_declarations.hh @@ -238,6 +238,42 @@ class MenuBuilder : public SocketDeclarationBuilder { MenuBuilder &default_value(int32_t value); }; +class BundleBuilder; + +class Bundle : public SocketDeclaration { + public: + static constexpr eNodeSocketDatatype static_socket_type = SOCK_BUNDLE; + + friend BundleBuilder; + + using Builder = BundleBuilder; + + bNodeSocket &build(bNodeTree &ntree, bNode &node) const override; + bool matches(const bNodeSocket &socket) const override; + bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; + bool can_connect(const bNodeSocket &socket) const override; +}; + +class BundleBuilder : public SocketDeclarationBuilder {}; + +class ClosureBuilder; + +class Closure : public SocketDeclaration { + public: + static constexpr eNodeSocketDatatype static_socket_type = SOCK_CLOSURE; + + friend ClosureBuilder; + + using Builder = ClosureBuilder; + + bNodeSocket &build(bNodeTree &ntree, bNode &node) const override; + bool matches(const bNodeSocket &socket) const override; + bNodeSocket &update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const override; + bool can_connect(const bNodeSocket &socket) const override; +}; + +class ClosureBuilder : public SocketDeclarationBuilder {}; + class IDSocketDeclaration : public SocketDeclaration { public: const char *idname; diff --git a/source/blender/nodes/NOD_socket_interface_key.hh b/source/blender/nodes/NOD_socket_interface_key.hh new file mode 100644 index 00000000000..5960631d25c --- /dev/null +++ b/source/blender/nodes/NOD_socket_interface_key.hh @@ -0,0 +1,32 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include + +#include "BLI_vector.hh" + +namespace blender::nodes { + +/** + * A key that identifies values in a bundle or inputs/outputs of a closure. + * Note that this key does not have a hash and thus can't be used in a hash table. This wouldn't + * work well if these items have multiple identifiers for compatibility reasons. While that's not + * used currently, it's good to keep it possible. + */ +class SocketInterfaceKey { + private: + /** May have multiple keys to improve compatibility between systems that use different keys. */ + Vector identifiers_; + + public: + explicit SocketInterfaceKey(Vector identifiers); + explicit SocketInterfaceKey(std::string identifier); + + bool matches(const SocketInterfaceKey &other) const; + Span identifiers() const; +}; + +} // namespace blender::nodes diff --git a/source/blender/nodes/NOD_socket_items_ui.hh b/source/blender/nodes/NOD_socket_items_ui.hh index 737e2c0f23c..d2260521874 100644 --- a/source/blender/nodes/NOD_socket_items_ui.hh +++ b/source/blender/nodes/NOD_socket_items_ui.hh @@ -13,6 +13,8 @@ #include "RNA_access.hh" #include "RNA_prototypes.hh" +#include "BLI_function_ref.hh" + #include "BKE_screen.hh" #include "NOD_socket_items.hh" diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 34ba9d4da15..f3e75c72a81 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -32,7 +32,9 @@ set(SRC nodes/node_geo_boolean.cc nodes/node_geo_bounding_box.cc nodes/node_geo_camera_info.cc + nodes/node_geo_closure.cc nodes/node_geo_collection_info.cc + nodes/node_geo_combine_bundle.cc nodes/node_geo_common.cc nodes/node_geo_convex_hull.cc nodes/node_geo_curve_endpoint_selection.cc @@ -73,6 +75,7 @@ set(SRC nodes/node_geo_edge_split.cc nodes/node_geo_edges_to_face_groups.cc nodes/node_geo_evaluate_at_index.cc + nodes/node_geo_evaluate_closure.cc nodes/node_geo_evaluate_on_domain.cc nodes/node_geo_extrude_mesh.cc nodes/node_geo_flip_faces.cc @@ -184,6 +187,7 @@ set(SRC nodes/node_geo_scale_instances.cc nodes/node_geo_sdf_grid_boolean.cc nodes/node_geo_self_object.cc + nodes/node_geo_separate_bundle.cc nodes/node_geo_separate_components.cc nodes/node_geo_separate_geometry.cc nodes/node_geo_set_curve_handles.cc diff --git a/source/blender/nodes/geometry/include/NOD_geo_bundle.hh b/source/blender/nodes/geometry/include/NOD_geo_bundle.hh new file mode 100644 index 00000000000..aed64614e4f --- /dev/null +++ b/source/blender/nodes/geometry/include/NOD_geo_bundle.hh @@ -0,0 +1,181 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "DNA_node_types.h" + +#include "NOD_socket_items.hh" + +namespace blender::nodes { + +inline bool socket_type_supported_in_bundle(const eNodeSocketDatatype socket_type) +{ + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_ROTATION, + SOCK_MATRIX, + SOCK_INT, + SOCK_STRING, + SOCK_GEOMETRY, + SOCK_OBJECT, + SOCK_MATERIAL, + SOCK_IMAGE, + SOCK_COLLECTION, + SOCK_BUNDLE, + SOCK_CLOSURE); +} + +struct CombineBundleItemsAccessor { + using ItemT = NodeGeometryCombineBundleItem; + static StructRNA *item_srna; + static int node_type; + static int item_dna_type; + static constexpr const char *node_idname = "GeometryNodeCombineBundle"; + static constexpr bool has_type = true; + static constexpr bool has_name = true; + static constexpr bool has_single_identifier_str = true; + struct operator_idnames { + static constexpr const char *add_item = "NODE_OT_combine_bundle_item_add"; + static constexpr const char *remove_item = "NODE_OT_combine_bundle_item_remove"; + static constexpr const char *move_item = "NODE_OT_combine_bundle_item_move"; + }; + struct ui_idnames { + static constexpr const char *list = "DATA_UL_combine_bundle_items"; + }; + struct rna_names { + static constexpr const char *items = "bundle_items"; + static constexpr const char *active_index = "active_index"; + }; + + static socket_items::SocketItemsRef get_items_from_node(bNode &node) + { + auto *storage = static_cast(node.storage); + return {&storage->items, &storage->items_num, &storage->active_index}; + } + + static void copy_item(const ItemT &src, ItemT &dst) + { + dst = src; + dst.name = BLI_strdup_null(dst.name); + } + + static void destruct_item(ItemT *item) + { + MEM_SAFE_FREE(item->name); + } + + static void blend_write_item(BlendWriter *writer, const ItemT &item); + static void blend_read_data_item(BlendDataReader *reader, ItemT &item); + + static eNodeSocketDatatype get_socket_type(const ItemT &item) + { + return eNodeSocketDatatype(item.socket_type); + } + + static char **get_name(ItemT &item) + { + return &item.name; + } + + static bool supports_socket_type(const eNodeSocketDatatype socket_type) + { + return socket_type_supported_in_bundle(socket_type); + } + + static void init_with_socket_type_and_name(bNode &node, + ItemT &item, + const eNodeSocketDatatype socket_type, + const char *name) + { + auto *storage = static_cast(node.storage); + item.socket_type = socket_type; + item.identifier = storage->next_identifier++; + socket_items::set_item_name_and_make_unique(node, item, name); + } + + static std::string socket_identifier_for_item(const ItemT &item) + { + return "Item_" + std::to_string(item.identifier); + } +}; + +struct SeparateBundleItemsAccessor { + using ItemT = NodeGeometrySeparateBundleItem; + static StructRNA *item_srna; + static int node_type; + static int item_dna_type; + static constexpr const char *node_idname = "GeometryNodeSeparateBundle"; + static constexpr bool has_type = true; + static constexpr bool has_name = true; + static constexpr bool has_single_identifier_str = true; + struct operator_idnames { + static constexpr const char *add_item = "NODE_OT_separate_bundle_item_add"; + static constexpr const char *remove_item = "NODE_OT_separate_bundle_item_remove"; + static constexpr const char *move_item = "NODE_OT_separate_bundle_item_move"; + }; + struct ui_idnames { + static constexpr const char *list = "DATA_UL_separate_bundle_items"; + }; + struct rna_names { + static constexpr const char *items = "bundle_items"; + static constexpr const char *active_index = "active_index"; + }; + + static socket_items::SocketItemsRef get_items_from_node(bNode &node) + { + auto *storage = static_cast(node.storage); + return {&storage->items, &storage->items_num, &storage->active_index}; + } + + static void copy_item(const ItemT &src, ItemT &dst) + { + dst = src; + dst.name = BLI_strdup_null(dst.name); + } + + static void destruct_item(ItemT *item) + { + MEM_SAFE_FREE(item->name); + } + + static void blend_write_item(BlendWriter *writer, const ItemT &item); + static void blend_read_data_item(BlendDataReader *reader, ItemT &item); + + static eNodeSocketDatatype get_socket_type(const ItemT &item) + { + return eNodeSocketDatatype(item.socket_type); + } + + static char **get_name(ItemT &item) + { + return &item.name; + } + + static bool supports_socket_type(const eNodeSocketDatatype socket_type) + { + return socket_type_supported_in_bundle(socket_type); + } + + static void init_with_socket_type_and_name(bNode &node, + ItemT &item, + const eNodeSocketDatatype socket_type, + const char *name) + { + auto *storage = static_cast(node.storage); + item.socket_type = socket_type; + item.identifier = storage->next_identifier++; + socket_items::set_item_name_and_make_unique(node, item, name); + } + + static std::string socket_identifier_for_item(const ItemT &item) + { + return "Item_" + std::to_string(item.identifier); + } +}; + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/include/NOD_geo_closure.hh b/source/blender/nodes/geometry/include/NOD_geo_closure.hh new file mode 100644 index 00000000000..f96d490509b --- /dev/null +++ b/source/blender/nodes/geometry/include/NOD_geo_closure.hh @@ -0,0 +1,339 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "DNA_node_types.h" + +#include "NOD_socket_items.hh" + +namespace blender::nodes { + +inline bool socket_type_supported_in_closure(const eNodeSocketDatatype socket_type) +{ + return ELEM(socket_type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_ROTATION, + SOCK_MATRIX, + SOCK_INT, + SOCK_STRING, + SOCK_GEOMETRY, + SOCK_OBJECT, + SOCK_MATERIAL, + SOCK_IMAGE, + SOCK_COLLECTION, + SOCK_BUNDLE, + SOCK_CLOSURE); +} + +struct ClosureInputItemsAccessor { + using ItemT = NodeGeometryClosureInputItem; + static StructRNA *item_srna; + static int node_type; + static int item_dna_type; + static constexpr const char *node_idname = "GeometryNodeClosureOutput"; + static constexpr bool has_type = true; + static constexpr bool has_name = true; + static constexpr bool has_single_identifier_str = true; + struct operator_idnames { + static constexpr const char *add_item = "NODE_OT_closure_input_item_add"; + static constexpr const char *remove_item = "NODE_OT_closure_input_item_remove"; + static constexpr const char *move_item = "NODE_OT_closure_input_item_move"; + }; + struct ui_idnames { + static constexpr const char *list = "DATA_UL_closure_input_items"; + }; + struct rna_names { + static constexpr const char *items = "input_items"; + static constexpr const char *active_index = "active_input_index"; + }; + + static socket_items::SocketItemsRef get_items_from_node(bNode &node) + { + auto *storage = static_cast(node.storage); + return {&storage->input_items.items, + &storage->input_items.items_num, + &storage->input_items.active_index}; + } + + static void copy_item(const ItemT &src, ItemT &dst) + { + dst = src; + dst.name = BLI_strdup_null(dst.name); + } + + static void destruct_item(ItemT *item) + { + MEM_SAFE_FREE(item->name); + } + + static void blend_write_item(BlendWriter *writer, const ItemT &item); + static void blend_read_data_item(BlendDataReader *reader, ItemT &item); + + static eNodeSocketDatatype get_socket_type(const ItemT &item) + { + return eNodeSocketDatatype(item.socket_type); + } + + static char **get_name(ItemT &item) + { + return &item.name; + } + + static bool supports_socket_type(const eNodeSocketDatatype socket_type) + { + return socket_type_supported_in_closure(socket_type); + } + + static void init_with_socket_type_and_name(bNode &node, + ItemT &item, + const eNodeSocketDatatype socket_type, + const char *name) + { + auto *storage = static_cast(node.storage); + item.socket_type = socket_type; + item.identifier = storage->input_items.next_identifier++; + socket_items::set_item_name_and_make_unique(node, item, name); + } + + static std::string socket_identifier_for_item(const ItemT &item) + { + return "Item_" + std::to_string(item.identifier); + } +}; + +struct ClosureOutputItemsAccessor { + using ItemT = NodeGeometryClosureOutputItem; + static StructRNA *item_srna; + static int node_type; + static int item_dna_type; + static constexpr const char *node_idname = "GeometryNodeClosureOutput"; + static constexpr bool has_type = true; + static constexpr bool has_name = true; + static constexpr bool has_single_identifier_str = true; + struct operator_idnames { + static constexpr const char *add_item = "NODE_OT_closure_output_item_add"; + static constexpr const char *remove_item = "NODE_OT_closure_output_item_remove"; + static constexpr const char *move_item = "NODE_OT_closure_output_item_move"; + }; + struct ui_idnames { + static constexpr const char *list = "DATA_UL_closure_output_items"; + }; + struct rna_names { + static constexpr const char *items = "output_items"; + static constexpr const char *active_index = "active_output_index"; + }; + + static socket_items::SocketItemsRef get_items_from_node(bNode &node) + { + auto *storage = static_cast(node.storage); + return {&storage->output_items.items, + &storage->output_items.items_num, + &storage->output_items.active_index}; + } + + static void copy_item(const ItemT &src, ItemT &dst) + { + dst = src; + dst.name = BLI_strdup_null(dst.name); + } + + static void destruct_item(ItemT *item) + { + MEM_SAFE_FREE(item->name); + } + + static void blend_write_item(BlendWriter *writer, const ItemT &item); + static void blend_read_data_item(BlendDataReader *reader, ItemT &item); + + static eNodeSocketDatatype get_socket_type(const ItemT &item) + { + return eNodeSocketDatatype(item.socket_type); + } + + static char **get_name(ItemT &item) + { + return &item.name; + } + + static bool supports_socket_type(const eNodeSocketDatatype socket_type) + { + return socket_type_supported_in_closure(socket_type); + } + + static void init_with_socket_type_and_name(bNode &node, + ItemT &item, + const eNodeSocketDatatype socket_type, + const char *name) + { + auto *storage = static_cast(node.storage); + item.socket_type = socket_type; + item.identifier = storage->output_items.next_identifier++; + socket_items::set_item_name_and_make_unique(node, item, name); + } + + static std::string socket_identifier_for_item(const ItemT &item) + { + return "Item_" + std::to_string(item.identifier); + } +}; + +struct EvaluateClosureInputItemsAccessor { + using ItemT = NodeGeometryEvaluateClosureInputItem; + static StructRNA *item_srna; + static int node_type; + static int item_dna_type; + static constexpr const char *node_idname = "GeometryNodeEvaluateClosure"; + static constexpr bool has_type = true; + static constexpr bool has_name = true; + static constexpr bool has_single_identifier_str = true; + struct operator_idnames { + static constexpr const char *add_item = "NODE_OT_evaluate_closure_input_item_add"; + static constexpr const char *remove_item = "NODE_OT_evaluate_closure_input_item_remove"; + static constexpr const char *move_item = "NODE_OT_evaluate_closure_input_item_move"; + }; + struct ui_idnames { + static constexpr const char *list = "DATA_UL_evaluate_closure_input_items"; + }; + struct rna_names { + static constexpr const char *items = "input_items"; + static constexpr const char *active_index = "active_input_index"; + }; + + static socket_items::SocketItemsRef get_items_from_node(bNode &node) + { + auto *storage = static_cast(node.storage); + return {&storage->input_items.items, + &storage->input_items.items_num, + &storage->input_items.active_index}; + } + + static void copy_item(const ItemT &src, ItemT &dst) + { + dst = src; + dst.name = BLI_strdup_null(dst.name); + } + + static void destruct_item(ItemT *item) + { + MEM_SAFE_FREE(item->name); + } + + static void blend_write_item(BlendWriter *writer, const ItemT &item); + static void blend_read_data_item(BlendDataReader *reader, ItemT &item); + + static eNodeSocketDatatype get_socket_type(const ItemT &item) + { + return eNodeSocketDatatype(item.socket_type); + } + + static char **get_name(ItemT &item) + { + return &item.name; + } + + static bool supports_socket_type(const eNodeSocketDatatype socket_type) + { + return socket_type_supported_in_closure(socket_type); + } + + static void init_with_socket_type_and_name(bNode &node, + ItemT &item, + const eNodeSocketDatatype socket_type, + const char *name) + { + auto *storage = static_cast(node.storage); + item.socket_type = socket_type; + item.identifier = storage->input_items.next_identifier++; + socket_items::set_item_name_and_make_unique( + node, item, name); + } + + static std::string socket_identifier_for_item(const ItemT &item) + { + return "Item_" + std::to_string(item.identifier); + } +}; + +struct EvaluateClosureOutputItemsAccessor { + using ItemT = NodeGeometryEvaluateClosureOutputItem; + static StructRNA *item_srna; + static int node_type; + static int item_dna_type; + static constexpr const char *node_idname = "GeometryNodeEvaluateClosure"; + static constexpr bool has_type = true; + static constexpr bool has_name = true; + static constexpr bool has_single_identifier_str = true; + struct operator_idnames { + static constexpr const char *add_item = "NODE_OT_evaluate_closure_output_item_add"; + static constexpr const char *remove_item = "NODE_OT_evaluate_closure_output_item_remove"; + static constexpr const char *move_item = "NODE_OT_evaluate_closure_output_item_move"; + }; + struct ui_idnames { + static constexpr const char *list = "DATA_UL_evaluate_closure_output_items"; + }; + struct rna_names { + static constexpr const char *items = "output_items"; + static constexpr const char *active_index = "active_output_index"; + }; + + static socket_items::SocketItemsRef get_items_from_node(bNode &node) + { + auto *storage = static_cast(node.storage); + return {&storage->output_items.items, + &storage->output_items.items_num, + &storage->output_items.active_index}; + } + + static void copy_item(const ItemT &src, ItemT &dst) + { + dst = src; + dst.name = BLI_strdup_null(dst.name); + } + + static void destruct_item(ItemT *item) + { + MEM_SAFE_FREE(item->name); + } + + static void blend_write_item(BlendWriter *writer, const ItemT &item); + static void blend_read_data_item(BlendDataReader *reader, ItemT &item); + + static eNodeSocketDatatype get_socket_type(const ItemT &item) + { + return eNodeSocketDatatype(item.socket_type); + } + + static char **get_name(ItemT &item) + { + return &item.name; + } + + static bool supports_socket_type(const eNodeSocketDatatype socket_type) + { + return socket_type_supported_in_closure(socket_type); + } + + static void init_with_socket_type_and_name(bNode &node, + ItemT &item, + const eNodeSocketDatatype socket_type, + const char *name) + { + auto *storage = static_cast(node.storage); + item.socket_type = socket_type; + item.identifier = storage->output_items.next_identifier++; + socket_items::set_item_name_and_make_unique( + node, item, name); + } + + static std::string socket_identifier_for_item(const ItemT &item) + { + return "Item_" + std::to_string(item.identifier); + } +}; + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/include/NOD_geo_repeat.hh b/source/blender/nodes/geometry/include/NOD_geo_repeat.hh index b0f4782c886..7daadab08a2 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_repeat.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_repeat.hh @@ -81,7 +81,9 @@ struct RepeatItemsAccessor { SOCK_OBJECT, SOCK_MATERIAL, SOCK_IMAGE, - SOCK_COLLECTION); + SOCK_COLLECTION, + SOCK_BUNDLE, + SOCK_CLOSURE); } static void init_with_socket_type_and_name(bNode &node, diff --git a/source/blender/nodes/geometry/include/NOD_geo_simulation.hh b/source/blender/nodes/geometry/include/NOD_geo_simulation.hh index f2f54836068..d4a018dd045 100644 --- a/source/blender/nodes/geometry/include/NOD_geo_simulation.hh +++ b/source/blender/nodes/geometry/include/NOD_geo_simulation.hh @@ -68,6 +68,7 @@ struct SimulationItemsAccessor { static bool supports_socket_type(const eNodeSocketDatatype socket_type) { + /* Data-block types and closures are not supported. */ return ELEM(socket_type, SOCK_FLOAT, SOCK_VECTOR, @@ -77,7 +78,8 @@ struct SimulationItemsAccessor { SOCK_MATRIX, SOCK_INT, SOCK_STRING, - SOCK_GEOMETRY); + SOCK_GEOMETRY, + SOCK_BUNDLE); } static void init_with_socket_type_and_name(bNode &node, diff --git a/source/blender/nodes/geometry/node_geometry_tree.cc b/source/blender/nodes/geometry/node_geometry_tree.cc index 398ea584278..d1a44eaf8b1 100644 --- a/source/blender/nodes/geometry/node_geometry_tree.cc +++ b/source/blender/nodes/geometry/node_geometry_tree.cc @@ -116,22 +116,24 @@ static bool geometry_node_tree_validate_link(eNodeSocketDatatype type_a, static bool geometry_node_tree_socket_type_valid(blender::bke::bNodeTreeType * /*treetype*/, blender::bke::bNodeSocketType *socket_type) { - return blender::bke::node_is_static_socket_type(*socket_type) && ELEM(socket_type->type, - SOCK_FLOAT, - SOCK_VECTOR, - SOCK_RGBA, - SOCK_BOOLEAN, - SOCK_ROTATION, - SOCK_MATRIX, - SOCK_INT, - SOCK_STRING, - SOCK_OBJECT, - SOCK_GEOMETRY, - SOCK_COLLECTION, - SOCK_TEXTURE, - SOCK_IMAGE, - SOCK_MATERIAL, - SOCK_MENU); + return blender::bke::node_is_static_socket_type(*socket_type) && + (ELEM(socket_type->type, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_RGBA, + SOCK_BOOLEAN, + SOCK_ROTATION, + SOCK_MATRIX, + SOCK_INT, + SOCK_STRING, + SOCK_OBJECT, + SOCK_GEOMETRY, + SOCK_COLLECTION, + SOCK_TEXTURE, + SOCK_IMAGE, + SOCK_MATERIAL, + SOCK_MENU) || + ELEM(socket_type->type, SOCK_BUNDLE, SOCK_CLOSURE)); } void register_node_tree_type_geo() diff --git a/source/blender/nodes/geometry/nodes/node_geo_bake.cc b/source/blender/nodes/geometry/nodes/node_geo_bake.cc index cdfabb11a8d..4aae5e8772a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_bake.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_bake.cc @@ -251,7 +251,7 @@ class LazyFunctionForBakeNode final : public LazyFunction { this->set_default_outputs(params); return; } - if (found_id->is_in_loop) { + if (found_id->is_in_loop || found_id->is_in_closure) { DummyDataBlockMap data_block_map; this->pass_through(params, user_data, &data_block_map); return; diff --git a/source/blender/nodes/geometry/nodes/node_geo_closure.cc b/source/blender/nodes/geometry/nodes/node_geo_closure.cc new file mode 100644 index 00000000000..f31462db299 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_closure.cc @@ -0,0 +1,237 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "node_geometry_util.hh" + +#include "BLI_string_utf8.h" + +#include "NOD_geo_closure.hh" +#include "NOD_socket_items_ops.hh" +#include "NOD_socket_items_ui.hh" + +#include "BLO_read_write.hh" + +namespace blender::nodes::node_geo_closure_cc { + +/** Shared between closure input and output node. */ +static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *current_node_ptr) +{ + bNodeTree &ntree = *reinterpret_cast(current_node_ptr->owner_id); + bNode *current_node = static_cast(current_node_ptr->data); + + const bke::bNodeTreeZones *zones = ntree.zones(); + if (!zones) { + return; + } + const bke::bNodeTreeZone *zone = zones->get_zone_by_node(current_node->identifier); + if (!zone) { + return; + } + if (!zone->output_node) { + return; + } + bNode &output_node = const_cast(*zone->output_node); + + if (current_node->type_legacy == GEO_NODE_CLOSURE_INPUT) { + if (uiLayout *panel = uiLayoutPanel(C, layout, "input_items", false, TIP_("Input Items"))) { + socket_items::ui::draw_items_list_with_operators( + C, panel, ntree, output_node); + socket_items::ui::draw_active_item_props( + ntree, output_node, [&](PointerRNA *item_ptr) { + uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE); + }); + } + } + else { + if (uiLayout *panel = uiLayoutPanel(C, layout, "output_items", false, TIP_("Output Items"))) { + socket_items::ui::draw_items_list_with_operators( + C, panel, ntree, output_node); + socket_items::ui::draw_active_item_props( + ntree, output_node, [&](PointerRNA *item_ptr) { + uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE); + }); + } + } +} + +namespace input_node { + +NODE_STORAGE_FUNCS(NodeGeometryClosureInput); + +static void node_declare(NodeDeclarationBuilder &b) +{ + const bNode *node = b.node_or_null(); + const bNodeTree *tree = b.tree_or_null(); + if (node && tree) { + const NodeGeometryClosureInput &storage = node_storage(*node); + const bNode *output_node = tree->node_by_id(storage.output_node_id); + if (output_node) { + const auto &output_storage = *static_cast( + output_node->storage); + for (const int i : IndexRange(output_storage.input_items.items_num)) { + const NodeGeometryClosureInputItem &item = output_storage.input_items.items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + const std::string identifier = ClosureInputItemsAccessor::socket_identifier_for_item(item); + b.add_output(socket_type, item.name, identifier); + } + } + } + b.add_output("", "__extend__"); +} + +static void node_label(const bNodeTree * /*ntree*/, + const bNode * /*node*/, + char *label, + const int label_maxncpy) +{ + BLI_strncpy_utf8(label, IFACE_("Closure"), label_maxncpy); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + NodeGeometryClosureInput *data = MEM_callocN(__func__); + node->storage = data; +} + +static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) +{ + bNode *output_node = ntree->node_by_id(node_storage(*node).output_node_id); + if (!output_node) { + return true; + } + return socket_items::try_add_item_via_any_extend_socket( + *ntree, *node, *output_node, *link); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "GeometryNodeClosureInput", GEO_NODE_CLOSURE_INPUT); + ntype.ui_name = "Closure Input"; + ntype.nclass = NODE_CLASS_INTERFACE; + ntype.declare = node_declare; + ntype.gather_link_search_ops = nullptr; + ntype.initfunc = node_init; + ntype.labelfunc = node_label; + ntype.no_muting = true; + ntype.insert_link = node_insert_link; + ntype.draw_buttons_ex = node_layout_ex; + blender::bke::node_type_storage( + ntype, "NodeGeometryClosureInput", node_free_standard_storage, node_copy_standard_storage); + blender::bke::node_register_type(ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace input_node + +namespace output_node { + +NODE_STORAGE_FUNCS(NodeGeometryClosureOutput); + +static void node_declare(NodeDeclarationBuilder &b) +{ + const bNodeTree *tree = b.tree_or_null(); + const bNode *node = b.node_or_null(); + if (node && tree) { + const NodeGeometryClosureOutput &storage = node_storage(*node); + for (const int i : IndexRange(storage.output_items.items_num)) { + const NodeGeometryClosureOutputItem &item = storage.output_items.items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + const std::string identifier = ClosureOutputItemsAccessor::socket_identifier_for_item(item); + b.add_input(socket_type, item.name, identifier); + } + } + b.add_input("", "__extend__"); + b.add_output("Closure"); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + NodeGeometryClosureOutput *data = MEM_callocN(__func__); + node->storage = data; +} + +static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node) +{ + const NodeGeometryClosureOutput &src_storage = node_storage(*src_node); + auto *dst_storage = MEM_dupallocN(__func__, src_storage); + dst_node->storage = dst_storage; + + socket_items::copy_array(*src_node, *dst_node); + socket_items::copy_array(*src_node, *dst_node); +} + +static void node_free_storage(bNode *node) +{ + socket_items::destruct_array(*node); + socket_items::destruct_array(*node); + MEM_freeN(node->storage); +} + +static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) +{ + return socket_items::try_add_item_via_any_extend_socket( + *ntree, *node, *node, *link); +} + +static void node_operators() +{ + socket_items::ops::make_common_operators(); + socket_items::ops::make_common_operators(); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + geo_node_type_base(&ntype, "GeometryNodeClosureOutput", GEO_NODE_CLOSURE_OUTPUT); + ntype.ui_name = "Closure Output"; + ntype.nclass = NODE_CLASS_INTERFACE; + ntype.declare = node_declare; + ntype.initfunc = node_init; + ntype.labelfunc = input_node::node_label; + ntype.no_muting = true; + ntype.register_operators = node_operators; + ntype.insert_link = node_insert_link; + ntype.draw_buttons_ex = node_layout_ex; + bke::node_type_storage(ntype, "NodeGeometryClosureOutput", node_free_storage, node_copy_storage); + blender::bke::node_register_type(ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace output_node + +} // namespace blender::nodes::node_geo_closure_cc + +namespace blender::nodes { + +StructRNA *ClosureInputItemsAccessor::item_srna = &RNA_NodeGeometryClosureInputItem; +int ClosureInputItemsAccessor::node_type = GEO_NODE_CLOSURE_OUTPUT; +int ClosureInputItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT(NodeGeometryClosureInputItem); + +void ClosureInputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item) +{ + BLO_write_string(writer, item.name); +} + +void ClosureInputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item) +{ + BLO_read_string(reader, &item.name); +} + +StructRNA *ClosureOutputItemsAccessor::item_srna = &RNA_NodeGeometryClosureOutputItem; +int ClosureOutputItemsAccessor::node_type = GEO_NODE_CLOSURE_OUTPUT; +int ClosureOutputItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT( + NodeGeometryClosureOutputItem); + +void ClosureOutputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item) +{ + BLO_write_string(writer, item.name); +} + +void ClosureOutputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item) +{ + BLO_read_string(reader, &item.name); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc b/source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc new file mode 100644 index 00000000000..44a28838f54 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_combine_bundle.cc @@ -0,0 +1,157 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "node_geometry_util.hh" + +#include "NOD_geo_bundle.hh" +#include "NOD_socket_items_ops.hh" +#include "NOD_socket_items_ui.hh" + +#include "BLO_read_write.hh" + +#include "NOD_geometry_nodes_bundle.hh" + +#include "UI_interface.hh" + +namespace blender::nodes::node_geo_combine_bundle_cc { + +NODE_STORAGE_FUNCS(NodeGeometryCombineBundle); + +static void node_declare(NodeDeclarationBuilder &b) +{ + const bNodeTree *tree = b.tree_or_null(); + const bNode *node = b.node_or_null(); + if (tree && node) { + const NodeGeometryCombineBundle &storage = node_storage(*node); + for (const int i : IndexRange(storage.items_num)) { + const NodeGeometryCombineBundleItem &item = storage.items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + const StringRef name = item.name ? item.name : ""; + const std::string identifier = CombineBundleItemsAccessor::socket_identifier_for_item(item); + b.add_input(socket_type, name, identifier) + .socket_name_ptr(&tree->id, CombineBundleItemsAccessor::item_srna, &item, "name"); + } + } + b.add_input("", "__extend__"); + b.add_output("Bundle").propagate_all().reference_pass_all(); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + auto *storage = MEM_callocN(__func__); + node->storage = storage; +} + +static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node) +{ + const NodeGeometryCombineBundle &src_storage = node_storage(*src_node); + auto *dst_storage = MEM_dupallocN(__func__, src_storage); + dst_node->storage = dst_storage; + + socket_items::copy_array(*src_node, *dst_node); +} + +static void node_free_storage(bNode *node) +{ + socket_items::destruct_array(*node); + MEM_freeN(node->storage); +} + +static bool node_insert_link(bNodeTree *tree, bNode *node, bNodeLink *link) +{ + return socket_items::try_add_item_via_any_extend_socket( + *tree, *node, *node, *link); +} + +static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *node_ptr) +{ + bNodeTree &ntree = *reinterpret_cast(node_ptr->owner_id); + bNode &node = *static_cast(node_ptr->data); + + if (uiLayout *panel = uiLayoutPanel(C, layout, "bundle_items", false, TIP_("Bundle Items"))) { + socket_items::ui::draw_items_list_with_operators( + C, panel, ntree, node); + socket_items::ui::draw_active_item_props( + ntree, node, [&](PointerRNA *item_ptr) { + uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, "Type", ICON_NONE); + }); + } +} + +static void node_operators() +{ + socket_items::ops::make_common_operators(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + if (!U.experimental.use_bundle_and_closure_nodes) { + params.set_default_remaining_outputs(); + return; + } + + const bNode &node = params.node(); + const NodeGeometryCombineBundle &storage = node_storage(node); + + BundlePtr bundle_ptr = Bundle::create(); + BLI_assert(bundle_ptr->is_mutable()); + Bundle &bundle = const_cast(*bundle_ptr); + + for (const int i : IndexRange(storage.items_num)) { + const NodeGeometryCombineBundleItem &item = storage.items[i]; + const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type); + if (!stype || !stype->geometry_nodes_cpp_type) { + continue; + } + const StringRef name = item.name; + if (name.is_empty()) { + continue; + } + void *input_ptr = params.low_level_lazy_function_params().try_get_input_data_ptr(i); + BLI_assert(input_ptr); + bundle.add(SocketInterfaceKey(name), *stype, input_ptr); + } + + params.set_output("Bundle", std::move(bundle_ptr)); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + + geo_node_type_base(&ntype, "GeometryNodeCombineBundle", GEO_NODE_COMBINE_BUNDLE); + ntype.ui_name = "Combine Bundle"; + ntype.ui_description = "Combine multiple socket values into one."; + ntype.nclass = NODE_CLASS_CONVERTER; + ntype.declare = node_declare; + ntype.initfunc = node_init; + ntype.geometry_node_execute = node_geo_exec; + ntype.insert_link = node_insert_link; + ntype.draw_buttons_ex = node_layout_ex; + ntype.register_operators = node_operators; + bke::node_type_storage(ntype, "NodeGeometryCombineBundle", node_free_storage, node_copy_storage); + blender::bke::node_register_type(ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_combine_bundle_cc + +namespace blender::nodes { + +StructRNA *CombineBundleItemsAccessor::item_srna = &RNA_NodeGeometryCombineBundleItem; +int CombineBundleItemsAccessor::node_type = GEO_NODE_COMBINE_BUNDLE; +int CombineBundleItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT( + NodeGeometryCombineBundleItem); + +void CombineBundleItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item) +{ + BLO_write_string(writer, item.name); +} + +void CombineBundleItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item) +{ + BLO_read_string(reader, &item.name); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc b/source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc new file mode 100644 index 00000000000..48d11714427 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_evaluate_closure.cc @@ -0,0 +1,163 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "UI_interface.hh" +#include "UI_resources.hh" + +#include "NOD_geo_closure.hh" +#include "NOD_socket_items_ops.hh" +#include "NOD_socket_items_ui.hh" + +#include "BLO_read_write.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_evaluate_closure_cc { + +NODE_STORAGE_FUNCS(NodeGeometryEvaluateClosure) + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Closure"); + + const bNode *node = b.node_or_null(); + if (node) { + const auto &storage = node_storage(*node); + for (const int i : IndexRange(storage.input_items.items_num)) { + const NodeGeometryEvaluateClosureInputItem &item = storage.input_items.items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + const std::string identifier = EvaluateClosureInputItemsAccessor::socket_identifier_for_item( + item); + b.add_input(socket_type, item.name, identifier); + } + for (const int i : IndexRange(storage.output_items.items_num)) { + const NodeGeometryEvaluateClosureOutputItem &item = storage.output_items.items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + const std::string identifier = + EvaluateClosureOutputItemsAccessor::socket_identifier_for_item(item); + b.add_output(socket_type, item.name, identifier).propagate_all().reference_pass_all(); + } + } + + b.add_input("", "__extend__"); + b.add_output("", "__extend__"); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + auto *storage = MEM_callocN(__func__); + node->storage = storage; +} + +static void node_copy_storage(bNodeTree * /*tree*/, bNode *dst_node, const bNode *src_node) +{ + const NodeGeometryEvaluateClosure &src_storage = node_storage(*src_node); + auto *dst_storage = MEM_dupallocN(__func__, src_storage); + dst_node->storage = dst_storage; + + socket_items::copy_array(*src_node, *dst_node); + socket_items::copy_array(*src_node, *dst_node); +} + +static void node_free_storage(bNode *node) +{ + socket_items::destruct_array(*node); + socket_items::destruct_array(*node); + MEM_freeN(node->storage); +} + +static bool node_insert_link(bNodeTree *ntree, bNode *node, bNodeLink *link) +{ + if (link->tonode == node) { + return socket_items::try_add_item_via_any_extend_socket( + *ntree, *node, *node, *link); + } + return socket_items::try_add_item_via_any_extend_socket( + *ntree, *node, *node, *link); +} + +static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *ptr) +{ + bNodeTree &tree = *reinterpret_cast(ptr->owner_id); + bNode &node = *static_cast(ptr->data); + + if (uiLayout *panel = uiLayoutPanel(C, layout, "input_items", false, IFACE_("Input Items"))) { + socket_items::ui::draw_items_list_with_operators( + C, panel, tree, node); + socket_items::ui::draw_active_item_props( + tree, node, [&](PointerRNA *item_ptr) { + uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE); + }); + } + if (uiLayout *panel = uiLayoutPanel(C, layout, "output_items", false, IFACE_("Output Items"))) { + socket_items::ui::draw_items_list_with_operators( + C, panel, tree, node); + socket_items::ui::draw_active_item_props( + tree, node, [&](PointerRNA *item_ptr) { + uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, std::nullopt, ICON_NONE); + }); + } +} + +static void node_operators() +{ + socket_items::ops::make_common_operators(); + socket_items::ops::make_common_operators(); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + + geo_node_type_base(&ntype, "GeometryNodeEvaluateClosure", GEO_NODE_EVALUATE_CLOSURE); + ntype.ui_name = "Evaluate Closure"; + ntype.nclass = NODE_CLASS_CONVERTER; + ntype.declare = node_declare; + ntype.initfunc = node_init; + ntype.insert_link = node_insert_link; + ntype.draw_buttons_ex = node_layout_ex; + ntype.register_operators = node_operators; + bke::node_type_storage( + ntype, "NodeGeometryEvaluateClosure", node_free_storage, node_copy_storage); + blender::bke::node_register_type(ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_evaluate_closure_cc + +namespace blender::nodes { + +StructRNA *EvaluateClosureInputItemsAccessor::item_srna = + &RNA_NodeGeometryEvaluateClosureInputItem; +int EvaluateClosureInputItemsAccessor::node_type = GEO_NODE_EVALUATE_CLOSURE; +int EvaluateClosureInputItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT( + NodeGeometryEvaluateClosureInputItem); + +void EvaluateClosureInputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item) +{ + BLO_write_string(writer, item.name); +} + +void EvaluateClosureInputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item) +{ + BLO_read_string(reader, &item.name); +} + +StructRNA *EvaluateClosureOutputItemsAccessor::item_srna = + &RNA_NodeGeometryEvaluateClosureOutputItem; +int EvaluateClosureOutputItemsAccessor::node_type = GEO_NODE_EVALUATE_CLOSURE; +int EvaluateClosureOutputItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT( + NodeGeometryEvaluateClosureOutputItem); + +void EvaluateClosureOutputItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item) +{ + BLO_write_string(writer, item.name); +} + +void EvaluateClosureOutputItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item) +{ + BLO_read_string(reader, &item.name); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc index 7fab14b2a59..b48c9e6eed1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_index_switch.cc @@ -340,7 +340,9 @@ static void node_rna(StructRNA *srna) SOCK_COLLECTION, SOCK_MATERIAL, SOCK_IMAGE, - SOCK_MENU); + SOCK_MENU, + SOCK_BUNDLE, + SOCK_CLOSURE); }); }); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_menu_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_menu_switch.cc index 1f237b299fb..73ba906e153 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_menu_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_menu_switch.cc @@ -46,7 +46,9 @@ static bool is_supported_socket_type(const eNodeSocketDatatype data_type) SOCK_COLLECTION, SOCK_MATERIAL, SOCK_IMAGE, - SOCK_MATRIX); + SOCK_MATRIX, + SOCK_BUNDLE, + SOCK_CLOSURE); } static void node_declare(blender::nodes::NodeDeclarationBuilder &b) diff --git a/source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc b/source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc new file mode 100644 index 00000000000..8bbd8c6e7e2 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_separate_bundle.cc @@ -0,0 +1,174 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "node_geometry_util.hh" + +#include "NOD_geo_bundle.hh" +#include "NOD_socket_items_ops.hh" +#include "NOD_socket_items_ui.hh" + +#include "BLO_read_write.hh" + +#include "NOD_geometry_nodes_bundle.hh" + +#include "UI_interface.hh" + +#include + +namespace blender::nodes::node_geo_separate_bundle_cc { + +NODE_STORAGE_FUNCS(NodeGeometrySeparateBundle); + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input("Bundle"); + const bNodeTree *tree = b.tree_or_null(); + const bNode *node = b.node_or_null(); + if (tree && node) { + const NodeGeometrySeparateBundle &storage = node_storage(*node); + for (const int i : IndexRange(storage.items_num)) { + const NodeGeometrySeparateBundleItem &item = storage.items[i]; + const eNodeSocketDatatype socket_type = eNodeSocketDatatype(item.socket_type); + const StringRef name = item.name ? item.name : ""; + const std::string identifier = SeparateBundleItemsAccessor::socket_identifier_for_item(item); + b.add_output(socket_type, name, identifier) + .socket_name_ptr(&tree->id, SeparateBundleItemsAccessor::item_srna, &item, "name") + .propagate_all() + .reference_pass_all(); + } + } + b.add_output("", "__extend__"); +} + +static void node_init(bNodeTree * /*tree*/, bNode *node) +{ + auto *storage = MEM_callocN(__func__); + node->storage = storage; +} + +static void node_copy_storage(bNodeTree * /*dst_tree*/, bNode *dst_node, const bNode *src_node) +{ + const NodeGeometrySeparateBundle &src_storage = node_storage(*src_node); + auto *dst_storage = MEM_dupallocN(__func__, src_storage); + dst_node->storage = dst_storage; + + socket_items::copy_array(*src_node, *dst_node); +} + +static void node_free_storage(bNode *node) +{ + socket_items::destruct_array(*node); + MEM_freeN(node->storage); +} + +static bool node_insert_link(bNodeTree *tree, bNode *node, bNodeLink *link) +{ + return socket_items::try_add_item_via_any_extend_socket( + *tree, *node, *node, *link); +} + +static void node_layout_ex(uiLayout *layout, bContext *C, PointerRNA *node_ptr) +{ + bNodeTree &ntree = *reinterpret_cast(node_ptr->owner_id); + bNode &node = *static_cast(node_ptr->data); + + if (uiLayout *panel = uiLayoutPanel(C, layout, "bundle_items", false, TIP_("Bundle Items"))) { + socket_items::ui::draw_items_list_with_operators( + C, panel, ntree, node); + socket_items::ui::draw_active_item_props( + ntree, node, [&](PointerRNA *item_ptr) { + uiItemR(panel, item_ptr, "socket_type", UI_ITEM_NONE, "Type", ICON_NONE); + }); + } +} + +static void node_operators() +{ + socket_items::ops::make_common_operators(); +} + +static void node_geo_exec(GeoNodeExecParams params) +{ + if (!U.experimental.use_bundle_and_closure_nodes) { + params.set_default_remaining_outputs(); + return; + } + + nodes::BundlePtr bundle = params.extract_input("Bundle"); + if (!bundle) { + params.set_default_remaining_outputs(); + return; + } + + const bNode &node = params.node(); + const NodeGeometrySeparateBundle &storage = node_storage(node); + + lf::Params &lf_params = params.low_level_lazy_function_params(); + + for (const int i : IndexRange(storage.items_num)) { + const NodeGeometrySeparateBundleItem &item = storage.items[i]; + const StringRef name = item.name; + if (name.is_empty()) { + continue; + } + const bke::bNodeSocketType *stype = bke::node_socket_type_find_static(item.socket_type); + if (!stype || !stype->geometry_nodes_cpp_type) { + continue; + } + const std::optional value = bundle->lookup(SocketInterfaceKey(name)); + if (!value) { + params.error_message_add(NodeWarningType::Error, + fmt::format(fmt::runtime(TIP_("Value not found: \"{}\"")), name)); + continue; + } + void *output_ptr = lf_params.get_output_data_ptr(i); + if (!implicitly_convert_socket_value(*value->type, value->value, *stype, output_ptr)) { + construct_socket_default_value(*stype, output_ptr); + } + lf_params.output_set(i); + } + + params.set_default_remaining_outputs(); +} + +static void node_register() +{ + static blender::bke::bNodeType ntype; + + geo_node_type_base(&ntype, "GeometryNodeSeparateBundle", GEO_NODE_SEPARATE_BUNDLE); + ntype.ui_name = "Separate Bundle"; + ntype.ui_description = "Split a bundle into multiple sockets."; + ntype.nclass = NODE_CLASS_CONVERTER; + ntype.declare = node_declare; + ntype.initfunc = node_init; + ntype.insert_link = node_insert_link; + ntype.geometry_node_execute = node_geo_exec; + ntype.draw_buttons_ex = node_layout_ex; + ntype.register_operators = node_operators; + bke::node_type_storage( + ntype, "NodeGeometrySeparateBundle", node_free_storage, node_copy_storage); + blender::bke::node_register_type(ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_separate_bundle_cc + +namespace blender::nodes { + +StructRNA *SeparateBundleItemsAccessor::item_srna = &RNA_NodeGeometrySeparateBundleItem; +int SeparateBundleItemsAccessor::node_type = GEO_NODE_SEPARATE_BUNDLE; +int SeparateBundleItemsAccessor::item_dna_type = SDNA_TYPE_FROM_STRUCT( + NodeGeometrySeparateBundleItem); + +void SeparateBundleItemsAccessor::blend_write_item(BlendWriter *writer, const ItemT &item) +{ + BLO_write_string(writer, item.name); +} + +void SeparateBundleItemsAccessor::blend_read_data_item(BlendDataReader *reader, ItemT &item) +{ + BLO_read_string(reader, &item.name); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/geometry/nodes/node_geo_simulation.cc b/source/blender/nodes/geometry/nodes/node_geo_simulation.cc index adf5d951135..57c5c91dba8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_simulation.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_simulation.cc @@ -301,7 +301,7 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction { this->set_default_outputs(params); return; } - if (found_id->is_in_loop) { + if (found_id->is_in_loop || found_id->is_in_closure) { this->set_default_outputs(params); return; } @@ -573,7 +573,7 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction { this->set_default_outputs(params); return; } - if (found_id->is_in_loop) { + if (found_id->is_in_loop || found_id->is_in_closure) { this->set_default_outputs(params); return; } diff --git a/source/blender/nodes/geometry/nodes/node_geo_switch.cc b/source/blender/nodes/geometry/nodes/node_geo_switch.cc index 6fe43c39ce6..7ed18e21f4c 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_switch.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_switch.cc @@ -239,7 +239,9 @@ static void node_rna(StructRNA *srna) SOCK_COLLECTION, SOCK_MATERIAL, SOCK_IMAGE, - SOCK_MENU); + SOCK_MENU, + SOCK_BUNDLE, + SOCK_CLOSURE); }); }); } diff --git a/source/blender/nodes/intern/geometry_nodes_bundle.cc b/source/blender/nodes/intern/geometry_nodes_bundle.cc new file mode 100644 index 00000000000..10760d435d9 --- /dev/null +++ b/source/blender/nodes/intern/geometry_nodes_bundle.cc @@ -0,0 +1,149 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_cpp_type.hh" + +#include "NOD_geometry_nodes_bundle.hh" + +namespace blender::nodes { + +SocketInterfaceKey::SocketInterfaceKey(std::string identifier) +{ + identifiers_.append(std::move(identifier)); +} + +SocketInterfaceKey::SocketInterfaceKey(Vector identifiers) + : identifiers_(std::move(identifiers)) +{ + BLI_assert(!identifiers_.is_empty()); +} + +Span SocketInterfaceKey::identifiers() const +{ + return identifiers_; +} + +bool SocketInterfaceKey::matches(const SocketInterfaceKey &other) const +{ + for (const std::string &identifier : other.identifiers_) { + if (identifiers_.contains(identifier)) { + return true; + } + } + return false; +} + +Bundle::Bundle() = default; + +Bundle::~Bundle() +{ + for (StoredItem &item : items_) { + item.type->geometry_nodes_cpp_type->destruct(item.value); + } + for (void *buffer : buffers_) { + MEM_freeN(buffer); + } +} + +Bundle::Bundle(const Bundle &other) +{ + for (const StoredItem &item : other.items_) { + this->add_new(item.key, *item.type, item.value); + } +} + +Bundle::Bundle(Bundle &&other) noexcept + : items_(std::move(other.items_)), buffers_(std::move(other.buffers_)) +{ +} + +Bundle &Bundle::operator=(const Bundle &other) +{ + if (this == &other) { + return *this; + } + this->~Bundle(); + new (this) Bundle(other); + return *this; +} + +Bundle &Bundle::operator=(Bundle &&other) noexcept +{ + if (this == &other) { + return *this; + } + this->~Bundle(); + new (this) Bundle(std::move(other)); + return *this; +} + +void Bundle::add_new(SocketInterfaceKey key, const bke::bNodeSocketType &type, const void *value) +{ + BLI_assert(!this->contains(key)); + BLI_assert(type.geometry_nodes_cpp_type); + const CPPType &cpp_type = *type.geometry_nodes_cpp_type; + void *buffer = MEM_mallocN_aligned(cpp_type.size(), cpp_type.alignment(), __func__); + cpp_type.copy_construct(value, buffer); + items_.append(StoredItem{std::move(key), &type, buffer}); + buffers_.append(buffer); +} + +bool Bundle::add(const SocketInterfaceKey &key, + const bke::bNodeSocketType &type, + const void *value) +{ + if (this->contains(key)) { + return false; + } + this->add_new(key, type, value); + return true; +} + +bool Bundle::add(SocketInterfaceKey &&key, const bke::bNodeSocketType &type, const void *value) +{ + if (this->contains(key)) { + return false; + } + this->add_new(std::move(key), type, value); + return true; +} + +std::optional Bundle::lookup(const SocketInterfaceKey &key) const +{ + for (const StoredItem &item : items_) { + if (item.key.matches(key)) { + return Item{item.type, item.value}; + } + } + return std::nullopt; +} + +bool Bundle::remove(const SocketInterfaceKey &key) +{ + const int removed_num = items_.remove_if([&key](StoredItem &item) { + if (item.key.matches(key)) { + item.type->geometry_nodes_cpp_type->destruct(item.value); + return true; + } + return false; + }); + return removed_num >= 1; +} + +bool Bundle::contains(const SocketInterfaceKey &key) const +{ + for (const StoredItem &item : items_) { + if (item.key.matches(key)) { + return true; + } + } + return false; +} + +void Bundle::delete_self() +{ + MEM_delete(this); +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/geometry_nodes_closure.cc b/source/blender/nodes/intern/geometry_nodes_closure.cc new file mode 100644 index 00000000000..383550fb9e0 --- /dev/null +++ b/source/blender/nodes/intern/geometry_nodes_closure.cc @@ -0,0 +1,31 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "NOD_geometry_nodes_closure.hh" + +namespace blender::nodes { + +std::optional ClosureSignature::find_input_index(const SocketInterfaceKey &key) const +{ + for (const int i : this->inputs.index_range()) { + const Item &item = this->inputs[i]; + if (item.key.matches(key)) { + return i; + } + } + return std::nullopt; +} + +std::optional ClosureSignature::find_output_index(const SocketInterfaceKey &key) const +{ + for (const int i : this->outputs.index_range()) { + const Item &item = this->outputs[i]; + if (item.key.matches(key)) { + return i; + } + } + return std::nullopt; +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/geometry_nodes_closure_zone.cc b/source/blender/nodes/intern/geometry_nodes_closure_zone.cc new file mode 100644 index 00000000000..1d674748dbe --- /dev/null +++ b/source/blender/nodes/intern/geometry_nodes_closure_zone.cc @@ -0,0 +1,717 @@ +/* SPDX-FileCopyrightText: 2025 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#include + +#include "NOD_geometry_nodes_closure_eval.hh" +#include "NOD_geometry_nodes_lazy_function.hh" + +#include "BKE_compute_contexts.hh" +#include "BKE_geometry_nodes_reference_set.hh" +#include "BKE_node_runtime.hh" +#include "BKE_node_socket_value.hh" +#include "BKE_node_tree_reference_lifetimes.hh" +#include "NOD_geometry_nodes_closure.hh" + +#include "DEG_depsgraph_query.hh" + +#include "FN_lazy_function_execute.hh" + +namespace blender::nodes { + +using bke::node_tree_reference_lifetimes::ReferenceSetInfo; +using bke::node_tree_reference_lifetimes::ReferenceSetType; + +/** + * A lazy function that internally has a lazy-function graph that mimics the "body" of the closure + * zone. + */ +class LazyFunctionForClosureZone : public LazyFunction { + private: + const bNodeTree &btree_; + const bke::bNodeTreeZone &zone_; + const bNode &output_bnode_; + const ZoneBuildInfo &zone_info_; + const ZoneBodyFunction &body_fn_; + std::shared_ptr closure_signature_; + + public: + LazyFunctionForClosureZone(const bNodeTree &btree, + const bke::bNodeTreeZone &zone, + ZoneBuildInfo &zone_info, + const ZoneBodyFunction &body_fn) + : btree_(btree), + zone_(zone), + output_bnode_(*zone.output_node), + zone_info_(zone_info), + body_fn_(body_fn) + { + debug_name_ = "Closure Zone"; + + initialize_zone_wrapper(zone, zone_info, body_fn, false, inputs_, outputs_); + for (const auto item : body_fn.indices.inputs.reference_sets.items()) { + const ReferenceSetInfo &reference_set = + btree.runtime->reference_lifetimes_info->reference_sets[item.key]; + if (reference_set.type == ReferenceSetType::ClosureInputReferenceSet) { + BLI_assert(&reference_set.socket->owner_node() != zone_.input_node); + } + if (reference_set.type == ReferenceSetType::ClosureOutputData) { + if (&reference_set.socket->owner_node() == zone_.output_node) { + /* This reference set comes from the caller of the closure and is not captured at the + * place where the closure is created. */ + continue; + } + } + zone_info.indices.inputs.reference_sets.add_new( + item.key, + inputs_.append_and_get_index_as("Reference Set", + CPPType::get())); + } + + /* All border links are used. */ + for (const int i : zone_.border_links.index_range()) { + inputs_[zone_info.indices.inputs.border_links[i]].usage = lf::ValueUsage::Used; + } + + const auto &storage = *static_cast(output_bnode_.storage); + + closure_signature_ = std::make_shared(); + + for (const int i : IndexRange(storage.input_items.items_num)) { + const bNodeSocket &bsocket = zone_.input_node->output_socket(i); + closure_signature_->inputs.append({SocketInterfaceKey(bsocket.name), bsocket.typeinfo}); + } + for (const int i : IndexRange(storage.output_items.items_num)) { + const bNodeSocket &bsocket = zone_.output_node->input_socket(i); + closure_signature_->outputs.append({SocketInterfaceKey(bsocket.name), bsocket.typeinfo}); + } + } + + void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override + { + /* All border links are captured currently. */ + for (const int i : zone_.border_links.index_range()) { + params.set_output(zone_info_.indices.outputs.border_link_usages[i], true); + } + if (!U.experimental.use_bundle_and_closure_nodes) { + params.set_output(zone_info_.indices.outputs.main[0], bke::SocketValueVariant(ClosurePtr())); + return; + } + + const auto &storage = *static_cast(output_bnode_.storage); + + std::unique_ptr closure_scope = std::make_unique(); + LinearAllocator<> &closure_allocator = closure_scope->linear_allocator(); + + lf::Graph &lf_graph = closure_scope->construct("Closure Graph"); + lf::FunctionNode &lf_body_node = lf_graph.add_function(*body_fn_.function); + ClosureFunctionIndices closure_indices; + Vector default_input_values; + + for (const int i : IndexRange(storage.input_items.items_num)) { + const NodeGeometryClosureInputItem &item = storage.input_items.items[i]; + const bNodeSocket &bsocket = zone_.input_node->output_socket(i); + const CPPType &cpp_type = *bsocket.typeinfo->geometry_nodes_cpp_type; + + lf::GraphInputSocket &lf_graph_input = lf_graph.add_input(cpp_type, item.name); + lf_graph.add_link(lf_graph_input, lf_body_node.input(body_fn_.indices.inputs.main[i])); + + lf::GraphOutputSocket &lf_graph_input_usage = lf_graph.add_output( + CPPType::get(), "Usage: " + StringRef(item.name)); + lf_graph.add_link(lf_body_node.output(body_fn_.indices.outputs.input_usages[i]), + lf_graph_input_usage); + + void *default_value = closure_allocator.allocate(cpp_type.size(), cpp_type.alignment()); + construct_socket_default_value(*bsocket.typeinfo, default_value); + default_input_values.append(default_value); + } + closure_indices.inputs.main = lf_graph.graph_inputs().index_range().take_back( + storage.input_items.items_num); + closure_indices.outputs.input_usages = lf_graph.graph_outputs().index_range().take_back( + storage.input_items.items_num); + + for (const int i : IndexRange(storage.output_items.items_num)) { + const NodeGeometryClosureOutputItem &item = storage.output_items.items[i]; + const bNodeSocket &bsocket = zone_.output_node->input_socket(i); + const CPPType &cpp_type = *bsocket.typeinfo->geometry_nodes_cpp_type; + + lf::GraphOutputSocket &lf_graph_output = lf_graph.add_output(cpp_type, item.name); + lf_graph.add_link(lf_body_node.output(body_fn_.indices.outputs.main[i]), lf_graph_output); + + lf::GraphInputSocket &lf_graph_output_usage = lf_graph.add_input( + CPPType::get(), "Usage: " + StringRef(item.name)); + lf_graph.add_link(lf_graph_output_usage, + lf_body_node.input(body_fn_.indices.inputs.output_usages[i])); + } + closure_indices.outputs.main = lf_graph.graph_outputs().index_range().take_back( + storage.output_items.items_num); + closure_indices.inputs.output_usages = lf_graph.graph_inputs().index_range().take_back( + storage.output_items.items_num); + + for (const int i : zone_.border_links.index_range()) { + const CPPType &cpp_type = *zone_.border_links[i]->tosock->typeinfo->geometry_nodes_cpp_type; + void *input_ptr = params.try_get_input_data_ptr(zone_info_.indices.inputs.border_links[i]); + void *stored_ptr = closure_allocator.allocate(cpp_type.size(), cpp_type.alignment()); + cpp_type.move_construct(input_ptr, stored_ptr); + if (!cpp_type.is_trivially_destructible()) { + closure_scope->add_destruct_call( + [&cpp_type, stored_ptr]() { cpp_type.destruct(stored_ptr); }); + } + lf_body_node.input(body_fn_.indices.inputs.border_links[i]).set_default_value(stored_ptr); + } + + for (const auto &item : body_fn_.indices.inputs.reference_sets.items()) { + const ReferenceSetInfo &reference_set = + btree_.runtime->reference_lifetimes_info->reference_sets[item.key]; + if (reference_set.type == ReferenceSetType::ClosureOutputData) { + const bNodeSocket &socket = *reference_set.socket; + const bNode &node = socket.owner_node(); + if (&node == zone_.output_node) { + /* This reference set is passed in by the code that invokes the closure. */ + lf::GraphInputSocket &lf_graph_input = lf_graph.add_input( + CPPType::get(), + StringRef("Reference Set: ") + reference_set.socket->name); + lf_graph.add_link( + lf_graph_input, + lf_body_node.input(body_fn_.indices.inputs.reference_sets.lookup(item.key))); + closure_indices.inputs.output_data_reference_sets.add_new(reference_set.socket->index(), + lf_graph_input.index()); + continue; + } + } + + auto &input_reference_set = *params.try_get_input_data_ptr( + zone_info_.indices.inputs.reference_sets.lookup(item.key)); + auto &stored = closure_scope->construct( + std::move(input_reference_set)); + lf_body_node.input(body_fn_.indices.inputs.reference_sets.lookup(item.key)) + .set_default_value(&stored); + } + + bNodeTree &btree_orig = *reinterpret_cast( + DEG_get_original_id(const_cast(&btree_.id))); + if (btree_orig.runtime->logged_zone_graphs) { + std::lock_guard lock{btree_orig.runtime->logged_zone_graphs->mutex}; + btree_orig.runtime->logged_zone_graphs->graph_by_zone_id.lookup_or_add_cb( + output_bnode_.identifier, [&]() { return lf_graph.to_dot(); }); + } + + lf_graph.update_node_indices(); + + lf::GraphExecutor &lf_graph_executor = closure_scope->construct( + lf_graph, nullptr, nullptr, nullptr); + + ClosurePtr closure{MEM_new(__func__, + closure_signature_, + std::move(closure_scope), + lf_graph_executor, + closure_indices, + std::move(default_input_values))}; + + params.set_output(zone_info_.indices.outputs.main[0], + bke::SocketValueVariant(std::move(closure))); + } +}; + +struct EvaluateClosureEvalStorage { + ResourceScope scope; + ClosurePtr closure; + lf::Graph graph; + std::optional graph_executor; + void *graph_executor_storage = nullptr; +}; + +/** + * A lazy function that is used to evaluate a passed in closure. Internally that has to build + * another lazy-function graph, which "fixes" different orderings of inputs/outputs, handles + * missing sockets and type conversions. + */ +class LazyFunctionForEvaluateClosureNode : public LazyFunction { + private: + const bNodeTree &btree_; + const bNode &bnode_; + EvaluateClosureFunctionIndices indices_; + + public: + LazyFunctionForEvaluateClosureNode(const bNode &bnode) + : btree_(bnode.owner_tree()), bnode_(bnode) + { + debug_name_ = bnode.name; + for (const int i : bnode.input_sockets().index_range().drop_back(1)) { + const bNodeSocket &bsocket = bnode.input_socket(i); + indices_.inputs.main.append(inputs_.append_and_get_index_as( + bsocket.name, *bsocket.typeinfo->geometry_nodes_cpp_type, lf::ValueUsage::Maybe)); + indices_.outputs.input_usages.append( + outputs_.append_and_get_index_as("Usage", CPPType::get())); + } + /* The closure input is always used. */ + inputs_[indices_.inputs.main[0]].usage = lf::ValueUsage::Used; + for (const int i : bnode.output_sockets().index_range().drop_back(1)) { + const bNodeSocket &bsocket = bnode.output_socket(i); + indices_.outputs.main.append(outputs_.append_and_get_index_as( + bsocket.name, *bsocket.typeinfo->geometry_nodes_cpp_type)); + indices_.inputs.output_usages.append( + inputs_.append_and_get_index_as("Usage", CPPType::get())); + if (bke::node_tree_reference_lifetimes::can_contain_referenced_data( + eNodeSocketDatatype(bsocket.type))) + { + const int input_i = inputs_.append_and_get_index_as( + "Reference Set", CPPType::get()); + indices_.inputs.reference_set_by_output.add(i, input_i); + } + } + } + + EvaluateClosureFunctionIndices indices() const + { + return indices_; + } + + void *init_storage(LinearAllocator<> &allocator) const override + { + return allocator.construct().release(); + } + + void destruct_storage(void *storage) const override + { + auto *s = static_cast(storage); + if (s->graph_executor_storage) { + s->graph_executor->destruct_storage(s->graph_executor_storage); + } + std::destroy_at(s); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &context) const override + { + const ScopedNodeTimer node_timer{context, bnode_}; + + auto &user_data = *static_cast(context.user_data); + auto &eval_storage = *static_cast(context.storage); + + if (!eval_storage.graph_executor) { + eval_storage.closure = params.extract_input(indices_.inputs.main[0]) + .extract(); + if (!eval_storage.closure) { + for (const bNodeSocket *bsocket : bnode_.output_sockets().drop_back(1)) { + const int index = bsocket->index(); + set_default_value_for_output_socket(params, indices_.outputs.main[index], *bsocket); + params.set_output(indices_.outputs.input_usages[index], false); + } + return; + } + this->generate_closure_compatibility_warnings(*eval_storage.closure, context); + this->initialize_execution_graph(eval_storage); + } + + bke::EvaluateClosureComputeContext closure_compute_context{user_data.compute_context, bnode_}; + GeoNodesLFUserData closure_user_data = user_data; + closure_user_data.compute_context = &closure_compute_context; + GeoNodesLFLocalUserData closure_local_user_data{closure_user_data}; + + lf::Context eval_graph_context{ + eval_storage.graph_executor_storage, &closure_user_data, &closure_local_user_data}; + eval_storage.graph_executor->execute(params, eval_graph_context); + } + + void generate_closure_compatibility_warnings(const Closure &closure, + const lf::Context &context) const + { + const auto &node_storage = *static_cast(bnode_.storage); + const auto &user_data = *static_cast(context.user_data); + const auto &local_user_data = *static_cast(context.local_user_data); + geo_eval_log::GeoTreeLogger *tree_logger = local_user_data.try_get_tree_logger(user_data); + if (tree_logger == nullptr) { + return; + } + const ClosureSignature &signature = closure.signature(); + for (const NodeGeometryEvaluateClosureInputItem &item : + Span{node_storage.input_items.items, node_storage.input_items.items_num}) + { + if (const std::optional i = signature.find_input_index(SocketInterfaceKey{item.name})) { + const ClosureSignature::Item &closure_item = signature.inputs[*i]; + if (!btree_.typeinfo->validate_link(eNodeSocketDatatype(item.socket_type), + eNodeSocketDatatype(closure_item.type->type))) + { + tree_logger->node_warnings.append( + *tree_logger->allocator, + {bnode_.identifier, + {geo_eval_log::NodeWarningType::Error, + fmt::format(fmt::runtime(TIP_("Closure input has incompatible type: \"{}\"")), + item.name)}}); + } + } + else { + tree_logger->node_warnings.append( + *tree_logger->allocator, + {bnode_.identifier, + { + geo_eval_log::NodeWarningType::Error, + fmt::format(fmt::runtime(TIP_("Closure does not have input: \"{}\"")), item.name), + }}); + } + } + for (const NodeGeometryEvaluateClosureOutputItem &item : + Span{node_storage.output_items.items, node_storage.output_items.items_num}) + { + if (const std::optional i = signature.find_output_index(SocketInterfaceKey{item.name})) + { + const ClosureSignature::Item &closure_item = signature.outputs[*i]; + if (!btree_.typeinfo->validate_link(eNodeSocketDatatype(closure_item.type->type), + eNodeSocketDatatype(item.socket_type))) + { + tree_logger->node_warnings.append( + *tree_logger->allocator, + {bnode_.identifier, + {geo_eval_log::NodeWarningType::Error, + fmt::format(fmt::runtime(TIP_("Closure output has incompatible type: \"{}\"")), + item.name)}}); + } + } + else { + tree_logger->node_warnings.append( + *tree_logger->allocator, + {bnode_.identifier, + {geo_eval_log::NodeWarningType::Error, + fmt::format(fmt::runtime(TIP_("Closure does not have output: \"{}\"")), + item.name)}}); + } + } + } + + void initialize_execution_graph(EvaluateClosureEvalStorage &eval_storage) const + { + const auto &node_storage = *static_cast(bnode_.storage); + + lf::Graph &lf_graph = eval_storage.graph; + + for (const lf::Input &input : inputs_) { + lf_graph.add_input(*input.type, input.debug_name); + } + for (const lf::Output &output : outputs_) { + lf_graph.add_output(*output.type, output.debug_name); + } + const Span lf_graph_inputs = lf_graph.graph_inputs(); + const Span lf_graph_outputs = lf_graph.graph_outputs(); + + const Closure &closure = *eval_storage.closure; + const ClosureSignature &closure_signature = closure.signature(); + const ClosureFunctionIndices &closure_indices = closure.indices(); + + Array> inputs_map(node_storage.input_items.items_num); + for (const int i : inputs_map.index_range()) { + inputs_map[i] = closure_signature.find_input_index( + SocketInterfaceKey(node_storage.input_items.items[i].name)); + } + Array> outputs_map(node_storage.output_items.items_num); + for (const int i : outputs_map.index_range()) { + outputs_map[i] = closure_signature.find_output_index( + SocketInterfaceKey(node_storage.output_items.items[i].name)); + } + + lf::FunctionNode &lf_closure_node = lf_graph.add_function(closure.function()); + + static constexpr bool static_true = true; + static constexpr bool static_false = false; + /* The closure input is always used. */ + lf_graph_outputs[indices_.outputs.input_usages[0]]->set_default_value(&static_true); + + for (const int input_item_i : IndexRange(node_storage.input_items.items_num)) { + lf::GraphOutputSocket &lf_usage_output = + *lf_graph_outputs[indices_.outputs.input_usages[input_item_i + 1]]; + if (const std::optional mapped_i = inputs_map[input_item_i]) { + const bke::bNodeSocketType &from_type = *bnode_.input_socket(input_item_i + 1).typeinfo; + const bke::bNodeSocketType &to_type = *closure_signature.inputs[*mapped_i].type; + lf::OutputSocket *lf_from = lf_graph_inputs[indices_.inputs.main[input_item_i + 1]]; + lf::InputSocket &lf_to = lf_closure_node.input(closure_indices.inputs.main[*mapped_i]); + if (&from_type != &to_type) { + if (const LazyFunction *conversion_fn = build_implicit_conversion_lazy_function( + from_type, to_type, eval_storage.scope)) + { + /* The provided type when evaluating the closure may be different from what the closure + * expects exactly, so do an implicit conversion. */ + lf::Node &conversion_node = lf_graph.add_function(*conversion_fn); + lf_graph.add_link(*lf_from, conversion_node.input(0)); + lf_from = &conversion_node.output(0); + } + else { + /* Use the default value if the provided input value is not compatible with what the + * closure expects. */ + const void *default_value = closure.default_input_value(*mapped_i); + BLI_assert(default_value); + lf_to.set_default_value(default_value); + lf_usage_output.set_default_value(&static_false); + continue; + } + } + lf_graph.add_link(*lf_from, lf_to); + lf_graph.add_link(lf_closure_node.output(closure_indices.outputs.input_usages[*mapped_i]), + lf_usage_output); + } + else { + lf_usage_output.set_default_value(&static_false); + } + } + + auto get_output_default_value = [&](const bke::bNodeSocketType &type) { + const CPPType &cpp_type = *type.geometry_nodes_cpp_type; + void *fallback_value = eval_storage.scope.linear_allocator().allocate(cpp_type.size(), + cpp_type.alignment()); + construct_socket_default_value(type, fallback_value); + if (!cpp_type.is_trivially_destructible()) { + eval_storage.scope.add_destruct_call( + [fallback_value, type = &cpp_type]() { type->destruct(fallback_value); }); + } + return fallback_value; + }; + + for (const int output_item_i : IndexRange(node_storage.output_items.items_num)) { + lf::GraphOutputSocket &lf_main_output = + *lf_graph_outputs[indices_.outputs.main[output_item_i]]; + const bke::bNodeSocketType &main_output_type = *bnode_.output_socket(output_item_i).typeinfo; + if (const std::optional mapped_i = outputs_map[output_item_i]) { + const bke::bNodeSocketType &closure_output_type = + *closure_signature.outputs[*mapped_i].type; + lf::OutputSocket *lf_from = &lf_closure_node.output( + closure_indices.outputs.main[*mapped_i]); + if (&closure_output_type != &main_output_type) { + if (const LazyFunction *conversion_fn = build_implicit_conversion_lazy_function( + closure_output_type, main_output_type, eval_storage.scope)) + { + /* Convert the type of the value coming out of the closure to the output socket type of + * the evaluation. */ + lf::Node &conversion_node = lf_graph.add_function(*conversion_fn); + lf_graph.add_link(*lf_from, conversion_node.input(0)); + lf_from = &conversion_node.output(0); + } + else { + /* The socket types are not compatible, so use the default value. */ + void *fallback_value = get_output_default_value(main_output_type); + lf_main_output.set_default_value(fallback_value); + continue; + } + } + /* Link the output of the closure to the output of the entire evaluation. */ + lf_graph.add_link(*lf_from, lf_main_output); + lf_graph.add_link(*lf_graph_inputs[indices_.inputs.output_usages[output_item_i]], + lf_closure_node.input(closure_indices.inputs.output_usages[*mapped_i])); + } + else { + void *fallback_value = get_output_default_value(main_output_type); + lf_main_output.set_default_value(fallback_value); + } + } + + for (const int i : closure_indices.inputs.main.index_range()) { + lf::InputSocket &lf_closure_input = lf_closure_node.input(closure_indices.inputs.main[i]); + if (lf_closure_input.origin()) { + /* Handled already. */ + continue; + } + const void *default_value = closure.default_input_value(i); + lf_closure_input.set_default_value(default_value); + } + + static const bke::GeometryNodesReferenceSet static_empty_reference_set; + for (const int i : closure_indices.outputs.main.index_range()) { + lf::OutputSocket &lf_closure_output = lf_closure_node.output( + closure_indices.outputs.main[i]); + if (const std::optional lf_reference_set_input_i = + closure_indices.inputs.output_data_reference_sets.lookup_try(i)) + { + lf::InputSocket &lf_reference_set_input = lf_closure_node.input(*lf_reference_set_input_i); + const int node_output_i = outputs_map.as_span().first_index_try(i); + if (node_output_i == -1) { + lf_reference_set_input.set_default_value(&static_empty_reference_set); + } + else { + if (const std::optional lf_evaluate_node_reference_set_input_i = + indices_.inputs.reference_set_by_output.lookup_try(node_output_i)) + { + lf_graph.add_link(*lf_graph_inputs[*lf_evaluate_node_reference_set_input_i], + lf_reference_set_input); + } + else { + lf_reference_set_input.set_default_value(&static_empty_reference_set); + } + } + } + if (!lf_closure_output.targets().is_empty()) { + /* Handled already. */ + continue; + } + lf_closure_node.input(closure_indices.inputs.output_usages[i]) + .set_default_value(&static_false); + } + + lf_graph.update_node_indices(); + eval_storage.graph_executor.emplace(lf_graph, nullptr, nullptr, nullptr); + eval_storage.graph_executor_storage = eval_storage.graph_executor->init_storage( + eval_storage.scope.linear_allocator()); + + /* Log graph for debugging purposes. */ + bNodeTree &btree_orig = *reinterpret_cast( + DEG_get_original_id(const_cast(&btree_.id))); + if (btree_orig.runtime->logged_zone_graphs) { + std::lock_guard lock{btree_orig.runtime->logged_zone_graphs->mutex}; + btree_orig.runtime->logged_zone_graphs->graph_by_zone_id.lookup_or_add_cb( + bnode_.identifier, [&]() { return lf_graph.to_dot(); }); + } + } +}; + +void evaluate_closure_eagerly(const Closure &closure, ClosureEagerEvalParams ¶ms) +{ + const LazyFunction &fn = closure.function(); + const ClosureFunctionIndices &indices = closure.indices(); + const ClosureSignature &signature = closure.signature(); + const int fn_inputs_num = fn.inputs().size(); + const int fn_outputs_num = fn.outputs().size(); + + ResourceScope scope; + LinearAllocator<> &allocator = scope.linear_allocator(); + + GeoNodesLFLocalUserData local_user_data(*params.user_data); + void *storage = fn.init_storage(allocator); + lf::Context lf_context{storage, params.user_data, &local_user_data}; + + Array lf_input_values(fn_inputs_num); + Array lf_output_values(fn_outputs_num); + Array> lf_input_usages(fn_inputs_num); + Array lf_output_usages(fn_outputs_num, lf::ValueUsage::Unused); + Array lf_set_outputs(fn_outputs_num, false); + + Array> inputs_map(params.inputs.size()); + for (const int i : inputs_map.index_range()) { + inputs_map[i] = signature.find_input_index(params.inputs[i].key); + } + Array> outputs_map(params.outputs.size()); + for (const int i : outputs_map.index_range()) { + outputs_map[i] = signature.find_output_index(params.outputs[i].key); + } + + for (const int input_item_i : params.inputs.index_range()) { + ClosureEagerEvalParams::InputItem &item = params.inputs[input_item_i]; + if (const std::optional mapped_i = inputs_map[input_item_i]) { + const bke::bNodeSocketType &from_type = *item.type; + const bke::bNodeSocketType &to_type = *signature.inputs[*mapped_i].type; + const CPPType &to_cpp_type = *to_type.geometry_nodes_cpp_type; + void *value = allocator.allocate(to_cpp_type.size(), to_cpp_type.alignment()); + if (&from_type == &to_type) { + to_cpp_type.copy_construct(item.value, value); + } + else { + if (!implicitly_convert_socket_value(from_type, item.value, to_type, value)) { + const void *default_value = closure.default_input_value(*mapped_i); + to_cpp_type.copy_construct(default_value, value); + } + } + lf_input_values[indices.inputs.main[*mapped_i]] = {to_cpp_type, value}; + } + else { + /* Provided input value is ignored. */ + } + } + for (const int output_item_i : params.outputs.index_range()) { + if (const std::optional mapped_i = outputs_map[output_item_i]) { + /* Tell the closure that this output is used. */ + lf_input_values[indices.inputs.output_usages[*mapped_i]] = { + CPPType::get(), allocator.construct(true).release()}; + lf_output_usages[indices.outputs.main[*mapped_i]] = lf::ValueUsage::Used; + } + } + + /* Set remaining main inputs to their default values. */ + for (const int main_input_i : indices.inputs.main.index_range()) { + const int lf_input_i = indices.inputs.main[main_input_i]; + if (!lf_input_values[lf_input_i]) { + const bke::bNodeSocketType &type = *signature.inputs[main_input_i].type; + const CPPType &cpp_type = *type.geometry_nodes_cpp_type; + const void *default_value = closure.default_input_value(main_input_i); + void *value = allocator.allocate(cpp_type.size(), cpp_type.alignment()); + cpp_type.copy_construct(default_value, value); + lf_input_values[lf_input_i] = {cpp_type, value}; + } + lf_output_values[indices.outputs.input_usages[main_input_i]] = allocator.allocate(); + } + /* Set remaining output usages to false.*/ + for (const int output_usage_i : indices.inputs.output_usages.index_range()) { + const int lf_input_i = indices.inputs.output_usages[output_usage_i]; + if (!lf_input_values[lf_input_i]) { + lf_input_values[lf_input_i] = {CPPType::get(), + allocator.construct(false).release()}; + } + } + /** Set output data reference sets. */ + for (auto &&[main_output_i, lf_input_i] : indices.inputs.output_data_reference_sets.items()) { + /* TODO: Propagate all attributes or let the caller decide. */ + auto *value = &scope.construct(); + lf_input_values[lf_input_i] = {value}; + } + /** Set main outputs. */ + for (const int main_output_i : indices.outputs.main.index_range()) { + const bke::bNodeSocketType &type = *signature.outputs[main_output_i].type; + const CPPType &cpp_type = *type.geometry_nodes_cpp_type; + lf_output_values[indices.outputs.main[main_output_i]] = { + cpp_type, allocator.allocate(cpp_type.size(), cpp_type.alignment())}; + } + + lf::BasicParams lf_params{ + fn, lf_input_values, lf_output_values, lf_input_usages, lf_output_usages, lf_set_outputs}; + fn.execute(lf_params, lf_context); + fn.destruct_storage(storage); + + for (const int output_item_i : params.outputs.index_range()) { + ClosureEagerEvalParams::OutputItem &item = params.outputs[output_item_i]; + if (const std::optional mapped_i = outputs_map[output_item_i]) { + const bke::bNodeSocketType &from_type = *signature.outputs[*mapped_i].type; + const bke::bNodeSocketType &to_type = *item.type; + const CPPType &to_cpp_type = *to_type.geometry_nodes_cpp_type; + void *computed_value = lf_output_values[indices.outputs.main[*mapped_i]].get(); + if (&from_type == &to_type) { + to_cpp_type.move_construct(computed_value, item.value); + } + else { + if (!implicitly_convert_socket_value(from_type, computed_value, to_type, item.value)) { + construct_socket_default_value(to_type, item.value); + } + } + } + else { + /* This output item is not computed by the closure, so set it to the default value. */ + construct_socket_default_value(*item.type, item.value); + } + } + + for (GMutablePointer value : lf_input_values) { + if (value) { + value.destruct(); + } + } + for (const int i : lf_output_values.index_range()) { + if (lf_set_outputs[i]) { + lf_output_values[i].destruct(); + } + } +} + +LazyFunction &build_closure_zone_lazy_function(ResourceScope &scope, + const bNodeTree &btree, + const bke::bNodeTreeZone &zone, + ZoneBuildInfo &zone_info, + const ZoneBodyFunction &body_fn) +{ + return scope.construct(btree, zone, zone_info, body_fn); +} + +EvaluateClosureFunction build_evaluate_closure_node_lazy_function(ResourceScope &scope, + const bNode &bnode) +{ + EvaluateClosureFunction info; + auto &fn = scope.construct(bnode); + info.lazy_function = &fn; + info.indices = fn.indices(); + return info; +} + +} // namespace blender::nodes diff --git a/source/blender/nodes/intern/geometry_nodes_execute.cc b/source/blender/nodes/intern/geometry_nodes_execute.cc index 6ec398af557..d7aede3406d 100644 --- a/source/blender/nodes/intern/geometry_nodes_execute.cc +++ b/source/blender/nodes/intern/geometry_nodes_execute.cc @@ -249,6 +249,8 @@ std::unique_ptr id_property_create_f case SOCK_CUSTOM: case SOCK_GEOMETRY: case SOCK_SHADER: + case SOCK_BUNDLE: + case SOCK_CLOSURE: return nullptr; } return nullptr; @@ -414,6 +416,8 @@ static bool old_id_property_type_matches_socket_convert_to_new( case SOCK_MATRIX: case SOCK_GEOMETRY: case SOCK_SHADER: + case SOCK_BUNDLE: + case SOCK_CLOSURE: return false; } BLI_assert_unreachable(); @@ -939,9 +943,8 @@ void update_input_properties_from_node_tree(const bNodeTree &tree, SOCK_CUSTOM; IDProperty *new_prop = id_property_create_from_socket(socket, use_name_for_ids).release(); if (new_prop == nullptr) { - /* Out of the set of supported input sockets, only - * geometry sockets aren't added to the modifier. */ - BLI_assert(ELEM(socket_type, SOCK_GEOMETRY, SOCK_MATRIX)); + /* Out of the set of supported input sockets, these sockets aren't added to the modifier. */ + BLI_assert(ELEM(socket_type, SOCK_GEOMETRY, SOCK_MATRIX, SOCK_BUNDLE, SOCK_CLOSURE)); continue; } diff --git a/source/blender/nodes/intern/geometry_nodes_foreach_geometry_element_zone.cc b/source/blender/nodes/intern/geometry_nodes_foreach_geometry_element_zone.cc index 7faeda868a3..55f24f0d0f6 100644 --- a/source/blender/nodes/intern/geometry_nodes_foreach_geometry_element_zone.cc +++ b/source/blender/nodes/intern/geometry_nodes_foreach_geometry_element_zone.cc @@ -269,7 +269,7 @@ class LazyFunctionForForeachGeometryElementZone : public LazyFunction { { debug_name_ = "Foreach Geometry Element"; - initialize_zone_wrapper(zone, zone_info, body_fn, inputs_, outputs_); + initialize_zone_wrapper(zone, zone_info, body_fn, true, inputs_, outputs_); /* All main inputs are always used for now. */ for (const int i : zone_info.indices.inputs.main) { inputs_[i].usage = lf::ValueUsage::Used; diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index caacd078fa9..685b5f4c594 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -413,18 +413,24 @@ class LazyFunctionForUndefinedNode : public LazyFunction { } }; +void construct_socket_default_value(const bke::bNodeSocketType &stype, void *r_value) +{ + const CPPType *cpp_type = stype.geometry_nodes_cpp_type; + BLI_assert(cpp_type); + if (stype.geometry_nodes_default_cpp_value) { + cpp_type->copy_construct(stype.geometry_nodes_default_cpp_value, r_value); + } + else { + cpp_type->value_initialize(r_value); + } +} + void set_default_value_for_output_socket(lf::Params ¶ms, const int lf_index, const bNodeSocket &bsocket) { - const CPPType &cpp_type = *bsocket.typeinfo->geometry_nodes_cpp_type; void *output_value = params.get_output_data_ptr(lf_index); - if (bsocket.typeinfo->geometry_nodes_default_cpp_value) { - cpp_type.copy_construct(bsocket.typeinfo->geometry_nodes_default_cpp_value, output_value); - } - else { - cpp_type.value_initialize(output_value); - } + construct_socket_default_value(*bsocket.typeinfo, output_value); params.output_set(lf_index); } @@ -546,6 +552,77 @@ static void execute_multi_function_on_value_variant(const MultiFunction &fn, } } +bool implicitly_convert_socket_value(const bke::bNodeSocketType &from_type, + const void *from_value, + const bke::bNodeSocketType &to_type, + void *r_to_value) +{ + BLI_assert(from_value != r_to_value); + if (&from_type == &to_type) { + from_type.geometry_nodes_cpp_type->copy_construct(from_value, r_to_value); + return true; + } + const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); + const CPPType *from_cpp_type = from_type.base_cpp_type; + const CPPType *to_cpp_type = to_type.base_cpp_type; + if (!from_cpp_type || !to_cpp_type) { + return false; + } + if (conversions.is_convertible(*from_cpp_type, *to_cpp_type)) { + const MultiFunction &multi_fn = *conversions.get_conversion_multi_function( + mf::DataType::ForSingle(*from_cpp_type), mf::DataType::ForSingle(*to_cpp_type)); + SocketValueVariant input_variant = *static_cast(from_value); + SocketValueVariant *output_variant = new (r_to_value) SocketValueVariant(); + execute_multi_function_on_value_variant(multi_fn, {}, {&input_variant}, {output_variant}); + return true; + } + return false; +} + +class LazyFunctionForImplicitConversion : public LazyFunction { + private: + const MultiFunction &fn_; + + public: + LazyFunctionForImplicitConversion(const MultiFunction &fn) : fn_(fn) + { + debug_name_ = "Convert"; + inputs_.append_as("From", CPPType::get()); + outputs_.append_as("To", CPPType::get()); + } + + void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override + { + SocketValueVariant *from_value = params.try_get_input_data_ptr(0); + SocketValueVariant *to_value = new (params.get_output_data_ptr(0)) SocketValueVariant(); + BLI_assert(from_value != nullptr); + BLI_assert(to_value != nullptr); + execute_multi_function_on_value_variant(fn_, {}, {from_value}, {to_value}); + params.output_set(0); + } +}; + +const LazyFunction *build_implicit_conversion_lazy_function(const bke::bNodeSocketType &from_type, + const bke::bNodeSocketType &to_type, + ResourceScope &scope) +{ + if (!from_type.geometry_nodes_cpp_type || !to_type.geometry_nodes_cpp_type) { + return nullptr; + } + if (&from_type == &to_type) { + return &scope.construct(*from_type.geometry_nodes_cpp_type); + } + const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); + const CPPType &from_base_type = *from_type.base_cpp_type; + const CPPType &to_base_type = *to_type.base_cpp_type; + if (conversions.is_convertible(from_base_type, to_base_type)) { + const MultiFunction &multi_fn = *conversions.get_conversion_multi_function( + mf::DataType::ForSingle(from_base_type), mf::DataType::ForSingle(to_base_type)); + return &scope.construct(multi_fn); + } + return nullptr; +} + /** * Behavior of muted nodes: * - Some inputs are forwarded to outputs without changes. @@ -610,21 +687,9 @@ class LazyFunctionForMutedNode : public LazyFunction { continue; } void *output_value = params.get_output_data_ptr(lf_output_index); - if (input_bsocket->type == output_bsocket->type) { - inputs_[lf_input_index].type->copy_construct(input_value, output_value); - params.output_set(lf_output_index); - continue; - } - const bke::DataTypeConversions &conversions = bke::get_implicit_type_conversions(); - if (conversions.is_convertible(*input_bsocket->typeinfo->base_cpp_type, - *output_bsocket->typeinfo->base_cpp_type)) + if (implicitly_convert_socket_value( + *input_bsocket->typeinfo, input_value, *output_bsocket->typeinfo, output_value)) { - const MultiFunction &multi_fn = *conversions.get_conversion_multi_function( - mf::DataType::ForSingle(*input_bsocket->typeinfo->base_cpp_type), - mf::DataType::ForSingle(*output_bsocket->typeinfo->base_cpp_type)); - SocketValueVariant input_variant = *static_cast(input_value); - SocketValueVariant *output_variant = new (output_value) SocketValueVariant(); - execute_multi_function_on_value_variant(multi_fn, {}, {&input_variant}, {output_variant}); params.output_set(lf_output_index); continue; } @@ -633,35 +698,6 @@ class LazyFunctionForMutedNode : public LazyFunction { } }; -/** - * 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_; - - public: - LazyFunctionForMultiFunctionConversion(const MultiFunction &fn) : fn_(fn) - { - debug_name_ = "Convert"; - inputs_.append_as("From", CPPType::get()); - outputs_.append_as("To", CPPType::get()); - } - - void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override - { - SocketValueVariant *from_value = params.try_get_input_data_ptr(0); - SocketValueVariant *to_value = new (params.get_output_data_ptr(0)) SocketValueVariant(); - BLI_assert(from_value != nullptr); - BLI_assert(to_value != nullptr); - - execute_multi_function_on_value_variant(fn_, {}, {from_value}, {to_value}); - - params.output_set(0); - } -}; - /** * This lazy-function wraps nodes that are implemented as multi-function (mostly math nodes). */ @@ -1556,6 +1592,7 @@ static bool ignore_zone_bsocket(const bNodeSocket &bsocket) void initialize_zone_wrapper(const bNodeTreeZone &zone, ZoneBuildInfo &zone_info, const ZoneBodyFunction &body_fn, + const bool expose_all_reference_sets, Vector &r_inputs, Vector &r_outputs) { @@ -1598,11 +1635,14 @@ void initialize_zone_wrapper(const bNodeTreeZone &zone, r_outputs.append_and_get_index_as("Border Link Usage", CPPType::get())); } - for (const auto item : body_fn.indices.inputs.reference_sets.items()) { - zone_info.indices.inputs.reference_sets.add_new( - item.key, - r_inputs.append_and_get_index_as( - "Reference Set", CPPType::get(), lf::ValueUsage::Maybe)); + /* Some zone types (e.g. the closure zone) do not expose all reference sets. */ + if (expose_all_reference_sets) { + for (const auto item : body_fn.indices.inputs.reference_sets.items()) { + zone_info.indices.inputs.reference_sets.add_new( + item.key, + r_inputs.append_and_get_index_as( + "Reference Set", CPPType::get(), lf::ValueUsage::Maybe)); + } } } @@ -1922,6 +1962,9 @@ struct GeometryNodesLazyFunctionBuilder { case GEO_NODE_FOREACH_GEOMETRY_ELEMENT_OUTPUT: this->build_foreach_geometry_element_zone_function(zone); break; + case GEO_NODE_CLOSURE_OUTPUT: + this->build_closure_zone_function(zone); + break; default: { BLI_assert_unreachable(); break; @@ -2081,7 +2124,8 @@ struct GeometryNodesLazyFunctionBuilder { { ZoneBuildInfo &zone_info = zone_build_infos_[zone.index]; /* Build a function for the loop body. */ - ZoneBodyFunction &body_fn = this->build_zone_body_function(zone, "Repeat Body"); + ZoneBodyFunction &body_fn = this->build_zone_body_function( + zone, "Repeat Body", &scope_.construct()); /* Wrap the loop body by another function that implements the repeat behavior. */ auto &zone_fn = build_repeat_zone_lazy_function(scope_, btree_, zone, zone_info, body_fn); zone_info.lazy_function = &zone_fn; @@ -2091,17 +2135,30 @@ struct GeometryNodesLazyFunctionBuilder { { ZoneBuildInfo &zone_info = zone_build_infos_[zone.index]; /* Build a function for the loop body. */ - ZoneBodyFunction &body_fn = this->build_zone_body_function(zone, "Foreach Body"); + ZoneBodyFunction &body_fn = this->build_zone_body_function( + zone, "Foreach Body", &scope_.construct()); /* Wrap the loop body in another function that implements the foreach behavior. */ auto &zone_fn = build_foreach_geometry_element_zone_lazy_function( scope_, btree_, zone, zone_info, body_fn); zone_info.lazy_function = &zone_fn; } + void build_closure_zone_function(const bNodeTreeZone &zone) + { + ZoneBuildInfo &zone_info = zone_build_infos_[zone.index]; + /* Build a function for the closure body. */ + ZoneBodyFunction &body_fn = this->build_zone_body_function(zone, "Closure Body", nullptr); + auto &zone_fn = build_closure_zone_lazy_function(scope_, btree_, zone, zone_info, body_fn); + zone_info.lazy_function = &zone_fn; + } + /** * Build a lazy-function for the "body" of a zone, i.e. for all the nodes within the zone. */ - ZoneBodyFunction &build_zone_body_function(const bNodeTreeZone &zone, const StringRef name) + ZoneBodyFunction &build_zone_body_function( + const bNodeTreeZone &zone, + const StringRef name, + const lf::GraphExecutorSideEffectProvider *side_effect_provider) { lf::Graph &lf_body_graph = scope_.construct(name); @@ -2198,13 +2255,12 @@ struct GeometryNodesLazyFunctionBuilder { lf_body_graph.update_node_indices(); auto &logger = scope_.construct(*lf_graph_info_); - auto &side_effect_provider = scope_.construct(); body_fn.function = &scope_.construct(lf_body_graph, lf_body_inputs.as_span(), lf_body_outputs.as_span(), &logger, - &side_effect_provider, + side_effect_provider, nullptr); lf_graph_info_->debug_zone_body_graphs.add(zone.output_node->identifier, &lf_body_graph); @@ -2259,7 +2315,9 @@ struct GeometryNodesLazyFunctionBuilder { add_reference_set_zone_input(reference_set_i); break; } - case ReferenceSetType::LocalReferenceSet: { + case ReferenceSetType::LocalReferenceSet: + case ReferenceSetType::ClosureOutputData: + case ReferenceSetType::ClosureInputReferenceSet: { const bNodeSocket &bsocket = *reference_set.socket; if (lf::OutputSocket *lf_socket = graph_params.lf_output_by_bsocket.lookup_default( &bsocket, nullptr)) @@ -2438,6 +2496,12 @@ struct GeometryNodesLazyFunctionBuilder { lf_reference_sets.add_new(reference_set_i, lf_reference_set_socket); break; } + case ReferenceSetType::ClosureOutputData: + case ReferenceSetType::ClosureInputReferenceSet: { + /* These reference sets are not used outside of zones. */ + BLI_assert_unreachable(); + break; + } } } } @@ -2788,6 +2852,10 @@ struct GeometryNodesLazyFunctionBuilder { this->build_menu_switch_node(bnode, graph_params); break; } + case GEO_NODE_EVALUATE_CLOSURE: { + this->build_evaluate_closure_node(bnode, graph_params); + break; + } default: { if (node_type->geometry_node_execute) { this->build_geometry_node(bnode, graph_params); @@ -3541,6 +3609,50 @@ struct GeometryNodesLazyFunctionBuilder { } } + void build_evaluate_closure_node(const bNode &bnode, BuildGraphParams &graph_params) + { + const EvaluateClosureFunction function = build_evaluate_closure_node_lazy_function(scope_, + bnode); + lf::FunctionNode &lf_node = graph_params.lf_graph.add_function(*function.lazy_function); + const int inputs_num = bnode.input_sockets().size() - 1; + const int outputs_num = bnode.output_sockets().size() - 1; + BLI_assert(inputs_num == function.indices.inputs.main.size()); + BLI_assert(inputs_num == function.indices.outputs.input_usages.size()); + BLI_assert(outputs_num == function.indices.outputs.main.size()); + BLI_assert(outputs_num == function.indices.inputs.output_usages.size()); + + for (const int i : IndexRange(inputs_num)) { + const bNodeSocket &bsocket = bnode.input_socket(i); + lf::InputSocket &lf_socket = lf_node.input(function.indices.inputs.main[i]); + graph_params.lf_inputs_by_bsocket.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + graph_params.usage_by_bsocket.add(&bsocket, + &lf_node.output(function.indices.outputs.input_usages[i])); + } + for (const int i : IndexRange(outputs_num)) { + const bNodeSocket &bsocket = bnode.output_socket(i); + lf::OutputSocket &lf_socket = lf_node.output(function.indices.outputs.main[i]); + graph_params.lf_output_by_bsocket.add(&bsocket, &lf_socket); + mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); + lf::InputSocket &lf_usage_socket = lf_node.input(function.indices.inputs.output_usages[i]); + graph_params.socket_usage_inputs.add(&lf_usage_socket); + if (lf::OutputSocket *output_is_used = graph_params.usage_by_bsocket.lookup(&bsocket)) { + graph_params.lf_graph.add_link(*output_is_used, lf_usage_socket); + } + else { + static const bool static_false = false; + lf_usage_socket.set_default_value(&static_false); + } + } + for (const auto item : function.indices.inputs.reference_set_by_output.items()) { + const bNodeSocket &bsocket = bnode.output_socket(item.key); + lf_graph_info_->mapping + .lf_input_index_for_reference_set_for_output[bsocket.index_in_all_outputs()] = + item.value; + graph_params.lf_reference_set_input_by_output.add(&bsocket, &lf_node.input(item.value)); + } + } + void build_undefined_node(const bNode &bnode, BuildGraphParams &graph_params) { auto &lazy_function = scope_.construct( @@ -3688,16 +3800,12 @@ struct GeometryNodesLazyFunctionBuilder { if (from_typeinfo.type == to_typeinfo.type) { return &from_socket; } - if (from_typeinfo.base_cpp_type && to_typeinfo.base_cpp_type) { - if (conversions_->is_convertible(*from_typeinfo.base_cpp_type, *to_typeinfo.base_cpp_type)) { - const MultiFunction &multi_fn = *conversions_->get_conversion_multi_function( - mf::DataType::ForSingle(*from_typeinfo.base_cpp_type), - mf::DataType::ForSingle(*to_typeinfo.base_cpp_type)); - auto &fn = scope_.construct(multi_fn); - lf::Node &conversion_node = lf_graph.add_function(fn); - lf_graph.add_link(from_socket, conversion_node.input(0)); - return &conversion_node.output(0); - } + if (const LazyFunction *conversion_fn = build_implicit_conversion_lazy_function( + from_typeinfo, to_typeinfo, scope_)) + { + lf::Node &conversion_node = lf_graph.add_function(*conversion_fn); + lf_graph.add_link(from_socket, conversion_node.input(0)); + return &conversion_node.output(0); } return nullptr; } @@ -4109,6 +4217,9 @@ std::optional find_nested_node_id(const GeoNodesLFUserData &u { found.is_in_loop = true; } + else if (dynamic_cast(context) != nullptr) { + found.is_in_closure = true; + } } std::reverse(node_ids.begin(), node_ids.end()); node_ids.append(node_id); diff --git a/source/blender/nodes/intern/geometry_nodes_log.cc b/source/blender/nodes/intern/geometry_nodes_log.cc index 4d8baf0d42c..a5df5449a82 100644 --- a/source/blender/nodes/intern/geometry_nodes_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_log.cc @@ -2,6 +2,8 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ +#include "NOD_geometry_nodes_bundle.hh" +#include "NOD_geometry_nodes_closure.hh" #include "NOD_geometry_nodes_log.hh" #include "BLI_listbase.h" @@ -213,6 +215,13 @@ GeometryInfoLog::GeometryInfoLog(const bke::GVolumeGrid &grid) #endif } +BundleValueLog::BundleValueLog(Vector items) : items(std::move(items)) {} + +ClosureValueLog::ClosureValueLog(Vector inputs, Vector outputs) + : inputs(std::move(inputs)), outputs(std::move(outputs)) +{ +} + /* Avoid generating these in every translation unit. */ GeoModifierLog::GeoModifierLog() = default; GeoModifierLog::~GeoModifierLog() = default; @@ -268,6 +277,30 @@ void GeoTreeLogger::log_value(const bNode &node, const bNodeSocket &socket, cons store_logged_value(this->allocator->construct(grid)); } #endif + else if (value_variant.valid_for_socket(SOCK_BUNDLE)) { + Vector items; + if (const BundlePtr bundle = value_variant.extract()) { + for (const Bundle::StoredItem &item : bundle->items()) { + items.append({item.key, item.type}); + } + } + store_logged_value(this->allocator->construct(std::move(items))); + } + else if (value_variant.valid_for_socket(SOCK_CLOSURE)) { + Vector inputs; + Vector outputs; + if (const ClosurePtr closure = value_variant.extract()) { + const ClosureSignature &signature = closure->signature(); + for (const ClosureSignature::Item &item : signature.inputs) { + inputs.append({item.key, item.type}); + } + for (const ClosureSignature::Item &item : signature.outputs) { + outputs.append({item.key, item.type}); + } + } + store_logged_value( + this->allocator->construct(std::move(inputs), std::move(outputs))); + } else { value_variant.convert_to_single(); const GPointer value = value_variant.get_single_ptr(); @@ -676,6 +709,11 @@ static void find_tree_zone_hash_recursive( *zone.output_node, storage.inspection_index); break; } + case GEO_NODE_CLOSURE_OUTPUT: { + /* Can't find hashes for closure zones. Nodes in these zones may be evaluated in different + * contexts based on where the closures are called. */ + return; + } } r_hash_by_zone.add_new(&zone, compute_context_builder.hash()); for (const bNodeTreeZone *child_zone : zone.child_zones) { diff --git a/source/blender/nodes/intern/geometry_nodes_repeat_zone.cc b/source/blender/nodes/intern/geometry_nodes_repeat_zone.cc index 719084d558c..8a0456203f0 100644 --- a/source/blender/nodes/intern/geometry_nodes_repeat_zone.cc +++ b/source/blender/nodes/intern/geometry_nodes_repeat_zone.cc @@ -127,7 +127,7 @@ class LazyFunctionForRepeatZone : public LazyFunction { { debug_name_ = "Repeat Zone"; - initialize_zone_wrapper(zone, zone_info, body_fn, inputs_, outputs_); + initialize_zone_wrapper(zone, zone_info, body_fn, true, inputs_, outputs_); /* Iterations input is always used. */ inputs_[zone_info.indices.inputs.main[0]].usage = lf::ValueUsage::Used; } diff --git a/source/blender/nodes/intern/node_common.cc b/source/blender/nodes/intern/node_common.cc index dd15fcf6487..a477e8c5dc7 100644 --- a/source/blender/nodes/intern/node_common.cc +++ b/source/blender/nodes/intern/node_common.cc @@ -356,6 +356,14 @@ static BaseSocketDeclarationBuilder &build_interface_socket_declaration( .default_value_fn(get_default_id_getter(tree.tree_interface, io_socket)); break; } + case SOCK_BUNDLE: { + decl = &b.add_socket(name, identifier, in_out); + break; + } + case SOCK_CLOSURE: { + decl = &b.add_socket(name, identifier, in_out); + break; + } case SOCK_CUSTOM: { decl = &b.add_socket(name, identifier, in_out) .idname(io_socket.socket_type) diff --git a/source/blender/nodes/intern/node_declaration.cc b/source/blender/nodes/intern/node_declaration.cc index febc5200b2c..60cf95ff179 100644 --- a/source/blender/nodes/intern/node_declaration.cc +++ b/source/blender/nodes/intern/node_declaration.cc @@ -38,7 +38,7 @@ void build_node_declaration(const bke::bNodeType &typeinfo, void NodeDeclarationBuilder::build_remaining_anonymous_attribute_relations() { auto is_data_socket_decl = [](const SocketDeclaration *socket_decl) { - return dynamic_cast(socket_decl); + return ELEM(socket_decl->socket_type, SOCK_GEOMETRY, SOCK_BUNDLE, SOCK_CLOSURE); }; Vector geometry_inputs; @@ -354,6 +354,12 @@ static bool socket_type_to_static_decl_type(const eNodeSocketDatatype socket_typ case SOCK_MENU: fn(TypeTag()); return true; + case SOCK_BUNDLE: + fn(TypeTag()); + return true; + case SOCK_CLOSURE: + fn(TypeTag()); + return true; default: return false; } diff --git a/source/blender/nodes/intern/node_register.cc b/source/blender/nodes/intern/node_register.cc index 2f0b01202eb..be96b87d1e6 100644 --- a/source/blender/nodes/intern/node_register.cc +++ b/source/blender/nodes/intern/node_register.cc @@ -102,14 +102,34 @@ class ForeachGeometryElementZoneType : public blender::bke::bNodeZoneType { } }; +class ClosureZoneType : public blender::bke::bNodeZoneType { + public: + ClosureZoneType() + { + this->input_idname = "GeometryNodeClosureInput"; + this->output_idname = "GeometryNodeClosureOutput"; + this->input_type = GEO_NODE_CLOSURE_INPUT; + this->output_type = GEO_NODE_CLOSURE_OUTPUT; + this->theme_id = TH_NODE_ZONE_CLOSURE; + } + + const int &get_corresponding_output_id(const bNode &input_bnode) const override + { + BLI_assert(input_bnode.type_legacy == this->input_type); + return static_cast(input_bnode.storage)->output_node_id; + } +}; + static void register_zone_types() { static SimulationZoneType simulation_zone_type; static RepeatZoneType repeat_zone_type; static ForeachGeometryElementZoneType foreach_geometry_element_zone_type; + static ClosureZoneType closure_zone_type; blender::bke::register_node_zone_type(simulation_zone_type); blender::bke::register_node_zone_type(repeat_zone_type); blender::bke::register_node_zone_type(foreach_geometry_element_zone_type); + blender::bke::register_node_zone_type(closure_zone_type); } void register_nodes() diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc index 7b19224ac64..ff35e597c1f 100644 --- a/source/blender/nodes/intern/node_socket.cc +++ b/source/blender/nodes/intern/node_socket.cc @@ -34,6 +34,8 @@ #include "MEM_guardedalloc.h" +#include "NOD_geometry_nodes_bundle.hh" +#include "NOD_geometry_nodes_closure.hh" #include "NOD_node_declaration.hh" #include "NOD_socket.hh" @@ -683,6 +685,8 @@ void node_socket_init_default_value_data(eNodeSocketDatatype datatype, int subty case SOCK_GEOMETRY: case SOCK_MATRIX: case SOCK_SHADER: + case SOCK_BUNDLE: + case SOCK_CLOSURE: break; } } @@ -782,6 +786,8 @@ void node_socket_copy_default_value_data(eNodeSocketDatatype datatype, void *to, case SOCK_GEOMETRY: case SOCK_MATRIX: case SOCK_SHADER: + case SOCK_BUNDLE: + case SOCK_CLOSURE: break; } } @@ -979,6 +985,38 @@ static bke::bNodeSocketType *make_socket_type_matrix() return socktype; } +static bke::bNodeSocketType *make_socket_type_bundle() +{ + bke::bNodeSocketType *socktype = make_standard_socket_type(SOCK_BUNDLE, PROP_NONE); + socktype->base_cpp_type = &blender::CPPType::get(); + socktype->get_base_cpp_value = [](const void * /*socket_value*/, void *r_value) { + new (r_value) nodes::BundlePtr(); + }; + socktype->geometry_nodes_cpp_type = &blender::CPPType::get(); + socktype->get_geometry_nodes_cpp_value = [](const void * /*socket_value*/, void *r_value) { + new (r_value) SocketValueVariant(nodes::BundlePtr()); + }; + static SocketValueVariant default_value{nodes::BundlePtr()}; + socktype->geometry_nodes_default_cpp_value = &default_value; + return socktype; +} + +static bke::bNodeSocketType *make_socket_type_closure() +{ + bke::bNodeSocketType *socktype = make_standard_socket_type(SOCK_CLOSURE, PROP_NONE); + socktype->base_cpp_type = &blender::CPPType::get(); + socktype->get_base_cpp_value = [](const void * /*socket_value*/, void *r_value) { + new (r_value) nodes::ClosurePtr(); + }; + socktype->geometry_nodes_cpp_type = &blender::CPPType::get(); + socktype->get_geometry_nodes_cpp_value = [](const void * /*socket_value*/, void *r_value) { + new (r_value) SocketValueVariant(nodes::ClosurePtr()); + }; + static SocketValueVariant default_value{nodes::ClosurePtr()}; + socktype->geometry_nodes_default_cpp_value = &default_value; + return socktype; +} + static bke::bNodeSocketType *make_socket_type_float(PropertySubType subtype) { bke::bNodeSocketType *socktype = make_standard_socket_type(SOCK_FLOAT, subtype); @@ -1207,5 +1245,8 @@ void register_standard_node_socket_types() bke::node_register_socket_type(*make_socket_type_material()); + bke::node_register_socket_type(*make_socket_type_bundle()); + bke::node_register_socket_type(*make_socket_type_closure()); + bke::node_register_socket_type(*make_socket_type_virtual()); } diff --git a/source/blender/nodes/intern/node_socket_declarations.cc b/source/blender/nodes/intern/node_socket_declarations.cc index 0b0f0a70df4..d74a073da2d 100644 --- a/source/blender/nodes/intern/node_socket_declarations.cc +++ b/source/blender/nodes/intern/node_socket_declarations.cc @@ -593,6 +593,102 @@ bNodeSocket &Menu::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &s /** \} */ +/* -------------------------------------------------------------------- */ +/** \name #Bundle + * \{ */ + +bNodeSocket &Bundle::build(bNodeTree &ntree, bNode &node) const +{ + bNodeSocket &socket = *bke::node_add_static_socket(ntree, + node, + this->in_out, + SOCK_BUNDLE, + PROP_NONE, + this->identifier.c_str(), + this->name.c_str()); + this->set_common_flags(socket); + return socket; +} + +bool Bundle::matches(const bNodeSocket &socket) const +{ + if (!this->matches_common_data(socket)) { + return false; + } + if (socket.type != SOCK_BUNDLE) { + return false; + } + return true; +} + +bool Bundle::can_connect(const bNodeSocket &socket) const +{ + if (!sockets_can_connect(*this, socket)) { + return false; + } + return ELEM(socket.type, SOCK_BUNDLE); +} + +bNodeSocket &Bundle::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const +{ + if (socket.type != SOCK_BUNDLE) { + BLI_assert(socket.in_out == this->in_out); + return this->build(ntree, node); + } + this->set_common_flags(socket); + return socket; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name #Closure + * \{ */ + +bNodeSocket &Closure::build(bNodeTree &ntree, bNode &node) const +{ + bNodeSocket &socket = *bke::node_add_static_socket(ntree, + node, + this->in_out, + SOCK_CLOSURE, + PROP_NONE, + this->identifier.c_str(), + this->name.c_str()); + this->set_common_flags(socket); + return socket; +} + +bool Closure::matches(const bNodeSocket &socket) const +{ + if (!this->matches_common_data(socket)) { + return false; + } + if (socket.type != SOCK_CLOSURE) { + return false; + } + return true; +} + +bool Closure::can_connect(const bNodeSocket &socket) const +{ + if (!sockets_can_connect(*this, socket)) { + return false; + } + return ELEM(socket.type, SOCK_CLOSURE); +} + +bNodeSocket &Closure::update_or_build(bNodeTree &ntree, bNode &node, bNodeSocket &socket) const +{ + if (socket.type != SOCK_CLOSURE) { + BLI_assert(socket.in_out == this->in_out); + return this->build(ntree, node); + } + this->set_common_flags(socket); + return socket; +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name #IDSocketDeclaration * \{ */