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 * \{ */