From 2ffd08e95249df2a068dd400cd3117cf8ca4a246 Mon Sep 17 00:00:00 2001 From: Jacques Lucke Date: Thu, 5 Jan 2023 14:05:30 +0100 Subject: [PATCH] Geometry Nodes: deterministic anonymous attribute lifetimes Previously, the lifetimes of anonymous attributes were determined by reference counts which were non-deterministic when multiple threads are used. Now the lifetimes of anonymous attributes are handled more explicitly and deterministically. This is a prerequisite for any kind of caching, because caching the output of nodes that do things non-deterministically and have "invisible inputs" (reference counts) doesn't really work. For more details for how deterministic lifetimes are achieved, see D16858. No functional changes are expected. Small performance changes are expected as well (within few percent, anything larger regressions should be reported as bugs). Differential Revision: https://developer.blender.org/D16858 --- .../blenkernel/BKE_anonymous_attribute.h | 29 - .../blenkernel/BKE_anonymous_attribute.hh | 155 -- .../blenkernel/BKE_anonymous_attribute_id.hh | 103 ++ source/blender/blenkernel/BKE_attribute.hh | 42 +- .../blender/blenkernel/BKE_curve_to_mesh.hh | 8 +- source/blender/blenkernel/BKE_curves.hh | 6 +- source/blender/blenkernel/BKE_customdata.h | 5 +- .../blender/blenkernel/BKE_geometry_fields.hh | 17 +- source/blender/blenkernel/BKE_geometry_set.hh | 3 +- source/blender/blenkernel/BKE_instances.hh | 3 +- source/blender/blenkernel/BKE_node_runtime.hh | 13 +- source/blender/blenkernel/CMakeLists.txt | 6 +- .../blenkernel/intern/anonymous_attribute.cc | 105 -- .../intern/anonymous_attribute_id.cc | 18 + .../blenkernel/intern/attribute_access.cc | 42 +- .../intern/attribute_access_intern.hh | 12 +- source/blender/blenkernel/intern/cpp_types.cc | 4 + .../intern/curve_to_mesh_convert.cc | 21 +- .../blenkernel/intern/curves_geometry.cc | 44 +- .../blender/blenkernel/intern/customdata.cc | 26 +- .../intern/geometry_component_mesh.cc | 6 +- .../blenkernel/intern/geometry_fields.cc | 5 +- .../blender/blenkernel/intern/geometry_set.cc | 4 +- source/blender/blenkernel/intern/instances.cc | 5 +- .../blender/blenkernel/intern/mesh_convert.cc | 4 +- source/blender/blenkernel/intern/node.cc | 5 + .../intern/node_tree_anonymous_attributes.cc | 452 +++++ .../blenkernel/intern/node_tree_update.cc | 3 + source/blender/editors/mesh/mesh_data.cc | 3 +- .../functions/FN_lazy_function_graph.hh | 54 + .../functions/intern/lazy_function_graph.cc | 37 + source/blender/geometry/GEO_fillet_curves.hh | 22 +- .../blender/geometry/GEO_mesh_split_edges.hh | 6 +- source/blender/geometry/GEO_mesh_to_curve.hh | 15 +- .../geometry/GEO_point_merge_by_distance.hh | 10 +- .../blender/geometry/GEO_realize_instances.hh | 2 + .../blender/geometry/GEO_resample_curves.hh | 2 +- source/blender/geometry/GEO_set_curve_type.hh | 3 +- .../blender/geometry/GEO_subdivide_curves.hh | 8 +- source/blender/geometry/GEO_trim_curves.hh | 3 +- .../geometry/intern/add_curves_on_mesh.cc | 2 +- .../blender/geometry/intern/fillet_curves.cc | 45 +- .../geometry/intern/mesh_split_edges.cc | 24 +- .../geometry/intern/mesh_to_curve_convert.cc | 19 +- .../intern/point_merge_by_distance.cc | 5 +- .../geometry/intern/realize_instances.cc | 21 +- .../geometry/intern/resample_curves.cc | 4 +- .../blender/geometry/intern/set_curve_type.cc | 21 +- .../geometry/intern/subdivide_curves.cc | 15 +- source/blender/geometry/intern/trim_curves.cc | 27 +- .../blender/makesdna/DNA_customdata_types.h | 18 +- source/blender/modifiers/intern/MOD_nodes.cc | 44 +- source/blender/nodes/NOD_geometry_exec.hh | 75 +- .../nodes/NOD_geometry_nodes_lazy_function.hh | 49 +- .../nodes/geometry/node_geometry_util.hh | 9 +- .../nodes/node_geo_attribute_capture.cc | 12 +- .../nodes/geometry/nodes/node_geo_boolean.cc | 7 +- .../geometry/nodes/node_geo_curve_fillet.cc | 12 +- .../nodes/node_geo_curve_primitive_star.cc | 12 +- .../nodes/node_geo_curve_spline_type.cc | 3 +- .../nodes/node_geo_curve_subdivide.cc | 2 +- .../geometry/nodes/node_geo_curve_to_mesh.cc | 12 +- .../nodes/node_geo_curve_to_points.cc | 19 +- .../geometry/nodes/node_geo_curve_trim.cc | 14 +- .../nodes/node_geo_delete_geometry.cc | 53 +- .../node_geo_distribute_points_on_faces.cc | 19 +- .../geometry/nodes/node_geo_dual_mesh.cc | 13 +- .../nodes/node_geo_duplicate_elements.cc | 126 +- .../nodes/node_geo_edge_paths_to_curves.cc | 15 +- .../geometry/nodes/node_geo_edge_split.cc | 3 +- .../geometry/nodes/node_geo_extrude_mesh.cc | 14 +- .../nodes/node_geo_instance_on_points.cc | 7 +- .../nodes/node_geo_instances_to_points.cc | 7 +- .../geometry/nodes/node_geo_join_geometry.cc | 23 +- .../nodes/node_geo_merge_by_distance.cc | 14 +- .../nodes/node_geo_mesh_primitive_cone.cc | 16 +- .../nodes/node_geo_mesh_primitive_cube.cc | 6 +- .../nodes/node_geo_mesh_primitive_cylinder.cc | 16 +- .../nodes/node_geo_mesh_primitive_grid.cc | 6 +- .../node_geo_mesh_primitive_ico_sphere.cc | 6 +- .../node_geo_mesh_primitive_uv_sphere.cc | 6 +- .../geometry/nodes/node_geo_mesh_to_curve.cc | 3 +- .../geometry/nodes/node_geo_mesh_to_points.cc | 47 +- .../nodes/node_geo_points_to_vertices.cc | 16 +- .../nodes/node_geo_realize_instances.cc | 1 + .../nodes/node_geo_separate_geometry.cc | 47 +- .../nodes/node_geo_string_to_curves.cc | 16 +- .../intern/geometry_nodes_lazy_function.cc | 1538 ++++++++++++++++- .../nodes/intern/geometry_nodes_log.cc | 2 +- .../nodes/intern/node_geometry_exec.cc | 23 + 90 files changed, 3003 insertions(+), 822 deletions(-) delete mode 100644 source/blender/blenkernel/BKE_anonymous_attribute.h delete mode 100644 source/blender/blenkernel/BKE_anonymous_attribute.hh create mode 100644 source/blender/blenkernel/BKE_anonymous_attribute_id.hh delete mode 100644 source/blender/blenkernel/intern/anonymous_attribute.cc create mode 100644 source/blender/blenkernel/intern/anonymous_attribute_id.cc create mode 100644 source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc diff --git a/source/blender/blenkernel/BKE_anonymous_attribute.h b/source/blender/blenkernel/BKE_anonymous_attribute.h deleted file mode 100644 index ab26f2c6224..00000000000 --- a/source/blender/blenkernel/BKE_anonymous_attribute.h +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -/** \file - * \ingroup bke - * - * An #AnonymousAttributeID is used to identify attributes that are not explicitly named. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct AnonymousAttributeID AnonymousAttributeID; - -AnonymousAttributeID *BKE_anonymous_attribute_id_new_weak(const char *debug_name); -AnonymousAttributeID *BKE_anonymous_attribute_id_new_strong(const char *debug_name); -bool BKE_anonymous_attribute_id_has_strong_references(const AnonymousAttributeID *anonymous_id); -void BKE_anonymous_attribute_id_increment_weak(const AnonymousAttributeID *anonymous_id); -void BKE_anonymous_attribute_id_increment_strong(const AnonymousAttributeID *anonymous_id); -void BKE_anonymous_attribute_id_decrement_weak(const AnonymousAttributeID *anonymous_id); -void BKE_anonymous_attribute_id_decrement_strong(const AnonymousAttributeID *anonymous_id); -const char *BKE_anonymous_attribute_id_debug_name(const AnonymousAttributeID *anonymous_id); -const char *BKE_anonymous_attribute_id_internal_name(const AnonymousAttributeID *anonymous_id); - -#ifdef __cplusplus -} -#endif diff --git a/source/blender/blenkernel/BKE_anonymous_attribute.hh b/source/blender/blenkernel/BKE_anonymous_attribute.hh deleted file mode 100644 index d9161bda572..00000000000 --- a/source/blender/blenkernel/BKE_anonymous_attribute.hh +++ /dev/null @@ -1,155 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#pragma once - -#include -#include - -#include "BLI_hash.hh" -#include "BLI_string_ref.hh" - -#include "BKE_anonymous_attribute.h" - -namespace blender::bke { - -/** - * Wrapper for #AnonymousAttributeID with RAII semantics. - * This class should typically not be used directly. Instead use #StrongAnonymousAttributeID or - * #WeakAnonymousAttributeID. - */ -template class OwnedAnonymousAttributeID { - private: - const AnonymousAttributeID *data_ = nullptr; - - template friend class OwnedAnonymousAttributeID; - - public: - OwnedAnonymousAttributeID() = default; - - /** Create a new anonymous attribute id. */ - explicit OwnedAnonymousAttributeID(StringRefNull debug_name) - { - if constexpr (IsStrongReference) { - data_ = BKE_anonymous_attribute_id_new_strong(debug_name.c_str()); - } - else { - data_ = BKE_anonymous_attribute_id_new_weak(debug_name.c_str()); - } - } - - /** - * This transfers ownership, so no incref is necessary. - * The caller has to make sure that it owned the anonymous id. - */ - explicit OwnedAnonymousAttributeID(const AnonymousAttributeID *anonymous_id) - : data_(anonymous_id) - { - } - - template - OwnedAnonymousAttributeID(const OwnedAnonymousAttributeID &other) - { - data_ = other.data_; - this->incref(); - } - - template - OwnedAnonymousAttributeID(OwnedAnonymousAttributeID &&other) - { - data_ = other.data_; - this->incref(); - other.decref(); - other.data_ = nullptr; - } - - ~OwnedAnonymousAttributeID() - { - this->decref(); - } - - template - OwnedAnonymousAttributeID &operator=(const OwnedAnonymousAttributeID &other) - { - if (this == &other) { - return *this; - } - this->~OwnedAnonymousAttributeID(); - new (this) OwnedAnonymousAttributeID(other); - return *this; - } - - template - OwnedAnonymousAttributeID &operator=(OwnedAnonymousAttributeID &&other) - { - if (this == &other) { - return *this; - } - this->~OwnedAnonymousAttributeID(); - new (this) OwnedAnonymousAttributeID(std::move(other)); - return *this; - } - - operator bool() const - { - return data_ != nullptr; - } - - StringRefNull debug_name() const - { - BLI_assert(data_ != nullptr); - return BKE_anonymous_attribute_id_debug_name(data_); - } - - bool has_strong_references() const - { - BLI_assert(data_ != nullptr); - return BKE_anonymous_attribute_id_has_strong_references(data_); - } - - /** Extract the ownership of the currently wrapped anonymous id. */ - const AnonymousAttributeID *extract() - { - const AnonymousAttributeID *extracted_data = data_; - /* Don't decref because the caller becomes the new owner. */ - data_ = nullptr; - return extracted_data; - } - - /** Get the wrapped anonymous id, without taking ownership. */ - const AnonymousAttributeID *get() const - { - return data_; - } - - private: - void incref() - { - if (data_ == nullptr) { - return; - } - if constexpr (IsStrongReference) { - BKE_anonymous_attribute_id_increment_strong(data_); - } - else { - BKE_anonymous_attribute_id_increment_weak(data_); - } - } - - void decref() - { - if (data_ == nullptr) { - return; - } - if constexpr (IsStrongReference) { - BKE_anonymous_attribute_id_decrement_strong(data_); - } - else { - BKE_anonymous_attribute_id_decrement_weak(data_); - } - } -}; - -using StrongAnonymousAttributeID = OwnedAnonymousAttributeID; -using WeakAnonymousAttributeID = OwnedAnonymousAttributeID; - -} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_anonymous_attribute_id.hh b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh new file mode 100644 index 00000000000..f9b6e2ca543 --- /dev/null +++ b/source/blender/blenkernel/BKE_anonymous_attribute_id.hh @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include + +#include "BLI_set.hh" +#include "BLI_string_ref.hh" +#include "BLI_user_counter.hh" + +namespace blender::bke { + +/** + * An #AnonymousAttributeID contains information about a specific anonymous attribute. + * Like normal attributes, anonymous attributes are also identified by their name, so one should + * not have to compare #AnonymousAttributeID pointers. + * + * Anonymous attributes don't need additional information besides their name, with a few + * exceptions: + * - The name of anonymous attributes is generated automatically, so it is generally not human + * readable (just random characters). #AnonymousAttributeID can provide more context as where a + * specific anonymous attribute was created which can simplify debugging. + * - [Not yet supported.] When anonymous attributes are contained in on-disk caches, we have to map + * those back to anonymous attributes at run-time. The issue is that (for various reasons) we + * might change how anonymous attribute names are generated in the future, which would lead to a + * mis-match between stored and new attribute names. To work around it, we should cache + * additional information for anonymous attributes on disk (like which node created it). This + * information can then be used to map stored attributes to their run-time counterpart. + * + * Once created, #AnonymousAttributeID is immutable. Also it is intrinsicly reference counted so + * that it can have shared ownership. `std::shared_ptr` can't be used for that purpose here, + * because that is not available in C code. If possible, the #AutoAnonymousAttributeID wrapper + * should be used to avoid manual reference counting in C++ code. + */ +class AnonymousAttributeID { + private: + mutable std::atomic users_ = 1; + + protected: + std::string name_; + + public: + virtual ~AnonymousAttributeID() = default; + + StringRefNull name() const + { + return name_; + } + + void user_add() const + { + users_.fetch_add(1); + } + + void user_remove() const + { + const int new_users = users_.fetch_sub(1) - 1; + if (new_users == 0) { + MEM_delete(this); + } + } +}; + +/** Wrapper for #AnonymousAttributeID that avoids manual reference counting. */ +using AutoAnonymousAttributeID = UserCounter; + +/** + * A set of anonymous attribute names that is passed around in geometry nodes. + */ +class AnonymousAttributeSet { + public: + /** + * This uses `std::shared_ptr` because attributes sets are passed around by value during geometry + * nodes evaluation, and this makes it very small if there is no name. Also it makes copying very + * cheap. + */ + std::shared_ptr> names; +}; + +/** + * Can be passed to algorithms which propagate attributes. It can tell the algorithm which + * anonymous attributes should be propagated and can be skipped. + */ +class AnonymousAttributePropagationInfo { + public: + /** + * This uses `std::shared_ptr` because it's usually initialized from an #AnonymousAttributeSet + * and then the set doesn't have to be copied. + */ + std::shared_ptr> names; + + /** + * Propagate all anonymous attributes even if the set above is empty. + */ + bool propagate_all = true; + + /** + * Return true when the anonymous attribute should be propagated and false otherwise. + */ + bool propagate(const AnonymousAttributeID &anonymous_id) const; +}; + +} // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_attribute.hh b/source/blender/blenkernel/BKE_attribute.hh index a4f9d73c31e..031a4bb86ea 100644 --- a/source/blender/blenkernel/BKE_attribute.hh +++ b/source/blender/blenkernel/BKE_attribute.hh @@ -11,7 +11,7 @@ #include "BLI_math_vec_types.hh" #include "BLI_set.hh" -#include "BKE_anonymous_attribute.hh" +#include "BKE_anonymous_attribute_id.hh" #include "BKE_attribute.h" struct Mesh; @@ -24,7 +24,7 @@ class GField; namespace blender::bke { /** - * Identifies an attribute that is either named or anonymous. + * Identifies an attribute with optional anonymous attribute information. * It does not own the identifier, so it is just a reference. */ class AttributeIDRef { @@ -38,15 +38,14 @@ class AttributeIDRef { AttributeIDRef(StringRefNull name); AttributeIDRef(const char *name); AttributeIDRef(const std::string &name); + AttributeIDRef(const AnonymousAttributeID &anonymous_id); AttributeIDRef(const AnonymousAttributeID *anonymous_id); operator bool() const; uint64_t hash() const; - bool is_named() const; bool is_anonymous() const; StringRef name() const; const AnonymousAttributeID &anonymous_id() const; - bool should_be_kept() const; friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b); friend std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id); @@ -749,6 +748,7 @@ Vector retrieve_attributes_for_transfer( const bke::AttributeAccessor src_attributes, bke::MutableAttributeAccessor dst_attributes, eAttrDomainMask domain_mask, + const AnonymousAttributePropagationInfo &propagation_info, const Set &skip = {}); /** @@ -762,6 +762,7 @@ void copy_attribute_domain(AttributeAccessor src_attributes, MutableAttributeAccessor dst_attributes, IndexMask selection, eAttrDomain domain, + const AnonymousAttributePropagationInfo &propagation_info, const Set &skip = {}); bool allow_procedural_attribute_access(StringRef attribute_name); @@ -852,29 +853,31 @@ inline AttributeIDRef::AttributeIDRef(const std::string &name) : name_(name) } /* The anonymous id is only borrowed, the caller has to keep a reference to it. */ -inline AttributeIDRef::AttributeIDRef(const AnonymousAttributeID *anonymous_id) - : anonymous_id_(anonymous_id) +inline AttributeIDRef::AttributeIDRef(const AnonymousAttributeID &anonymous_id) + : AttributeIDRef(anonymous_id.name()) { + anonymous_id_ = &anonymous_id; +} + +inline AttributeIDRef::AttributeIDRef(const AnonymousAttributeID *anonymous_id) + : AttributeIDRef(anonymous_id ? anonymous_id->name() : "") +{ + anonymous_id_ = anonymous_id; } inline bool operator==(const AttributeIDRef &a, const AttributeIDRef &b) { - return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_; + return a.name_ == b.name_; } inline AttributeIDRef::operator bool() const { - return this->is_named() || this->is_anonymous(); + return !name_.is_empty(); } inline uint64_t AttributeIDRef::hash() const { - return get_default_hash_2(name_, anonymous_id_); -} - -inline bool AttributeIDRef::is_named() const -{ - return !name_.is_empty(); + return get_default_hash(name_); } inline bool AttributeIDRef::is_anonymous() const @@ -884,7 +887,6 @@ inline bool AttributeIDRef::is_anonymous() const inline StringRef AttributeIDRef::name() const { - BLI_assert(this->is_named()); return name_; } @@ -894,14 +896,4 @@ inline const AnonymousAttributeID &AttributeIDRef::anonymous_id() const return *anonymous_id_; } -/** - * \return True if the attribute should not be removed automatically as an optimization during - * processing or copying. Anonymous attributes can be removed when they no longer have any - * references. - */ -inline bool AttributeIDRef::should_be_kept() const -{ - return this->is_named() || BKE_anonymous_attribute_id_has_strong_references(anonymous_id_); -} - } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_curve_to_mesh.hh b/source/blender/blenkernel/BKE_curve_to_mesh.hh index 6e657542e0f..0f67da2d8a5 100644 --- a/source/blender/blenkernel/BKE_curve_to_mesh.hh +++ b/source/blender/blenkernel/BKE_curve_to_mesh.hh @@ -11,6 +11,8 @@ struct Mesh; namespace blender::bke { +class AnonymousAttributePropagationInfo; + /** * Extrude all splines in the profile curve along the path of every spline in the curve input. * Transfer curve attributes to the mesh. @@ -23,11 +25,13 @@ namespace blender::bke { */ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, const CurvesGeometry &profile, - bool fill_caps); + bool fill_caps, + const AnonymousAttributePropagationInfo &propagation_info); /** * Create a loose-edge mesh based on the evaluated path of the curve's splines. * Transfer curve attributes to the mesh. */ -Mesh *curve_to_wire_mesh(const CurvesGeometry &curve); +Mesh *curve_to_wire_mesh(const CurvesGeometry &curve, + const AnonymousAttributePropagationInfo &propagation_info); } // namespace blender::bke diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index 31776676940..9f849584710 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -404,8 +404,10 @@ class CurvesGeometry : public ::CurvesGeometry { void calculate_bezier_auto_handles(); - void remove_points(IndexMask points_to_delete); - void remove_curves(IndexMask curves_to_delete); + void remove_points(IndexMask points_to_delete, + const AnonymousAttributePropagationInfo &propagation_info = {}); + void remove_curves(IndexMask curves_to_delete, + const AnonymousAttributePropagationInfo &propagation_info = {}); /** * Change the direction of selected curves (switch the start and end) without changing their diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h index 1cdd3c02d8d..582dd1b5a29 100644 --- a/source/blender/blenkernel/BKE_customdata.h +++ b/source/blender/blenkernel/BKE_customdata.h @@ -23,7 +23,6 @@ extern "C" { #endif -struct AnonymousAttributeID; struct BMesh; struct BlendDataReader; struct BlendWriter; @@ -227,7 +226,7 @@ void *CustomData_add_layer_anonymous(struct CustomData *data, eCDAllocType alloctype, void *layer, int totelem, - const struct AnonymousAttributeID *anonymous_id); + const AnonymousAttributeIDHandle *anonymous_id); /** * Frees the active or first data layer with the give type. @@ -275,8 +274,6 @@ void *CustomData_duplicate_referenced_layer_named(struct CustomData *data, int type, const char *name, int totelem); -void *CustomData_duplicate_referenced_layer_anonymous( - CustomData *data, int type, const struct AnonymousAttributeID *anonymous_id, int totelem); bool CustomData_is_referenced_layer(struct CustomData *data, int type); /** diff --git a/source/blender/blenkernel/BKE_geometry_fields.hh b/source/blender/blenkernel/BKE_geometry_fields.hh index 7b493ea5ca9..967bb912cc6 100644 --- a/source/blender/blenkernel/BKE_geometry_fields.hh +++ b/source/blender/blenkernel/BKE_geometry_fields.hh @@ -261,18 +261,14 @@ class NormalFieldInput : public GeometryFieldInput { class AnonymousAttributeFieldInput : public GeometryFieldInput { private: - /** - * A strong reference is required to make sure that the referenced attribute is not removed - * automatically. - */ - StrongAnonymousAttributeID anonymous_id_; + AutoAnonymousAttributeID anonymous_id_; std::string producer_name_; public: - AnonymousAttributeFieldInput(StrongAnonymousAttributeID anonymous_id, + AnonymousAttributeFieldInput(AutoAnonymousAttributeID anonymous_id, const CPPType &type, std::string producer_name) - : GeometryFieldInput(type, anonymous_id.debug_name()), + : GeometryFieldInput(type, anonymous_id->name()), anonymous_id_(std::move(anonymous_id)), producer_name_(producer_name) { @@ -280,7 +276,7 @@ class AnonymousAttributeFieldInput : public GeometryFieldInput { } template - static fn::Field Create(StrongAnonymousAttributeID anonymous_id, std::string producer_name) + static fn::Field Create(AutoAnonymousAttributeID anonymous_id, std::string producer_name) { const CPPType &type = CPPType::get(); auto field_input = std::make_shared( @@ -288,6 +284,11 @@ class AnonymousAttributeFieldInput : public GeometryFieldInput { return fn::Field{field_input}; } + const AutoAnonymousAttributeID &anonymous_id() const + { + return anonymous_id_; + } + GVArray get_varray_for_context(const GeometryFieldContext &context, IndexMask mask) const override; diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh index b488806c8e7..e6f55a8500f 100644 --- a/source/blender/blenkernel/BKE_geometry_set.hh +++ b/source/blender/blenkernel/BKE_geometry_set.hh @@ -19,7 +19,7 @@ #include "BLI_user_counter.hh" #include "BLI_vector_set.hh" -#include "BKE_anonymous_attribute.hh" +#include "BKE_anonymous_attribute_id.hh" #include "BKE_attribute.hh" #include "BKE_geometry_set.h" @@ -213,6 +213,7 @@ struct GeometrySet { blender::Span component_types, GeometryComponentType dst_component_type, bool include_instances, + const blender::bke::AnonymousAttributePropagationInfo &propagation_info, blender::Map &r_attributes) const; blender::Vector gather_component_types(bool include_instances, diff --git a/source/blender/blenkernel/BKE_instances.hh b/source/blender/blenkernel/BKE_instances.hh index f17ebba0dfa..3d44e3a4686 100644 --- a/source/blender/blenkernel/BKE_instances.hh +++ b/source/blender/blenkernel/BKE_instances.hh @@ -155,7 +155,8 @@ class Instances { * Remove the indices that are not contained in the mask input, and remove unused instance * references afterwards. */ - void remove(const blender::IndexMask mask); + void remove(const blender::IndexMask mask, + const blender::bke::AnonymousAttributePropagationInfo &propagation_info); /** * Get an id for every instance. These can be used for e.g. motion blur. */ diff --git a/source/blender/blenkernel/BKE_node_runtime.hh b/source/blender/blenkernel/BKE_node_runtime.hh index 6941bcb023a..ca2fa5d0b01 100644 --- a/source/blender/blenkernel/BKE_node_runtime.hh +++ b/source/blender/blenkernel/BKE_node_runtime.hh @@ -7,6 +7,7 @@ #include "BLI_cache_mutex.hh" #include "BLI_multi_value_map.hh" +#include "BLI_resource_scope.hh" #include "BLI_utility_mixins.hh" #include "BLI_vector.hh" #include "BLI_vector_set.hh" @@ -24,6 +25,10 @@ namespace blender::nodes { struct FieldInferencingInterface; class NodeDeclaration; struct GeometryNodesLazyFunctionGraphInfo; +namespace anonymous_attribute_lifetime { +struct RelationsInNode; +} +namespace aal = anonymous_attribute_lifetime; } // namespace blender::nodes namespace blender { @@ -106,6 +111,8 @@ class bNodeTreeRuntime : NonCopyable, NonMovable { /** Information about how inputs and outputs of the node group interact with fields. */ std::unique_ptr field_inferencing_interface; + /** Information about usage of anonymous attributes within the group. */ + std::unique_ptr anonymous_attribute_relations; /** * For geometry nodes, a lazy function graph with some additional info is cached. This is used to @@ -330,7 +337,11 @@ inline bool topology_cache_is_available(const bNodeSocket &socket) namespace node_field_inferencing { bool update_field_inferencing(const bNodeTree &tree); } - +namespace anonymous_attribute_inferencing { +Array get_relations_by_node(const bNodeTree &tree, + ResourceScope &scope); +bool update_anonymous_attribute_relations(bNodeTree &tree); +} // namespace anonymous_attribute_inferencing } // namespace blender::bke /* -------------------------------------------------------------------- */ diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index bfd9eaaa23e..101baf1e983 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -64,7 +64,7 @@ set(SRC intern/anim_path.c intern/anim_sys.c intern/anim_visualization.c - intern/anonymous_attribute.cc + intern/anonymous_attribute_id.cc intern/appdir.c intern/armature.c intern/armature_deform.c @@ -229,6 +229,7 @@ set(SRC intern/nla.c intern/node.cc intern/node_runtime.cc + intern/node_tree_anonymous_attributes.cc intern/node_tree_field_inferencing.cc intern/node_tree_update.cc intern/object.cc @@ -315,8 +316,7 @@ set(SRC BKE_anim_path.h BKE_anim_visualization.h BKE_animsys.h - BKE_anonymous_attribute.h - BKE_anonymous_attribute.hh + BKE_anonymous_attribute_id.hh BKE_appdir.h BKE_armature.h BKE_armature.hh diff --git a/source/blender/blenkernel/intern/anonymous_attribute.cc b/source/blender/blenkernel/intern/anonymous_attribute.cc deleted file mode 100644 index 636e0af0edf..00000000000 --- a/source/blender/blenkernel/intern/anonymous_attribute.cc +++ /dev/null @@ -1,105 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -#include "BKE_anonymous_attribute.hh" - -using namespace blender::bke; - -/** - * A struct that identifies an attribute. It's lifetime is managed by an atomic reference count. - * - * Additionally, this struct can be strongly or weakly owned. The difference is that strong - * ownership means that attributes with this id will be kept around. Weak ownership just makes sure - * that the struct itself stays alive, but corresponding attributes might still be removed - * automatically. - */ -struct AnonymousAttributeID { - /** - * Total number of references to this attribute id. Once this reaches zero, the struct can be - * freed. This includes strong and weak references. - */ - mutable std::atomic refcount_tot = 0; - - /** - * Number of strong references to this attribute id. When this is zero, the corresponding - * attributes can be removed from geometries automatically. - */ - mutable std::atomic refcount_strong = 0; - - /** - * Only used to identify this struct in a debugging session. - */ - std::string debug_name; - - /** - * Unique name of the this attribute id during the current session. - */ - std::string internal_name; -}; - -/** Every time this function is called, it outputs a different name. */ -static std::string get_new_internal_name() -{ - static std::atomic index = 0; - const int next_index = index.fetch_add(1); - return ".a_" + std::to_string(next_index); -} - -AnonymousAttributeID *BKE_anonymous_attribute_id_new_weak(const char *debug_name) -{ - AnonymousAttributeID *anonymous_id = new AnonymousAttributeID(); - anonymous_id->debug_name = debug_name; - anonymous_id->internal_name = get_new_internal_name(); - anonymous_id->refcount_tot.store(1); - return anonymous_id; -} - -AnonymousAttributeID *BKE_anonymous_attribute_id_new_strong(const char *debug_name) -{ - AnonymousAttributeID *anonymous_id = new AnonymousAttributeID(); - anonymous_id->debug_name = debug_name; - anonymous_id->internal_name = get_new_internal_name(); - anonymous_id->refcount_tot.store(1); - anonymous_id->refcount_strong.store(1); - return anonymous_id; -} - -bool BKE_anonymous_attribute_id_has_strong_references(const AnonymousAttributeID *anonymous_id) -{ - return anonymous_id->refcount_strong.load() >= 1; -} - -void BKE_anonymous_attribute_id_increment_weak(const AnonymousAttributeID *anonymous_id) -{ - anonymous_id->refcount_tot.fetch_add(1); -} - -void BKE_anonymous_attribute_id_increment_strong(const AnonymousAttributeID *anonymous_id) -{ - anonymous_id->refcount_tot.fetch_add(1); - anonymous_id->refcount_strong.fetch_add(1); -} - -void BKE_anonymous_attribute_id_decrement_weak(const AnonymousAttributeID *anonymous_id) -{ - const int new_refcount = anonymous_id->refcount_tot.fetch_sub(1) - 1; - if (new_refcount == 0) { - BLI_assert(anonymous_id->refcount_strong == 0); - delete anonymous_id; - } -} - -void BKE_anonymous_attribute_id_decrement_strong(const AnonymousAttributeID *anonymous_id) -{ - anonymous_id->refcount_strong.fetch_sub(1); - BKE_anonymous_attribute_id_decrement_weak(anonymous_id); -} - -const char *BKE_anonymous_attribute_id_debug_name(const AnonymousAttributeID *anonymous_id) -{ - return anonymous_id->debug_name.c_str(); -} - -const char *BKE_anonymous_attribute_id_internal_name(const AnonymousAttributeID *anonymous_id) -{ - return anonymous_id->internal_name.c_str(); -} diff --git a/source/blender/blenkernel/intern/anonymous_attribute_id.cc b/source/blender/blenkernel/intern/anonymous_attribute_id.cc new file mode 100644 index 00000000000..e15ea6b643c --- /dev/null +++ b/source/blender/blenkernel/intern/anonymous_attribute_id.cc @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BKE_anonymous_attribute_id.hh" + +namespace blender::bke { + +bool AnonymousAttributePropagationInfo::propagate(const AnonymousAttributeID &anonymous_id) const +{ + if (this->propagate_all) { + return true; + } + if (!this->names) { + return false; + } + return this->names->contains_as(anonymous_id.name()); +} + +} // namespace blender::bke diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc index e5c43a3f90e..a5dbc4645fb 100644 --- a/source/blender/blenkernel/intern/attribute_access.cc +++ b/source/blender/blenkernel/intern/attribute_access.cc @@ -40,13 +40,9 @@ namespace blender::bke { std::ostream &operator<<(std::ostream &stream, const AttributeIDRef &attribute_id) { - if (attribute_id.is_named()) { + if (attribute_id) { stream << attribute_id.name(); } - else if (attribute_id.is_anonymous()) { - const AnonymousAttributeID &anonymous_id = attribute_id.anonymous_id(); - stream << "<" << BKE_anonymous_attribute_id_debug_name(&anonymous_id) << ">"; - } else { stream << ""; } @@ -153,7 +149,7 @@ eAttrDomain attribute_domain_highest_priority(Span domains) static AttributeIDRef attribute_id_from_custom_data_layer(const CustomDataLayer &layer) { if (layer.anonymous_id != nullptr) { - return layer.anonymous_id; + return *layer.anonymous_id; } return layer.name; } @@ -207,7 +203,7 @@ static void *add_generic_custom_data_layer(CustomData &custom_data, const int domain_num, const AttributeIDRef &attribute_id) { - if (attribute_id.is_named()) { + if (!attribute_id.is_anonymous()) { char attribute_name_c[MAX_NAME]; attribute_id.name().copy(attribute_name_c); return CustomData_add_layer_named( @@ -261,9 +257,6 @@ static bool custom_data_layer_matches_attribute_id(const CustomDataLayer &layer, if (!attribute_id) { return false; } - if (attribute_id.is_anonymous()) { - return layer.anonymous_id == &attribute_id.anonymous_id(); - } return layer.name == attribute_id.name(); } @@ -451,14 +444,8 @@ GAttributeWriter CustomDataAttributeProvider::try_get_for_write( if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) { continue; } - if (attribute_id.is_named()) { - CustomData_duplicate_referenced_layer_named( - custom_data, layer.type, layer.name, element_num); - } - else { - CustomData_duplicate_referenced_layer_anonymous( - custom_data, layer.type, &attribute_id.anonymous_id(), element_num); - } + CustomData_duplicate_referenced_layer_named(custom_data, layer.type, layer.name, element_num); + const CPPType *type = custom_data_type_to_cpp_type((eCustomDataType)layer.type); if (type == nullptr) { continue; @@ -854,7 +841,7 @@ void MutableAttributeAccessor::remove_anonymous() } while (!anonymous_ids.is_empty()) { - this->remove(anonymous_ids.pop_last()); + this->remove(*anonymous_ids.pop_last()); } } @@ -883,12 +870,7 @@ GAttributeWriter MutableAttributeAccessor::lookup_for_write(const AttributeIDRef #ifdef DEBUG if (attribute) { auto checker = std::make_shared(); - if (attribute_id.is_named()) { - checker->name = attribute_id.name(); - } - else { - checker->name = BKE_anonymous_attribute_id_debug_name(&attribute_id.anonymous_id()); - } + checker->name = attribute_id.name(); checker->real_finish_fn = attribute.tag_modified_fn; attribute.tag_modified_fn = [checker]() { if (checker->real_finish_fn) { @@ -968,6 +950,7 @@ Vector retrieve_attributes_for_transfer( const bke::AttributeAccessor src_attributes, bke::MutableAttributeAccessor dst_attributes, const eAttrDomainMask domain_mask, + const AnonymousAttributePropagationInfo &propagation_info, const Set &skip) { Vector attributes; @@ -976,10 +959,10 @@ Vector retrieve_attributes_for_transfer( if (!(ATTR_DOMAIN_AS_MASK(meta_data.domain) & domain_mask)) { return true; } - if (id.is_named() && skip.contains(id.name())) { + if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) { return true; } - if (!id.should_be_kept()) { + if (skip.contains(id.name())) { return true; } @@ -999,6 +982,7 @@ void copy_attribute_domain(const AttributeAccessor src_attributes, MutableAttributeAccessor dst_attributes, const IndexMask selection, const eAttrDomain domain, + const AnonymousAttributePropagationInfo &propagation_info, const Set &skip) { src_attributes.for_all( @@ -1006,10 +990,10 @@ void copy_attribute_domain(const AttributeAccessor src_attributes, if (meta_data.domain != domain) { return true; } - if (id.is_named() && skip.contains(id.name())) { + if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) { return true; } - if (!id.should_be_kept()) { + if (skip.contains(id.name())) { return true; } diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh index 5ab7c6aadf3..ccf9ae83182 100644 --- a/source/blender/blenkernel/intern/attribute_access_intern.hh +++ b/source/blender/blenkernel/intern/attribute_access_intern.hh @@ -336,7 +336,7 @@ namespace attribute_accessor_functions { template inline bool is_builtin(const void * /*owner*/, const AttributeIDRef &attribute_id) { - if (!attribute_id.is_named()) { + if (attribute_id.is_anonymous()) { return false; } const StringRef name = attribute_id.name(); @@ -346,7 +346,7 @@ inline bool is_builtin(const void * /*owner*/, const AttributeIDRef &attribute_i template inline GAttributeReader lookup(const void *owner, const AttributeIDRef &attribute_id) { - if (attribute_id.is_named()) { + if (!attribute_id.is_anonymous()) { const StringRef name = attribute_id.name(); if (const BuiltinAttributeProvider *provider = providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) { @@ -396,7 +396,7 @@ template inline AttributeValidator lookup_validator(const void * /*owner*/, const blender::bke::AttributeIDRef &attribute_id) { - if (!attribute_id.is_named()) { + if (attribute_id.is_anonymous()) { return {}; } const BuiltinAttributeProvider *provider = @@ -443,7 +443,7 @@ inline std::optional lookup_meta_data(const void *owner, template inline GAttributeWriter lookup_for_write(void *owner, const AttributeIDRef &attribute_id) { - if (attribute_id.is_named()) { + if (!attribute_id.is_anonymous()) { const StringRef name = attribute_id.name(); if (const BuiltinAttributeProvider *provider = providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) { @@ -462,7 +462,7 @@ inline GAttributeWriter lookup_for_write(void *owner, const AttributeIDRef &attr template inline bool remove(void *owner, const AttributeIDRef &attribute_id) { - if (attribute_id.is_named()) { + if (!attribute_id.is_anonymous()) { const StringRef name = attribute_id.name(); if (const BuiltinAttributeProvider *provider = providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) { @@ -487,7 +487,7 @@ inline bool add(void *owner, if (contains(owner, attribute_id)) { return false; } - if (attribute_id.is_named()) { + if (!attribute_id.is_anonymous()) { const StringRef name = attribute_id.name(); if (const BuiltinAttributeProvider *provider = providers.builtin_attribute_providers().lookup_default_as(name, nullptr)) { diff --git a/source/blender/blenkernel/intern/cpp_types.cc b/source/blender/blenkernel/intern/cpp_types.cc index e6b33dd5b1a..d1f2c5efa55 100644 --- a/source/blender/blenkernel/intern/cpp_types.cc +++ b/source/blender/blenkernel/intern/cpp_types.cc @@ -28,6 +28,8 @@ BLI_CPP_TYPE_MAKE(Material *, CPPTypeFlags::BasicType) BLI_CPP_TYPE_MAKE(MStringProperty, CPPTypeFlags::None); +BLI_CPP_TYPE_MAKE(blender::bke::AnonymousAttributeSet, CPPTypeFlags::None); + void BKE_cpp_types_init() { blender::register_cpp_types(); @@ -45,4 +47,6 @@ void BKE_cpp_types_init() BLI_CPP_TYPE_REGISTER(Material *); BLI_CPP_TYPE_REGISTER(MStringProperty); + + BLI_CPP_TYPE_REGISTER(blender::bke::AnonymousAttributeSet); } diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc index e5cafd405df..14c41811505 100644 --- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -331,18 +331,19 @@ static eAttrDomain get_attribute_domain_for_mesh(const AttributeAccessor &mesh_a static bool should_add_attribute_to_mesh(const AttributeAccessor &curve_attributes, const AttributeAccessor &mesh_attributes, const AttributeIDRef &id, - const AttributeMetaData &meta_data) + const AttributeMetaData &meta_data, + const AnonymousAttributePropagationInfo &propagation_info) { /* The position attribute has special non-generic evaluation. */ - if (id.is_named() && id.name() == "position") { + if (id.name() == "position") { return false; } /* Don't propagate built-in curves attributes that are not built-in on meshes. */ if (curve_attributes.is_builtin(id) && !mesh_attributes.is_builtin(id)) { return false; } - if (!id.should_be_kept()) { + if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) { return false; } if (meta_data.data_type == CD_PROP_STRING) { @@ -629,7 +630,8 @@ static void copy_curve_domain_attribute_to_mesh(const ResultOffsets &mesh_offset Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, const CurvesGeometry &profile, - const bool fill_caps) + const bool fill_caps, + const AnonymousAttributePropagationInfo &propagation_info) { const CurvesInfo curves_info = get_curves_info(main, profile); @@ -716,7 +718,8 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, MutableAttributeAccessor mesh_attributes = mesh->attributes_for_write(); main_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) { - if (!should_add_attribute_to_mesh(main_attributes, mesh_attributes, id, meta_data)) { + if (!should_add_attribute_to_mesh( + main_attributes, mesh_attributes, id, meta_data, propagation_info)) { return true; } main_attributes_set.add_new(id); @@ -753,7 +756,8 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, if (main_attributes.contains(id)) { return true; } - if (!should_add_attribute_to_mesh(profile_attributes, mesh_attributes, id, meta_data)) { + if (!should_add_attribute_to_mesh( + profile_attributes, mesh_attributes, id, meta_data, propagation_info)) { return true; } const eAttrDomain src_domain = meta_data.domain; @@ -797,10 +801,11 @@ static CurvesGeometry get_curve_single_vert() return curves; } -Mesh *curve_to_wire_mesh(const CurvesGeometry &curve) +Mesh *curve_to_wire_mesh(const CurvesGeometry &curve, + const AnonymousAttributePropagationInfo &propagation_info) { static const CurvesGeometry vert_curve = get_curve_single_vert(); - return curve_to_mesh_sweep(curve, vert_curve, false); + return curve_to_mesh_sweep(curve, vert_curve, false, propagation_info); } } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index 401dd113f2c..835b6728e99 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -1047,8 +1047,10 @@ static Array build_point_to_curve_map(const CurvesGeometry &curves) return point_to_curve_map; } -static CurvesGeometry copy_with_removed_points(const CurvesGeometry &curves, - const IndexMask points_to_delete) +static CurvesGeometry copy_with_removed_points( + const CurvesGeometry &curves, + const IndexMask points_to_delete, + const AnonymousAttributePropagationInfo &propagation_info) { /* Use a map from points to curves to facilitate using an #IndexMask input. */ const Array point_to_curve_map = build_point_to_curve_map(curves); @@ -1093,9 +1095,15 @@ static CurvesGeometry copy_with_removed_points(const CurvesGeometry &curves, CurvesGeometry new_curves{new_point_count, new_curve_count}; Vector point_attributes = bke::retrieve_attributes_for_transfer( - curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_POINT); + curves.attributes(), + new_curves.attributes_for_write(), + ATTR_DOMAIN_MASK_POINT, + propagation_info); Vector curve_attributes = bke::retrieve_attributes_for_transfer( - curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_CURVE); + curves.attributes(), + new_curves.attributes_for_write(), + ATTR_DOMAIN_MASK_CURVE, + propagation_info); threading::parallel_invoke( 256 < new_point_count * new_curve_count, @@ -1144,7 +1152,8 @@ static CurvesGeometry copy_with_removed_points(const CurvesGeometry &curves, return new_curves; } -void CurvesGeometry::remove_points(const IndexMask points_to_delete) +void CurvesGeometry::remove_points(const IndexMask points_to_delete, + const AnonymousAttributePropagationInfo &propagation_info) { if (points_to_delete.is_empty()) { return; @@ -1152,11 +1161,13 @@ void CurvesGeometry::remove_points(const IndexMask points_to_delete) if (points_to_delete.size() == this->points_num()) { *this = {}; } - *this = copy_with_removed_points(*this, points_to_delete); + *this = copy_with_removed_points(*this, points_to_delete, propagation_info); } -static CurvesGeometry copy_with_removed_curves(const CurvesGeometry &curves, - const IndexMask curves_to_delete) +static CurvesGeometry copy_with_removed_curves( + const CurvesGeometry &curves, + const IndexMask curves_to_delete, + const AnonymousAttributePropagationInfo &propagation_info) { const Span old_offsets = curves.offsets(); const Vector old_curve_ranges = curves_to_delete.extract_ranges_invert( @@ -1178,9 +1189,15 @@ static CurvesGeometry copy_with_removed_curves(const CurvesGeometry &curves, CurvesGeometry new_curves{new_tot_points, new_tot_curves}; Vector point_attributes = bke::retrieve_attributes_for_transfer( - curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_POINT); + curves.attributes(), + new_curves.attributes_for_write(), + ATTR_DOMAIN_MASK_POINT, + propagation_info); Vector curve_attributes = bke::retrieve_attributes_for_transfer( - curves.attributes(), new_curves.attributes_for_write(), ATTR_DOMAIN_MASK_CURVE); + curves.attributes(), + new_curves.attributes_for_write(), + ATTR_DOMAIN_MASK_CURVE, + propagation_info); threading::parallel_invoke( 256 < new_tot_points * new_tot_curves, @@ -1251,7 +1268,8 @@ static CurvesGeometry copy_with_removed_curves(const CurvesGeometry &curves, return new_curves; } -void CurvesGeometry::remove_curves(const IndexMask curves_to_delete) +void CurvesGeometry::remove_curves(const IndexMask curves_to_delete, + const AnonymousAttributePropagationInfo &propagation_info) { if (curves_to_delete.is_empty()) { return; @@ -1260,7 +1278,7 @@ void CurvesGeometry::remove_curves(const IndexMask curves_to_delete) *this = {}; return; } - *this = copy_with_removed_curves(*this, curves_to_delete); + *this = copy_with_removed_curves(*this, curves_to_delete, propagation_info); } template @@ -1315,7 +1333,7 @@ void CurvesGeometry::reverse_curves(const IndexMask curves_to_reverse) if (meta_data.data_type == CD_PROP_STRING) { return true; } - if (id.is_named() && bezier_handle_names.contains(id.name())) { + if (bezier_handle_names.contains(id.name())) { return true; } diff --git a/source/blender/blenkernel/intern/customdata.cc b/source/blender/blenkernel/intern/customdata.cc index cb22f4a650c..60d38895a89 100644 --- a/source/blender/blenkernel/intern/customdata.cc +++ b/source/blender/blenkernel/intern/customdata.cc @@ -39,7 +39,7 @@ #include "BLT_translation.h" -#include "BKE_anonymous_attribute.h" +#include "BKE_anonymous_attribute_id.hh" #include "BKE_customdata.h" #include "BKE_customdata_file.h" #include "BKE_deform.h" @@ -2356,7 +2356,7 @@ bool CustomData_merge(const CustomData *source, layer->anonymous_id = nullptr; } else { - BKE_anonymous_attribute_id_increment_weak(layer->anonymous_id); + layer->anonymous_id->user_add(); } } if (alloctype == CD_ASSIGN) { @@ -2460,7 +2460,7 @@ static void customData_free_layer__internal(CustomDataLayer *layer, const int to const LayerTypeInfo *typeInfo; if (layer->anonymous_id != nullptr) { - BKE_anonymous_attribute_id_decrement_weak(layer->anonymous_id); + layer->anonymous_id->user_remove(); layer->anonymous_id = nullptr; } if (!(layer->flag & CD_FLAG_NOFREE) && layer->data) { @@ -2957,9 +2957,9 @@ void *CustomData_add_layer_anonymous(CustomData *data, const eCDAllocType alloctype, void *layerdata, const int totelem, - const AnonymousAttributeID *anonymous_id) + const AnonymousAttributeIDHandle *anonymous_id) { - const char *name = BKE_anonymous_attribute_id_internal_name(anonymous_id); + const char *name = anonymous_id->name().c_str(); CustomDataLayer *layer = customData_add_layer__internal( data, type, alloctype, layerdata, totelem, name); CustomData_update_typemap(data); @@ -2968,7 +2968,7 @@ void *CustomData_add_layer_anonymous(CustomData *data, return nullptr; } - BKE_anonymous_attribute_id_increment_weak(anonymous_id); + anonymous_id->user_add(); layer->anonymous_id = anonymous_id; return layer->data; } @@ -3147,20 +3147,6 @@ void *CustomData_duplicate_referenced_layer_named(CustomData *data, return customData_duplicate_referenced_layer_index(data, layer_index, totelem); } -void *CustomData_duplicate_referenced_layer_anonymous(CustomData *data, - const int /*type*/, - const AnonymousAttributeID *anonymous_id, - const int totelem) -{ - for (int i = 0; i < data->totlayer; i++) { - if (data->layers[i].anonymous_id == anonymous_id) { - return customData_duplicate_referenced_layer_index(data, i, totelem); - } - } - BLI_assert_unreachable(); - return nullptr; -} - void CustomData_duplicate_referenced_layers(CustomData *data, const int totelem) { for (int i = 0; i < data->totlayer; i++) { diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index b9702466d17..855d422251d 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -1055,7 +1055,7 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { GAttributeReader try_get_for_read(const void *owner, const AttributeIDRef &attribute_id) const final { - if (!attribute_id.is_named()) { + if (attribute_id.is_anonymous()) { return {}; } const Mesh *mesh = static_cast(owner); @@ -1079,7 +1079,7 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { GAttributeWriter try_get_for_write(void *owner, const AttributeIDRef &attribute_id) const final { - if (!attribute_id.is_named()) { + if (attribute_id.is_anonymous()) { return {}; } Mesh *mesh = static_cast(owner); @@ -1100,7 +1100,7 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider { bool try_delete(void *owner, const AttributeIDRef &attribute_id) const final { - if (!attribute_id.is_named()) { + if (attribute_id.is_anonymous()) { return false; } Mesh *mesh = static_cast(owner); diff --git a/source/blender/blenkernel/intern/geometry_fields.cc b/source/blender/blenkernel/intern/geometry_fields.cc index 82ffda57398..6fe822d6dc6 100644 --- a/source/blender/blenkernel/intern/geometry_fields.cc +++ b/source/blender/blenkernel/intern/geometry_fields.cc @@ -332,7 +332,7 @@ GVArray AnonymousAttributeFieldInput::get_varray_for_context(const GeometryField const IndexMask /*mask*/) const { const eCustomDataType data_type = cpp_type_to_custom_data_type(*type_); - return context.attributes()->lookup(anonymous_id_.get(), context.domain(), data_type); + return context.attributes()->lookup(*anonymous_id_, context.domain(), data_type); } std::string AnonymousAttributeFieldInput::socket_inspection_name() const @@ -363,8 +363,7 @@ std::optional AnonymousAttributeFieldInput::preferred_domain( if (!attributes.has_value()) { return std::nullopt; } - const std::optional meta_data = attributes->lookup_meta_data( - anonymous_id_.get()); + const std::optional meta_data = attributes->lookup_meta_data(*anonymous_id_); if (!meta_data.has_value()) { return std::nullopt; } diff --git a/source/blender/blenkernel/intern/geometry_set.cc b/source/blender/blenkernel/intern/geometry_set.cc index 1e03b8d235a..8fff80e709f 100644 --- a/source/blender/blenkernel/intern/geometry_set.cc +++ b/source/blender/blenkernel/intern/geometry_set.cc @@ -581,6 +581,7 @@ void GeometrySet::gather_attributes_for_propagation( const Span component_types, const GeometryComponentType dst_component_type, bool include_instances, + const blender::bke::AnonymousAttributePropagationInfo &propagation_info, blender::Map &r_attributes) const { using namespace blender; @@ -605,7 +606,8 @@ void GeometrySet::gather_attributes_for_propagation( /* Propagating string attributes is not supported yet. */ return; } - if (!attribute_id.should_be_kept()) { + if (attribute_id.is_anonymous() && + !propagation_info.propagate(attribute_id.anonymous_id())) { return; } diff --git a/source/blender/blenkernel/intern/instances.cc b/source/blender/blenkernel/intern/instances.cc index a2c344e918b..d98ad27f02c 100644 --- a/source/blender/blenkernel/intern/instances.cc +++ b/source/blender/blenkernel/intern/instances.cc @@ -105,7 +105,8 @@ blender::Span Instances::references() const return references_; } -void Instances::remove(const IndexMask mask) +void Instances::remove(const IndexMask mask, + const AnonymousAttributePropagationInfo &propagation_info) { using namespace blender; if (mask.is_range() && mask.as_range().start() == 0) { @@ -132,7 +133,7 @@ void Instances::remove(const IndexMask mask) src_attributes.foreach_attribute( [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData &meta_data) { - if (!id.should_be_kept()) { + if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) { return true; } diff --git a/source/blender/blenkernel/intern/mesh_convert.cc b/source/blender/blenkernel/intern/mesh_convert.cc index c59210d9d47..bbd70a9d152 100644 --- a/source/blender/blenkernel/intern/mesh_convert.cc +++ b/source/blender/blenkernel/intern/mesh_convert.cc @@ -839,7 +839,9 @@ static Mesh *mesh_new_from_evaluated_curve_type_object(const Object *evaluated_o return BKE_mesh_copy_for_eval(mesh, false); } if (const Curves *curves = get_evaluated_curves_from_object(evaluated_object)) { - return blender::bke::curve_to_wire_mesh(blender::bke::CurvesGeometry::wrap(curves->geometry)); + const blender::bke::AnonymousAttributePropagationInfo propagation_info; + return blender::bke::curve_to_wire_mesh(blender::bke::CurvesGeometry::wrap(curves->geometry), + propagation_info); } return nullptr; } diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index eeba322e30d..f72ba7a1378 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -207,6 +207,11 @@ static void ntree_copy_data(Main * /*bmain*/, ID *id_dst, const ID *id_src, cons dst_runtime.field_inferencing_interface = std::make_unique( *ntree_src->runtime->field_inferencing_interface); } + if (ntree_src->runtime->anonymous_attribute_relations) { + dst_runtime.anonymous_attribute_relations = + std::make_unique( + *ntree_src->runtime->anonymous_attribute_relations); + } if (flag & LIB_ID_COPY_NO_PREVIEW) { ntree_dst->preview = nullptr; diff --git a/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc b/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc new file mode 100644 index 00000000000..9ade129afb5 --- /dev/null +++ b/source/blender/blenkernel/intern/node_tree_anonymous_attributes.cc @@ -0,0 +1,452 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "NOD_node_declaration.hh" + +#include "BKE_node_runtime.hh" + +#include "BLI_multi_value_map.hh" +#include "BLI_resource_scope.hh" +#include "BLI_set.hh" +#include "BLI_stack.hh" +#include "BLI_timeit.hh" + +namespace blender::bke::anonymous_attribute_inferencing { +namespace aal = nodes::aal; +using nodes::NodeDeclaration; + +static const aal::RelationsInNode &get_relations_in_node(const bNode &node, ResourceScope &scope) +{ + if (node.is_group()) { + if (const bNodeTree *group = reinterpret_cast(node.id)) { + BLI_assert(group->runtime->anonymous_attribute_relations); + return *group->runtime->anonymous_attribute_relations; + } + } + if (const NodeDeclaration *node_decl = node.declaration()) { + if (const aal::RelationsInNode *relations = node_decl->anonymous_attribute_relations()) { + return *relations; + } + } + return scope.construct(); +} + +Array get_relations_by_node(const bNodeTree &tree, + ResourceScope &scope) +{ + const Span nodes = tree.all_nodes(); + Array relations_by_node(nodes.size()); + for (const int i : nodes.index_range()) { + relations_by_node[i] = &get_relations_in_node(*nodes[i], scope); + } + return relations_by_node; +} + +static bool socket_is_field(const bNodeSocket &socket) +{ + return socket.display_shape == SOCK_DISPLAY_SHAPE_DIAMOND; +} + +/** + * Start at a group output socket and find all linked group inputs. + */ +static Vector find_linked_group_inputs( + const bNodeTree &tree, + const bNodeSocket &group_output_socket, + const FunctionRef(const bNodeSocket &)> get_linked_node_inputs) +{ + Set found_sockets; + Stack sockets_to_check; + + Vector input_indices; + + found_sockets.add_new(&group_output_socket); + sockets_to_check.push(&group_output_socket); + + while (!sockets_to_check.is_empty()) { + const bNodeSocket &socket = *sockets_to_check.pop(); + if (socket.is_input()) { + for (const bNodeLink *link : socket.directly_linked_links()) { + if (link->is_used()) { + const bNodeSocket &from_socket = *link->fromsock; + if (found_sockets.add(&from_socket)) { + sockets_to_check.push(&from_socket); + } + } + } + } + else { + const bNode &node = socket.owner_node(); + for (const int input_index : get_linked_node_inputs(socket)) { + const bNodeSocket &input_socket = node.input_socket(input_index); + if (input_socket.is_available()) { + if (found_sockets.add(&input_socket)) { + sockets_to_check.push(&input_socket); + } + } + } + } + } + + for (const bNode *node : tree.group_input_nodes()) { + for (const bNodeSocket *socket : node->output_sockets()) { + if (found_sockets.contains(socket)) { + input_indices.append_non_duplicates(socket->index()); + } + } + } + + return input_indices; +} + +static void infer_propagate_relations(const bNodeTree &tree, + const Span relations_by_node, + const bNode &group_output_node, + aal::RelationsInNode &r_relations) +{ + for (const bNodeSocket *group_output_socket : group_output_node.input_sockets().drop_back(1)) { + if (group_output_socket->type != SOCK_GEOMETRY) { + continue; + } + const Vector input_indices = find_linked_group_inputs( + tree, *group_output_socket, [&](const bNodeSocket &output_socket) { + Vector indices; + for (const aal::PropagateRelation &relation : + relations_by_node[output_socket.owner_node().index()]->propagate_relations) { + if (relation.to_geometry_output == output_socket.index()) { + indices.append(relation.from_geometry_input); + } + } + return indices; + }); + for (const int input_index : input_indices) { + aal::PropagateRelation relation; + relation.from_geometry_input = input_index; + relation.to_geometry_output = group_output_socket->index(); + r_relations.propagate_relations.append(relation); + } + } +} + +static void infer_reference_relations(const bNodeTree &tree, + const Span relations_by_node, + const bNode &group_output_node, + aal::RelationsInNode &r_relations) +{ + for (const bNodeSocket *group_output_socket : group_output_node.input_sockets().drop_back(1)) { + if (!socket_is_field(*group_output_socket)) { + continue; + } + const Vector input_indices = find_linked_group_inputs( + tree, *group_output_socket, [&](const bNodeSocket &output_socket) { + Vector indices; + for (const aal::ReferenceRelation &relation : + relations_by_node[output_socket.owner_node().index()]->reference_relations) { + if (relation.to_field_output == output_socket.index()) { + indices.append(relation.from_field_input); + } + } + return indices; + }); + for (const int input_index : input_indices) { + if (tree.runtime->field_inferencing_interface->inputs[input_index] != + nodes::InputSocketFieldType::None) { + aal::ReferenceRelation relation; + relation.from_field_input = input_index; + relation.to_field_output = group_output_socket->index(); + r_relations.reference_relations.append(relation); + } + } + } +} + +/** + * Find group output geometries that contain anonymous attributes referenced by the field. + * If `nullopt` is returned, the field does not depend on any anonymous attribute created in this + * node tree. + */ +static std::optional> find_available_on_outputs( + const bNodeSocket &initial_group_output_socket, + const bNode &group_output_node, + const Span relations_by_node) +{ + Set geometry_sockets; + + { + /* Find the nodes that added anonymous attributes to the field. */ + Set found_sockets; + Stack sockets_to_check; + + found_sockets.add_new(&initial_group_output_socket); + sockets_to_check.push(&initial_group_output_socket); + + while (!sockets_to_check.is_empty()) { + const bNodeSocket &socket = *sockets_to_check.pop(); + if (socket.is_input()) { + for (const bNodeLink *link : socket.directly_linked_links()) { + if (link->is_used()) { + const bNodeSocket &from_socket = *link->fromsock; + if (found_sockets.add(&from_socket)) { + sockets_to_check.push(&from_socket); + } + } + } + } + else { + const bNode &node = socket.owner_node(); + const aal::RelationsInNode &relations = *relations_by_node[node.index()]; + for (const aal::AvailableRelation &relation : relations.available_relations) { + if (socket.index() == relation.field_output) { + const bNodeSocket &geometry_output = node.output_socket(relation.geometry_output); + if (geometry_output.is_available()) { + geometry_sockets.add(&geometry_output); + } + } + } + for (const aal::ReferenceRelation &relation : relations.reference_relations) { + if (socket.index() == relation.to_field_output) { + const bNodeSocket &field_input = node.input_socket(relation.from_field_input); + if (field_input.is_available()) { + if (found_sockets.add(&field_input)) { + sockets_to_check.push(&field_input); + } + } + } + } + } + } + } + + if (geometry_sockets.is_empty()) { + /* The field does not depend on any anonymous attribute created within this node tree. */ + return std::nullopt; + } + + /* Find the group output geometries that contain the anonymous attribute referenced by the field + * output. */ + Set found_sockets; + Stack sockets_to_check; + + for (const bNodeSocket *socket : geometry_sockets) { + found_sockets.add_new(socket); + sockets_to_check.push(socket); + } + + while (!sockets_to_check.is_empty()) { + const bNodeSocket &socket = *sockets_to_check.pop(); + if (socket.is_input()) { + const bNode &node = socket.owner_node(); + const aal::RelationsInNode &relations = *relations_by_node[node.index()]; + for (const aal::PropagateRelation &relation : relations.propagate_relations) { + if (socket.index() == relation.from_geometry_input) { + const bNodeSocket &output_socket = node.output_socket(relation.to_geometry_output); + if (output_socket.is_available()) { + if (found_sockets.add(&output_socket)) { + sockets_to_check.push(&output_socket); + } + } + } + } + } + else { + for (const bNodeLink *link : socket.directly_linked_links()) { + if (link->is_used()) { + const bNodeSocket &to_socket = *link->tosock; + if (found_sockets.add(&to_socket)) { + sockets_to_check.push(&to_socket); + } + } + } + } + } + + Vector output_indices; + for (const bNodeSocket *socket : group_output_node.input_sockets().drop_back(1)) { + if (found_sockets.contains(socket)) { + output_indices.append(socket->index()); + } + } + return output_indices; +} + +static void infer_available_relations(const Span relations_by_node, + const bNode &group_output_node, + aal::RelationsInNode &r_relations) +{ + for (const bNodeSocket *group_output_socket : group_output_node.input_sockets().drop_back(1)) { + if (!socket_is_field(*group_output_socket)) { + continue; + } + const std::optional> output_indices = find_available_on_outputs( + *group_output_socket, group_output_node, relations_by_node); + if (output_indices.has_value()) { + if (output_indices->is_empty()) { + r_relations.available_on_none.append(group_output_socket->index()); + } + else { + for (const int output_index : *output_indices) { + aal::AvailableRelation relation; + relation.field_output = group_output_socket->index(); + relation.geometry_output = output_index; + r_relations.available_relations.append(relation); + } + } + } + } +} + +/** + * Returns a list of all the geometry inputs that the field input may be evaluated on. + */ +static Vector find_eval_on_inputs(const bNodeTree &tree, + const int field_input_index, + const Span relations_by_node) +{ + const Span group_input_nodes = tree.group_input_nodes(); + Set geometry_sockets; + + { + /* Find all the nodes that evaluate the input field. */ + Set found_sockets; + Stack sockets_to_check; + + for (const bNode *node : group_input_nodes) { + const bNodeSocket &socket = node->output_socket(field_input_index); + found_sockets.add_new(&socket); + sockets_to_check.push(&socket); + } + + while (!sockets_to_check.is_empty()) { + const bNodeSocket &socket = *sockets_to_check.pop(); + if (socket.is_input()) { + const bNode &node = socket.owner_node(); + const aal::RelationsInNode &relations = *relations_by_node[node.index()]; + for (const aal::EvalRelation &relation : relations.eval_relations) { + if (socket.index() == relation.field_input) { + const bNodeSocket &geometry_input = node.input_socket(relation.geometry_input); + if (geometry_input.is_available()) { + geometry_sockets.add(&geometry_input); + } + } + } + for (const aal::ReferenceRelation &relation : relations.reference_relations) { + if (socket.index() == relation.from_field_input) { + const bNodeSocket &field_output = node.output_socket(relation.to_field_output); + if (field_output.is_available()) { + if (found_sockets.add(&field_output)) { + sockets_to_check.push(&field_output); + } + } + } + } + } + else { + for (const bNodeLink *link : socket.directly_linked_links()) { + if (link->is_used()) { + const bNodeSocket &to_socket = *link->tosock; + if (found_sockets.add(&to_socket)) { + sockets_to_check.push(&to_socket); + } + } + } + } + } + } + + if (geometry_sockets.is_empty()) { + return {}; + } + + /* Find the group input geometries whose attributes are propagated to the nodes that evaluate the + * field. */ + Set found_sockets; + Stack sockets_to_check; + + Vector geometry_input_indices; + + for (const bNodeSocket *socket : geometry_sockets) { + found_sockets.add_new(socket); + sockets_to_check.push(socket); + } + + while (!sockets_to_check.is_empty()) { + const bNodeSocket &socket = *sockets_to_check.pop(); + if (socket.is_input()) { + for (const bNodeLink *link : socket.directly_linked_links()) { + if (link->is_used()) { + const bNodeSocket &from_socket = *link->fromsock; + if (found_sockets.add(&from_socket)) { + sockets_to_check.push(&from_socket); + } + } + } + } + else { + const bNode &node = socket.owner_node(); + if (node.is_group_input()) { + geometry_input_indices.append_non_duplicates(socket.index()); + } + else { + const aal::RelationsInNode &relations = *relations_by_node[node.index()]; + for (const aal::PropagateRelation &relation : relations.propagate_relations) { + if (socket.index() == relation.to_geometry_output) { + const bNodeSocket &input_socket = node.input_socket(relation.from_geometry_input); + if (input_socket.is_available()) { + if (found_sockets.add(&input_socket)) { + sockets_to_check.push(&input_socket); + } + } + } + } + } + } + } + + return geometry_input_indices; +} + +static void infer_eval_relations(const bNodeTree &tree, + const Span relations_by_node, + aal::RelationsInNode &r_relations) +{ + for (const int input_index : tree.interface_inputs().index_range()) { + if (tree.runtime->field_inferencing_interface->inputs[input_index] == + nodes::InputSocketFieldType::None) { + continue; + } + const Vector geometry_input_indices = find_eval_on_inputs( + tree, input_index, relations_by_node); + for (const int geometry_input : geometry_input_indices) { + aal::EvalRelation relation; + relation.field_input = input_index; + relation.geometry_input = geometry_input; + r_relations.eval_relations.append(std::move(relation)); + } + } +} + +bool update_anonymous_attribute_relations(bNodeTree &tree) +{ + tree.ensure_topology_cache(); + + ResourceScope scope; + Array relations_by_node = get_relations_by_node(tree, scope); + + std::unique_ptr new_relations = std::make_unique(); + if (!tree.has_available_link_cycle()) { + if (const bNode *group_output_node = tree.group_output_node()) { + infer_propagate_relations(tree, relations_by_node, *group_output_node, *new_relations); + infer_reference_relations(tree, relations_by_node, *group_output_node, *new_relations); + infer_available_relations(relations_by_node, *group_output_node, *new_relations); + } + infer_eval_relations(tree, relations_by_node, *new_relations); + } + + const bool group_interface_changed = !tree.runtime->anonymous_attribute_relations || + *tree.runtime->anonymous_attribute_relations != + *new_relations; + tree.runtime->anonymous_attribute_relations = std::move(new_relations); + + return group_interface_changed; +} + +} // namespace blender::bke::anonymous_attribute_inferencing diff --git a/source/blender/blenkernel/intern/node_tree_update.cc b/source/blender/blenkernel/intern/node_tree_update.cc index d01cbd1d62d..93e8f24787a 100644 --- a/source/blender/blenkernel/intern/node_tree_update.cc +++ b/source/blender/blenkernel/intern/node_tree_update.cc @@ -473,6 +473,9 @@ class NodeTreeMainUpdater { if (node_field_inferencing::update_field_inferencing(ntree)) { result.interface_changed = true; } + if (anonymous_attribute_inferencing::update_anonymous_attribute_relations(ntree)) { + result.interface_changed = true; + } } result.output_changed = this->check_if_output_changed(ntree); diff --git a/source/blender/editors/mesh/mesh_data.cc b/source/blender/editors/mesh/mesh_data.cc index a2310dddc61..204fb7012ab 100644 --- a/source/blender/editors/mesh/mesh_data.cc +++ b/source/blender/editors/mesh/mesh_data.cc @@ -1484,5 +1484,6 @@ void ED_mesh_split_faces(Mesh *mesh) return; } - geometry::split_edges(*mesh, split_mask); + const bke::AnonymousAttributePropagationInfo propagation_info; + geometry::split_edges(*mesh, split_mask, propagation_info); } diff --git a/source/blender/functions/FN_lazy_function_graph.hh b/source/blender/functions/FN_lazy_function_graph.hh index 460e858774f..6d66afafe82 100644 --- a/source/blender/functions/FN_lazy_function_graph.hh +++ b/source/blender/functions/FN_lazy_function_graph.hh @@ -53,6 +53,10 @@ class Socket : NonCopyable, NonMovable { * Index of the socket. E.g. 0 for the first input and the first output socket. */ int index_in_node_; + /** + * Index of the socket in the entire graph. Every socket has a different index. + */ + int index_in_graph_; friend Graph; @@ -61,6 +65,7 @@ class Socket : NonCopyable, NonMovable { bool is_output() const; int index() const; + int index_in_graph() const; InputSocket &as_input(); OutputSocket &as_output(); @@ -179,6 +184,20 @@ class DummyDebugInfo { virtual std::string output_name(const int i) const; }; +/** + * Just stores a string per socket in a dummy node. + */ +class SimpleDummyDebugInfo : public DummyDebugInfo { + public: + std::string name; + Vector input_names; + Vector output_names; + + std::string node_name() const override; + std::string input_name(const int i) const override; + std::string output_name(const int i) const override; +}; + /** * A #Node that does *not* correspond to a #LazyFunction. Instead it can be used to indicate inputs * and outputs of the entire graph. It can have an arbitrary number of inputs and outputs. @@ -205,6 +224,11 @@ class Graph : NonCopyable, NonMovable { * Contains all nodes in the graph so that it is efficient to iterate over them. */ Vector nodes_; + /** + * Number of sockets in the graph. Can be used as array size when indexing using + * `Socket::index_in_graph`. + */ + int socket_num_ = 0; public: ~Graph(); @@ -213,6 +237,7 @@ class Graph : NonCopyable, NonMovable { * Get all nodes in the graph. The index in the span corresponds to #Node::index_in_graph. */ Span nodes() const; + Span nodes(); /** * Add a new function node with sockets that match the passed in #LazyFunction. @@ -232,10 +257,24 @@ class Graph : NonCopyable, NonMovable { */ void add_link(OutputSocket &from, InputSocket &to); + /** + * If the socket is linked, remove the link. + */ + void clear_origin(InputSocket &socket); + /** * Make sure that #Node::index_in_graph is up to date. */ void update_node_indices(); + /** + * Make sure that #Socket::index_in_graph is up to date. + */ + void update_socket_indices(); + + /** + * Number of sockets in the graph. + */ + int socket_num() const; /** * Can be used to assert that #update_node_indices has been called. @@ -280,6 +319,11 @@ inline int Socket::index() const return index_in_node_; } +inline int Socket::index_in_graph() const +{ + return index_in_graph_; +} + inline InputSocket &Socket::as_input() { BLI_assert(this->is_input()); @@ -445,6 +489,16 @@ inline Span Graph::nodes() const return nodes_; } +inline Span Graph::nodes() +{ + return nodes_; +} + +inline int Graph::socket_num() const +{ + return socket_num_; +} + /** \} */ } // namespace blender::fn::lazy_function diff --git a/source/blender/functions/intern/lazy_function_graph.cc b/source/blender/functions/intern/lazy_function_graph.cc index e8a20fbf9a3..e07cce7204b 100644 --- a/source/blender/functions/intern/lazy_function_graph.cc +++ b/source/blender/functions/intern/lazy_function_graph.cc @@ -86,6 +86,14 @@ void Graph::add_link(OutputSocket &from, InputSocket &to) from.targets_.append(&to); } +void Graph::clear_origin(InputSocket &socket) +{ + if (socket.origin_ != nullptr) { + socket.origin_->targets_.remove_first_occurrence_and_reorder(&socket); + socket.origin_ = nullptr; + } +} + void Graph::update_node_indices() { for (const int i : nodes_.index_range()) { @@ -93,6 +101,20 @@ void Graph::update_node_indices() } } +void Graph::update_socket_indices() +{ + int socket_counter = 0; + for (const int i : nodes_.index_range()) { + for (InputSocket *socket : nodes_[i]->inputs()) { + socket->index_in_graph_ = socket_counter++; + } + for (OutputSocket *socket : nodes_[i]->outputs()) { + socket->index_in_graph_ = socket_counter++; + } + } + socket_num_ = socket_counter; +} + bool Graph::node_indices_are_valid() const { for (const int i : nodes_.index_range()) { @@ -152,6 +174,21 @@ std::string DummyDebugInfo::output_name(const int /*i*/) const return fallback_name; } +std::string SimpleDummyDebugInfo::node_name() const +{ + return this->name; +} + +std::string SimpleDummyDebugInfo::input_name(const int i) const +{ + return this->input_names[i]; +} + +std::string SimpleDummyDebugInfo::output_name(const int i) const +{ + return this->output_names[i]; +} + std::string Graph::ToDotOptions::socket_name(const Socket &socket) const { return socket.name(); diff --git a/source/blender/geometry/GEO_fillet_curves.hh b/source/blender/geometry/GEO_fillet_curves.hh index 1f832f8b6cc..8019e6bf7ed 100644 --- a/source/blender/geometry/GEO_fillet_curves.hh +++ b/source/blender/geometry/GEO_fillet_curves.hh @@ -9,15 +9,19 @@ namespace blender::geometry { -bke::CurvesGeometry fillet_curves_poly(const bke::CurvesGeometry &src_curves, - IndexMask curve_selection, - const VArray &radius, - const VArray &counts, - bool limit_radius); +bke::CurvesGeometry fillet_curves_poly( + const bke::CurvesGeometry &src_curves, + IndexMask curve_selection, + const VArray &radius, + const VArray &counts, + bool limit_radius, + const bke::AnonymousAttributePropagationInfo &propagation_info); -bke::CurvesGeometry fillet_curves_bezier(const bke::CurvesGeometry &src_curves, - IndexMask curve_selection, - const VArray &radius, - bool limit_radius); +bke::CurvesGeometry fillet_curves_bezier( + const bke::CurvesGeometry &src_curves, + IndexMask curve_selection, + const VArray &radius, + bool limit_radius, + const bke::AnonymousAttributePropagationInfo &propagation_info); } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_mesh_split_edges.hh b/source/blender/geometry/GEO_mesh_split_edges.hh index f104a55ae4d..cb0e3de8bd1 100644 --- a/source/blender/geometry/GEO_mesh_split_edges.hh +++ b/source/blender/geometry/GEO_mesh_split_edges.hh @@ -4,10 +4,14 @@ #include "BLI_index_mask.hh" +#include "BKE_attribute.hh" + struct Mesh; namespace blender::geometry { -void split_edges(Mesh &mesh, IndexMask mask); +void split_edges(Mesh &mesh, + IndexMask mask, + const bke::AnonymousAttributePropagationInfo &propagation_info); } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_mesh_to_curve.hh b/source/blender/geometry/GEO_mesh_to_curve.hh index b0f24853dbc..17eb311ccc2 100644 --- a/source/blender/geometry/GEO_mesh_to_curve.hh +++ b/source/blender/geometry/GEO_mesh_to_curve.hh @@ -19,11 +19,16 @@ namespace blender::geometry { * intersections of more than three edges will become breaks in curves. Attributes that * are not built-in on meshes and not curves are transferred to the result curve. */ -bke::CurvesGeometry mesh_to_curve_convert(const Mesh &mesh, const IndexMask selection); +bke::CurvesGeometry mesh_to_curve_convert( + const Mesh &mesh, + const IndexMask selection, + const bke::AnonymousAttributePropagationInfo &propagation_info); -bke::CurvesGeometry create_curve_from_vert_indices(const Mesh &mesh, - Span vert_indices, - Span curve_offsets, - IndexRange cyclic_curves); +bke::CurvesGeometry create_curve_from_vert_indices( + const Mesh &mesh, + Span vert_indices, + Span curve_offsets, + IndexRange cyclic_curves, + const bke::AnonymousAttributePropagationInfo &propagation_info); } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_point_merge_by_distance.hh b/source/blender/geometry/GEO_point_merge_by_distance.hh index 92d871c62ab..054ecffd358 100644 --- a/source/blender/geometry/GEO_point_merge_by_distance.hh +++ b/source/blender/geometry/GEO_point_merge_by_distance.hh @@ -2,6 +2,8 @@ #include "BLI_index_mask.hh" +#include "BKE_attribute.hh" + #pragma once struct PointCloud; @@ -17,8 +19,10 @@ namespace blender::geometry { * Merge selected points into other selected points within the \a merge_distance. The merged * indices favor speed over accuracy, since the results will depend on the order of the points. */ -PointCloud *point_merge_by_distance(const PointCloud &src_points, - const float merge_distance, - const IndexMask selection); +PointCloud *point_merge_by_distance( + const PointCloud &src_points, + const float merge_distance, + const IndexMask selection, + const bke::AnonymousAttributePropagationInfo &propagation_info); } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_realize_instances.hh b/source/blender/geometry/GEO_realize_instances.hh index 9c46de6fdca..b13d764c685 100644 --- a/source/blender/geometry/GEO_realize_instances.hh +++ b/source/blender/geometry/GEO_realize_instances.hh @@ -19,6 +19,8 @@ struct RealizeInstancesOptions { * instances. Otherwise, instance attributes are ignored. */ bool realize_instance_attributes = true; + + bke::AnonymousAttributePropagationInfo propagation_info; }; /** diff --git a/source/blender/geometry/GEO_resample_curves.hh b/source/blender/geometry/GEO_resample_curves.hh index 35365167eba..fd1e6b7dad3 100644 --- a/source/blender/geometry/GEO_resample_curves.hh +++ b/source/blender/geometry/GEO_resample_curves.hh @@ -4,7 +4,7 @@ #include "FN_field.hh" -#include "BKE_anonymous_attribute.hh" +#include "BKE_anonymous_attribute_id.hh" #include "BKE_curves.hh" namespace blender::geometry { diff --git a/source/blender/geometry/GEO_set_curve_type.hh b/source/blender/geometry/GEO_set_curve_type.hh index f38e63b1fc8..3860664c1c5 100644 --- a/source/blender/geometry/GEO_set_curve_type.hh +++ b/source/blender/geometry/GEO_set_curve_type.hh @@ -27,6 +27,7 @@ bool try_curves_conversion_in_place(IndexMask selection, */ bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves, IndexMask selection, - CurveType dst_type); + CurveType dst_type, + const bke::AnonymousAttributePropagationInfo &propagation_info); } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_subdivide_curves.hh b/source/blender/geometry/GEO_subdivide_curves.hh index ba55118baa4..46e63839056 100644 --- a/source/blender/geometry/GEO_subdivide_curves.hh +++ b/source/blender/geometry/GEO_subdivide_curves.hh @@ -17,8 +17,10 @@ namespace blender::geometry { * * \param selection: A selection of curves to consider when subdividing. */ -bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, - IndexMask selection, - const VArray &cuts); +bke::CurvesGeometry subdivide_curves( + const bke::CurvesGeometry &src_curves, + IndexMask selection, + const VArray &cuts, + const bke::AnonymousAttributePropagationInfo &propagation_info); } // namespace blender::geometry diff --git a/source/blender/geometry/GEO_trim_curves.hh b/source/blender/geometry/GEO_trim_curves.hh index 197ef79c25b..1cae65875e2 100644 --- a/source/blender/geometry/GEO_trim_curves.hh +++ b/source/blender/geometry/GEO_trim_curves.hh @@ -19,6 +19,7 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, IndexMask selection, const VArray &starts, const VArray &ends, - GeometryNodeCurveSampleMode mode); + GeometryNodeCurveSampleMode mode, + const bke::AnonymousAttributePropagationInfo &propagation_info); } // namespace blender::geometry diff --git a/source/blender/geometry/intern/add_curves_on_mesh.cc b/source/blender/geometry/intern/add_curves_on_mesh.cc index 0e9b89b35b4..55f0398fd3c 100644 --- a/source/blender/geometry/intern/add_curves_on_mesh.cc +++ b/source/blender/geometry/intern/add_curves_on_mesh.cc @@ -372,7 +372,7 @@ AddCurvesOnMeshOutputs add_curves_on_mesh(CurvesGeometry &curves, Set attributes_to_skip{{"position", "curve_type", "surface_uv_coordinate"}}; attributes.for_all( [&](const bke::AttributeIDRef &id, const bke::AttributeMetaData /*meta_data*/) { - if (id.is_named() && attributes_to_skip.contains(id.name())) { + if (attributes_to_skip.contains(id.name())) { return true; } bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id); diff --git a/source/blender/geometry/intern/fillet_curves.cc b/source/blender/geometry/intern/fillet_curves.cc index cb513641a5f..5e4f2e35c67 100644 --- a/source/blender/geometry/intern/fillet_curves.cc +++ b/source/blender/geometry/intern/fillet_curves.cc @@ -397,12 +397,14 @@ static void calculate_bezier_handles_poly_mode(const Span src_handles_l, }); } -static bke::CurvesGeometry fillet_curves(const bke::CurvesGeometry &src_curves, - const IndexMask curve_selection, - const VArray &radius_input, - const VArray &counts, - const bool limit_radius, - const bool use_bezier_mode) +static bke::CurvesGeometry fillet_curves( + const bke::CurvesGeometry &src_curves, + const IndexMask curve_selection, + const VArray &radius_input, + const VArray &counts, + const bool limit_radius, + const bool use_bezier_mode, + const bke::AnonymousAttributePropagationInfo &propagation_info) { const Vector unselected_ranges = curve_selection.extract_ranges_invert( src_curves.curves_range()); @@ -520,6 +522,7 @@ static bke::CurvesGeometry fillet_curves(const bke::CurvesGeometry &src_curves, src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, + propagation_info, {"position", "handle_type_left", "handle_type_right", "handle_right", "handle_left"})) { duplicate_fillet_point_data( src_curves, dst_curves, curve_selection, point_offsets, attribute.src, attribute.dst.span); @@ -528,7 +531,7 @@ static bke::CurvesGeometry fillet_curves(const bke::CurvesGeometry &src_curves, if (!unselected_ranges.is_empty()) { for (auto &attribute : bke::retrieve_attributes_for_transfer( - src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info)) { bke::curves::copy_point_data( src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.span); attribute.dst.finish(); @@ -538,26 +541,32 @@ static bke::CurvesGeometry fillet_curves(const bke::CurvesGeometry &src_curves, return dst_curves; } -bke::CurvesGeometry fillet_curves_poly(const bke::CurvesGeometry &src_curves, - const IndexMask curve_selection, - const VArray &radius, - const VArray &count, - const bool limit_radius) +bke::CurvesGeometry fillet_curves_poly( + const bke::CurvesGeometry &src_curves, + const IndexMask curve_selection, + const VArray &radius, + const VArray &count, + const bool limit_radius, + const bke::AnonymousAttributePropagationInfo &propagation_info) { - return fillet_curves(src_curves, curve_selection, radius, count, limit_radius, false); + return fillet_curves( + src_curves, curve_selection, radius, count, limit_radius, false, propagation_info); } -bke::CurvesGeometry fillet_curves_bezier(const bke::CurvesGeometry &src_curves, - const IndexMask curve_selection, - const VArray &radius, - const bool limit_radius) +bke::CurvesGeometry fillet_curves_bezier( + const bke::CurvesGeometry &src_curves, + const IndexMask curve_selection, + const VArray &radius, + const bool limit_radius, + const bke::AnonymousAttributePropagationInfo &propagation_info) { return fillet_curves(src_curves, curve_selection, radius, VArray::ForSingle(1, src_curves.points_num()), limit_radius, - true); + true, + propagation_info); } } // namespace blender::geometry diff --git a/source/blender/geometry/intern/mesh_split_edges.cc b/source/blender/geometry/intern/mesh_split_edges.cc index 19db9cbfc03..5cf7d4bb935 100644 --- a/source/blender/geometry/intern/mesh_split_edges.cc +++ b/source/blender/geometry/intern/mesh_split_edges.cc @@ -3,6 +3,7 @@ #include "BLI_array_utils.hh" #include "BLI_devirtualize_parameters.hh" #include "BLI_index_mask.hh" +#include "BLI_user_counter.hh" #include "BKE_attribute.hh" #include "BKE_attribute_math.hh" @@ -59,35 +60,36 @@ static void add_new_vertices(Mesh &mesh, const Span new_to_old_verts_map) static void add_new_edges(Mesh &mesh, const Span new_edges, - const Span new_to_old_edges_map) + const Span new_to_old_edges_map, + const bke::AnonymousAttributePropagationInfo &propagation_info) { bke::MutableAttributeAccessor attributes = mesh.attributes_for_write(); /* Store a copy of the IDs locally since we will remove the existing attributes which * can also free the names, since the API does not provide pointer stability. */ Vector named_ids; - Vector anonymous_ids; + Vector> anonymous_ids; for (const bke::AttributeIDRef &id : attributes.all_ids()) { if (attributes.lookup_meta_data(id)->domain != ATTR_DOMAIN_EDGE) { continue; } - if (!id.should_be_kept()) { + if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) { continue; } - if (id.is_named()) { + if (!id.is_anonymous()) { named_ids.append(id.name()); } else { - anonymous_ids.append(bke::WeakAnonymousAttributeID(&id.anonymous_id())); - BKE_anonymous_attribute_id_increment_weak(&id.anonymous_id()); + anonymous_ids.append(&id.anonymous_id()); + id.anonymous_id().user_add(); } } Vector local_edge_ids; for (const StringRef name : named_ids) { local_edge_ids.append(name); } - for (const bke::WeakAnonymousAttributeID &id : anonymous_ids) { - local_edge_ids.append(id.get()); + for (const UserCounter &id : anonymous_ids) { + local_edge_ids.append(*id); } /* Build new arrays for the copied edge attributes. Unlike vertices, new edges aren't all at the @@ -348,7 +350,9 @@ static void split_edge_per_poly(const int edge_i, edge_to_loop_map[edge_i].resize(1); } -void split_edges(Mesh &mesh, const IndexMask mask) +void split_edges(Mesh &mesh, + const IndexMask mask, + const bke::AnonymousAttributePropagationInfo &propagation_info) { /* Flag vertices that need to be split. */ Array should_split_vert(mesh.totvert, false); @@ -483,7 +487,7 @@ void split_edges(Mesh &mesh, const IndexMask mask) /* Step 5: Resize the mesh to add the new vertices and rebuild the edges. */ add_new_vertices(mesh, new_to_old_verts_map); - add_new_edges(mesh, new_edges, new_to_old_edges_map); + add_new_edges(mesh, new_edges, new_to_old_edges_map, propagation_info); BKE_mesh_tag_edges_split(&mesh); } diff --git a/source/blender/geometry/intern/mesh_to_curve_convert.cc b/source/blender/geometry/intern/mesh_to_curve_convert.cc index c2a9b16c8b6..8047f1dc312 100644 --- a/source/blender/geometry/intern/mesh_to_curve_convert.cc +++ b/source/blender/geometry/intern/mesh_to_curve_convert.cc @@ -19,10 +19,12 @@ namespace blender::geometry { -bke::CurvesGeometry create_curve_from_vert_indices(const Mesh &mesh, - const Span vert_indices, - const Span curve_offsets, - const IndexRange cyclic_curves) +bke::CurvesGeometry create_curve_from_vert_indices( + const Mesh &mesh, + const Span vert_indices, + const Span curve_offsets, + const IndexRange cyclic_curves, + const bke::AnonymousAttributePropagationInfo &propagation_info) { bke::CurvesGeometry curves(vert_indices.size(), curve_offsets.size()); curves.offsets_for_write().drop_back(1).copy_from(curve_offsets); @@ -43,7 +45,7 @@ bke::CurvesGeometry create_curve_from_vert_indices(const Mesh &mesh, continue; } - if (!attribute_id.should_be_kept()) { + if (attribute_id.is_anonymous() && !propagation_info.propagate(attribute_id.anonymous_id())) { continue; } @@ -209,14 +211,17 @@ static Vector> get_selected_edges(const Mesh &mesh, const In return selected_edges; } -bke::CurvesGeometry mesh_to_curve_convert(const Mesh &mesh, const IndexMask selection) +bke::CurvesGeometry mesh_to_curve_convert( + const Mesh &mesh, + const IndexMask selection, + const bke::AnonymousAttributePropagationInfo &propagation_info) { Vector> selected_edges = get_selected_edges(mesh, selection); const Span verts = mesh.verts(); CurveFromEdgesOutput output = edges_to_curve_point_indices(verts, selected_edges); return create_curve_from_vert_indices( - mesh, output.vert_indices, output.curve_offsets, output.cyclic_curves); + mesh, output.vert_indices, output.curve_offsets, output.cyclic_curves, propagation_info); } } // namespace blender::geometry diff --git a/source/blender/geometry/intern/point_merge_by_distance.cc b/source/blender/geometry/intern/point_merge_by_distance.cc index 81f57f785a3..3387afc7279 100644 --- a/source/blender/geometry/intern/point_merge_by_distance.cc +++ b/source/blender/geometry/intern/point_merge_by_distance.cc @@ -15,7 +15,8 @@ namespace blender::geometry { PointCloud *point_merge_by_distance(const PointCloud &src_points, const float merge_distance, - const IndexMask selection) + const IndexMask selection, + const bke::AnonymousAttributePropagationInfo &propagation_info) { const bke::AttributeAccessor src_attributes = src_points.attributes(); VArraySpan positions = src_attributes.lookup_or_default( @@ -125,7 +126,7 @@ PointCloud *point_merge_by_distance(const PointCloud &src_points, /* Transfer all other attributes. */ for (const bke::AttributeIDRef &id : attribute_ids) { - if (!id.should_be_kept()) { + if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) { continue; } diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 57a4ae70b5f..9729d31779c 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -636,8 +636,11 @@ static OrderedAttributes gather_generic_pointcloud_attributes_to_propagate( } Map attributes_to_propagate; - in_geometry_set.gather_attributes_for_propagation( - src_component_types, GEO_COMPONENT_TYPE_POINT_CLOUD, true, attributes_to_propagate); + in_geometry_set.gather_attributes_for_propagation(src_component_types, + GEO_COMPONENT_TYPE_POINT_CLOUD, + true, + options.propagation_info, + attributes_to_propagate); attributes_to_propagate.remove("position"); r_create_id = attributes_to_propagate.pop_try("id").has_value(); r_create_radii = attributes_to_propagate.pop_try("radius").has_value(); @@ -829,8 +832,11 @@ static OrderedAttributes gather_generic_mesh_attributes_to_propagate( } Map attributes_to_propagate; - in_geometry_set.gather_attributes_for_propagation( - src_component_types, GEO_COMPONENT_TYPE_MESH, true, attributes_to_propagate); + in_geometry_set.gather_attributes_for_propagation(src_component_types, + GEO_COMPONENT_TYPE_MESH, + true, + options.propagation_info, + attributes_to_propagate); attributes_to_propagate.remove("position"); attributes_to_propagate.remove("normal"); attributes_to_propagate.remove("shade_smooth"); @@ -1149,8 +1155,11 @@ static OrderedAttributes gather_generic_curve_attributes_to_propagate( } Map attributes_to_propagate; - in_geometry_set.gather_attributes_for_propagation( - src_component_types, GEO_COMPONENT_TYPE_CURVE, true, attributes_to_propagate); + in_geometry_set.gather_attributes_for_propagation(src_component_types, + GEO_COMPONENT_TYPE_CURVE, + true, + options.propagation_info, + attributes_to_propagate); attributes_to_propagate.remove("position"); attributes_to_propagate.remove("radius"); attributes_to_propagate.remove("resolution"); diff --git a/source/blender/geometry/intern/resample_curves.cc b/source/blender/geometry/intern/resample_curves.cc index c8f6e9a9226..cccf659ac5e 100644 --- a/source/blender/geometry/intern/resample_curves.cc +++ b/source/blender/geometry/intern/resample_curves.cc @@ -53,7 +53,7 @@ static fn::Field get_count_input_from_length(const fn::Field &length static bool interpolate_attribute_to_curves(const bke::AttributeIDRef &attribute_id, const std::array &type_counts) { - if (!attribute_id.is_named()) { + if (attribute_id.is_anonymous()) { return true; } if (ELEM(attribute_id.name(), @@ -81,7 +81,7 @@ static bool interpolate_attribute_to_poly_curve(const bke::AttributeIDRef &attri "handle_left", "nurbs_weight", }}; - return !(attribute_id.is_named() && no_interpolation.contains(attribute_id.name())); + return !no_interpolation.contains(attribute_id.name()); } /** diff --git a/source/blender/geometry/intern/set_curve_type.cc b/source/blender/geometry/intern/set_curve_type.cc index e069732ca9b..ab35f90b279 100644 --- a/source/blender/geometry/intern/set_curve_type.cc +++ b/source/blender/geometry/intern/set_curve_type.cc @@ -286,8 +286,10 @@ static void retrieve_curve_sizes(const bke::CurvesGeometry &curves, MutableSpan< }); } -static bke::CurvesGeometry convert_curves_to_bezier(const bke::CurvesGeometry &src_curves, - const IndexMask selection) +static bke::CurvesGeometry convert_curves_to_bezier( + const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const bke::AnonymousAttributePropagationInfo &propagation_info) { const VArray src_knot_modes = src_curves.nurbs_knots_modes(); const VArray src_types = src_curves.curve_types(); @@ -315,6 +317,7 @@ static bke::CurvesGeometry convert_curves_to_bezier(const bke::CurvesGeometry &s src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, + propagation_info, {"position", "handle_type_left", "handle_type_right", @@ -460,8 +463,10 @@ static bke::CurvesGeometry convert_curves_to_bezier(const bke::CurvesGeometry &s return dst_curves; } -static bke::CurvesGeometry convert_curves_to_nurbs(const bke::CurvesGeometry &src_curves, - const IndexMask selection) +static bke::CurvesGeometry convert_curves_to_nurbs( + const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const bke::AnonymousAttributePropagationInfo &propagation_info) { const VArray src_types = src_curves.curve_types(); const VArray src_cyclic = src_curves.cyclic(); @@ -487,6 +492,7 @@ static bke::CurvesGeometry convert_curves_to_nurbs(const bke::CurvesGeometry &sr src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, + propagation_info, {"position", "handle_type_left", "handle_type_right", @@ -639,16 +645,17 @@ static bke::CurvesGeometry convert_curves_trivial(const bke::CurvesGeometry &src bke::CurvesGeometry convert_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, - const CurveType dst_type) + const CurveType dst_type, + const bke::AnonymousAttributePropagationInfo &propagation_info) { switch (dst_type) { case CURVE_TYPE_CATMULL_ROM: case CURVE_TYPE_POLY: return convert_curves_trivial(src_curves, selection, dst_type); case CURVE_TYPE_BEZIER: - return convert_curves_to_bezier(src_curves, selection); + return convert_curves_to_bezier(src_curves, selection, propagation_info); case CURVE_TYPE_NURBS: - return convert_curves_to_nurbs(src_curves, selection); + return convert_curves_to_nurbs(src_curves, selection, propagation_info); } BLI_assert_unreachable(); return {}; diff --git a/source/blender/geometry/intern/subdivide_curves.cc b/source/blender/geometry/intern/subdivide_curves.cc index 021fa091364..e69a8398653 100644 --- a/source/blender/geometry/intern/subdivide_curves.cc +++ b/source/blender/geometry/intern/subdivide_curves.cc @@ -294,9 +294,11 @@ static void subdivide_bezier_positions(const Span src_positions, cyclic, dst_types_l, dst_types_r, dst_positions, dst_handles_l, dst_handles_r); } -bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, - const IndexMask selection, - const VArray &cuts) +bke::CurvesGeometry subdivide_curves( + const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const VArray &cuts, + const bke::AnonymousAttributePropagationInfo &propagation_info) { const Vector unselected_ranges = selection.extract_ranges_invert( src_curves.curves_range()); @@ -338,7 +340,7 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, auto subdivide_catmull_rom = [&](IndexMask selection) { for (auto &attribute : bke::retrieve_attributes_for_transfer( - src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info)) { subdivide_attribute_catmull_rom(src_curves, dst_curves, selection, @@ -352,7 +354,7 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, auto subdivide_poly = [&](IndexMask selection) { for (auto &attribute : bke::retrieve_attributes_for_transfer( - src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info)) { subdivide_attribute_linear( src_curves, dst_curves, selection, point_offsets, attribute.src, attribute.dst.span); attribute.dst.finish(); @@ -396,6 +398,7 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, for (auto &attribute : bke::retrieve_attributes_for_transfer(src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, + propagation_info, {"position", "handle_type_left", "handle_type_right", @@ -421,7 +424,7 @@ bke::CurvesGeometry subdivide_curves(const bke::CurvesGeometry &src_curves, if (!unselected_ranges.is_empty()) { for (auto &attribute : bke::retrieve_attributes_for_transfer( - src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT)) { + src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, propagation_info)) { bke::curves::copy_point_data( src_curves, dst_curves, unselected_ranges, attribute.src, attribute.dst.span); attribute.dst.finish(); diff --git a/source/blender/geometry/intern/trim_curves.cc b/source/blender/geometry/intern/trim_curves.cc index 630ee8b8bab..53a22b581b5 100644 --- a/source/blender/geometry/intern/trim_curves.cc +++ b/source/blender/geometry/intern/trim_curves.cc @@ -929,7 +929,8 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, const IndexMask selection, const VArray &starts, const VArray &ends, - const GeometryNodeCurveSampleMode mode) + const GeometryNodeCurveSampleMode mode, + const bke::AnonymousAttributePropagationInfo &propagation_info) { BLI_assert(selection.size() > 0); BLI_assert(selection.last() <= src_curves.curves_num()); @@ -991,14 +992,19 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, transfer_curve_skip.remove("nurbs_order"); transfer_curve_skip.remove("knots_mode"); } - bke::copy_attribute_domain( - src_attributes, dst_attributes, selection, ATTR_DOMAIN_CURVE, transfer_curve_skip); + bke::copy_attribute_domain(src_attributes, + dst_attributes, + selection, + ATTR_DOMAIN_CURVE, + propagation_info, + transfer_curve_skip); /* Fetch custom point domain attributes for transfer (copy). */ Vector transfer_attributes = bke::retrieve_attributes_for_transfer( src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, + propagation_info, {"position", "handle_left", "handle_right", @@ -1063,8 +1069,12 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, /* Copy unselected */ if (!inverse_selection.is_empty()) { transfer_curve_skip.remove("cyclic"); - bke::copy_attribute_domain( - src_attributes, dst_attributes, inverse_selection, ATTR_DOMAIN_CURVE, transfer_curve_skip); + bke::copy_attribute_domain(src_attributes, + dst_attributes, + inverse_selection, + ATTR_DOMAIN_CURVE, + propagation_info, + transfer_curve_skip); /* Trim curves are no longer cyclic. If all curves are trimmed, this will be set implicitly. */ dst_curves.cyclic_for_write().fill_indices(selection, false); @@ -1075,8 +1085,11 @@ bke::CurvesGeometry trim_curves(const bke::CurvesGeometry &src_curves, } /* Copy point domain. */ - for (auto &attribute : bke::retrieve_attributes_for_transfer( - src_attributes, dst_attributes, ATTR_DOMAIN_MASK_POINT, copy_point_skip)) { + for (auto &attribute : bke::retrieve_attributes_for_transfer(src_attributes, + dst_attributes, + ATTR_DOMAIN_MASK_POINT, + propagation_info, + copy_point_skip)) { bke::curves::copy_point_data( src_curves, dst_curves, inverse_selection, attribute.src, attribute.dst.span); attribute.dst.finish(); diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h index 3bff2f4316c..0710ed6d30f 100644 --- a/source/blender/makesdna/DNA_customdata_types.h +++ b/source/blender/makesdna/DNA_customdata_types.h @@ -15,7 +15,15 @@ extern "C" { #endif -struct AnonymousAttributeID; +/** Workaround to forward-declare C++ type in C header. */ +#ifdef __cplusplus +namespace blender::bke { +class AnonymousAttributeID; +} // namespace blender::bke +using AnonymousAttributeIDHandle = blender::bke::AnonymousAttributeID; +#else +typedef struct AnonymousAttributeIDHandle AnonymousAttributeIDHandle; +#endif /** Descriptor and storage for a custom data layer. */ typedef struct CustomDataLayer { @@ -40,12 +48,10 @@ typedef struct CustomDataLayer { /** Layer data. */ void *data; /** - * Run-time identifier for this layer. If no one has a strong reference to this id anymore, - * the layer can be removed. The custom data layer only has a weak reference to the id, because - * otherwise there will always be a strong reference and the attribute can't be removed - * automatically. + * Run-time identifier for this layer. Can be used to retrieve information about where this + * attribute was created. */ - const struct AnonymousAttributeID *anonymous_id; + const AnonymousAttributeIDHandle *anonymous_id; } CustomDataLayer; #define MAX_CUSTOMDATA_LAYER_NAME 64 diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index ebd5bf351ea..93b61ad76ab 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -884,12 +884,12 @@ static void find_side_effect_nodes_for_viewer_path( /* Not only mark the viewer node as having side effects, but also all group nodes it is contained * in. */ - r_side_effect_nodes.add(compute_context_builder.hash(), - &find_viewer_lf_node(*found_viewer_node)); + r_side_effect_nodes.add_non_duplicates(compute_context_builder.hash(), + &find_viewer_lf_node(*found_viewer_node)); compute_context_builder.pop(); while (!compute_context_builder.is_empty()) { - r_side_effect_nodes.add(compute_context_builder.hash(), - &find_group_lf_node(*group_node_stack.pop())); + r_side_effect_nodes.add_non_duplicates(compute_context_builder.hash(), + &find_group_lf_node(*group_node_stack.pop())); compute_context_builder.pop(); } } @@ -1124,12 +1124,11 @@ static GeometrySet compute_geometry( { const blender::nodes::GeometryNodeLazyFunctionGraphMapping &mapping = lf_graph_info.mapping; - Span graph_inputs = mapping.group_input_sockets; - Vector graph_outputs; - for (const bNodeSocket *bsocket : output_node.input_sockets().drop_back(1)) { - const lf::InputSocket &socket = mapping.dummy_socket_map.lookup(bsocket)->as_input(); - graph_outputs.append(&socket); - } + Vector graph_inputs = mapping.group_input_sockets; + graph_inputs.extend(mapping.group_output_used_sockets); + graph_inputs.extend(mapping.attribute_set_by_geometry_output.values().begin(), + mapping.attribute_set_by_geometry_output.values().end()); + Vector graph_outputs = mapping.standard_group_output_sockets; Array param_inputs(graph_inputs.size()); Array param_outputs(graph_outputs.size()); @@ -1166,21 +1165,36 @@ static GeometrySet compute_geometry( blender::LinearAllocator<> allocator; Vector inputs_to_destruct; - int input_index; - LISTBASE_FOREACH_INDEX (bNodeSocket *, interface_socket, &btree.inputs, input_index) { - if (interface_socket->type == SOCK_GEOMETRY && input_index == 0) { + int input_index = -1; + for (const int i : btree.interface_inputs().index_range()) { + input_index++; + const bNodeSocket &interface_socket = *btree.interface_inputs()[i]; + if (interface_socket.type == SOCK_GEOMETRY && input_index == 0) { param_inputs[input_index] = &input_geometry_set; continue; } - const CPPType *type = interface_socket->typeinfo->geometry_nodes_cpp_type; + const CPPType *type = interface_socket.typeinfo->geometry_nodes_cpp_type; BLI_assert(type != nullptr); void *value = allocator.allocate(type->size(), type->alignment()); - initialize_group_input(*nmd, *interface_socket, input_index, value); + initialize_group_input(*nmd, interface_socket, i, value); param_inputs[input_index] = {type, value}; inputs_to_destruct.append({type, value}); } + Array output_used_inputs(btree.interface_outputs().size(), true); + for (const int i : btree.interface_outputs().index_range()) { + input_index++; + param_inputs[input_index] = &output_used_inputs[i]; + } + + Array attributes_to_propagate( + mapping.attribute_set_by_geometry_output.size()); + for (const int i : attributes_to_propagate.index_range()) { + input_index++; + param_inputs[input_index] = &attributes_to_propagate[i]; + } + for (const int i : graph_outputs.index_range()) { const lf::InputSocket &socket = *graph_outputs[i]; const CPPType &type = socket.type(); diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh index 73e82f741ab..60f58f4c215 100644 --- a/source/blender/nodes/NOD_geometry_exec.hh +++ b/source/blender/nodes/NOD_geometry_exec.hh @@ -19,6 +19,8 @@ struct ModifierData; namespace blender::nodes { using bke::AnonymousAttributeFieldInput; +using bke::AnonymousAttributeID; +using bke::AnonymousAttributePropagationInfo; using bke::AttributeAccessor; using bke::AttributeFieldInput; using bke::AttributeIDRef; @@ -26,13 +28,12 @@ using bke::AttributeKind; using bke::AttributeMetaData; using bke::AttributeReader; using bke::AttributeWriter; +using bke::AutoAnonymousAttributeID; using bke::GAttributeReader; using bke::GAttributeWriter; using bke::GSpanAttributeWriter; using bke::MutableAttributeAccessor; using bke::SpanAttributeWriter; -using bke::StrongAnonymousAttributeID; -using bke::WeakAnonymousAttributeID; using fn::Field; using fn::FieldContext; using fn::FieldEvaluator; @@ -43,15 +44,38 @@ using fn::ValueOrField; using geo_eval_log::NamedAttributeUsage; using geo_eval_log::NodeWarningType; +/** + * An anonymous attribute created by a node. + */ +class NodeAnonymousAttributeID : public AnonymousAttributeID { + std::string long_name_; + + public: + NodeAnonymousAttributeID(const Object &object, + const ComputeContext &compute_context, + const bNode &bnode, + const StringRef identifier); +}; + class GeoNodeExecParams { private: const bNode &node_; lf::Params ¶ms_; const lf::Context &lf_context_; + const Map &lf_input_for_output_bsocket_usage_; + const Map &lf_input_for_attribute_propagation_to_output_; public: - GeoNodeExecParams(const bNode &node, lf::Params ¶ms, const lf::Context &lf_context) - : node_(node), params_(params), lf_context_(lf_context) + GeoNodeExecParams(const bNode &node, + lf::Params ¶ms, + const lf::Context &lf_context, + const Map &lf_input_for_output_bsocket_usage, + const Map &lf_input_for_attribute_propagation_to_output) + : node_(node), + params_(params), + lf_context_(lf_context), + lf_input_for_output_bsocket_usage_(lf_input_for_output_bsocket_usage), + lf_input_for_attribute_propagation_to_output_(lf_input_for_attribute_propagation_to_output) { } @@ -245,6 +269,49 @@ class GeoNodeExecParams { void used_named_attribute(StringRef attribute_name, NamedAttributeUsage usage); + /** + * Return true when the anonymous attribute referenced by the given output should be created. + */ + bool anonymous_attribute_output_is_required(const StringRef output_identifier) + { + const int lf_index = lf_input_for_output_bsocket_usage_.lookup(output_identifier); + return params_.get_input(lf_index); + } + + /** + * Return a new anonymous attribute id for the given output. None is returned if the anonymous + * attribute is not needed. + */ + AutoAnonymousAttributeID get_output_anonymous_attribute_id_if_needed( + const StringRef output_identifier) + { + if (!this->anonymous_attribute_output_is_required(output_identifier)) { + return {}; + } + const GeoNodesLFUserData &user_data = *this->user_data(); + const ComputeContext &compute_context = *user_data.compute_context; + return MEM_new(__func__, + *user_data.modifier_data->self_object, + compute_context, + node_, + output_identifier); + } + + /** + * Get information about which anonymous attributes should be propagated to the given output. + */ + AnonymousAttributePropagationInfo get_output_propagation_info( + const StringRef output_identifier) const + { + const int lf_index = lf_input_for_attribute_propagation_to_output_.lookup(output_identifier); + const bke::AnonymousAttributeSet &set = params_.get_input( + lf_index); + AnonymousAttributePropagationInfo info; + info.names = set.names; + info.propagate_all = false; + return info; + } + private: /* Utilities for detecting common errors at when using this class. */ void check_input_access(StringRef identifier, const CPPType *requested_type = nullptr) const; diff --git a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh index 84c264f4976..7f49d067061 100644 --- a/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh +++ b/source/blender/nodes/NOD_geometry_nodes_lazy_function.hh @@ -78,6 +78,28 @@ struct GeoNodesLFUserData : public lf::UserData { bool log_socket_values = true; }; +/** + * In the general case, this is #DynamicSocket. That means that to determine if a node group will + * use a particular input, it has to be partially executed. + * + * In other cases, it's not necessary to look into the node group to determine if an input is + * necessary. + */ +enum class InputUsageHintType { + /** The input socket is never used. */ + Never, + /** The input socket is used when a subset of the outputs is used. */ + DependsOnOutput, + /** Can't determine statically if the input is used, check the corresponding output socket. */ + DynamicSocket, +}; + +struct InputUsageHint { + InputUsageHintType type = InputUsageHintType::DependsOnOutput; + /** Used in depends-on-output mode. */ + Vector output_dependencies; +}; + /** * Contains the mapping between the #bNodeTree and the corresponding lazy-function graph. * This is *not* a one-to-one mapping. @@ -91,7 +113,32 @@ struct GeometryNodeLazyFunctionGraphMapping { * The inputs sockets in the graph. Multiple group input nodes are combined into one in the * lazy-function graph. */ - Vector group_input_sockets; + Vector group_input_sockets; + /** + * Dummy output sockets that correspond to the active group output node. If there is no such + * node, defaulted fallback outputs are created. + */ + Vector standard_group_output_sockets; + /** + * Dummy boolean sockets that have to be passed in from the outside and indicate whether a + * specific output will be used. + */ + Vector group_output_used_sockets; + /** + * Dummy boolean sockets that can be used as group output that indicate whether a specific input + * will be used (this may depend on the used outputs as well as other inputs). + */ + Vector group_input_usage_sockets; + /** + * This is an optimization to avoid partially evaluating a node group just to figure out which + * inputs are needed. + */ + Vector group_input_usage_hints; + /** + * If the node group propagates attributes from an input geometry to the output, it has to know + * which attributes should be propagated and which can be removed (for optimization purposes). + */ + Map attribute_set_by_geometry_output; /** * A mapping used for logging intermediate values. */ diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index d99b4c01ed7..ce6b4cd6cfe 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -50,10 +50,10 @@ Mesh *create_grid_mesh( int verts_x, int verts_y, float size_x, float size_y, const AttributeIDRef &uv_map_id); struct ConeAttributeOutputs { - StrongAnonymousAttributeID top_id; - StrongAnonymousAttributeID bottom_id; - StrongAnonymousAttributeID side_id; - StrongAnonymousAttributeID uv_map_id; + AutoAnonymousAttributeID top_id; + AutoAnonymousAttributeID bottom_id; + AutoAnonymousAttributeID side_id; + AutoAnonymousAttributeID uv_map_id; }; Mesh *create_cylinder_or_cone_mesh(float radius_top, @@ -81,6 +81,7 @@ void separate_geometry(GeometrySet &geometry_set, eAttrDomain domain, GeometryNodeDeleteGeometryMode mode, const Field &selection_field, + const AnonymousAttributePropagationInfo &propagation_info, bool &r_is_error); void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data, diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc index 826f08e4c8d..a07cd1437d6 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc @@ -142,9 +142,12 @@ static void node_geo_exec(GeoNodeExecParams params) const eAttrDomain domain = eAttrDomain(storage.domain); const std::string output_identifier = "Attribute" + identifier_suffix(data_type); + AutoAnonymousAttributeID attribute_id = params.get_output_anonymous_attribute_id_if_needed( + output_identifier); - if (!params.output_is_required(output_identifier)) { + if (!attribute_id) { params.set_output("Geometry", geometry_set); + params.set_default_remaining_outputs(); return; } @@ -171,7 +174,6 @@ static void node_geo_exec(GeoNodeExecParams params) break; } - WeakAnonymousAttributeID anonymous_id{"Attribute"}; const CPPType &type = field.cpp_type(); /* Run on the instances component separately to only affect the top level of instances. */ @@ -179,7 +181,7 @@ static void node_geo_exec(GeoNodeExecParams params) if (geometry_set.has_instances()) { GeometryComponent &component = geometry_set.get_component_for_write( GEO_COMPONENT_TYPE_INSTANCES); - bke::try_capture_field_on_geometry(component, anonymous_id.get(), domain, field); + bke::try_capture_field_on_geometry(component, *attribute_id, domain, field); } } else { @@ -190,14 +192,14 @@ static void node_geo_exec(GeoNodeExecParams params) for (const GeometryComponentType type : types) { if (geometry_set.has(type)) { GeometryComponent &component = geometry_set.get_component_for_write(type); - bke::try_capture_field_on_geometry(component, anonymous_id.get(), domain, field); + bke::try_capture_field_on_geometry(component, *attribute_id, domain, field); } } }); } GField output_field{std::make_shared( - std::move(anonymous_id), type, params.attribute_producer_name())}; + std::move(attribute_id), type, params.attribute_producer_name())}; switch (data_type) { case CD_PROP_FLOAT: { diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc index a1c4af79851..0135567c5e4 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -30,7 +30,7 @@ static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) } struct AttributeOutputs { - StrongAnonymousAttributeID intersecting_edges_id; + AutoAnonymousAttributeID intersecting_edges_id; }; static void node_update(bNodeTree *ntree, bNode *node) @@ -125,9 +125,8 @@ static void node_geo_exec(GeoNodeExecParams params) } AttributeOutputs attribute_outputs; - if (params.output_is_required("Intersecting Edges")) { - attribute_outputs.intersecting_edges_id = StrongAnonymousAttributeID("Intersecting Edges"); - } + attribute_outputs.intersecting_edges_id = params.get_output_anonymous_attribute_id_if_needed( + "Intersecting Edges"); Vector intersecting_edges; Mesh *result = blender::meshintersect::direct_mesh_boolean( diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc index ef839bf2acb..627abcab861 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_fillet.cc @@ -67,6 +67,9 @@ static void node_geo_exec(GeoNodeExecParams params) count_field.emplace(params.extract_input>("Count")); } + const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( + "Curve"); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (!geometry_set.has_curves()) { return; @@ -82,7 +85,11 @@ static void node_geo_exec(GeoNodeExecParams params) case GEO_NODE_CURVE_FILLET_BEZIER: { evaluator.evaluate(); bke::CurvesGeometry dst_curves = geometry::fillet_curves_bezier( - curves, curves.curves_range(), evaluator.get_evaluated(0), limit_radius); + curves, + curves.curves_range(), + evaluator.get_evaluated(0), + limit_radius, + propagation_info); Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); bke::curves_copy_parameters(curves_id, *dst_curves_id); geometry_set.replace_curves(dst_curves_id); @@ -96,7 +103,8 @@ static void node_geo_exec(GeoNodeExecParams params) curves.curves_range(), evaluator.get_evaluated(0), evaluator.get_evaluated(1), - limit_radius); + limit_radius, + propagation_info); Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); bke::curves_copy_parameters(curves_id, *dst_curves_id); geometry_set.replace_curves(dst_curves_id); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc index 13353553379..873b66688c5 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_primitive_star.cc @@ -59,10 +59,10 @@ static Curves *create_star_curve(const float inner_radius, } static void create_selection_output(CurveComponent &component, - StrongAnonymousAttributeID &r_attribute) + AutoAnonymousAttributeID &r_attribute) { SpanAttributeWriter selection = - component.attributes_for_write()->lookup_or_add_for_write_only_span(r_attribute.get(), + component.attributes_for_write()->lookup_or_add_for_write_only_span(*r_attribute, ATTR_DOMAIN_POINT); for (int i : selection.span.index_range()) { selection.span[i] = i % 2 == 0; @@ -78,12 +78,12 @@ static void node_geo_exec(GeoNodeExecParams params) std::max(params.extract_input("Points"), 3)); GeometrySet output = GeometrySet::create_with_curves(curves); - if (params.output_is_required("Outer Points")) { - StrongAnonymousAttributeID attribute_output("Outer Points"); - create_selection_output(output.get_component_for_write(), attribute_output); + if (AutoAnonymousAttributeID outer_points_id = + params.get_output_anonymous_attribute_id_if_needed("Outer Points")) { + create_selection_output(output.get_component_for_write(), outer_points_id); params.set_output("Outer Points", AnonymousAttributeFieldInput::Create( - std::move(attribute_output), params.attribute_producer_name())); + std::move(outer_points_id), params.attribute_producer_name())); } params.set_output("Curve", std::move(output)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc index 3d8d14473db..64a9ae08c9b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc @@ -67,7 +67,8 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - bke::CurvesGeometry dst_curves = geometry::convert_curves(src_curves, selection, dst_type); + bke::CurvesGeometry dst_curves = geometry::convert_curves( + src_curves, selection, dst_type, params.get_output_propagation_info("Curve")); Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); bke::curves_copy_parameters(src_curves_id, *dst_curves_id); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc index b01dd2e5361..479fdef56e7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc @@ -49,7 +49,7 @@ static void node_geo_exec(GeoNodeExecParams params) } bke::CurvesGeometry dst_curves = geometry::subdivide_curves( - src_curves, src_curves.curves_range(), cuts); + src_curves, src_curves.curves_range(), cuts, params.get_output_propagation_info("Curve")); Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); bke::curves_copy_parameters(src_curves_id, *dst_curves_id); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc index 057496144d5..0b46fe569d1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_mesh.cc @@ -25,7 +25,8 @@ static void node_declare(NodeDeclarationBuilder &b) static void geometry_set_curve_to_mesh(GeometrySet &geometry_set, const GeometrySet &profile_set, - const bool fill_caps) + const bool fill_caps, + const AnonymousAttributePropagationInfo &propagation_info) { const Curves &curves = *geometry_set.get_curves_for_read(); const Curves *profile_curves = profile_set.get_curves_for_read(); @@ -33,13 +34,15 @@ static void geometry_set_curve_to_mesh(GeometrySet &geometry_set, GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); if (profile_curves == nullptr) { - Mesh *mesh = bke::curve_to_wire_mesh(bke::CurvesGeometry::wrap(curves.geometry)); + Mesh *mesh = bke::curve_to_wire_mesh(bke::CurvesGeometry::wrap(curves.geometry), + propagation_info); geometry_set.replace_mesh(mesh); } else { Mesh *mesh = bke::curve_to_mesh_sweep(bke::CurvesGeometry::wrap(curves.geometry), bke::CurvesGeometry::wrap(profile_curves->geometry), - fill_caps); + fill_caps, + propagation_info); geometry_set.replace_mesh(mesh); } } @@ -52,7 +55,8 @@ static void node_geo_exec(GeoNodeExecParams params) curve_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (geometry_set.has_curves()) { - geometry_set_curve_to_mesh(geometry_set, profile_set, fill_caps); + geometry_set_curve_to_mesh( + geometry_set, profile_set, fill_caps, params.get_output_propagation_info("Mesh")); } geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH}); }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc index 4442dfa0fdb..6e443f1efb7 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc @@ -111,19 +111,12 @@ static void node_geo_exec(GeoNodeExecParams params) GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); - StrongAnonymousAttributeID tangent_anonymous_id; - StrongAnonymousAttributeID normal_anonymous_id; - StrongAnonymousAttributeID rotation_anonymous_id; - const bool rotation_required = params.output_is_required("Rotation"); - if (params.output_is_required("Tangent") || rotation_required) { - tangent_anonymous_id = StrongAnonymousAttributeID("Tangent"); - } - if (params.output_is_required("Normal") || rotation_required) { - normal_anonymous_id = StrongAnonymousAttributeID("Normal"); - } - if (rotation_required) { - rotation_anonymous_id = StrongAnonymousAttributeID("Rotation"); - } + AutoAnonymousAttributeID tangent_anonymous_id = + params.get_output_anonymous_attribute_id_if_needed("Tangent"); + AutoAnonymousAttributeID normal_anonymous_id = + params.get_output_anonymous_attribute_id_if_needed("Normal"); + AutoAnonymousAttributeID rotation_anonymous_id = + params.get_output_anonymous_attribute_id_if_needed("Rotation"); geometry::ResampleCurvesOutputAttributeIDs resample_attributes; resample_attributes.tangent_id = tangent_anonymous_id.get(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc index 97b72838b39..56912931571 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc @@ -112,7 +112,8 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set, const GeometryNodeCurveSampleMode mode, Field &selection_field, Field &start_field, - Field &end_field) + Field &end_field, + const AnonymousAttributePropagationInfo &propagation_info) { if (!geometry_set.has_curves()) { return; @@ -139,7 +140,7 @@ static void geometry_set_curve_trim(GeometrySet &geometry_set, } bke::CurvesGeometry dst_curves = geometry::trim_curves( - src_curves, selection, starts, ends, mode); + src_curves, selection, starts, ends, mode, propagation_info); Curves *dst_curves_id = bke::curves_new_nomain(std::move(dst_curves)); bke::curves_copy_parameters(src_curves_id, *dst_curves_id); geometry_set.replace_curves(dst_curves_id); @@ -153,19 +154,24 @@ static void node_geo_exec(GeoNodeExecParams params) GeometrySet geometry_set = params.extract_input("Curve"); GeometryComponentEditData::remember_deformed_curve_positions_if_necessary(geometry_set); + const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( + "Curve"); + Field selection_field = params.extract_input>("Selection"); if (mode == GEO_NODE_CURVE_SAMPLE_FACTOR) { Field start_field = params.extract_input>("Start"); Field end_field = params.extract_input>("End"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - geometry_set_curve_trim(geometry_set, mode, selection_field, start_field, end_field); + geometry_set_curve_trim( + geometry_set, mode, selection_field, start_field, end_field, propagation_info); }); } else if (mode == GEO_NODE_CURVE_SAMPLE_LENGTH) { Field start_field = params.extract_input>("Start_001"); Field end_field = params.extract_input>("End_001"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - geometry_set_curve_trim(geometry_set, mode, selection_field, start_field, end_field); + geometry_set_curve_trim( + geometry_set, mode, selection_field, start_field, end_field, propagation_info); }); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc index 92937482116..e92fe1a613d 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc @@ -306,7 +306,8 @@ static void copy_masked_polys_to_new_mesh(const Mesh &src_mesh, static void delete_curves_selection(GeometrySet &geometry_set, const Field &selection_field, - const eAttrDomain selection_domain) + const eAttrDomain selection_domain, + const bke::AnonymousAttributePropagationInfo &propagation_info) { const Curves &src_curves_id = *geometry_set.get_curves_for_read(); const bke::CurvesGeometry &src_curves = bke::CurvesGeometry::wrap(src_curves_id.geometry); @@ -330,15 +331,17 @@ static void delete_curves_selection(GeometrySet &geometry_set, bke::CurvesGeometry &curves = bke::CurvesGeometry::wrap(curves_id.geometry); if (selection_domain == ATTR_DOMAIN_POINT) { - curves.remove_points(selection); + curves.remove_points(selection, propagation_info); } else if (selection_domain == ATTR_DOMAIN_CURVE) { - curves.remove_curves(selection); + curves.remove_curves(selection, propagation_info); } } -static void separate_point_cloud_selection(GeometrySet &geometry_set, - const Field &selection_field) +static void separate_point_cloud_selection( + GeometrySet &geometry_set, + const Field &selection_field, + const AnonymousAttributePropagationInfo &propagation_info) { const PointCloud &src_pointcloud = *geometry_set.get_pointcloud_for_read(); @@ -355,8 +358,11 @@ static void separate_point_cloud_selection(GeometrySet &geometry_set, PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size()); Map attributes; - geometry_set.gather_attributes_for_propagation( - {GEO_COMPONENT_TYPE_POINT_CLOUD}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes); + geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_POINT_CLOUD}, + GEO_COMPONENT_TYPE_POINT_CLOUD, + false, + propagation_info, + attributes); copy_attributes_based_on_mask(attributes, src_pointcloud.attributes(), @@ -367,7 +373,8 @@ static void separate_point_cloud_selection(GeometrySet &geometry_set, } static void delete_selected_instances(GeometrySet &geometry_set, - const Field &selection_field) + const Field &selection_field, + const AnonymousAttributePropagationInfo &propagation_info) { bke::Instances &instances = *geometry_set.get_instances_for_write(); bke::InstancesFieldContext field_context{instances}; @@ -381,7 +388,7 @@ static void delete_selected_instances(GeometrySet &geometry_set, return; } - instances.remove(selection); + instances.remove(selection, propagation_info); } static void compute_selected_verts_from_vertex_selection(const Span vertex_selection, @@ -819,7 +826,8 @@ static void do_mesh_separation(GeometrySet &geometry_set, const Mesh &mesh_in, const Span selection, const eAttrDomain domain, - const GeometryNodeDeleteGeometryMode mode) + const GeometryNodeDeleteGeometryMode mode, + const AnonymousAttributePropagationInfo &propagation_info) { /* Needed in all cases. */ Vector selected_poly_indices; @@ -831,7 +839,7 @@ static void do_mesh_separation(GeometrySet &geometry_set, Map attributes; geometry_set.gather_attributes_for_propagation( - {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_MESH, false, attributes); + {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_MESH, false, propagation_info, attributes); switch (mode) { case GEO_NODE_DELETE_GEOMETRY_MODE_ALL: { @@ -1059,7 +1067,8 @@ static void do_mesh_separation(GeometrySet &geometry_set, static void separate_mesh_selection(GeometrySet &geometry_set, const Field &selection_field, const eAttrDomain selection_domain, - const GeometryNodeDeleteGeometryMode mode) + const GeometryNodeDeleteGeometryMode mode, + const AnonymousAttributePropagationInfo &propagation_info) { const Mesh &src_mesh = *geometry_set.get_mesh_for_read(); bke::MeshFieldContext field_context{src_mesh, selection_domain}; @@ -1074,7 +1083,8 @@ static void separate_mesh_selection(GeometrySet &geometry_set, const VArraySpan selection_span{selection}; - do_mesh_separation(geometry_set, src_mesh, selection_span, selection_domain, mode); + do_mesh_separation( + geometry_set, src_mesh, selection_span, selection_domain, mode, propagation_info); } } // namespace blender::nodes::node_geo_delete_geometry_cc @@ -1085,6 +1095,7 @@ void separate_geometry(GeometrySet &geometry_set, const eAttrDomain domain, const GeometryNodeDeleteGeometryMode mode, const Field &selection_field, + const AnonymousAttributePropagationInfo &propagation_info, bool &r_is_error) { namespace file_ns = blender::nodes::node_geo_delete_geometry_cc; @@ -1092,26 +1103,27 @@ void separate_geometry(GeometrySet &geometry_set, bool some_valid_domain = false; if (geometry_set.has_pointcloud()) { if (domain == ATTR_DOMAIN_POINT) { - file_ns::separate_point_cloud_selection(geometry_set, selection_field); + file_ns::separate_point_cloud_selection(geometry_set, selection_field, propagation_info); some_valid_domain = true; } } if (geometry_set.has_mesh()) { if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_EDGE, ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER)) { - file_ns::separate_mesh_selection(geometry_set, selection_field, domain, mode); + file_ns::separate_mesh_selection( + geometry_set, selection_field, domain, mode, propagation_info); some_valid_domain = true; } } if (geometry_set.has_curves()) { if (ELEM(domain, ATTR_DOMAIN_POINT, ATTR_DOMAIN_CURVE)) { file_ns::delete_curves_selection( - geometry_set, fn::invert_boolean_field(selection_field), domain); + geometry_set, fn::invert_boolean_field(selection_field), domain, propagation_info); some_valid_domain = true; } } if (geometry_set.has_instances()) { if (domain == ATTR_DOMAIN_INSTANCE) { - file_ns::delete_selected_instances(geometry_set, selection_field); + file_ns::delete_selected_instances(geometry_set, selection_field, propagation_info); some_valid_domain = true; } } @@ -1171,15 +1183,18 @@ static void node_geo_exec(GeoNodeExecParams params) const eAttrDomain domain = eAttrDomain(storage.domain); const GeometryNodeDeleteGeometryMode mode = (GeometryNodeDeleteGeometryMode)storage.mode; + const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( + "Geometry"); + if (domain == ATTR_DOMAIN_INSTANCE) { bool is_error; - separate_geometry(geometry_set, domain, mode, selection, is_error); + separate_geometry(geometry_set, domain, mode, selection, propagation_info, is_error); } else { geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { bool is_error; /* Invert here because we want to keep the things not in the selection. */ - separate_geometry(geometry_set, domain, mode, selection, is_error); + separate_geometry(geometry_set, domain, mode, selection, propagation_info, is_error); }); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc index a5bdc989a04..91fa215d117 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_distribute_points_on_faces.cc @@ -320,8 +320,8 @@ BLI_NOINLINE static void propagate_existing_attributes( namespace { struct AttributeOutputs { - StrongAnonymousAttributeID normal_id; - StrongAnonymousAttributeID rotation_id; + AutoAnonymousAttributeID normal_id; + AutoAnonymousAttributeID rotation_id; }; } // namespace @@ -496,8 +496,11 @@ static void point_distribution_calculate(GeometrySet &geometry_set, geometry_set.replace_pointcloud(pointcloud); Map attributes; - geometry_set.gather_attributes_for_propagation( - {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes); + geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_MESH}, + GEO_COMPONENT_TYPE_POINT_CLOUD, + false, + params.get_output_propagation_info("Points"), + attributes); /* Position is set separately. */ attributes.remove("position"); @@ -518,12 +521,8 @@ static void node_geo_exec(GeoNodeExecParams params) const Field selection_field = params.extract_input>("Selection"); AttributeOutputs attribute_outputs; - if (params.output_is_required("Normal")) { - attribute_outputs.normal_id = StrongAnonymousAttributeID("Normal"); - } - if (params.output_is_required("Rotation")) { - attribute_outputs.rotation_id = StrongAnonymousAttributeID("Rotation"); - } + attribute_outputs.normal_id = params.get_output_anonymous_attribute_id_if_needed("Normal"); + attribute_outputs.rotation_id = params.get_output_anonymous_attribute_id_if_needed("Rotation"); lazy_threading::send_hint(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc index bb48db48559..b076f0b7261 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_dual_mesh.cc @@ -132,6 +132,7 @@ static void transfer_attributes( const Span new_to_old_edges_map, const Span new_to_old_face_corners_map, const Span> boundary_vertex_to_relevant_face_map, + const AnonymousAttributePropagationInfo &propagation_info, const AttributeAccessor src_attributes, MutableAttributeAccessor dst_attributes) { @@ -139,7 +140,9 @@ static void transfer_attributes( * Remove anonymous attributes that don't need to be propagated. */ Set attribute_ids = src_attributes.all_ids(); attribute_ids.remove("position"); - attribute_ids.remove_if([](const AttributeIDRef &id) { return !id.should_be_kept(); }); + attribute_ids.remove_if([&](const AttributeIDRef &id) { + return id.is_anonymous() && !propagation_info.propagate(id.anonymous_id()); + }); for (const AttributeIDRef &id : attribute_ids) { GAttributeReader src_attribute = src_attributes.lookup(id); @@ -606,7 +609,9 @@ static void dissolve_redundant_verts(const Span edges, * * Some special cases are needed for boundaries and non-manifold geometry. */ -static Mesh *calc_dual_mesh(const Mesh &src_mesh, const bool keep_boundaries) +static Mesh *calc_dual_mesh(const Mesh &src_mesh, + const bool keep_boundaries, + const AnonymousAttributePropagationInfo &propagation_info) { const Span src_verts = src_mesh.verts(); const Span src_edges = src_mesh.edges(); @@ -887,6 +892,7 @@ static Mesh *calc_dual_mesh(const Mesh &src_mesh, const bool keep_boundaries) new_to_old_edges_map, new_to_old_face_corners_map, boundary_vertex_to_relevant_face_map, + propagation_info, src_mesh.attributes(), mesh_out->attributes_for_write()); @@ -918,7 +924,8 @@ static void node_geo_exec(GeoNodeExecParams params) const bool keep_boundaries = params.extract_input("Keep Boundaries"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (const Mesh *mesh = geometry_set.get_mesh_for_read()) { - Mesh *new_mesh = calc_dual_mesh(*mesh, keep_boundaries); + Mesh *new_mesh = calc_dual_mesh( + *mesh, keep_boundaries, params.get_output_propagation_info("Dual Mesh")); geometry_set.replace_mesh(new_mesh); } }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc index a53f92e3b9f..cd191fa8498 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc @@ -56,7 +56,7 @@ static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) } struct IndexAttributes { - StrongAnonymousAttributeID duplicate_index; + AutoAnonymousAttributeID duplicate_index; }; /* -------------------------------------------------------------------- */ @@ -64,11 +64,13 @@ struct IndexAttributes { * \{ */ static Map gather_attributes_without_id( - const GeometrySet &geometry_set, const GeometryComponentType component_type) + const GeometrySet &geometry_set, + const GeometryComponentType component_type, + const AnonymousAttributePropagationInfo &propagation_info) { Map attributes; geometry_set.gather_attributes_for_propagation( - {component_type}, component_type, false, attributes); + {component_type}, component_type, false, propagation_info, attributes); attributes.remove("id"); return attributes; }; @@ -181,11 +183,12 @@ static void copy_attributes_without_id(GeometrySet &geometry_set, const eAttrDomain domain, const Span offsets, const IndexMask selection, + const AnonymousAttributePropagationInfo &propagation_info, const bke::AttributeAccessor src_attributes, bke::MutableAttributeAccessor dst_attributes) { const Map attributes = gather_attributes_without_id( - geometry_set, component_type); + geometry_set, component_type, propagation_info); for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; @@ -221,14 +224,16 @@ static void copy_attributes_without_id(GeometrySet &geometry_set, * Copies the attributes for curve duplicates. If copying the curve domain, the attributes are * copied with an offset fill, otherwise a mapping is used. */ -static void copy_curve_attributes_without_id(const GeometrySet &geometry_set, - const bke::CurvesGeometry &src_curves, - const IndexMask selection, - const Span curve_offsets, - bke::CurvesGeometry &dst_curves) +static void copy_curve_attributes_without_id( + const GeometrySet &geometry_set, + const bke::CurvesGeometry &src_curves, + const IndexMask selection, + const Span curve_offsets, + const AnonymousAttributePropagationInfo &propagation_info, + bke::CurvesGeometry &dst_curves) { Map attributes = gather_attributes_without_id( - geometry_set, GEO_COMPONENT_TYPE_CURVE); + geometry_set, GEO_COMPONENT_TYPE_CURVE, propagation_info); for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; @@ -318,7 +323,8 @@ static void copy_stable_id_curves(const bke::CurvesGeometry &src_curves, static void duplicate_curves(GeometrySet &geometry_set, const Field &count_field, const Field &selection_field, - const IndexAttributes &attribute_outputs) + const IndexAttributes &attribute_outputs, + const AnonymousAttributePropagationInfo &propagation_info) { if (!geometry_set.has_curves()) { geometry_set.remove_geometry_during_modify(); @@ -373,7 +379,8 @@ static void duplicate_curves(GeometrySet &geometry_set, }); all_dst_offsets.last() = dst_points_num; - copy_curve_attributes_without_id(geometry_set, curves, selection, curve_offsets, new_curves); + copy_curve_attributes_without_id( + geometry_set, curves, selection, curve_offsets, propagation_info, new_curves); copy_stable_id_curves(curves, selection, curve_offsets, new_curves); @@ -398,17 +405,19 @@ static void duplicate_curves(GeometrySet &geometry_set, * Copies the attributes for face duplicates. If copying the face domain, the attributes are * copied with an offset fill, otherwise a mapping is used. */ -static void copy_face_attributes_without_id(GeometrySet &geometry_set, - const Span edge_mapping, - const Span vert_mapping, - const Span loop_mapping, - const Span offsets, - const IndexMask selection, - const bke::AttributeAccessor src_attributes, - bke::MutableAttributeAccessor dst_attributes) +static void copy_face_attributes_without_id( + GeometrySet &geometry_set, + const Span edge_mapping, + const Span vert_mapping, + const Span loop_mapping, + const Span offsets, + const IndexMask selection, + const AnonymousAttributePropagationInfo &propagation_info, + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes) { Map attributes = gather_attributes_without_id( - geometry_set, GEO_COMPONENT_TYPE_MESH); + geometry_set, GEO_COMPONENT_TYPE_MESH, propagation_info); for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; @@ -506,7 +515,8 @@ static void copy_stable_id_faces(const Mesh &mesh, static void duplicate_faces(GeometrySet &geometry_set, const Field &count_field, const Field &selection_field, - const IndexAttributes &attribute_outputs) + const IndexAttributes &attribute_outputs, + const AnonymousAttributePropagationInfo &propagation_info) { if (!geometry_set.has_mesh()) { geometry_set.remove_geometry_during_modify(); @@ -588,6 +598,7 @@ static void duplicate_faces(GeometrySet &geometry_set, loop_mapping, offsets, selection, + propagation_info, mesh.attributes(), new_mesh->attributes_for_write()); @@ -612,15 +623,17 @@ static void duplicate_faces(GeometrySet &geometry_set, * Copies the attributes for edge duplicates. If copying the edge domain, the attributes are * copied with an offset fill, for point domain a mapping is used. */ -static void copy_edge_attributes_without_id(GeometrySet &geometry_set, - const Span point_mapping, - const Span offsets, - const IndexMask selection, - const bke::AttributeAccessor src_attributes, - bke::MutableAttributeAccessor dst_attributes) +static void copy_edge_attributes_without_id( + GeometrySet &geometry_set, + const Span point_mapping, + const Span offsets, + const IndexMask selection, + const AnonymousAttributePropagationInfo &propagation_info, + const bke::AttributeAccessor src_attributes, + bke::MutableAttributeAccessor dst_attributes) { Map attributes = gather_attributes_without_id( - geometry_set, GEO_COMPONENT_TYPE_MESH); + geometry_set, GEO_COMPONENT_TYPE_MESH, propagation_info); for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; @@ -704,7 +717,8 @@ static void copy_stable_id_edges(const Mesh &mesh, static void duplicate_edges(GeometrySet &geometry_set, const Field &count_field, const Field &selection_field, - const IndexAttributes &attribute_outputs) + const IndexAttributes &attribute_outputs, + const AnonymousAttributePropagationInfo &propagation_info) { if (!geometry_set.has_mesh()) { geometry_set.remove_geometry_during_modify(); @@ -756,6 +770,7 @@ static void duplicate_edges(GeometrySet &geometry_set, vert_orig_indices, edge_offsets, selection, + propagation_info, mesh.attributes(), new_mesh->attributes_for_write()); @@ -782,7 +797,8 @@ static void duplicate_edges(GeometrySet &geometry_set, static void duplicate_points_curve(GeometrySet &geometry_set, const Field &count_field, const Field &selection_field, - const IndexAttributes &attribute_outputs) + const IndexAttributes &attribute_outputs, + const AnonymousAttributePropagationInfo &propagation_info) { const Curves &src_curves_id = *geometry_set.get_curves_for_read(); const bke::CurvesGeometry &src_curves = bke::CurvesGeometry::wrap(src_curves_id.geometry); @@ -819,7 +835,7 @@ static void duplicate_points_curve(GeometrySet &geometry_set, new_curve_offsets.last() = dst_num; Map attributes = gather_attributes_without_id( - geometry_set, GEO_COMPONENT_TYPE_CURVE); + geometry_set, GEO_COMPONENT_TYPE_CURVE, propagation_info); for (const Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; @@ -885,7 +901,8 @@ static void duplicate_points_curve(GeometrySet &geometry_set, static void duplicate_points_mesh(GeometrySet &geometry_set, const Field &count_field, const Field &selection_field, - const IndexAttributes &attribute_outputs) + const IndexAttributes &attribute_outputs, + const AnonymousAttributePropagationInfo &propagation_info) { const Mesh &mesh = *geometry_set.get_mesh_for_read(); const Span src_verts = mesh.verts(); @@ -910,6 +927,7 @@ static void duplicate_points_mesh(GeometrySet &geometry_set, ATTR_DOMAIN_POINT, offsets, selection, + propagation_info, mesh.attributes(), new_mesh->attributes_for_write()); @@ -935,7 +953,8 @@ static void duplicate_points_mesh(GeometrySet &geometry_set, static void duplicate_points_pointcloud(GeometrySet &geometry_set, const Field &count_field, const Field &selection_field, - const IndexAttributes &attribute_outputs) + const IndexAttributes &attribute_outputs, + const AnonymousAttributePropagationInfo &propagation_info) { const PointCloud &src_points = *geometry_set.get_pointcloud_for_read(); @@ -956,6 +975,7 @@ static void duplicate_points_pointcloud(GeometrySet &geometry_set, ATTR_DOMAIN_POINT, offsets, selection, + propagation_info, src_points.attributes(), pointcloud->attributes_for_write()); @@ -980,7 +1000,8 @@ static void duplicate_points_pointcloud(GeometrySet &geometry_set, static void duplicate_points(GeometrySet &geometry_set, const Field &count_field, const Field &selection_field, - const IndexAttributes &attribute_outputs) + const IndexAttributes &attribute_outputs, + const AnonymousAttributePropagationInfo &propagation_info) { Vector component_types = geometry_set.gather_component_types(true, true); for (const GeometryComponentType component_type : component_types) { @@ -988,17 +1009,19 @@ static void duplicate_points(GeometrySet &geometry_set, case GEO_COMPONENT_TYPE_POINT_CLOUD: if (geometry_set.has_pointcloud()) { duplicate_points_pointcloud( - geometry_set, count_field, selection_field, attribute_outputs); + geometry_set, count_field, selection_field, attribute_outputs, propagation_info); } break; case GEO_COMPONENT_TYPE_MESH: if (geometry_set.has_mesh()) { - duplicate_points_mesh(geometry_set, count_field, selection_field, attribute_outputs); + duplicate_points_mesh( + geometry_set, count_field, selection_field, attribute_outputs, propagation_info); } break; case GEO_COMPONENT_TYPE_CURVE: if (geometry_set.has_curves()) { - duplicate_points_curve(geometry_set, count_field, selection_field, attribute_outputs); + duplicate_points_curve( + geometry_set, count_field, selection_field, attribute_outputs, propagation_info); } break; default: @@ -1018,7 +1041,8 @@ static void duplicate_points(GeometrySet &geometry_set, static void duplicate_instances(GeometrySet &geometry_set, const Field &count_field, const Field &selection_field, - const IndexAttributes &attribute_outputs) + const IndexAttributes &attribute_outputs, + const AnonymousAttributePropagationInfo &propagation_info) { if (!geometry_set.has_instances()) { geometry_set.clear(); @@ -1062,6 +1086,7 @@ static void duplicate_instances(GeometrySet &geometry_set, ATTR_DOMAIN_INSTANCE, offsets, selection, + propagation_info, src_instances.attributes(), dst_instances->attributes_for_write()); @@ -1092,27 +1117,34 @@ static void node_geo_exec(GeoNodeExecParams params) Field count_field = params.extract_input>("Amount"); Field selection_field = params.extract_input>("Selection"); IndexAttributes attribute_outputs; - if (params.output_is_required("Duplicate Index")) { - attribute_outputs.duplicate_index = StrongAnonymousAttributeID("duplicate_index"); - } + attribute_outputs.duplicate_index = params.get_output_anonymous_attribute_id_if_needed( + "Duplicate Index"); + + const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( + "Geometry"); if (duplicate_domain == ATTR_DOMAIN_INSTANCE) { - duplicate_instances(geometry_set, count_field, selection_field, attribute_outputs); + duplicate_instances( + geometry_set, count_field, selection_field, attribute_outputs, propagation_info); } else { geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { switch (duplicate_domain) { case ATTR_DOMAIN_CURVE: - duplicate_curves(geometry_set, count_field, selection_field, attribute_outputs); + duplicate_curves( + geometry_set, count_field, selection_field, attribute_outputs, propagation_info); break; case ATTR_DOMAIN_FACE: - duplicate_faces(geometry_set, count_field, selection_field, attribute_outputs); + duplicate_faces( + geometry_set, count_field, selection_field, attribute_outputs, propagation_info); break; case ATTR_DOMAIN_EDGE: - duplicate_edges(geometry_set, count_field, selection_field, attribute_outputs); + duplicate_edges( + geometry_set, count_field, selection_field, attribute_outputs, propagation_info); break; case ATTR_DOMAIN_POINT: - duplicate_points(geometry_set, count_field, selection_field, attribute_outputs); + duplicate_points( + geometry_set, count_field, selection_field, attribute_outputs, propagation_info); break; default: BLI_assert_unreachable(); diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_paths_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_edge_paths_to_curves.cc index c70d45ce334..772c4721284 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_edge_paths_to_curves.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_edge_paths_to_curves.cc @@ -18,9 +18,11 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output(N_("Curves")).propagate_all(); } -static Curves *edge_paths_to_curves_convert(const Mesh &mesh, - const IndexMask start_verts_mask, - const Span next_indices) +static Curves *edge_paths_to_curves_convert( + const Mesh &mesh, + const IndexMask start_verts_mask, + const Span next_indices, + const AnonymousAttributePropagationInfo &propagation_info) { Vector vert_indices; Vector curve_offsets; @@ -58,8 +60,8 @@ static Curves *edge_paths_to_curves_convert(const Mesh &mesh, if (vert_indices.is_empty()) { return nullptr; } - Curves *curves_id = bke::curves_new_nomain( - geometry::create_curve_from_vert_indices(mesh, vert_indices, curve_offsets, IndexRange(0))); + Curves *curves_id = bke::curves_new_nomain(geometry::create_curve_from_vert_indices( + mesh, vert_indices, curve_offsets, IndexRange(0), propagation_info)); return curves_id; } @@ -87,7 +89,8 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - geometry_set.replace_curves(edge_paths_to_curves_convert(*mesh, start_verts, next_vert)); + geometry_set.replace_curves(edge_paths_to_curves_convert( + *mesh, start_verts, next_vert, params.get_output_propagation_info("Curves"))); geometry_set.keep_only({GEO_COMPONENT_TYPE_CURVE, GEO_COMPONENT_TYPE_INSTANCES}); }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc index 057c09c9936..2948713852b 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc @@ -33,7 +33,8 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - geometry::split_edges(*geometry_set.get_mesh_for_write(), mask); + geometry::split_edges( + *geometry_set.get_mesh_for_write(), mask, params.get_output_propagation_info("Mesh")); } }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc index a36413e49b2..27f34db2f9f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_extrude_mesh.cc @@ -27,7 +27,7 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_input(N_("Selection")).default_value(true).field_on_all().hide_value(); b.add_input(N_("Offset")) .subtype(PROP_TRANSLATION) - .implicit_field(implicit_field_inputs::normal) + .implicit_field_on_all(implicit_field_inputs::normal) .hide_value(); b.add_input(N_("Offset Scale")).default_value(1.0f).field_on_all(); b.add_input(N_("Individual")).default_value(true); @@ -61,8 +61,8 @@ static void node_update(bNodeTree *ntree, bNode *node) } struct AttributeOutputs { - StrongAnonymousAttributeID top_id; - StrongAnonymousAttributeID side_id; + AutoAnonymousAttributeID top_id; + AutoAnonymousAttributeID side_id; }; static void save_selection_as_attribute(Mesh &mesh, @@ -1334,12 +1334,8 @@ static void node_geo_exec(GeoNodeExecParams params) const Field final_offset{std::move(multiply_op)}; AttributeOutputs attribute_outputs; - if (params.output_is_required("Top")) { - attribute_outputs.top_id = StrongAnonymousAttributeID("Top"); - } - if (params.output_is_required("Side")) { - attribute_outputs.side_id = StrongAnonymousAttributeID("Side"); - } + attribute_outputs.top_id = params.get_output_anonymous_attribute_id_if_needed("Top"); + attribute_outputs.side_id = params.get_output_anonymous_attribute_id_if_needed("Side"); const bool extrude_individual = mode == GEO_NODE_EXTRUDE_MESH_FACES && params.extract_input("Individual"); diff --git a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc index 44172cfee60..6c38c8a91ab 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_instance_on_points.cc @@ -198,8 +198,11 @@ static void node_geo_exec(GeoNodeExecParams params) GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}; Map attributes_to_propagate; - geometry_set.gather_attributes_for_propagation( - types, GEO_COMPONENT_TYPE_INSTANCES, false, attributes_to_propagate); + geometry_set.gather_attributes_for_propagation(types, + GEO_COMPONENT_TYPE_INSTANCES, + false, + params.get_output_propagation_info("Instances"), + attributes_to_propagate); attributes_to_propagate.remove("position"); for (const GeometryComponentType type : types) { diff --git a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc index e43aac9d3ad..0be8743f9a1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_instances_to_points.cc @@ -26,7 +26,8 @@ static void node_declare(NodeDeclarationBuilder &b) static void convert_instances_to_points(GeometrySet &geometry_set, Field position_field, Field radius_field, - const Field selection_field) + const Field selection_field, + const AnonymousAttributePropagationInfo &propagation_info) { const bke::Instances &instances = *geometry_set.get_instances_for_read(); @@ -62,6 +63,7 @@ static void convert_instances_to_points(GeometrySet &geometry_set, geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_INSTANCES}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, + propagation_info, attributes_to_propagate); /* These two attributes are added by the implicit inputs above. */ attributes_to_propagate.remove("position"); @@ -91,7 +93,8 @@ static void node_geo_exec(GeoNodeExecParams params) convert_instances_to_points(geometry_set, params.extract_input>("Position"), params.extract_input>("Radius"), - params.extract_input>("Selection")); + params.extract_input>("Selection"), + params.get_output_propagation_info("Points")); geometry_set.keep_only({GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_EDIT}); params.set_output("Points", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc index 03ecb7c4c2f..d49f2583bde 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc @@ -28,7 +28,7 @@ static Map get_final_attribute_info( for (const GeometryComponent *component : components) { component->attributes()->for_all( [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) { + if (ignored_attributes.contains(attribute_id.name())) { return true; } if (meta_data.data_type == CD_PROP_STRING) { @@ -143,7 +143,9 @@ static void join_components(Span /*src_components*/, } template -static void join_component_type(Span src_geometry_sets, GeometrySet &result) +static void join_component_type(Span src_geometry_sets, + GeometrySet &result, + const AnonymousAttributePropagationInfo &propagation_info) { Vector components; for (const GeometrySet &geometry_set : src_geometry_sets) { @@ -176,6 +178,7 @@ static void join_component_type(Span src_geometry_sets, GeometrySet geometry::RealizeInstancesOptions options; options.keep_original_ids = true; options.realize_instance_attributes = false; + options.propagation_info = propagation_info; GeometrySet joined_components = geometry::realize_instances( GeometrySet::create_with_instances(instances.release()), options); result.add(joined_components.get_component_for_write()); @@ -186,13 +189,17 @@ static void node_geo_exec(GeoNodeExecParams params) { Vector geometry_sets = params.extract_input>("Geometry"); + const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( + "Geometry"); + GeometrySet geometry_set_result; - join_component_type(geometry_sets, geometry_set_result); - join_component_type(geometry_sets, geometry_set_result); - join_component_type(geometry_sets, geometry_set_result); - join_component_type(geometry_sets, geometry_set_result); - join_component_type(geometry_sets, geometry_set_result); - join_component_type(geometry_sets, geometry_set_result); + join_component_type(geometry_sets, geometry_set_result, propagation_info); + join_component_type(geometry_sets, geometry_set_result, propagation_info); + join_component_type(geometry_sets, geometry_set_result, propagation_info); + join_component_type(geometry_sets, geometry_set_result, propagation_info); + join_component_type(geometry_sets, geometry_set_result, propagation_info); + join_component_type( + geometry_sets, geometry_set_result, propagation_info); params.set_output("Geometry", std::move(geometry_set_result)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc b/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc index 04c48a82e42..2fafc77bc45 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_merge_by_distance.cc @@ -38,9 +38,11 @@ static void node_init(bNodeTree * /*tree*/, bNode *node) node->storage = data; } -static PointCloud *pointcloud_merge_by_distance(const PointCloud &src_points, - const float merge_distance, - const Field &selection_field) +static PointCloud *pointcloud_merge_by_distance( + const PointCloud &src_points, + const float merge_distance, + const Field &selection_field, + const AnonymousAttributePropagationInfo &propagation_info) { bke::PointCloudFieldContext context{src_points}; FieldEvaluator evaluator{context, src_points.totpoint}; @@ -52,7 +54,8 @@ static PointCloud *pointcloud_merge_by_distance(const PointCloud &src_points, return nullptr; } - return geometry::point_merge_by_distance(src_points, merge_distance, selection); + return geometry::point_merge_by_distance( + src_points, merge_distance, selection, propagation_info); } static std::optional mesh_merge_by_distance_connected(const Mesh &mesh, @@ -97,7 +100,8 @@ static void node_geo_exec(GeoNodeExecParams params) geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { if (const PointCloud *pointcloud = geometry_set.get_pointcloud_for_read()) { - PointCloud *result = pointcloud_merge_by_distance(*pointcloud, merge_distance, selection); + PointCloud *result = pointcloud_merge_by_distance( + *pointcloud, merge_distance, selection, params.get_output_propagation_info("Geometry")); if (result) { geometry_set.replace_pointcloud(result); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc index 8f261ad9e66..9e92527f393 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc @@ -814,18 +814,10 @@ static void node_geo_exec(GeoNodeExecParams params) const float depth = params.extract_input("Depth"); ConeAttributeOutputs attribute_outputs; - if (params.output_is_required("Top")) { - attribute_outputs.top_id = StrongAnonymousAttributeID("top_selection"); - } - if (params.output_is_required("Bottom")) { - attribute_outputs.bottom_id = StrongAnonymousAttributeID("bottom_selection"); - } - if (params.output_is_required("Side")) { - attribute_outputs.side_id = StrongAnonymousAttributeID("side_selection"); - } - if (params.output_is_required("UV Map")) { - attribute_outputs.uv_map_id = StrongAnonymousAttributeID("uv_map"); - } + attribute_outputs.top_id = params.get_output_anonymous_attribute_id_if_needed("Top"); + attribute_outputs.bottom_id = params.get_output_anonymous_attribute_id_if_needed("Bottom"); + attribute_outputs.side_id = params.get_output_anonymous_attribute_id_if_needed("Side"); + attribute_outputs.uv_map_id = params.get_output_anonymous_attribute_id_if_needed("UV Map"); Mesh *mesh = create_cylinder_or_cone_mesh(radius_top, radius_bottom, diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc index 879de80e73d..88fb4c72b1a 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cube.cc @@ -107,10 +107,8 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - StrongAnonymousAttributeID uv_map_id; - if (params.output_is_required("UV Map")) { - uv_map_id = StrongAnonymousAttributeID("uv_map"); - } + AutoAnonymousAttributeID uv_map_id = params.get_output_anonymous_attribute_id_if_needed( + "UV Map"); Mesh *mesh = create_cube_mesh(size, verts_x, verts_y, verts_z, uv_map_id.get()); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc index b0e857feb64..bc651cdb652 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cylinder.cc @@ -107,18 +107,10 @@ static void node_geo_exec(GeoNodeExecParams params) } ConeAttributeOutputs attribute_outputs; - if (params.output_is_required("Top")) { - attribute_outputs.top_id = StrongAnonymousAttributeID("top_selection"); - } - if (params.output_is_required("Bottom")) { - attribute_outputs.bottom_id = StrongAnonymousAttributeID("bottom_selection"); - } - if (params.output_is_required("Side")) { - attribute_outputs.side_id = StrongAnonymousAttributeID("side_selection"); - } - if (params.output_is_required("UV Map")) { - attribute_outputs.uv_map_id = StrongAnonymousAttributeID("uv_map"); - } + attribute_outputs.top_id = params.get_output_anonymous_attribute_id_if_needed("Top"); + attribute_outputs.bottom_id = params.get_output_anonymous_attribute_id_if_needed("Bottom"); + attribute_outputs.side_id = params.get_output_anonymous_attribute_id_if_needed("Side"); + attribute_outputs.uv_map_id = params.get_output_anonymous_attribute_id_if_needed("UV Map"); /* The cylinder is a special case of the cone mesh where the top and bottom radius are equal. */ Mesh *mesh = create_cylinder_or_cone_mesh(radius, diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc index 98ff797addc..b05a6d8019e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc @@ -194,10 +194,8 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - StrongAnonymousAttributeID uv_map_id; - if (params.output_is_required("UV Map")) { - uv_map_id = StrongAnonymousAttributeID("uv_map"); - } + AutoAnonymousAttributeID uv_map_id = params.get_output_anonymous_attribute_id_if_needed( + "UV Map"); Mesh *mesh = create_grid_mesh(verts_x, verts_y, size_x, size_y, uv_map_id.get()); BKE_id_material_eval_ensure_default_slot(&mesh->id); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc index cd8c6a19612..f0993da1ba2 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_ico_sphere.cc @@ -77,10 +77,8 @@ static void node_geo_exec(GeoNodeExecParams params) const int subdivisions = std::min(params.extract_input("Subdivisions"), 10); const float radius = params.extract_input("Radius"); - StrongAnonymousAttributeID uv_map_id; - if (params.output_is_required("UV Map")) { - uv_map_id = StrongAnonymousAttributeID("uv_map"); - } + AutoAnonymousAttributeID uv_map_id = params.get_output_anonymous_attribute_id_if_needed( + "UV Map"); Mesh *mesh = create_ico_sphere_mesh(subdivisions, radius, uv_map_id.get()); params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc index 5b424534d7b..ff40a970785 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc @@ -362,10 +362,8 @@ static void node_geo_exec(GeoNodeExecParams params) const float radius = params.extract_input("Radius"); - StrongAnonymousAttributeID uv_map_id; - if (params.output_is_required("UV Map")) { - uv_map_id = StrongAnonymousAttributeID("uv_map"); - } + AutoAnonymousAttributeID uv_map_id = params.get_output_anonymous_attribute_id_if_needed( + "UV Map"); Mesh *mesh = create_uv_sphere_mesh(radius, segments_num, rings_num, uv_map_id.get()); params.set_output("Mesh", GeometrySet::create_with_mesh(mesh)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc index 40626a1c2c5..2b80c6df51f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc @@ -36,7 +36,8 @@ static void node_geo_exec(GeoNodeExecParams params) return; } - bke::CurvesGeometry curves = geometry::mesh_to_curve_convert(*mesh, selection); + bke::CurvesGeometry curves = geometry::mesh_to_curve_convert( + *mesh, selection, params.get_output_propagation_info("Curve")); geometry_set.replace_curves(bke::curves_new_nomain(std::move(curves))); geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_CURVE}); }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc index 790f0ceda9c..f551faee2e0 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc @@ -24,7 +24,7 @@ static void node_declare(NodeDeclarationBuilder &b) { b.add_input(N_("Mesh")).supported_type(GEO_COMPONENT_TYPE_MESH); b.add_input(N_("Selection")).default_value(true).field_on_all().hide_value(); - b.add_input(N_("Position")).implicit_field(implicit_field_inputs::position); + b.add_input(N_("Position")).implicit_field_on_all(implicit_field_inputs::position); b.add_input(N_("Radius")) .default_value(0.05f) .min(0.0f) @@ -49,7 +49,8 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, Field &position_field, Field &radius_field, Field &selection_field, - const eAttrDomain domain) + const eAttrDomain domain, + const AnonymousAttributePropagationInfo &propagation_info) { const Mesh *mesh = geometry_set.get_mesh_for_read(); if (mesh == nullptr) { @@ -87,8 +88,11 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, radius.finish(); Map attributes; - geometry_set.gather_attributes_for_propagation( - {GEO_COMPONENT_TYPE_MESH}, GEO_COMPONENT_TYPE_POINT_CLOUD, false, attributes); + geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_MESH}, + GEO_COMPONENT_TYPE_POINT_CLOUD, + false, + propagation_info, + attributes); attributes.remove("position"); const AttributeAccessor src_attributes = mesh->attributes(); @@ -128,23 +132,42 @@ static void node_geo_exec(GeoNodeExecParams params) const NodeGeometryMeshToPoints &storage = node_storage(params.node()); const GeometryNodeMeshToPointsMode mode = (GeometryNodeMeshToPointsMode)storage.mode; + const AnonymousAttributePropagationInfo &propagation_info = params.get_output_propagation_info( + "Points"); + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { switch (mode) { case GEO_NODE_MESH_TO_POINTS_VERTICES: - geometry_set_mesh_to_points( - geometry_set, position, positive_radius, selection, ATTR_DOMAIN_POINT); + geometry_set_mesh_to_points(geometry_set, + position, + positive_radius, + selection, + ATTR_DOMAIN_POINT, + propagation_info); break; case GEO_NODE_MESH_TO_POINTS_EDGES: - geometry_set_mesh_to_points( - geometry_set, position, positive_radius, selection, ATTR_DOMAIN_EDGE); + geometry_set_mesh_to_points(geometry_set, + position, + positive_radius, + selection, + ATTR_DOMAIN_EDGE, + propagation_info); break; case GEO_NODE_MESH_TO_POINTS_FACES: - geometry_set_mesh_to_points( - geometry_set, position, positive_radius, selection, ATTR_DOMAIN_FACE); + geometry_set_mesh_to_points(geometry_set, + position, + positive_radius, + selection, + ATTR_DOMAIN_FACE, + propagation_info); break; case GEO_NODE_MESH_TO_POINTS_CORNERS: - geometry_set_mesh_to_points( - geometry_set, position, positive_radius, selection, ATTR_DOMAIN_CORNER); + geometry_set_mesh_to_points(geometry_set, + position, + positive_radius, + selection, + ATTR_DOMAIN_CORNER, + propagation_info); break; } }); diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc index 2e9d5037c00..5cd5bbe690e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc @@ -21,8 +21,10 @@ static void node_declare(NodeDeclarationBuilder &b) } /* One improvement would be to move the attribute arrays directly to the mesh when possible. */ -static void geometry_set_points_to_vertices(GeometrySet &geometry_set, - Field &selection_field) +static void geometry_set_points_to_vertices( + GeometrySet &geometry_set, + Field &selection_field, + const AnonymousAttributePropagationInfo &propagation_info) { const PointCloud *points = geometry_set.get_pointcloud_for_read(); if (points == nullptr) { @@ -41,8 +43,11 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set, const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0); Map attributes; - geometry_set.gather_attributes_for_propagation( - {GEO_COMPONENT_TYPE_POINT_CLOUD}, GEO_COMPONENT_TYPE_MESH, false, attributes); + geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_POINT_CLOUD}, + GEO_COMPONENT_TYPE_MESH, + false, + propagation_info, + attributes); Mesh *mesh = BKE_mesh_new_nomain(selection.size(), 0, 0, 0, 0); geometry_set.replace_mesh(mesh); @@ -73,7 +78,8 @@ static void node_geo_exec(GeoNodeExecParams params) Field selection_field = params.extract_input>("Selection"); geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - geometry_set_points_to_vertices(geometry_set, selection_field); + geometry_set_points_to_vertices( + geometry_set, selection_field, params.get_output_propagation_info("Mesh")); }); params.set_output("Mesh", std::move(geometry_set)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc b/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc index 920d3f77666..385b47a2f1e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_realize_instances.cc @@ -37,6 +37,7 @@ static void node_geo_exec(GeoNodeExecParams params) geometry::RealizeInstancesOptions options; options.keep_original_ids = legacy_behavior; options.realize_instance_attributes = !legacy_behavior; + options.propagation_info = params.get_output_propagation_info("Geometry"); geometry_set = geometry::realize_instances(geometry_set, options); params.set_output("Geometry", std::move(geometry_set)); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_separate_geometry.cc b/source/blender/nodes/geometry/nodes/node_geo_separate_geometry.cc index 8b01e147d0d..28c0bf84160 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_separate_geometry.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_separate_geometry.cc @@ -47,29 +47,42 @@ static void node_geo_exec(GeoNodeExecParams params) const NodeGeometrySeparateGeometry &storage = node_storage(params.node()); const eAttrDomain domain = eAttrDomain(storage.domain); - auto separate_geometry_maybe_recursively = [&](GeometrySet &geometry_set, - const Field &selection) { - bool is_error; - if (domain == ATTR_DOMAIN_INSTANCE) { - /* Only delete top level instances. */ - separate_geometry( - geometry_set, domain, GEO_NODE_DELETE_GEOMETRY_MODE_ALL, selection, is_error); - } - else { - geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { - separate_geometry( - geometry_set, domain, GEO_NODE_DELETE_GEOMETRY_MODE_ALL, selection, is_error); - }); - } - }; + auto separate_geometry_maybe_recursively = + [&](GeometrySet &geometry_set, + const Field &selection, + const AnonymousAttributePropagationInfo &propagation_info) { + bool is_error; + if (domain == ATTR_DOMAIN_INSTANCE) { + /* Only delete top level instances. */ + separate_geometry(geometry_set, + domain, + GEO_NODE_DELETE_GEOMETRY_MODE_ALL, + selection, + propagation_info, + is_error); + } + else { + geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) { + separate_geometry(geometry_set, + domain, + GEO_NODE_DELETE_GEOMETRY_MODE_ALL, + selection, + propagation_info, + is_error); + }); + } + }; GeometrySet second_set(geometry_set); if (params.output_is_required("Selection")) { - separate_geometry_maybe_recursively(geometry_set, selection_field); + separate_geometry_maybe_recursively( + geometry_set, selection_field, params.get_output_propagation_info("Selection")); params.set_output("Selection", std::move(geometry_set)); } if (params.output_is_required("Inverted")) { - separate_geometry_maybe_recursively(second_set, fn::invert_boolean_field(selection_field)); + separate_geometry_maybe_recursively(second_set, + fn::invert_boolean_field(selection_field), + params.get_output_propagation_info("Inverted")); params.set_output("Inverted", std::move(second_set)); } } diff --git a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc index 93dcdd37ed2..5eb98fe8f01 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_string_to_curves.cc @@ -249,7 +249,7 @@ static std::optional get_text_layout(GeoNodeExecParams ¶ms) } } - if (params.output_is_required("Line")) { + if (params.anonymous_attribute_output_is_required("Line")) { layout.line_numbers.reinitialize(layout.positions.size()); for (const int i : layout.positions.index_range()) { CharTrans &ct = chartransdata[i]; @@ -278,7 +278,7 @@ static Map create_curve_instances(GeoNodeExecParams ¶ms, { VFont *vfont = reinterpret_cast(params.node().id); Map handles; - bool pivot_required = params.output_is_required("Pivot Point"); + bool pivot_required = params.anonymous_attribute_output_is_required("Pivot Point"); for (int i : layout.char_codes.index_range()) { if (handles.contains(layout.char_codes[i])) { @@ -341,10 +341,10 @@ static void create_attributes(GeoNodeExecParams ¶ms, { MutableAttributeAccessor attributes = instances.attributes_for_write(); - if (params.output_is_required("Line")) { - StrongAnonymousAttributeID line_id = StrongAnonymousAttributeID("Line"); + if (AutoAnonymousAttributeID line_id = params.get_output_anonymous_attribute_id_if_needed( + "Line")) { SpanAttributeWriter line_attribute = attributes.lookup_or_add_for_write_only_span( - line_id.get(), ATTR_DOMAIN_INSTANCE); + *line_id, ATTR_DOMAIN_INSTANCE); line_attribute.span.copy_from(layout.line_numbers); line_attribute.finish(); params.set_output("Line", @@ -352,10 +352,10 @@ static void create_attributes(GeoNodeExecParams ¶ms, params.attribute_producer_name())); } - if (params.output_is_required("Pivot Point")) { - StrongAnonymousAttributeID pivot_id = StrongAnonymousAttributeID("Pivot"); + if (AutoAnonymousAttributeID pivot_id = params.get_output_anonymous_attribute_id_if_needed( + "Pivot Point")) { SpanAttributeWriter pivot_attribute = - attributes.lookup_or_add_for_write_only_span(pivot_id.get(), ATTR_DOMAIN_INSTANCE); + attributes.lookup_or_add_for_write_only_span(*pivot_id, ATTR_DOMAIN_INSTANCE); for (const int i : layout.char_codes.index_range()) { pivot_attribute.span[i] = layout.pivot_points.lookup(layout.char_codes[i]); diff --git a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc index 454fe4da23b..bfda854d4c2 100644 --- a/source/blender/nodes/intern/geometry_nodes_lazy_function.cc +++ b/source/blender/nodes/intern/geometry_nodes_lazy_function.cc @@ -17,6 +17,8 @@ #include "NOD_node_declaration.hh" #include "BLI_cpp_types.hh" +#include "BLI_dot_export.hh" +#include "BLI_hash.h" #include "BLI_lazy_threading.hh" #include "BLI_map.hh" @@ -111,6 +113,16 @@ class LazyFunctionForGeometryNode : public LazyFunction { const bNode &node_; public: + /** + * Index of a boolean input that indicates whether the output socket is used. + */ + Map lf_input_for_output_bsocket_usage_; + /** + * Index of an attribute set input that indicates which anonymous attributes should be + * propagated to the output. + */ + Map lf_input_for_attribute_propagation_to_output_; + LazyFunctionForGeometryNode(const bNode &node, Vector &r_used_inputs, Vector &r_used_outputs) @@ -119,6 +131,32 @@ class LazyFunctionForGeometryNode : public LazyFunction { BLI_assert(node.typeinfo->geometry_node_execute != nullptr); debug_name_ = node.name; lazy_function_interface_from_node(node, r_used_inputs, r_used_outputs, inputs_, outputs_); + + const NodeDeclaration &node_decl = *node.declaration(); + const aal::RelationsInNode *relations = node_decl.anonymous_attribute_relations(); + if (relations == nullptr) { + return; + } + Vector handled_field_outputs; + for (const aal::AvailableRelation &relation : relations->available_relations) { + const bNodeSocket &output_bsocket = node.output_socket(relation.field_output); + if (output_bsocket.is_available() && !handled_field_outputs.contains(&output_bsocket)) { + handled_field_outputs.append(&output_bsocket); + const int lf_index = inputs_.append_and_get_index_as("Output Used", CPPType::get()); + lf_input_for_output_bsocket_usage_.add(output_bsocket.identifier, lf_index); + } + } + + Vector handled_geometry_outputs; + for (const aal::PropagateRelation &relation : relations->propagate_relations) { + const bNodeSocket &output_bsocket = node.output_socket(relation.to_geometry_output); + if (output_bsocket.is_available() && !handled_geometry_outputs.contains(&output_bsocket)) { + handled_geometry_outputs.append(&output_bsocket); + const int lf_index = inputs_.append_and_get_index_as( + "Propagate to Output", CPPType::get()); + lf_input_for_attribute_propagation_to_output_.add(output_bsocket.identifier, lf_index); + } + } } void execute_impl(lf::Params ¶ms, const lf::Context &context) const override @@ -126,7 +164,11 @@ class LazyFunctionForGeometryNode : public LazyFunction { GeoNodesLFUserData *user_data = dynamic_cast(context.user_data); BLI_assert(user_data != nullptr); - GeoNodeExecParams geo_params{node_, params, context}; + GeoNodeExecParams geo_params{node_, + params, + context, + lf_input_for_output_bsocket_usage_, + lf_input_for_attribute_propagation_to_output_}; geo_eval_log::TimePoint start_time = geo_eval_log::Clock::now(); node_.typeinfo->geometry_node_execute(geo_params); @@ -138,6 +180,27 @@ class LazyFunctionForGeometryNode : public LazyFunction { tree_logger.node_execution_times.append({node_.identifier, start_time, end_time}); } } + + std::string input_name(const int index) const override + { + for (const auto [identifier, lf_index] : lf_input_for_output_bsocket_usage_.items()) { + if (index == lf_index) { + return "Use Output '" + identifier + "'"; + } + } + for (const auto [identifier, lf_index] : + lf_input_for_attribute_propagation_to_output_.items()) { + if (index == lf_index) { + return "Propagate to '" + identifier + "'"; + } + } + return inputs_[index].debug_name; + } + + std::string output_name(const int index) const override + { + return outputs_[index].debug_name; + } }; /** @@ -588,6 +651,35 @@ class LazyFunctionForViewerNode : public LazyFunction { } }; +/** + * Outputs true when a specific viewer node is used in the current context and false otherwise. + */ +class LazyFunctionForViewerInputUsage : public LazyFunction { + private: + const lf::FunctionNode &lf_viewer_node_; + + public: + LazyFunctionForViewerInputUsage(const lf::FunctionNode &lf_viewer_node) + : lf_viewer_node_(lf_viewer_node) + { + debug_name_ = "Viewer Input Usage"; + outputs_.append_as("Viewer is Used", CPPType::get()); + } + + void execute_impl(lf::Params ¶ms, const lf::Context &context) const override + { + GeoNodesLFUserData *user_data = dynamic_cast(context.user_data); + BLI_assert(user_data != nullptr); + const ComputeContextHash &context_hash = user_data->compute_context->hash(); + const GeoNodesModifierData &modifier_data = *user_data->modifier_data; + const Span nodes_with_side_effects = + modifier_data.side_effect_nodes->lookup(context_hash); + + const bool viewer_is_used = nodes_with_side_effects.contains(&lf_viewer_node_); + params.set_output(0, viewer_is_used); + } +}; + /** * This lazy-function wraps a group node. Internally it just executes the lazy-function graph of * the referenced group. @@ -596,7 +688,6 @@ class LazyFunctionForGroupNode : public LazyFunction { private: const bNode &group_node_; bool has_many_nodes_ = false; - bool use_fallback_outputs_ = false; std::optional lf_logger_; std::optional lf_side_effect_provider_; std::optional graph_executor_; @@ -608,40 +699,71 @@ class LazyFunctionForGroupNode : public LazyFunction { }; public: + /** + * For every input bsocket there is a corresponding boolean output that indicates whether that + * input is used. + */ + Map lf_output_for_input_bsocket_usage_; + /** + * For every output bsocket there is a corresponding boolean input that indicates whether the + * output is used. + */ + Map lf_input_for_output_bsocket_usage_; + /** + * For every geometry output that can propagate attributes from an input, there is an attribute + * set input. It indicates which attributes should be propagated to the output. + */ + Map lf_input_for_attribute_propagation_to_output_; + LazyFunctionForGroupNode(const bNode &group_node, - const GeometryNodesLazyFunctionGraphInfo &lf_graph_info, - Vector &r_used_inputs, - Vector &r_used_outputs) + const GeometryNodesLazyFunctionGraphInfo &lf_graph_info) : group_node_(group_node) { debug_name_ = group_node.name; - lazy_function_interface_from_node( - group_node, r_used_inputs, r_used_outputs, inputs_, outputs_); + allow_missing_requested_inputs_ = true; - bNodeTree *group_btree = reinterpret_cast(group_node_.id); - BLI_assert(group_btree != nullptr); + Vector tmp_inputs; + Vector tmp_outputs; + lazy_function_interface_from_node(group_node, tmp_inputs, tmp_outputs, inputs_, outputs_); has_many_nodes_ = lf_graph_info.num_inline_nodes_approximate > 1000; Vector graph_inputs; - for (const lf::OutputSocket *socket : lf_graph_info.mapping.group_input_sockets) { - if (socket != nullptr) { - graph_inputs.append(socket); - } + /* Add inputs that also exist on the bnode. */ + graph_inputs.extend(lf_graph_info.mapping.group_input_sockets); + + /* Add a boolean input for every output bsocket that indicates whether that socket is used. */ + for (const int i : group_node.output_sockets().index_range()) { + lf_input_for_output_bsocket_usage_.add_new( + i, + graph_inputs.append_and_get_index(lf_graph_info.mapping.group_output_used_sockets[i])); + inputs_.append_as("Output is Used", CPPType::get(), lf::ValueUsage::Maybe); } + graph_inputs.extend(lf_graph_info.mapping.group_output_used_sockets); + + /* Add an attribute set input for every output geometry socket that can propagate attributes + * from inputs. */ + for (auto [output_index, lf_socket] : + lf_graph_info.mapping.attribute_set_by_geometry_output.items()) { + const int lf_index = inputs_.append_and_get_index_as( + "Attribute Set", CPPType::get(), lf::ValueUsage::Maybe); + graph_inputs.append(lf_socket); + lf_input_for_attribute_propagation_to_output_.add(output_index, lf_index); + } + Vector graph_outputs; - if (const bNode *group_output_bnode = group_btree->group_output_node()) { - for (const bNodeSocket *bsocket : group_output_bnode->input_sockets().drop_back(1)) { - const lf::Socket *socket = lf_graph_info.mapping.dummy_socket_map.lookup_default(bsocket, - nullptr); - if (socket != nullptr) { - graph_outputs.append(&socket->as_input()); - } + /* Add outputs that also exist on the bnode. */ + graph_outputs.extend(lf_graph_info.mapping.standard_group_output_sockets); + /* Add a boolean output for every input bsocket that indicates whether that socket is used. */ + for (const int i : group_node.input_sockets().index_range()) { + const InputUsageHint &input_usage_hint = lf_graph_info.mapping.group_input_usage_hints[i]; + if (input_usage_hint.type == InputUsageHintType::DynamicSocket) { + const lf::InputSocket *lf_socket = lf_graph_info.mapping.group_input_usage_sockets[i]; + lf_output_for_input_bsocket_usage_.add_new(i, + graph_outputs.append_and_get_index(lf_socket)); + outputs_.append_as("Input is Used", CPPType::get()); } } - else { - use_fallback_outputs_ = true; - } lf_logger_.emplace(lf_graph_info); lf_side_effect_provider_.emplace(); @@ -662,11 +784,6 @@ class LazyFunctionForGroupNode : public LazyFunction { * if every individual node is very small. */ lazy_threading::send_hint(); } - if (use_fallback_outputs_) { - /* The node group itself does not have an output node, so use default values as outputs. - * The group should still be executed in case it has side effects. */ - params.set_default_remaining_outputs(); - } Storage *storage = static_cast(context.storage); @@ -702,6 +819,53 @@ class LazyFunctionForGroupNode : public LazyFunction { graph_executor_->destruct_storage(s->graph_executor_storage); std::destroy_at(s); } + + std::string name() const override + { + std::stringstream ss; + ss << "Group '" << (group_node_.id->name + 2) << "' (" << group_node_.name << ")"; + return ss.str(); + } + + std::string input_name(const int i) const override + { + if (i < group_node_.input_sockets().size()) { + return group_node_.input_socket(i).name; + } + for (const auto [bsocket_index, lf_socket_index] : + lf_input_for_output_bsocket_usage_.items()) { + if (i == lf_socket_index) { + std::stringstream ss; + ss << "'" << group_node_.output_socket(bsocket_index).name << "' output is used"; + return ss.str(); + } + } + for (const auto [bsocket_index, lf_index] : + lf_input_for_attribute_propagation_to_output_.items()) { + if (i == lf_index) { + std::stringstream ss; + ss << "Propagate to '" << group_node_.output_socket(bsocket_index).name << "'"; + return ss.str(); + } + } + return inputs_[i].debug_name; + } + + std::string output_name(const int i) const override + { + if (i < group_node_.output_sockets().size()) { + return group_node_.output_socket(i).name; + } + for (const auto [bsocket_index, lf_socket_index] : + lf_output_for_input_bsocket_usage_.items()) { + if (i == lf_socket_index) { + std::stringstream ss; + ss << "'" << group_node_.input_socket(bsocket_index).name << "' input is used"; + return ss.str(); + } + } + return outputs_[i].debug_name; + } }; static GMutablePointer get_socket_default_value(LinearAllocator<> &allocator, @@ -747,6 +911,230 @@ class GroupOutputDebugInfo : public lf::DummyDebugInfo { } }; +/** + * Computes the logical or of the inputs and supports short-circuit evaluation (i.e. if the first + * input is true already, the other inputs are not checked). + */ +class LazyFunctionForLogicalOr : public lf::LazyFunction { + public: + LazyFunctionForLogicalOr(const int inputs_num) + { + debug_name_ = "Logical Or"; + for ([[maybe_unused]] const int i : IndexRange(inputs_num)) { + inputs_.append_as("Input", CPPType::get(), lf::ValueUsage::Maybe); + } + outputs_.append_as("Output", CPPType::get()); + } + + void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override + { + int first_unavailable_input = -1; + for (const int i : inputs_.index_range()) { + if (const bool *value = params.try_get_input_data_ptr(i)) { + if (*value) { + params.set_output(0, true); + return; + } + } + else { + first_unavailable_input = i; + } + } + if (first_unavailable_input == -1) { + params.set_output(0, false); + return; + } + params.try_get_input_data_ptr_or_request(first_unavailable_input); + } +}; + +/** + * Outputs booleans that indicate which inputs of a switch node are used. Note that it's possible + * that both inputs are used when the condition is a field. + */ +class LazyFunctionForSwitchSocketUsage : public lf::LazyFunction { + public: + LazyFunctionForSwitchSocketUsage() + { + debug_name_ = "Switch Socket Usage"; + inputs_.append_as("Condition", CPPType::get>()); + outputs_.append_as("False", CPPType::get()); + outputs_.append_as("True", CPPType::get()); + } + + void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override + { + const ValueOrField &condition = params.get_input>(0); + if (condition.is_field()) { + params.set_output(0, true); + params.set_output(1, true); + } + else { + const bool value = condition.as_value(); + params.set_output(0, !value); + params.set_output(1, value); + } + } +}; + +/** + * Takes a field as input and extracts the set of anonymous attributes that it references. + */ +class LazyFunctionForAnonymousAttributeSetExtract : public lf::LazyFunction { + private: + const ValueOrFieldCPPType &type_; + + public: + LazyFunctionForAnonymousAttributeSetExtract(const ValueOrFieldCPPType &type) : type_(type) + { + debug_name_ = "Extract Attribute Set"; + inputs_.append_as("Field", type.self); + outputs_.append_as("Attributes", CPPType::get()); + } + + void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override + { + const void *value_or_field = params.try_get_input_data_ptr(0); + bke::AnonymousAttributeSet attributes; + if (type_.is_field(value_or_field)) { + const GField &field = *type_.get_field_ptr(value_or_field); + field.node().for_each_field_input_recursive([&](const FieldInput &field_input) { + if (const auto *attr_field_input = dynamic_cast( + &field_input)) { + if (!attributes.names) { + attributes.names = std::make_shared>(); + } + attributes.names->add_as(attr_field_input->anonymous_id()->name()); + } + }); + } + params.set_output(0, std::move(attributes)); + } +}; + +/** + * Conditionally joines multiple attribute sets. Each input attribute set can be disabled with a + * corresponding boolean input. + */ +class LazyFunctionForAnonymousAttributeSetJoin : public lf::LazyFunction { + const int amount_; + + public: + LazyFunctionForAnonymousAttributeSetJoin(const int amount) : amount_(amount) + { + debug_name_ = "Join Attribute Sets"; + for ([[maybe_unused]] const int i : IndexRange(amount)) { + inputs_.append_as("Use", CPPType::get()); + inputs_.append_as( + "Attribute Set", CPPType::get(), lf::ValueUsage::Maybe); + } + outputs_.append_as("Attribute Set", CPPType::get()); + } + + void execute_impl(lf::Params ¶ms, const lf::Context & /*context*/) const override + { + Vector sets; + bool set_is_missing = false; + for (const int i : IndexRange(amount_)) { + if (params.get_input(this->get_use_input(i))) { + if (bke::AnonymousAttributeSet *set = + params.try_get_input_data_ptr_or_request( + this->get_attribute_set_input(i))) { + sets.append(set); + } + else { + set_is_missing = true; + } + } + } + if (set_is_missing) { + return; + } + bke::AnonymousAttributeSet joined_set; + if (sets.is_empty()) { + /* Nothing to do. */ + } + else if (sets.size() == 1) { + joined_set.names = std::move(sets[0]->names); + } + else { + joined_set.names = std::make_shared>(); + for (const bke::AnonymousAttributeSet *set : sets) { + if (set->names) { + for (const std::string &name : *set->names) { + joined_set.names->add(name); + } + } + } + } + params.set_output(0, std::move(joined_set)); + } + + int get_use_input(const int i) const + { + return 2 * i; + } + + int get_attribute_set_input(const int i) const + { + return 2 * i + 1; + } +}; + +enum class AttributeReferenceKeyType { + /** Attribute referenced by a field passed into the group. */ + InputField, + /** Attributes referenced on the output geometry outside of the current group. */ + OutputGeometry, + /** Attribute referenced by a field created within the current group. */ + Socket, +}; + +/** + * Identifier for something that can reference anonymous attributes that should be propagated. + */ +struct AttributeReferenceKey { + AttributeReferenceKeyType type; + /* Used when type is InputField or OutputGeometry. */ + int index = 0; + /* Used when type is Socket. */ + const bNodeSocket *bsocket = nullptr; + + uint64_t hash() const + { + return get_default_hash_3(this->type, this->bsocket, this->index); + } + + friend bool operator==(const AttributeReferenceKey &a, const AttributeReferenceKey &b) + { + return a.type == b.type && a.bsocket == b.bsocket && a.index == b.index; + } + + friend std::ostream &operator<<(std::ostream &stream, const AttributeReferenceKey &value) + { + if (value.type == AttributeReferenceKeyType::InputField) { + stream << "Input Field: " << value.index; + } + else if (value.type == AttributeReferenceKeyType::OutputGeometry) { + stream << "Output Geometry: " << value.index; + } + else { + stream << "Socket: " << value.bsocket->owner_node().name << " -> " << value.bsocket->name; + } + return stream; + } +}; + +/** + * Additional information that corresponds to an #AttributeReferenceKey. + */ +struct AttributeReferenceInfo { + /** Output socket that contains an attribute set containing the referenced attributes. */ + lf::OutputSocket *lf_attribute_set_socket = nullptr; + /** Geometry sockets that contain the referenced attributes. */ + Vector initial_geometry_sockets; +}; + /** * Utility class to build a lazy-function graph based on a geometry nodes tree. * This is mainly a separate class because it makes it easier to have variables that can be @@ -762,12 +1150,34 @@ struct GeometryNodesLazyFunctionGraphBuilder { Map output_socket_map_; Map multi_input_socket_nodes_; const bke::DataTypeConversions *conversions_; + /** + * Maps bsockets to boolean sockets in the graph whereby each boolean socket indicates whether + * the bsocket is used. Sockets not contained in this map are not used. + * This is indexed by `bNodeSocket::index_in_tree()`. + */ + Array socket_is_used_map_; + /** + * Some built-in nodes get additional boolean inputs that indicate whether certain outputs are + * used (field output sockets that contain new anonymous attribute references). + */ + Vector> output_used_sockets_for_builtin_nodes_; + /** + * Maps from output geometry sockets to corresponding attribute set inputs. + */ + Map attribute_set_propagation_map_; + /** + * Boolean inputs that tell a node if some socket (of the same or another node) is used. If this + * socket is in a link-cycle, its input can become a constant true. + */ + Set socket_usage_inputs_; /** * All group input nodes are combined into one dummy node in the lazy-function graph. */ lf::DummyNode *group_input_lf_node_; + friend class UsedSocketVisualizeOptions; + public: GeometryNodesLazyFunctionGraphBuilder(const bNodeTree &btree, GeometryNodesLazyFunctionGraphInfo &lf_graph_info) @@ -783,12 +1193,28 @@ struct GeometryNodesLazyFunctionGraphBuilder { mapping_ = &lf_graph_info_->mapping; conversions_ = &bke::get_implicit_type_conversions(); + socket_is_used_map_.reinitialize(btree_.all_sockets().size()); + socket_is_used_map_.fill(nullptr); + this->prepare_node_multi_functions(); this->build_group_input_node(); + if (btree_.group_output_node() == nullptr) { + this->build_fallback_output_node(); + } this->handle_nodes(); this->handle_links(); this->add_default_inputs(); + this->build_attribute_propagation_input_node(); + this->build_output_usage_input_node(); + this->build_input_usage_output_node(); + this->build_socket_usages(); + + this->build_attribute_propagation_sets(); + this->fix_link_cycles(); + + // this->print_graph(); + lf_graph_->update_node_indices(); lf_graph_info_->num_inline_nodes_approximate += lf_graph_->nodes().size(); } @@ -818,6 +1244,29 @@ struct GeometryNodesLazyFunctionGraphBuilder { lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); } + /** + * Build an output node that just outputs default values in the case when there is no Group + * Output node in the tree. + */ + void build_fallback_output_node() + { + Vector output_cpp_types; + auto debug_info = std::make_unique(); + for (const bNodeSocket *interface_output : btree_.interface_outputs()) { + output_cpp_types.append(interface_output->typeinfo->geometry_nodes_cpp_type); + debug_info->socket_names.append(interface_output->name); + } + + lf::Node &lf_node = lf_graph_->add_dummy(output_cpp_types, {}, debug_info.get()); + for (lf::InputSocket *lf_socket : lf_node.inputs()) { + const CPPType &type = lf_socket->type(); + lf_socket->set_default_value(type.default_value()); + } + mapping_->standard_group_output_sockets = lf_node.inputs(); + + lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); + } + void handle_nodes() { /* Insert all nodes into the lazy function graph. */ @@ -935,22 +1384,25 @@ struct GeometryNodesLazyFunctionGraphBuilder { void handle_group_output_node(const bNode &bnode) { Vector output_cpp_types; - const Span interface_outputs = btree_.interface_outputs(); - for (const bNodeSocket *interface_input : interface_outputs) { + auto debug_info = std::make_unique(); + for (const bNodeSocket *interface_input : btree_.interface_outputs()) { output_cpp_types.append(interface_input->typeinfo->geometry_nodes_cpp_type); + debug_info->socket_names.append(interface_input->name); } - auto debug_info = std::make_unique(); lf::DummyNode &group_output_lf_node = lf_graph_->add_dummy( output_cpp_types, {}, debug_info.get()); - for (const int i : interface_outputs.index_range()) { + for (const int i : group_output_lf_node.inputs().index_range()) { const bNodeSocket &bsocket = bnode.input_socket(i); lf::InputSocket &lf_socket = group_output_lf_node.input(i); input_socket_map_.add(&bsocket, &lf_socket); mapping_->dummy_socket_map.add(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); - debug_info->socket_names.append(interface_outputs[i]->name); + } + + if (&bnode == btree_.group_output_node()) { + mapping_->standard_group_output_sockets = group_output_lf_node.inputs(); } lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); @@ -968,21 +1420,18 @@ struct GeometryNodesLazyFunctionGraphBuilder { return; } - Vector used_inputs; - Vector used_outputs; - auto lazy_function = std::make_unique( - bnode, *group_lf_graph_info, used_inputs, used_outputs); + auto lazy_function = std::make_unique(bnode, *group_lf_graph_info); lf::FunctionNode &lf_node = lf_graph_->add_function(*lazy_function); - lf_graph_info_->functions.append(std::move(lazy_function)); - for (const int i : used_inputs.index_range()) { - const bNodeSocket &bsocket = *used_inputs[i]; + + for (const int i : bnode.input_sockets().index_range()) { + const bNodeSocket &bsocket = bnode.input_socket(i); BLI_assert(!bsocket.is_multi_input()); lf::InputSocket &lf_socket = lf_node.input(i); input_socket_map_.add(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } - for (const int i : used_outputs.index_range()) { - const bNodeSocket &bsocket = *used_outputs[i]; + for (const int i : bnode.output_sockets().index_range()) { + const bNodeSocket &bsocket = bnode.output_socket(i); lf::OutputSocket &lf_socket = lf_node.output(i); output_socket_map_.add_new(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); @@ -990,6 +1439,18 @@ struct GeometryNodesLazyFunctionGraphBuilder { mapping_->group_node_map.add(&bnode, &lf_node); lf_graph_info_->num_inline_nodes_approximate += group_lf_graph_info->num_inline_nodes_approximate; + static const bool static_false = false; + for (const int i : lazy_function->lf_input_for_output_bsocket_usage_.values()) { + lf_node.input(i).set_default_value(&static_false); + socket_usage_inputs_.add(&lf_node.input(i)); + } + /* Keep track of attribute set inputs that need to be populated later. */ + for (const auto [output_index, lf_input_index] : + lazy_function->lf_input_for_attribute_propagation_to_output_.items()) { + attribute_set_propagation_map_.add(&bnode.output_socket(output_index), + &lf_node.input(lf_input_index)); + } + lf_graph_info_->functions.append(std::move(lazy_function)); } void handle_geometry_node(const bNode &bnode) @@ -999,7 +1460,6 @@ struct GeometryNodesLazyFunctionGraphBuilder { auto lazy_function = std::make_unique( bnode, used_inputs, used_outputs); lf::Node &lf_node = lf_graph_->add_function(*lazy_function); - lf_graph_info_->functions.append(std::move(lazy_function)); for (const int i : used_inputs.index_range()) { const bNodeSocket &bsocket = *used_inputs[i]; @@ -1026,6 +1486,21 @@ struct GeometryNodesLazyFunctionGraphBuilder { output_socket_map_.add_new(&bsocket, &lf_socket); mapping_->bsockets_by_lf_socket_map.add(&lf_socket, &bsocket); } + + for (const auto [identifier, lf_input_index] : + lazy_function->lf_input_for_output_bsocket_usage_.items()) { + output_used_sockets_for_builtin_nodes_.append_as(&bnode.output_by_identifier(identifier), + &lf_node.input(lf_input_index)); + socket_usage_inputs_.add_new(&lf_node.input(lf_input_index)); + } + /* Keep track of attribute set inputs that need to be populated later. */ + for (const auto [identifier, lf_input_index] : + lazy_function->lf_input_for_attribute_propagation_to_output_.items()) { + attribute_set_propagation_map_.add(&bnode.output_by_identifier(identifier), + &lf_node.input(lf_input_index)); + } + + lf_graph_info_->functions.append(std::move(lazy_function)); } void handle_multi_function_node(const bNode &bnode, const NodeMultiFunctions::Item &fn_item) @@ -1269,8 +1744,985 @@ struct GeometryNodesLazyFunctionGraphBuilder { lf_graph_->add_link(lf_node.output(0), input_lf_socket); return true; } + + /** + * Every output geometry socket that may propagate attributes has to know which attributes should + * be propagated. Therefore, every one of these outputs gets a corresponding attribute set input. + */ + void build_attribute_propagation_input_node() + { + const aal::RelationsInNode &tree_relations = *btree_.runtime->anonymous_attribute_relations; + Vector output_indices; + for (const aal::PropagateRelation &relation : tree_relations.propagate_relations) { + output_indices.append_non_duplicates(relation.to_geometry_output); + } + Vector cpp_types; + auto debug_info = std::make_unique(); + debug_info->name = "Attributes to Propagate to Output"; + cpp_types.append_n_times(&CPPType::get(), output_indices.size()); + lf::Node &lf_node = lf_graph_->add_dummy({}, cpp_types, debug_info.get()); + for (const int i : output_indices.index_range()) { + const int output_index = output_indices[i]; + mapping_->attribute_set_by_geometry_output.add(output_index, &lf_node.output(i)); + debug_info->output_names.append(btree_.interface_outputs()[output_index]->name); + } + lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); + } + + /** + * Build new boolean group inputs that indicate which group outputs are used. + */ + void build_output_usage_input_node() + { + const Span interface_outputs = btree_.interface_outputs(); + + Vector cpp_types; + cpp_types.append_n_times(&CPPType::get(), interface_outputs.size()); + auto debug_info = std::make_unique(); + debug_info->name = "Output Socket Usage"; + lf::Node &lf_node = lf_graph_->add_dummy({}, cpp_types, debug_info.get()); + for (const int i : interface_outputs.index_range()) { + mapping_->group_output_used_sockets.append(&lf_node.output(i)); + debug_info->output_names.append(interface_outputs[i]->name); + } + lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); + } + + /** + * Build new boolean group outputs that indicate which group inputs are used depending on other + * group inputs. + */ + void build_input_usage_output_node() + { + const Span interface_inputs = btree_.interface_inputs(); + + Vector cpp_types; + cpp_types.append_n_times(&CPPType::get(), interface_inputs.size()); + auto debug_info = std::make_unique(); + debug_info->name = "Input Socket Usage"; + lf::Node &lf_node = lf_graph_->add_dummy(cpp_types, {}, debug_info.get()); + for (const int i : interface_inputs.index_range()) { + mapping_->group_input_usage_sockets.append(&lf_node.input(i)); + debug_info->input_names.append(interface_inputs[i]->name); + } + lf_graph_info_->dummy_debug_infos_.append(std::move(debug_info)); + } + + /** + * For every socket we want to determine if it will be used depending on the inputs of the node + * group (just static analysis is not enough when there are e.g. Switch nodes). This function + * populates #socket_is_used_map_ with that information. + */ + void build_socket_usages() + { + OrSocketUsagesCache or_socket_usages_cache; + + if (const bNode *group_output_bnode = btree_.group_output_node()) { + /* Whether a group output is used is determined by a group input that has been created + * exactly for this purpose. */ + for (const bNodeSocket *bsocket : group_output_bnode->input_sockets().drop_back(1)) { + const int index = bsocket->index(); + socket_is_used_map_[bsocket->index_in_tree()] = const_cast( + mapping_->group_output_used_sockets[index]); + } + } + + /* Iterate over all nodes from right to left to determine when which sockets are used. */ + for (const bNode *bnode : btree_.toposort_right_to_left()) { + const bNodeType *node_type = bnode->typeinfo; + if (node_type == nullptr) { + /* Ignore. */ + continue; + } + + this->build_output_socket_usages(*bnode, or_socket_usages_cache); + + if (bnode->is_muted()) { + this->build_muted_node_usages(*bnode, or_socket_usages_cache); + continue; + } + + switch (node_type->type) { + case NODE_GROUP_OUTPUT: { + /* Handled before this loop already. */ + break; + } + case NODE_GROUP_INPUT: { + /* Handled after this loop. */ + break; + } + case NODE_FRAME: { + /* Ignored. */ + break; + } + case NODE_REROUTE: { + /* The input is used exactly when the output is used. */ + socket_is_used_map_[bnode->input_socket(0).index_in_tree()] = + socket_is_used_map_[bnode->output_socket(0).index_in_tree()]; + break; + } + case GEO_NODE_SWITCH: { + this->build_switch_node_socket_usage(*bnode); + break; + } + case GEO_NODE_VIEWER: { + this->build_viewer_node_socket_usage(*bnode); + break; + } + case NODE_GROUP: + case NODE_CUSTOM_GROUP: { + this->build_group_node_socket_usage(*bnode, or_socket_usages_cache); + break; + } + default: { + this->build_standard_node_input_socket_usage(*bnode, or_socket_usages_cache); + break; + } + } + } + + this->build_group_input_usages(or_socket_usages_cache); + this->link_output_used_sockets_for_builtin_nodes(); + } + + using OrSocketUsagesCache = Map, lf::OutputSocket *>; + + /** + * Combine multiple socket usages with a logical or. Inserts a new node for that purpose if + * necessary. + */ + lf::OutputSocket *or_socket_usages(MutableSpan usages, + OrSocketUsagesCache &cache) + { + if (usages.is_empty()) { + return nullptr; + } + if (usages.size() == 1) { + return usages[0]; + } + + std::sort(usages.begin(), usages.end()); + return cache.lookup_or_add_cb_as(usages, [&]() { + auto logical_or_fn = std::make_unique(usages.size()); + lf::Node &logical_or_node = lf_graph_->add_function(*logical_or_fn); + lf_graph_info_->functions.append(std::move(logical_or_fn)); + + for (const int i : usages.index_range()) { + lf_graph_->add_link(*usages[i], logical_or_node.input(i)); + } + return &logical_or_node.output(0); + }); + } + + void build_output_socket_usages(const bNode &bnode, OrSocketUsagesCache &or_socket_usages_cache) + { + /* Output sockets are used when any of their linked inputs are used. */ + for (const bNodeSocket *socket : bnode.output_sockets()) { + if (!socket->is_available()) { + continue; + } + /* Determine when linked target sockets are used. */ + Vector target_usages; + for (const bNodeLink *link : socket->directly_linked_links()) { + if (!link->is_used()) { + continue; + } + const bNodeSocket &target_socket = *link->tosock; + if (lf::OutputSocket *is_used_socket = + socket_is_used_map_[target_socket.index_in_tree()]) { + target_usages.append_non_duplicates(is_used_socket); + } + } + /* Combine target socket usages into the usage of the current socket. */ + socket_is_used_map_[socket->index_in_tree()] = this->or_socket_usages( + target_usages, or_socket_usages_cache); + } + } + + /** + * An input of a muted node is used when any of its internally linked outputs is used. + */ + void build_muted_node_usages(const bNode &bnode, OrSocketUsagesCache &or_socket_usages_cache) + { + /* Find all outputs that use a specific input. */ + MultiValueMap outputs_by_input; + for (const bNodeLink *blink : bnode.internal_links()) { + outputs_by_input.add(blink->fromsock, blink->tosock); + } + for (const auto item : outputs_by_input.items()) { + const bNodeSocket &input_bsocket = *item.key; + const Span output_bsockets = item.value; + + /* The input is used if any of the internally linked outputs is used. */ + Vector lf_socket_usages; + for (const bNodeSocket *output_bsocket : output_bsockets) { + if (lf::OutputSocket *lf_socket = socket_is_used_map_[output_bsocket->index_in_tree()]) { + lf_socket_usages.append(lf_socket); + } + } + socket_is_used_map_[input_bsocket.index_in_tree()] = this->or_socket_usages( + lf_socket_usages, or_socket_usages_cache); + } + } + + void build_switch_node_socket_usage(const bNode &bnode) + { + const bNodeSocket *switch_input_bsocket = nullptr; + const bNodeSocket *false_input_bsocket = nullptr; + const bNodeSocket *true_input_bsocket = nullptr; + const bNodeSocket *output_bsocket = nullptr; + for (const bNodeSocket *socket : bnode.input_sockets()) { + if (!socket->is_available()) { + continue; + } + if (socket->name == StringRef("Switch")) { + switch_input_bsocket = socket; + } + else if (socket->name == StringRef("False")) { + false_input_bsocket = socket; + } + else if (socket->name == StringRef("True")) { + true_input_bsocket = socket; + } + } + for (const bNodeSocket *socket : bnode.output_sockets()) { + if (socket->is_available()) { + output_bsocket = socket; + break; + } + } + lf::OutputSocket *output_is_used_socket = socket_is_used_map_[output_bsocket->index_in_tree()]; + if (output_is_used_socket == nullptr) { + return; + } + socket_is_used_map_[switch_input_bsocket->index_in_tree()] = output_is_used_socket; + lf::InputSocket *lf_switch_input = input_socket_map_.lookup(switch_input_bsocket)[0]; + if (lf::OutputSocket *lf_switch_origin = lf_switch_input->origin()) { + /* The condition input is dynamic, so the usage of the other inputs is as well. */ + static const LazyFunctionForSwitchSocketUsage switch_socket_usage_fn; + lf::Node &lf_node = lf_graph_->add_function(switch_socket_usage_fn); + lf_graph_->add_link(*lf_switch_origin, lf_node.input(0)); + socket_is_used_map_[false_input_bsocket->index_in_tree()] = &lf_node.output(0); + socket_is_used_map_[true_input_bsocket->index_in_tree()] = &lf_node.output(1); + } + else { + if (switch_input_bsocket->default_value_typed()->value) { + socket_is_used_map_[true_input_bsocket->index_in_tree()] = output_is_used_socket; + } + else { + socket_is_used_map_[false_input_bsocket->index_in_tree()] = output_is_used_socket; + } + } + } + + void build_viewer_node_socket_usage(const bNode &bnode) + { + const lf::FunctionNode &lf_viewer_node = *mapping_->viewer_node_map.lookup(&bnode); + auto lazy_function = std::make_unique(lf_viewer_node); + lf::Node &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_info_->functions.append(std::move(lazy_function)); + + for (const bNodeSocket *bsocket : bnode.input_sockets()) { + if (bsocket->is_available()) { + socket_is_used_map_[bsocket->index_in_tree()] = &lf_node.output(0); + } + } + } + + void build_group_node_socket_usage(const bNode &bnode, + OrSocketUsagesCache &or_socket_usages_cache) + { + const bNodeTree *bgroup = reinterpret_cast(bnode.id); + if (bgroup == nullptr) { + return; + } + const GeometryNodesLazyFunctionGraphInfo *group_lf_graph_info = + ensure_geometry_nodes_lazy_function_graph(*bgroup); + if (group_lf_graph_info == nullptr) { + return; + } + lf::FunctionNode &lf_group_node = const_cast( + *mapping_->group_node_map.lookup(&bnode)); + const auto &fn = static_cast(lf_group_node.function()); + + for (const bNodeSocket *input_bsocket : bnode.input_sockets()) { + const int input_index = input_bsocket->index(); + const InputUsageHint &input_usage_hint = + group_lf_graph_info->mapping.group_input_usage_hints[input_index]; + switch (input_usage_hint.type) { + case InputUsageHintType::Never: { + /* Nothing to do. */ + break; + } + case InputUsageHintType::DependsOnOutput: { + Vector output_usages; + for (const int i : input_usage_hint.output_dependencies) { + if (lf::OutputSocket *lf_socket = + socket_is_used_map_[bnode.output_socket(i).index_in_tree()]) { + output_usages.append(lf_socket); + } + } + socket_is_used_map_[input_bsocket->index_in_tree()] = this->or_socket_usages( + output_usages, or_socket_usages_cache); + break; + } + case InputUsageHintType::DynamicSocket: { + socket_is_used_map_[input_bsocket->index_in_tree()] = &const_cast( + lf_group_node.output(fn.lf_output_for_input_bsocket_usage_.lookup(input_index))); + break; + } + } + } + + for (const bNodeSocket *output_bsocket : bnode.output_sockets()) { + const int output_index = output_bsocket->index(); + const int lf_input_index = fn.lf_input_for_output_bsocket_usage_.lookup(output_index); + lf::InputSocket &lf_socket = lf_group_node.input(lf_input_index); + if (lf::OutputSocket *lf_output_is_used = + socket_is_used_map_[output_bsocket->index_in_tree()]) { + lf_graph_->add_link(*lf_output_is_used, lf_socket); + } + else { + static const bool static_false = false; + lf_socket.set_default_value(&static_false); + } + } + } + + void build_standard_node_input_socket_usage(const bNode &bnode, + OrSocketUsagesCache &or_socket_usages_cache) + { + if (bnode.input_sockets().is_empty()) { + return; + } + + Vector output_usages; + for (const bNodeSocket *output_socket : bnode.output_sockets()) { + if (!output_socket->is_available()) { + continue; + } + if (lf::OutputSocket *is_used_socket = socket_is_used_map_[output_socket->index_in_tree()]) { + output_usages.append_non_duplicates(is_used_socket); + } + } + + /* Assume every input is used when any output is used. */ + lf::OutputSocket *lf_usage = this->or_socket_usages(output_usages, or_socket_usages_cache); + if (lf_usage == nullptr) { + return; + } + + for (const bNodeSocket *input_socket : bnode.input_sockets()) { + if (input_socket->is_available()) { + socket_is_used_map_[input_socket->index_in_tree()] = lf_usage; + } + } + } + + void build_group_input_usages(OrSocketUsagesCache &or_socket_usages_cache) + { + const Span group_input_nodes = btree_.group_input_nodes(); + for (const int i : btree_.interface_inputs().index_range()) { + Vector target_usages; + for (const bNode *group_input_node : group_input_nodes) { + if (lf::OutputSocket *lf_socket = + socket_is_used_map_[group_input_node->output_socket(i).index_in_tree()]) { + target_usages.append_non_duplicates(lf_socket); + } + } + + lf::OutputSocket *lf_socket = this->or_socket_usages(target_usages, or_socket_usages_cache); + lf::InputSocket *lf_group_output = const_cast( + mapping_->group_input_usage_sockets[i]); + InputUsageHint input_usage_hint; + if (lf_socket == nullptr) { + static const bool static_false = false; + lf_group_output->set_default_value(&static_false); + input_usage_hint.type = InputUsageHintType::Never; + } + else { + lf_graph_->add_link(*lf_socket, *lf_group_output); + if (lf_socket->node().is_dummy()) { + /* Can support slightly more complex cases where it depends on more than one output in + * the future. */ + input_usage_hint.type = InputUsageHintType::DependsOnOutput; + input_usage_hint.output_dependencies = { + mapping_->group_output_used_sockets.first_index_of(lf_socket)}; + } + else { + input_usage_hint.type = InputUsageHintType::DynamicSocket; + } + } + lf_graph_info_->mapping.group_input_usage_hints.append(std::move(input_usage_hint)); + } + } + + void link_output_used_sockets_for_builtin_nodes() + { + for (const auto &[output_bsocket, lf_input] : output_used_sockets_for_builtin_nodes_) { + if (lf::OutputSocket *lf_is_used = socket_is_used_map_[output_bsocket->index_in_tree()]) { + lf_graph_->add_link(*lf_is_used, *lf_input); + } + else { + static const bool static_false = false; + lf_input->set_default_value(&static_false); + } + } + } + + void build_attribute_propagation_sets() + { + ResourceScope scope; + const Array relations_by_node = + bke::anonymous_attribute_inferencing::get_relations_by_node(btree_, scope); + + VectorSet attribute_reference_keys; + /* Indexed by reference key index. */ + Vector attribute_reference_infos; + this->build_attribute_references( + relations_by_node, attribute_reference_keys, attribute_reference_infos); + + MultiValueMap referenced_by_field_socket; + MultiValueMap propagated_to_geometry_socket; + this->gather_referenced_and_potentially_propagated_data(relations_by_node, + attribute_reference_keys, + attribute_reference_infos, + referenced_by_field_socket, + propagated_to_geometry_socket); + + MultiValueMap required_propagated_to_geometry_socket; + this->gather_required_propagated_data(relations_by_node, + attribute_reference_keys, + referenced_by_field_socket, + propagated_to_geometry_socket, + required_propagated_to_geometry_socket); + + this->build_attribute_sets_to_propagate(attribute_reference_keys, + attribute_reference_infos, + required_propagated_to_geometry_socket); + } + + void build_attribute_references(const Span relations_by_node, + VectorSet &r_attribute_reference_keys, + Vector &r_attribute_reference_infos) + { + auto add_get_attributes_node = [&](lf::OutputSocket &lf_field_socket) -> lf::OutputSocket & { + const ValueOrFieldCPPType &type = *ValueOrFieldCPPType::get_from_self( + lf_field_socket.type()); + auto lazy_function = std::make_unique(type); + lf::Node &lf_node = lf_graph_->add_function(*lazy_function); + lf_graph_->add_link(lf_field_socket, lf_node.input(0)); + lf_graph_info_->functions.append(std::move(lazy_function)); + return lf_node.output(0); + }; + + /* Find nodes that create new anonymous attributes. */ + for (const bNode *node : btree_.all_nodes()) { + const aal::RelationsInNode &relations = *relations_by_node[node->index()]; + for (const aal::AvailableRelation &relation : relations.available_relations) { + const bNodeSocket &geometry_bsocket = node->output_socket(relation.geometry_output); + const bNodeSocket &field_bsocket = node->output_socket(relation.field_output); + if (!field_bsocket.is_available()) { + continue; + } + if (!field_bsocket.is_directly_linked()) { + continue; + } + AttributeReferenceKey key; + key.type = AttributeReferenceKeyType::Socket; + key.bsocket = &field_bsocket; + const int key_index = r_attribute_reference_keys.index_of_or_add(key); + if (key_index >= r_attribute_reference_infos.size()) { + AttributeReferenceInfo info; + lf::OutputSocket &lf_field_socket = *output_socket_map_.lookup(&field_bsocket); + info.lf_attribute_set_socket = &add_get_attributes_node(lf_field_socket); + r_attribute_reference_infos.append(info); + } + AttributeReferenceInfo &info = r_attribute_reference_infos[key_index]; + if (geometry_bsocket.is_available()) { + info.initial_geometry_sockets.append(&geometry_bsocket); + } + } + } + + /* Find field group inputs that are evaluated within this node tree. */ + const aal::RelationsInNode &tree_relations = *btree_.runtime->anonymous_attribute_relations; + for (const aal::EvalRelation &relation : tree_relations.eval_relations) { + AttributeReferenceKey key; + key.type = AttributeReferenceKeyType::InputField; + key.index = relation.field_input; + r_attribute_reference_keys.add_new(key); + AttributeReferenceInfo info; + lf::OutputSocket &lf_field_socket = *const_cast( + mapping_->group_input_sockets[relation.field_input]); + info.lf_attribute_set_socket = &add_get_attributes_node(lf_field_socket); + for (const bNode *bnode : btree_.group_input_nodes()) { + info.initial_geometry_sockets.append(&bnode->output_socket(relation.geometry_input)); + } + r_attribute_reference_infos.append(std::move(info)); + } + /* Find group outputs that attributes need to be propagated to. */ + for (const aal::PropagateRelation &relation : tree_relations.propagate_relations) { + AttributeReferenceKey key; + key.type = AttributeReferenceKeyType::OutputGeometry; + key.index = relation.to_geometry_output; + const int key_index = r_attribute_reference_keys.index_of_or_add(key); + if (key_index >= r_attribute_reference_infos.size()) { + AttributeReferenceInfo info; + info.lf_attribute_set_socket = const_cast( + mapping_->attribute_set_by_geometry_output.lookup(relation.to_geometry_output)); + r_attribute_reference_infos.append(info); + } + AttributeReferenceInfo &info = r_attribute_reference_infos[key_index]; + for (const bNode *bnode : btree_.group_input_nodes()) { + info.initial_geometry_sockets.append(&bnode->output_socket(relation.from_geometry_input)); + } + } + } + + /** + * For every field socket, figure out which anonymous attributes it may reference. + * For every geometry socket, figure out which anonymous attributes may be propagated to it. + */ + void gather_referenced_and_potentially_propagated_data( + const Span relations_by_node, + const Span attribute_reference_keys, + const Span attribute_reference_infos, + MultiValueMap &r_referenced_by_field_socket, + MultiValueMap &r_propagated_to_geometry_socket) + { + /* Initialize maps. */ + for (const int key_index : attribute_reference_keys.index_range()) { + const AttributeReferenceKey &key = attribute_reference_keys[key_index]; + const AttributeReferenceInfo &info = attribute_reference_infos[key_index]; + switch (key.type) { + case AttributeReferenceKeyType::InputField: { + for (const bNode *bnode : btree_.group_input_nodes()) { + const bNodeSocket &bsocket = bnode->output_socket(key.index); + r_referenced_by_field_socket.add(&bsocket, key_index); + } + break; + } + case AttributeReferenceKeyType::OutputGeometry: { + break; + } + case AttributeReferenceKeyType::Socket: { + r_referenced_by_field_socket.add(key.bsocket, key_index); + break; + } + } + for (const bNodeSocket *geometry_bsocket : info.initial_geometry_sockets) { + r_propagated_to_geometry_socket.add(geometry_bsocket, key_index); + } + } + /* Propagate attribute usages from left to right. */ + for (const bNode *bnode : btree_.toposort_left_to_right()) { + for (const bNodeSocket *bsocket : bnode->input_sockets()) { + if (bsocket->is_available()) { + Vector referenced_keys; + Vector propagated_keys; + for (const bNodeLink *blink : bsocket->directly_linked_links()) { + if (blink->is_used()) { + referenced_keys.extend_non_duplicates( + r_referenced_by_field_socket.lookup(blink->fromsock)); + propagated_keys.extend_non_duplicates( + r_propagated_to_geometry_socket.lookup(blink->fromsock)); + } + } + if (!referenced_keys.is_empty()) { + r_referenced_by_field_socket.add_multiple(bsocket, referenced_keys); + } + if (!propagated_keys.is_empty()) { + r_propagated_to_geometry_socket.add_multiple(bsocket, propagated_keys); + } + } + } + const aal::RelationsInNode &relations = *relations_by_node[bnode->index()]; + for (const aal::ReferenceRelation &relation : relations.reference_relations) { + const bNodeSocket &input_bsocket = bnode->input_socket(relation.from_field_input); + const bNodeSocket &output_bsocket = bnode->output_socket(relation.to_field_output); + if (!input_bsocket.is_available() || !output_bsocket.is_available()) { + continue; + } + r_referenced_by_field_socket.add_multiple( + &output_bsocket, Vector(r_referenced_by_field_socket.lookup(&input_bsocket))); + } + for (const aal::PropagateRelation &relation : relations.propagate_relations) { + const bNodeSocket &input_bsocket = bnode->input_socket(relation.from_geometry_input); + const bNodeSocket &output_bsocket = bnode->output_socket(relation.to_geometry_output); + if (!input_bsocket.is_available() || !output_bsocket.is_available()) { + continue; + } + r_propagated_to_geometry_socket.add_multiple( + &output_bsocket, Vector(r_propagated_to_geometry_socket.lookup(&input_bsocket))); + } + } + } + + /** + * Determines which anonymous attributes should be propagated to which geometry sockets. + */ + void gather_required_propagated_data( + const Span relations_by_node, + const VectorSet &attribute_reference_keys, + const MultiValueMap &referenced_by_field_socket, + const MultiValueMap &propagated_to_geometry_socket, + MultiValueMap &r_required_propagated_to_geometry_socket) + { + const aal::RelationsInNode &tree_relations = *btree_.runtime->anonymous_attribute_relations; + MultiValueMap required_by_geometry_socket; + + /* Initialize required attributes at group output. */ + if (const bNode *group_output_bnode = btree_.group_output_node()) { + for (const aal::PropagateRelation &relation : tree_relations.propagate_relations) { + AttributeReferenceKey key; + key.type = AttributeReferenceKeyType::OutputGeometry; + key.index = relation.to_geometry_output; + const int key_index = attribute_reference_keys.index_of(key); + required_by_geometry_socket.add( + &group_output_bnode->input_socket(relation.to_geometry_output), key_index); + } + for (const aal::AvailableRelation &relation : tree_relations.available_relations) { + const bNodeSocket &geometry_bsocket = group_output_bnode->input_socket( + relation.geometry_output); + const bNodeSocket &field_bsocket = group_output_bnode->input_socket(relation.field_output); + required_by_geometry_socket.add_multiple( + &geometry_bsocket, referenced_by_field_socket.lookup(&field_bsocket)); + } + } + + /* Propagate attribute usages from right to left. */ + for (const bNode *bnode : btree_.toposort_right_to_left()) { + const aal::RelationsInNode &relations = *relations_by_node[bnode->index()]; + for (const bNodeSocket *bsocket : bnode->output_sockets()) { + if (!bsocket->is_available()) { + continue; + } + Vector required_attributes; + for (const bNodeLink *blink : bsocket->directly_linked_links()) { + if (blink->is_used()) { + const bNodeSocket &to_socket = *blink->tosock; + required_attributes.extend_non_duplicates( + required_by_geometry_socket.lookup(&to_socket)); + } + } + const Span available_attributes = propagated_to_geometry_socket.lookup(bsocket); + for (const int key_index : required_attributes) { + if (available_attributes.contains(key_index)) { + required_by_geometry_socket.add(bsocket, key_index); + + const AttributeReferenceKey &key = attribute_reference_keys[key_index]; + if (key.type != AttributeReferenceKeyType::Socket || + &key.bsocket->owner_node() != bnode) { + r_required_propagated_to_geometry_socket.add(bsocket, key_index); + } + } + } + } + + for (const bNodeSocket *bsocket : bnode->input_sockets()) { + if (!bsocket->is_available()) { + continue; + } + Vector required_attributes; + for (const aal::PropagateRelation &relation : relations.propagate_relations) { + if (relation.from_geometry_input == bsocket->index()) { + const bNodeSocket &output_bsocket = bnode->output_socket(relation.to_geometry_output); + required_attributes.extend_non_duplicates( + required_by_geometry_socket.lookup(&output_bsocket)); + } + } + for (const aal::EvalRelation &relation : relations.eval_relations) { + if (relation.geometry_input == bsocket->index()) { + const bNodeSocket &field_bsocket = bnode->input_socket(relation.field_input); + if (field_bsocket.is_available()) { + required_attributes.extend_non_duplicates( + referenced_by_field_socket.lookup(&field_bsocket)); + } + } + } + const Span available_attributes = propagated_to_geometry_socket.lookup(bsocket); + for (const int key_index : required_attributes) { + if (available_attributes.contains(key_index)) { + required_by_geometry_socket.add(bsocket, key_index); + } + } + } + } + } + + /** + * For every node that propagates attributes, prepare an attribute set containing information + * about which attributes should be propagated. + */ + void build_attribute_sets_to_propagate( + const Span attribute_reference_keys, + const Span attribute_reference_infos, + const MultiValueMap &required_propagated_to_geometry_socket) + { + JoinAttibuteSetsCache join_attribute_sets_cache; + + for (const auto [geometry_output_bsocket, lf_attribute_set_input] : + attribute_set_propagation_map_.items()) { + const Span required = required_propagated_to_geometry_socket.lookup( + geometry_output_bsocket); + + Vector attribute_set_sockets; + Vector used_sockets; + + for (const int i : required.index_range()) { + const int key_index = required[i]; + const AttributeReferenceKey &key = attribute_reference_keys[key_index]; + const AttributeReferenceInfo &info = attribute_reference_infos[key_index]; + lf::OutputSocket *lf_socket_usage = nullptr; + switch (key.type) { + case AttributeReferenceKeyType::InputField: { + lf_socket_usage = const_cast( + mapping_->group_input_usage_sockets[key.index]) + ->origin(); + break; + } + case AttributeReferenceKeyType::OutputGeometry: { + lf_socket_usage = const_cast( + mapping_->group_output_used_sockets[key.index]); + break; + } + case AttributeReferenceKeyType::Socket: { + lf_socket_usage = socket_is_used_map_[key.bsocket->index_in_tree()]; + break; + } + } + if (lf_socket_usage) { + attribute_set_sockets.append(info.lf_attribute_set_socket); + used_sockets.append(lf_socket_usage); + } + } + if (lf::OutputSocket *joined_attribute_set = this->join_attribute_sets( + attribute_set_sockets, used_sockets, join_attribute_sets_cache)) { + lf_graph_->add_link(*joined_attribute_set, *lf_attribute_set_input); + } + else { + static const bke::AnonymousAttributeSet empty_set; + lf_attribute_set_input->set_default_value(&empty_set); + } + } + } + + using JoinAttibuteSetsCache = Map, lf::OutputSocket *>; + + /** + * Join multiple attributes set into a single attribute set that can be passed into a node. + */ + lf::OutputSocket *join_attribute_sets(const Span attribute_set_sockets, + const Span used_sockets, + JoinAttibuteSetsCache &cache) + { + BLI_assert(attribute_set_sockets.size() == used_sockets.size()); + if (attribute_set_sockets.is_empty()) { + return nullptr; + } + if (attribute_set_sockets.size() == 1) { + return attribute_set_sockets[0]; + } + + Vector key; + key.extend(attribute_set_sockets); + key.extend(used_sockets); + std::sort(key.begin(), key.end()); + return cache.lookup_or_add_cb(key, [&]() { + auto lazy_function = std::make_unique( + attribute_set_sockets.size()); + lf::Node &lf_node = lf_graph_->add_function(*lazy_function); + for (const int i : attribute_set_sockets.index_range()) { + lf::InputSocket &lf_use_input = lf_node.input(lazy_function->get_use_input(i)); + socket_usage_inputs_.add(&lf_use_input); + lf::InputSocket &lf_attributes_input = lf_node.input( + lazy_function->get_attribute_set_input(i)); + lf_graph_->add_link(*used_sockets[i], lf_use_input); + lf_graph_->add_link(*attribute_set_sockets[i], lf_attributes_input); + } + lf_graph_info_->functions.append(std::move(lazy_function)); + return &lf_node.output(0); + }); + } + + /** + * By depending on "the future" (whether a specific socket is used in the future), it is possible + * to introduce cycles in the graph. This function finds those cycles and breaks them by removing + * specific links. + * + * Example for a cycle: There is a `Distribute Points on Faces` node and its `Normal` output is + * only used when the number of generated points is larger than 1000 because of some switch node + * later in the tree. In this case, to know whether the `Normal` output is needed, one first has + * to compute the points, but for that one has to know whether the normal information has to be + * added to the points. The fix is to always add the normal information in this case. + */ + void fix_link_cycles() + { + lf_graph_->update_socket_indices(); + const int sockets_num = lf_graph_->socket_num(); + + struct SocketState { + bool done = false; + bool in_stack = false; + }; + + Array socket_states(sockets_num); + + Stack lf_sockets_to_check; + for (lf::Node *lf_node : lf_graph_->nodes()) { + if (lf_node->is_function()) { + for (lf::OutputSocket *lf_socket : lf_node->outputs()) { + if (lf_socket->targets().is_empty()) { + lf_sockets_to_check.push(lf_socket); + } + } + } + if (lf_node->outputs().is_empty()) { + for (lf::InputSocket *lf_socket : lf_node->inputs()) { + lf_sockets_to_check.push(lf_socket); + } + } + } + Vector lf_socket_stack; + while (!lf_sockets_to_check.is_empty()) { + lf::Socket *lf_inout_socket = lf_sockets_to_check.peek(); + lf::Node &lf_node = lf_inout_socket->node(); + SocketState &state = socket_states[lf_inout_socket->index_in_graph()]; + lf_socket_stack.append(lf_inout_socket); + state.in_stack = true; + + Vector lf_origin_sockets; + if (lf_inout_socket->is_input()) { + lf::InputSocket &lf_input_socket = lf_inout_socket->as_input(); + if (lf::OutputSocket *lf_origin_socket = lf_input_socket.origin()) { + lf_origin_sockets.append(lf_origin_socket); + } + } + else { + lf::OutputSocket &lf_output_socket = lf_inout_socket->as_output(); + if (lf_node.is_function()) { + lf::FunctionNode &lf_function_node = static_cast(lf_node); + const lf::LazyFunction &fn = lf_function_node.function(); + fn.possible_output_dependencies( + lf_output_socket.index(), [&](const Span input_indices) { + for (const int input_index : input_indices) { + lf_origin_sockets.append(&lf_node.input(input_index)); + } + }); + } + } + + bool pushed_socket = false; + for (lf::Socket *lf_origin_socket : lf_origin_sockets) { + if (socket_states[lf_origin_socket->index_in_graph()].in_stack) { + const Span cycle = lf_socket_stack.as_span().drop_front( + lf_socket_stack.first_index_of(lf_origin_socket)); + + bool broke_cycle = false; + for (lf::Socket *lf_cycle_socket : cycle) { + if (lf_cycle_socket->is_input() && + socket_usage_inputs_.contains(&lf_cycle_socket->as_input())) { + lf::InputSocket &lf_cycle_input_socket = lf_cycle_socket->as_input(); + lf_graph_->clear_origin(lf_cycle_input_socket); + static const bool static_true = true; + lf_cycle_input_socket.set_default_value(&static_true); + broke_cycle = true; + } + } + if (!broke_cycle) { + BLI_assert_unreachable(); + } + } + else if (!socket_states[lf_origin_socket->index_in_graph()].done) { + lf_sockets_to_check.push(lf_origin_socket); + pushed_socket = true; + } + } + if (pushed_socket) { + continue; + } + + state.done = true; + state.in_stack = false; + lf_sockets_to_check.pop(); + lf_socket_stack.pop_last(); + } + } + + void print_graph(); }; +class UsedSocketVisualizeOptions : public lf::Graph::ToDotOptions { + private: + const GeometryNodesLazyFunctionGraphBuilder &builder_; + Map socket_font_colors_; + Map socket_name_suffixes_; + + public: + UsedSocketVisualizeOptions(const GeometryNodesLazyFunctionGraphBuilder &builder) + : builder_(builder) + { + VectorSet found; + for (const int bsocket_index : builder_.socket_is_used_map_.index_range()) { + const bNodeSocket *bsocket = builder_.btree_.all_sockets()[bsocket_index]; + lf::OutputSocket *lf_used_socket = builder_.socket_is_used_map_[bsocket_index]; + if (lf_used_socket == nullptr) { + continue; + } + const float hue = BLI_hash_int_01(uintptr_t(lf_used_socket)); + std::stringstream ss; + ss.precision(3); + ss << hue << " 0.9 0.5"; + const std::string color_str = ss.str(); + const std::string suffix = " (" + std::to_string(found.index_of_or_add(lf_used_socket)) + + ")"; + socket_font_colors_.add(lf_used_socket, color_str); + socket_name_suffixes_.add(lf_used_socket, suffix); + + if (bsocket->is_input()) { + for (const lf::InputSocket *lf_socket : builder_.input_socket_map_.lookup(bsocket)) { + socket_font_colors_.add(lf_socket, color_str); + socket_name_suffixes_.add(lf_socket, suffix); + } + } + else if (lf::OutputSocket *lf_socket = builder_.output_socket_map_.lookup(bsocket)) { + socket_font_colors_.add(lf_socket, color_str); + socket_name_suffixes_.add(lf_socket, suffix); + } + } + } + + std::optional socket_font_color(const lf::Socket &socket) const override + { + if (const std::string *color = socket_font_colors_.lookup_ptr(&socket)) { + return *color; + } + return std::nullopt; + } + + std::string socket_name(const lf::Socket &socket) const override + { + return socket.name() + socket_name_suffixes_.lookup_default(&socket, ""); + } + + void add_edge_attributes(const lf::OutputSocket & /*from*/, + const lf::InputSocket &to, + dot::DirectedEdge &dot_edge) const + { + if (builder_.socket_usage_inputs_.contains_as(&to)) { + // dot_edge.attributes.set("constraint", "false"); + dot_edge.attributes.set("color", "#00000055"); + } + } +}; + +void GeometryNodesLazyFunctionGraphBuilder::print_graph() +{ + UsedSocketVisualizeOptions options{*this}; + std::cout << "\n\n" << lf_graph_->to_dot(options) << "\n\n"; +} + const GeometryNodesLazyFunctionGraphInfo *ensure_geometry_nodes_lazy_function_graph( const bNodeTree &btree) { diff --git a/source/blender/nodes/intern/geometry_nodes_log.cc b/source/blender/nodes/intern/geometry_nodes_log.cc index 660f878c1f7..66ccab2f77f 100644 --- a/source/blender/nodes/intern/geometry_nodes_log.cc +++ b/source/blender/nodes/intern/geometry_nodes_log.cc @@ -70,7 +70,7 @@ GeometryInfoLog::GeometryInfoLog(const GeometrySet &geometry_set) [&](const bke::AttributeIDRef &attribute_id, const bke::AttributeMetaData &meta_data, const GeometryComponent & /*component*/) { - if (attribute_id.is_named() && names.add(attribute_id.name())) { + if (!attribute_id.is_anonymous() && names.add(attribute_id.name())) { this->attributes.append({attribute_id.name(), meta_data.domain, meta_data.data_type}); } }); diff --git a/source/blender/nodes/intern/node_geometry_exec.cc b/source/blender/nodes/intern/node_geometry_exec.cc index 868b3b2c539..c338ecacbc9 100644 --- a/source/blender/nodes/intern/node_geometry_exec.cc +++ b/source/blender/nodes/intern/node_geometry_exec.cc @@ -9,10 +9,33 @@ #include "NOD_geometry_exec.hh" +#include "BLI_hash_md5.h" + #include "node_geometry_util.hh" namespace blender::nodes { +NodeAnonymousAttributeID::NodeAnonymousAttributeID(const Object &object, + const ComputeContext &compute_context, + const bNode &bnode, + const StringRef identifier) +{ + const ComputeContextHash &hash = compute_context.hash(); + { + std::stringstream ss; + ss << hash << "_" << object.id.name << "_" << bnode.identifier << "_" << identifier; + long_name_ = ss.str(); + } + { + uint64_t hash_result[2]; + BLI_hash_md5_buffer(long_name_.data(), long_name_.size(), hash_result); + std::stringstream ss; + ss << ".a_" << std::hex << hash_result[0] << hash_result[1]; + name_ = ss.str(); + BLI_assert(name_.size() < MAX_CUSTOMDATA_LAYER_NAME); + } +} + void GeoNodeExecParams::error_message_add(const NodeWarningType type, const StringRef message) const {