Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/startup/nodeitems_builtins.py5
-rw-r--r--source/blender/blenkernel/BKE_anonymous_attribute.h43
-rw-r--r--source/blender/blenkernel/BKE_anonymous_attribute.hh169
-rw-r--r--source/blender/blenkernel/BKE_attribute_access.hh98
-rw-r--r--source/blender/blenkernel/BKE_customdata.h12
-rw-r--r--source/blender/blenkernel/BKE_geometry_set.hh114
-rw-r--r--source/blender/blenkernel/BKE_geometry_set_instances.hh9
-rw-r--r--source/blender/blenkernel/BKE_node.h5
-rw-r--r--source/blender/blenkernel/CMakeLists.txt3
-rw-r--r--source/blender/blenkernel/intern/anonymous_attribute.cc118
-rw-r--r--source/blender/blenkernel/intern/attribute_access.cc422
-rw-r--r--source/blender/blenkernel/intern/attribute_access_intern.hh23
-rw-r--r--source/blender/blenkernel/intern/curve_eval.cc8
-rw-r--r--source/blender/blenkernel/intern/customdata.c53
-rw-r--r--source/blender/blenkernel/intern/geometry_component_curve.cc24
-rw-r--r--source/blender/blenkernel/intern/geometry_component_mesh.cc24
-rw-r--r--source/blender/blenkernel/intern/geometry_set_instances.cc53
-rw-r--r--source/blender/blenkernel/intern/node.cc5
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc23
-rw-r--r--source/blender/functions/CMakeLists.txt11
-rw-r--r--source/blender/functions/FN_field.hh458
-rw-r--r--source/blender/functions/FN_field_cpp_type.hh73
-rw-r--r--source/blender/functions/FN_multi_function_builder.hh9
-rw-r--r--source/blender/functions/FN_multi_function_params.hh2
-rw-r--r--source/blender/functions/FN_multi_function_procedure.hh454
-rw-r--r--source/blender/functions/FN_multi_function_procedure_builder.hh260
-rw-r--r--source/blender/functions/FN_multi_function_procedure_executor.hh39
-rw-r--r--source/blender/functions/intern/cpp_types.cc9
-rw-r--r--source/blender/functions/intern/field.cc569
-rw-r--r--source/blender/functions/intern/multi_function_builder.cc28
-rw-r--r--source/blender/functions/intern/multi_function_procedure.cc808
-rw-r--r--source/blender/functions/intern/multi_function_procedure_builder.cc175
-rw-r--r--source/blender/functions/intern/multi_function_procedure_executor.cc1212
-rw-r--r--source/blender/functions/tests/FN_field_test.cc278
-rw-r--r--source/blender/functions/tests/FN_multi_function_procedure_test.cc344
-rw-r--r--source/blender/makesdna/DNA_customdata_types.h9
-rw-r--r--source/blender/makesdna/DNA_node_types.h7
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c20
-rw-r--r--source/blender/modifiers/intern/MOD_nodes.cc17
-rw-r--r--source/blender/modifiers/intern/MOD_nodes_evaluator.cc63
-rw-r--r--source/blender/nodes/CMakeLists.txt5
-rw-r--r--source/blender/nodes/NOD_geometry.h5
-rw-r--r--source/blender/nodes/NOD_geometry_exec.hh72
-rw-r--r--source/blender/nodes/NOD_static_types.h7
-rw-r--r--source/blender/nodes/geometry/node_geometry_util.hh2
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc210
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc51
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc16
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc4
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc8
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc8
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_to_points.cc82
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc16
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc8
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_index.cc60
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_normal.cc211
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_position.cc43
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc90
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_to_curve.cc21
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc16
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_separate.cc6
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_point_translate.cc84
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_set_position.cc79
-rw-r--r--source/blender/nodes/intern/geometry_nodes_eval_log.cc6
-rw-r--r--source/blender/nodes/intern/node_socket.cc63
65 files changed, 6755 insertions, 474 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index 668ea4bfae5..b2aefd63a78 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -478,6 +478,7 @@ geometry_node_categories = [
GeometryNodeCategory("GEO_ATTRIBUTE", "Attribute", items=[
NodeItem("GeometryNodeAttributeRandomize"),
NodeItem("GeometryNodeAttributeMath"),
+ NodeItem("GeometryNodeAttributeCapture"),
NodeItem("GeometryNodeAttributeClamp"),
NodeItem("GeometryNodeAttributeCompare"),
NodeItem("GeometryNodeAttributeConvert"),
@@ -534,6 +535,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeJoinGeometry"),
NodeItem("GeometryNodeSeparateComponents"),
NodeItem("GeometryNodeRaycast"),
+ NodeItem("GeometryNodeSetPosition"),
]),
GeometryNodeCategory("GEO_INPUT", "Input", items=[
NodeItem("GeometryNodeObjectInfo"),
@@ -544,6 +546,9 @@ geometry_node_categories = [
NodeItem("FunctionNodeInputVector"),
NodeItem("GeometryNodeInputMaterial"),
NodeItem("GeometryNodeIsViewport"),
+ NodeItem("GeometryNodeInputPosition"),
+ NodeItem("GeometryNodeInputIndex"),
+ NodeItem("GeometryNodeInputNormal"),
]),
GeometryNodeCategory("GEO_MATERIAL", "Material", items=[
NodeItem("GeometryNodeMaterialAssign"),
diff --git a/source/blender/blenkernel/BKE_anonymous_attribute.h b/source/blender/blenkernel/BKE_anonymous_attribute.h
new file mode 100644
index 00000000000..ebdb0b05160
--- /dev/null
+++ b/source/blender/blenkernel/BKE_anonymous_attribute.h
@@ -0,0 +1,43 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#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
new file mode 100644
index 00000000000..201fa2b2f52
--- /dev/null
+++ b/source/blender/blenkernel/BKE_anonymous_attribute.hh
@@ -0,0 +1,169 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <string>
+
+#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<bool IsStrongReference> class OwnedAnonymousAttributeID {
+ private:
+ const AnonymousAttributeID *data_ = nullptr;
+
+ template<bool OtherIsStrongReference> 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<bool OtherIsStrong>
+ OwnedAnonymousAttributeID(const OwnedAnonymousAttributeID<OtherIsStrong> &other)
+ {
+ data_ = other.data_;
+ this->incref();
+ }
+
+ template<bool OtherIsStrong>
+ OwnedAnonymousAttributeID(OwnedAnonymousAttributeID<OtherIsStrong> &&other)
+ {
+ data_ = other.data_;
+ this->incref();
+ other.decref();
+ other.data_ = nullptr;
+ }
+
+ ~OwnedAnonymousAttributeID()
+ {
+ this->decref();
+ }
+
+ template<bool OtherIsStrong>
+ OwnedAnonymousAttributeID &operator=(const OwnedAnonymousAttributeID<OtherIsStrong> &other)
+ {
+ if (this == &other) {
+ return *this;
+ }
+ this->~OwnedAnonymousAttributeID();
+ new (this) OwnedAnonymousAttributeID(other);
+ return *this;
+ }
+
+ template<bool OtherIsStrong>
+ OwnedAnonymousAttributeID &operator=(OwnedAnonymousAttributeID<OtherIsStrong> &&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 onwership 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<true>;
+using WeakAnonymousAttributeID = OwnedAnonymousAttributeID<false>;
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh
index c3f7dbd4bd9..9d309d8a1c1 100644
--- a/source/blender/blenkernel/BKE_attribute_access.hh
+++ b/source/blender/blenkernel/BKE_attribute_access.hh
@@ -22,6 +22,7 @@
#include "FN_generic_span.hh"
#include "FN_generic_virtual_array.hh"
+#include "BKE_anonymous_attribute.hh"
#include "BKE_attribute.h"
#include "BLI_color.hh"
@@ -29,6 +30,81 @@
#include "BLI_float3.hh"
#include "BLI_function_ref.hh"
+namespace blender::bke {
+
+/**
+ * Identifies an attribute that is either named or anonymous.
+ * It does not own the identifier, so it is just a reference.
+ */
+class AttributeIDRef {
+ private:
+ StringRef name_;
+ const AnonymousAttributeID *anonymous_id_ = nullptr;
+
+ public:
+ AttributeIDRef() = default;
+
+ AttributeIDRef(StringRef name) : name_(name)
+ {
+ }
+
+ AttributeIDRef(StringRefNull name) : name_(name)
+ {
+ }
+
+ AttributeIDRef(const char *name) : name_(name)
+ {
+ }
+
+ AttributeIDRef(const std::string &name) : name_(name)
+ {
+ }
+
+ /* The anonymous id is only borrowed, the caller has to keep a reference to it. */
+ AttributeIDRef(const AnonymousAttributeID *anonymous_id) : anonymous_id_(anonymous_id)
+ {
+ }
+
+ operator bool() const
+ {
+ return this->is_named() || this->is_anonymous();
+ }
+
+ friend bool operator==(const AttributeIDRef &a, const AttributeIDRef &b)
+ {
+ return a.anonymous_id_ == b.anonymous_id_ && a.name_ == b.name_;
+ }
+
+ uint64_t hash() const
+ {
+ return get_default_hash_2(name_, anonymous_id_);
+ }
+
+ bool is_named() const
+ {
+ return !name_.is_empty();
+ }
+
+ bool is_anonymous() const
+ {
+ return anonymous_id_ != nullptr;
+ }
+
+ StringRef name() const
+ {
+ BLI_assert(this->is_named());
+ return name_;
+ }
+
+ const AnonymousAttributeID &anonymous_id() const
+ {
+ BLI_assert(this->is_anonymous());
+ return *anonymous_id_;
+ }
+};
+
+} // namespace blender::bke
+
/**
* Contains information about an attribute in a geometry component.
* More information can be added in the future. E.g. whether the attribute is builtin and how it is
@@ -104,8 +180,8 @@ struct AttributeInitMove : public AttributeInit {
};
/* Returns false when the iteration should be stopped. */
-using AttributeForeachCallback = blender::FunctionRef<bool(blender::StringRefNull attribute_name,
- const AttributeMetaData &meta_data)>;
+using AttributeForeachCallback = blender::FunctionRef<bool(
+ const blender::bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data)>;
namespace blender::bke {
@@ -333,26 +409,28 @@ class CustomDataAttributes {
void reallocate(const int size);
- std::optional<blender::fn::GSpan> get_for_read(const blender::StringRef name) const;
+ std::optional<blender::fn::GSpan> get_for_read(const AttributeIDRef &attribute_id) const;
- blender::fn::GVArrayPtr get_for_read(const StringRef name,
+ blender::fn::GVArrayPtr get_for_read(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
const void *default_value) const;
template<typename T>
- blender::fn::GVArray_Typed<T> get_for_read(const blender::StringRef name,
+ blender::fn::GVArray_Typed<T> get_for_read(const AttributeIDRef &attribute_id,
const T &default_value) const
{
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
- GVArrayPtr varray = this->get_for_read(name, type, &default_value);
+ GVArrayPtr varray = this->get_for_read(attribute_id, type, &default_value);
return blender::fn::GVArray_Typed<T>(std::move(varray));
}
- std::optional<blender::fn::GMutableSpan> get_for_write(const blender::StringRef name);
- bool create(const blender::StringRef name, const CustomDataType data_type);
- bool create_by_move(const blender::StringRef name, const CustomDataType data_type, void *buffer);
- bool remove(const blender::StringRef name);
+ std::optional<blender::fn::GMutableSpan> get_for_write(const AttributeIDRef &attribute_id);
+ bool create(const AttributeIDRef &attribute_id, const CustomDataType data_type);
+ bool create_by_move(const AttributeIDRef &attribute_id,
+ const CustomDataType data_type,
+ void *buffer);
+ bool remove(const AttributeIDRef &attribute_id);
bool foreach_attribute(const AttributeForeachCallback callback,
const AttributeDomain domain) const;
diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h
index 7a44553c565..0732f1e5190 100644
--- a/source/blender/blenkernel/BKE_customdata.h
+++ b/source/blender/blenkernel/BKE_customdata.h
@@ -33,6 +33,7 @@
extern "C" {
#endif
+struct AnonymousAttributeID;
struct BMesh;
struct BlendDataReader;
struct BlendWriter;
@@ -193,6 +194,12 @@ void *CustomData_add_layer_named(struct CustomData *data,
void *layer,
int totelem,
const char *name);
+void *CustomData_add_layer_anonymous(struct CustomData *data,
+ int type,
+ eCDAllocType alloctype,
+ void *layer,
+ int totelem,
+ const struct AnonymousAttributeID *anonymous_id);
/* frees the active or first data layer with the give type.
* returns 1 on success, 0 if no layer with the given type is found
@@ -231,6 +238,11 @@ void *CustomData_duplicate_referenced_layer_named(struct CustomData *data,
const int type,
const char *name,
const int totelem);
+void *CustomData_duplicate_referenced_layer_anonymous(
+ CustomData *data,
+ const int type,
+ const struct AnonymousAttributeID *anonymous_id,
+ const int totelem);
bool CustomData_is_referenced_layer(struct CustomData *data, int type);
/* Duplicate all the layers with flag NOFREE, and remove the flag from duplicated layers. */
diff --git a/source/blender/blenkernel/BKE_geometry_set.hh b/source/blender/blenkernel/BKE_geometry_set.hh
index 08b6cb951a8..c3d594d7dcd 100644
--- a/source/blender/blenkernel/BKE_geometry_set.hh
+++ b/source/blender/blenkernel/BKE_geometry_set.hh
@@ -31,9 +31,12 @@
#include "BLI_user_counter.hh"
#include "BLI_vector_set.hh"
+#include "BKE_anonymous_attribute.hh"
#include "BKE_attribute_access.hh"
#include "BKE_geometry_set.h"
+#include "FN_field.hh"
+
struct Collection;
struct Curve;
struct CurveEval;
@@ -88,11 +91,11 @@ class GeometryComponent {
GeometryComponentType type() const;
/* Return true when any attribute with this name exists, including built in attributes. */
- bool attribute_exists(const blender::StringRef attribute_name) const;
+ bool attribute_exists(const blender::bke::AttributeIDRef &attribute_id) const;
/* Return the data type and domain of an attribute with the given name if it exists. */
std::optional<AttributeMetaData> attribute_get_meta_data(
- const blender::StringRef attribute_name) const;
+ const blender::bke::AttributeIDRef &attribute_id) const;
/* Returns true when the geometry component supports this attribute domain. */
bool attribute_domain_supported(const AttributeDomain domain) const;
@@ -104,12 +107,12 @@ class GeometryComponent {
/* Get read-only access to the highest priority attribute with the given name.
* Returns null if the attribute does not exist. */
blender::bke::ReadAttributeLookup attribute_try_get_for_read(
- const blender::StringRef attribute_name) const;
+ const blender::bke::AttributeIDRef &attribute_id) const;
/* Get read and write access to the highest priority attribute with the given name.
* Returns null if the attribute does not exist. */
blender::bke::WriteAttributeLookup attribute_try_get_for_write(
- const blender::StringRef attribute_name);
+ const blender::bke::AttributeIDRef &attribute_id);
/* Get a read-only attribute for the domain based on the given attribute. This can be used to
* interpolate from one domain to another.
@@ -120,10 +123,10 @@ class GeometryComponent {
const AttributeDomain to_domain) const;
/* Returns true when the attribute has been deleted. */
- bool attribute_try_delete(const blender::StringRef attribute_name);
+ bool attribute_try_delete(const blender::bke::AttributeIDRef &attribute_id);
/* Returns true when the attribute has been created. */
- bool attribute_try_create(const blender::StringRef attribute_name,
+ bool attribute_try_create(const blender::bke::AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer);
@@ -133,7 +136,7 @@ class GeometryComponent {
bool attribute_try_create_builtin(const blender::StringRef attribute_name,
const AttributeInit &initializer);
- blender::Set<std::string> attribute_names() const;
+ blender::Set<blender::bke::AttributeIDRef> attribute_ids() const;
bool attribute_foreach(const AttributeForeachCallback callback) const;
virtual bool is_empty() const;
@@ -142,7 +145,7 @@ class GeometryComponent {
* Returns null when the attribute does not exist or cannot be converted to the requested domain
* and data type. */
std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read(
- const blender::StringRef attribute_name,
+ const blender::bke::AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type) const;
@@ -150,18 +153,18 @@ class GeometryComponent {
* left unchanged. Returns null when the attribute does not exist or cannot be adapted to the
* requested domain. */
std::unique_ptr<blender::fn::GVArray> attribute_try_get_for_read(
- const blender::StringRef attribute_name, const AttributeDomain domain) const;
+ const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain) const;
/* Get a virtual array to read data of an attribute with the given data type. The domain is
* left unchanged. Returns null when the attribute does not exist or cannot be converted to the
* requested data type. */
blender::bke::ReadAttributeLookup attribute_try_get_for_read(
- const blender::StringRef attribute_name, const CustomDataType data_type) const;
+ const blender::bke::AttributeIDRef &attribute_id, const CustomDataType data_type) const;
/* Get a virtual array to read the data of an attribute. If that is not possible, the returned
* virtual array will contain a default value. This never returns null. */
std::unique_ptr<blender::fn::GVArray> attribute_get_for_read(
- const blender::StringRef attribute_name,
+ const blender::bke::AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const void *default_value = nullptr) const;
@@ -169,14 +172,15 @@ class GeometryComponent {
/* Should be used instead of the method above when the requested data type is known at compile
* time for better type safety. */
template<typename T>
- blender::fn::GVArray_Typed<T> attribute_get_for_read(const blender::StringRef attribute_name,
- const AttributeDomain domain,
- const T &default_value) const
+ blender::fn::GVArray_Typed<T> attribute_get_for_read(
+ const blender::bke::AttributeIDRef &attribute_id,
+ const AttributeDomain domain,
+ const T &default_value) const
{
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
const CustomDataType type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
std::unique_ptr varray = this->attribute_get_for_read(
- attribute_name, domain, type, &default_value);
+ attribute_id, domain, type, &default_value);
return blender::fn::GVArray_Typed<T>(std::move(varray));
}
@@ -191,7 +195,7 @@ class GeometryComponent {
* is created that will overwrite the existing attribute in the end.
*/
blender::bke::OutputAttribute attribute_try_get_for_output(
- const blender::StringRef attribute_name,
+ const blender::bke::AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const void *default_value = nullptr);
@@ -200,28 +204,30 @@ class GeometryComponent {
* attributes are not read, i.e. the attribute is used only for output. Since values are not read
* from this attribute, no default value is necessary. */
blender::bke::OutputAttribute attribute_try_get_for_output_only(
- const blender::StringRef attribute_name,
+ const blender::bke::AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type);
/* Statically typed method corresponding to the equally named generic one. */
template<typename T>
blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output(
- const blender::StringRef attribute_name, const AttributeDomain domain, const T default_value)
+ const blender::bke::AttributeIDRef &attribute_id,
+ const AttributeDomain domain,
+ const T default_value)
{
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
- return this->attribute_try_get_for_output(attribute_name, domain, data_type, &default_value);
+ return this->attribute_try_get_for_output(attribute_id, domain, data_type, &default_value);
}
/* Statically typed method corresponding to the equally named generic one. */
template<typename T>
blender::bke::OutputAttribute_Typed<T> attribute_try_get_for_output_only(
- const blender::StringRef attribute_name, const AttributeDomain domain)
+ const blender::bke::AttributeIDRef &attribute_id, const AttributeDomain domain)
{
const blender::fn::CPPType &cpp_type = blender::fn::CPPType::get<T>();
const CustomDataType data_type = blender::bke::cpp_type_to_custom_data_type(cpp_type);
- return this->attribute_try_get_for_output_only(attribute_name, domain, data_type);
+ return this->attribute_try_get_for_output_only(attribute_id, domain, data_type);
}
private:
@@ -616,3 +622,69 @@ class VolumeComponent : public GeometryComponent {
static constexpr inline GeometryComponentType static_type = GEO_COMPONENT_TYPE_VOLUME;
};
+
+namespace blender::bke {
+
+class GeometryComponentFieldContext : public fn::FieldContext {
+ private:
+ const GeometryComponent &component_;
+ const AttributeDomain domain_;
+
+ public:
+ GeometryComponentFieldContext(const GeometryComponent &component, const AttributeDomain domain)
+ : component_(component), domain_(domain)
+ {
+ }
+
+ const GeometryComponent &geometry_component() const
+ {
+ return component_;
+ }
+
+ AttributeDomain domain() const
+ {
+ return domain_;
+ }
+};
+
+class AttributeFieldInput : public fn::FieldInput {
+ private:
+ std::string name_;
+
+ public:
+ AttributeFieldInput(std::string name, const CPPType &type)
+ : fn::FieldInput(type, name), name_(std::move(name))
+ {
+ }
+
+ const GVArray *get_varray_for_context(const fn::FieldContext &context,
+ IndexMask mask,
+ ResourceScope &scope) const override;
+
+ uint64_t hash() const override;
+ bool is_equal_to(const fn::FieldNode &other) const override;
+};
+
+class AnonymousAttributeFieldInput : public fn::FieldInput {
+ private:
+ /**
+ * A strong reference is required to make sure that the referenced attribute is not removed
+ * automatically.
+ */
+ StrongAnonymousAttributeID anonymous_id_;
+
+ public:
+ AnonymousAttributeFieldInput(StrongAnonymousAttributeID anonymous_id, const CPPType &type)
+ : fn::FieldInput(type, anonymous_id.debug_name()), anonymous_id_(std::move(anonymous_id))
+ {
+ }
+
+ const GVArray *get_varray_for_context(const fn::FieldContext &context,
+ IndexMask mask,
+ ResourceScope &scope) const override;
+
+ uint64_t hash() const override;
+ bool is_equal_to(const fn::FieldNode &other) const override;
+};
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_geometry_set_instances.hh b/source/blender/blenkernel/BKE_geometry_set_instances.hh
index 25876296a47..44a0ee30c4c 100644
--- a/source/blender/blenkernel/BKE_geometry_set_instances.hh
+++ b/source/blender/blenkernel/BKE_geometry_set_instances.hh
@@ -59,9 +59,10 @@ struct AttributeKind {
* will contain the highest complexity data type and the highest priority domain among every
* attribute with the given name on all of the input components.
*/
-void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups,
- Span<GeometryComponentType> component_types,
- const Set<std::string> &ignored_attributes,
- Map<std::string, AttributeKind> &r_attributes);
+void geometry_set_gather_instances_attribute_info(
+ Span<GeometryInstanceGroup> set_groups,
+ Span<GeometryComponentType> component_types,
+ const Set<std::string> &ignored_attributes,
+ Map<AttributeIDRef, AttributeKind> &r_attributes);
} // namespace blender::bke
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index b41fbae6075..0d70e812ecb 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1484,6 +1484,11 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_CURVE_SPLINE_TYPE 1073
#define GEO_NODE_CURVE_SELECT_HANDLES 1074
#define GEO_NODE_CURVE_FILL 1075
+#define GEO_NODE_INPUT_POSITION 1076
+#define GEO_NODE_SET_POSITION 1077
+#define GEO_NODE_INPUT_INDEX 1078
+#define GEO_NODE_INPUT_NORMAL 1079
+#define GEO_NODE_ATTRIBUTE_CAPTURE 1080
/** \} */
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index 64cfbd8f491..26d81ec3b34 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -76,6 +76,7 @@ set(SRC
intern/anim_path.c
intern/anim_sys.c
intern/anim_visualization.c
+ intern/anonymous_attribute.cc
intern/appdir.c
intern/armature.c
intern/armature_deform.c
@@ -295,6 +296,8 @@ set(SRC
BKE_anim_path.h
BKE_anim_visualization.h
BKE_animsys.h
+ BKE_anonymous_attribute.h
+ BKE_anonymous_attribute.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
new file mode 100644
index 00000000000..67611053d83
--- /dev/null
+++ b/source/blender/blenkernel/intern/anonymous_attribute.cc
@@ -0,0 +1,118 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#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<int> 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<int> 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<int> index = 0;
+ const int next_index = index.fetch_add(1);
+ return "anonymous_attribute_" + 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) {
+ 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/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc
index aa0af294bc3..08bd5dc5981 100644
--- a/source/blender/blenkernel/intern/attribute_access.cc
+++ b/source/blender/blenkernel/intern/attribute_access.cc
@@ -44,6 +44,8 @@ using blender::float3;
using blender::Set;
using blender::StringRef;
using blender::StringRefNull;
+using blender::bke::AttributeIDRef;
+using blender::bke::OutputAttribute;
using blender::fn::GMutableSpan;
using blender::fn::GSpan;
using blender::fn::GVArray_For_GSpan;
@@ -334,8 +336,20 @@ bool BuiltinCustomDataLayerProvider::exists(const GeometryComponent &component)
return data != nullptr;
}
+static bool custom_data_layer_matches_attribute_id(const CustomDataLayer &layer,
+ const AttributeIDRef &attribute_id)
+{
+ if (!attribute_id) {
+ return false;
+ }
+ if (attribute_id.is_anonymous()) {
+ return layer.anonymous_id == &attribute_id.anonymous_id();
+ }
+ return layer.name == attribute_id.name();
+}
+
ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
- const GeometryComponent &component, const StringRef attribute_name) const
+ const GeometryComponent &component, const AttributeIDRef &attribute_id) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
if (custom_data == nullptr) {
@@ -343,7 +357,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
}
const int domain_size = component.attribute_domain_size(domain_);
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
- if (layer.name != attribute_name) {
+ if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) {
continue;
}
const CustomDataType data_type = (CustomDataType)layer.type;
@@ -368,7 +382,7 @@ ReadAttributeLookup CustomDataAttributeProvider::try_get_for_read(
}
WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
- GeometryComponent &component, const StringRef attribute_name) const
+ GeometryComponent &component, const AttributeIDRef &attribute_id) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
@@ -376,10 +390,17 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
}
const int domain_size = component.attribute_domain_size(domain_);
for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) {
- if (layer.name != attribute_name) {
+ if (!custom_data_layer_matches_attribute_id(layer, attribute_id)) {
continue;
}
- CustomData_duplicate_referenced_layer_named(custom_data, layer.type, layer.name, domain_size);
+ if (attribute_id.is_named()) {
+ CustomData_duplicate_referenced_layer_named(
+ custom_data, layer.type, layer.name, domain_size);
+ }
+ else {
+ CustomData_duplicate_referenced_layer_anonymous(
+ custom_data, layer.type, &attribute_id.anonymous_id(), domain_size);
+ }
const CustomDataType data_type = (CustomDataType)layer.type;
switch (data_type) {
case CD_PROP_FLOAT:
@@ -402,7 +423,7 @@ WriteAttributeLookup CustomDataAttributeProvider::try_get_for_write(
}
bool CustomDataAttributeProvider::try_delete(GeometryComponent &component,
- const StringRef attribute_name) const
+ const AttributeIDRef &attribute_id) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
@@ -411,7 +432,8 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component,
const int domain_size = component.attribute_domain_size(domain_);
for (const int i : IndexRange(custom_data->totlayer)) {
const CustomDataLayer &layer = custom_data->layers[i];
- if (this->type_is_supported((CustomDataType)layer.type) && layer.name == attribute_name) {
+ if (this->type_is_supported((CustomDataType)layer.type) &&
+ custom_data_layer_matches_attribute_id(layer, attribute_id)) {
CustomData_free_layer(custom_data, layer.type, domain_size, i);
return true;
}
@@ -419,24 +441,39 @@ bool CustomDataAttributeProvider::try_delete(GeometryComponent &component,
return false;
}
-static bool add_named_custom_data_layer_from_attribute_init(const StringRef attribute_name,
- CustomData &custom_data,
- const CustomDataType data_type,
- const int domain_size,
- const AttributeInit &initializer)
+static void *add_generic_custom_data_layer(CustomData &custom_data,
+ const CustomDataType data_type,
+ const eCDAllocType alloctype,
+ void *layer_data,
+ const int domain_size,
+ const AttributeIDRef &attribute_id)
{
- char attribute_name_c[MAX_NAME];
- attribute_name.copy(attribute_name_c);
+ if (attribute_id.is_named()) {
+ char attribute_name_c[MAX_NAME];
+ attribute_id.name().copy(attribute_name_c);
+ return CustomData_add_layer_named(
+ &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c);
+ }
+ const AnonymousAttributeID &anonymous_id = attribute_id.anonymous_id();
+ return CustomData_add_layer_anonymous(
+ &custom_data, data_type, alloctype, layer_data, domain_size, &anonymous_id);
+}
+static bool add_custom_data_layer_from_attribute_init(const AttributeIDRef &attribute_id,
+ CustomData &custom_data,
+ const CustomDataType data_type,
+ const int domain_size,
+ const AttributeInit &initializer)
+{
switch (initializer.type) {
case AttributeInit::Type::Default: {
- void *data = CustomData_add_layer_named(
- &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c);
+ void *data = add_generic_custom_data_layer(
+ custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_id);
return data != nullptr;
}
case AttributeInit::Type::VArray: {
- void *data = CustomData_add_layer_named(
- &custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_name_c);
+ void *data = add_generic_custom_data_layer(
+ custom_data, data_type, CD_DEFAULT, nullptr, domain_size, attribute_id);
if (data == nullptr) {
return false;
}
@@ -446,8 +483,8 @@ static bool add_named_custom_data_layer_from_attribute_init(const StringRef attr
}
case AttributeInit::Type::MoveArray: {
void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
- void *data = CustomData_add_layer_named(
- &custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_name_c);
+ void *data = add_generic_custom_data_layer(
+ custom_data, data_type, CD_ASSIGN, source_data, domain_size, attribute_id);
if (data == nullptr) {
MEM_freeN(source_data);
return false;
@@ -461,7 +498,7 @@ static bool add_named_custom_data_layer_from_attribute_init(const StringRef attr
}
bool CustomDataAttributeProvider::try_create(GeometryComponent &component,
- const StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer) const
@@ -477,13 +514,13 @@ bool CustomDataAttributeProvider::try_create(GeometryComponent &component,
return false;
}
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
- if (layer.name == attribute_name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
return false;
}
}
const int domain_size = component.attribute_domain_size(domain_);
- add_named_custom_data_layer_from_attribute_init(
- attribute_name, *custom_data, data_type, domain_size, initializer);
+ add_custom_data_layer_from_attribute_init(
+ attribute_id, *custom_data, data_type, domain_size, initializer);
return true;
}
@@ -498,7 +535,14 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com
const CustomDataType data_type = (CustomDataType)layer.type;
if (this->type_is_supported(data_type)) {
AttributeMetaData meta_data{domain_, data_type};
- if (!callback(layer.name, meta_data)) {
+ AttributeIDRef attribute_id;
+ if (layer.anonymous_id != nullptr) {
+ attribute_id = layer.anonymous_id;
+ }
+ else {
+ attribute_id = layer.name;
+ }
+ if (!callback(attribute_id, meta_data)) {
return false;
}
}
@@ -507,7 +551,7 @@ bool CustomDataAttributeProvider::foreach_attribute(const GeometryComponent &com
}
ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read(
- const GeometryComponent &component, const StringRef attribute_name) const
+ const GeometryComponent &component, const AttributeIDRef &attribute_id) const
{
const CustomData *custom_data = custom_data_access_.get_const_custom_data(component);
if (custom_data == nullptr) {
@@ -515,7 +559,7 @@ ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read(
}
for (const CustomDataLayer &layer : Span(custom_data->layers, custom_data->totlayer)) {
if (layer.type == stored_type_) {
- if (layer.name == attribute_name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
const int domain_size = component.attribute_domain_size(domain_);
return {as_read_attribute_(layer.data, domain_size), domain_};
}
@@ -525,7 +569,7 @@ ReadAttributeLookup NamedLegacyCustomDataProvider::try_get_for_read(
}
WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write(
- GeometryComponent &component, const StringRef attribute_name) const
+ GeometryComponent &component, const AttributeIDRef &attribute_id) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
@@ -533,7 +577,7 @@ WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write(
}
for (CustomDataLayer &layer : MutableSpan(custom_data->layers, custom_data->totlayer)) {
if (layer.type == stored_type_) {
- if (layer.name == attribute_name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
const int domain_size = component.attribute_domain_size(domain_);
void *data_old = layer.data;
void *data_new = CustomData_duplicate_referenced_layer_named(
@@ -549,7 +593,7 @@ WriteAttributeLookup NamedLegacyCustomDataProvider::try_get_for_write(
}
bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component,
- const StringRef attribute_name) const
+ const AttributeIDRef &attribute_id) const
{
CustomData *custom_data = custom_data_access_.get_custom_data(component);
if (custom_data == nullptr) {
@@ -558,7 +602,7 @@ bool NamedLegacyCustomDataProvider::try_delete(GeometryComponent &component,
for (const int i : IndexRange(custom_data->totlayer)) {
const CustomDataLayer &layer = custom_data->layers[i];
if (layer.type == stored_type_) {
- if (layer.name == attribute_name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
const int domain_size = component.attribute_domain_size(domain_);
CustomData_free_layer(custom_data, stored_type_, domain_size, i);
custom_data_access_.update_custom_data_pointers(component);
@@ -627,11 +671,11 @@ CustomDataAttributes &CustomDataAttributes::operator=(const CustomDataAttributes
return *this;
}
-std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) const
+std::optional<GSpan> CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id) const
{
BLI_assert(size_ != 0);
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
- if (layer.name == name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
BLI_assert(cpp_type != nullptr);
return GSpan(*cpp_type, layer.data, size_);
@@ -645,13 +689,13 @@ std::optional<GSpan> CustomDataAttributes::get_for_read(const StringRef name) co
* value if the attribute doesn't exist. If no default value is provided, the default value for the
* type will be used.
*/
-GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name,
+GVArrayPtr CustomDataAttributes::get_for_read(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
const void *default_value) const
{
const CPPType *type = blender::bke::custom_data_type_to_cpp_type(data_type);
- std::optional<GSpan> attribute = this->get_for_read(name);
+ std::optional<GSpan> attribute = this->get_for_read(attribute_id);
if (!attribute) {
const int domain_size = this->size_;
return std::make_unique<GVArray_For_SingleValue>(
@@ -666,12 +710,12 @@ GVArrayPtr CustomDataAttributes::get_for_read(const StringRef name,
return conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), *type);
}
-std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef name)
+std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const AttributeIDRef &attribute_id)
{
/* If this assert hits, it most likely means that #reallocate was not called at some point. */
BLI_assert(size_ != 0);
for (CustomDataLayer &layer : MutableSpan(data.layers, data.totlayer)) {
- if (layer.name == name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
const CPPType *cpp_type = custom_data_type_to_cpp_type((CustomDataType)layer.type);
BLI_assert(cpp_type != nullptr);
return GMutableSpan(*cpp_type, layer.data, size_);
@@ -680,30 +724,29 @@ std::optional<GMutableSpan> CustomDataAttributes::get_for_write(const StringRef
return {};
}
-bool CustomDataAttributes::create(const StringRef name, const CustomDataType data_type)
+bool CustomDataAttributes::create(const AttributeIDRef &attribute_id,
+ const CustomDataType data_type)
{
- char name_c[MAX_NAME];
- name.copy(name_c);
- void *result = CustomData_add_layer_named(&data, data_type, CD_DEFAULT, nullptr, size_, name_c);
+ void *result = add_generic_custom_data_layer(
+ data, data_type, CD_DEFAULT, nullptr, size_, attribute_id);
return result != nullptr;
}
-bool CustomDataAttributes::create_by_move(const blender::StringRef name,
+bool CustomDataAttributes::create_by_move(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
void *buffer)
{
- char name_c[MAX_NAME];
- name.copy(name_c);
- void *result = CustomData_add_layer_named(&data, data_type, CD_ASSIGN, buffer, size_, name_c);
+ void *result = add_generic_custom_data_layer(
+ data, data_type, CD_ASSIGN, buffer, size_, attribute_id);
return result != nullptr;
}
-bool CustomDataAttributes::remove(const blender::StringRef name)
+bool CustomDataAttributes::remove(const AttributeIDRef &attribute_id)
{
bool result = false;
for (const int i : IndexRange(data.totlayer)) {
const CustomDataLayer &layer = data.layers[i];
- if (layer.name == name) {
+ if (custom_data_layer_matches_attribute_id(layer, attribute_id)) {
CustomData_free_layer(&data, layer.type, size_, i);
result = true;
}
@@ -722,7 +765,14 @@ bool CustomDataAttributes::foreach_attribute(const AttributeForeachCallback call
{
for (const CustomDataLayer &layer : Span(data.layers, data.totlayer)) {
AttributeMetaData meta_data{domain, (CustomDataType)layer.type};
- if (!callback(layer.name, meta_data)) {
+ AttributeIDRef attribute_id;
+ if (layer.anonymous_id != nullptr) {
+ attribute_id = layer.anonymous_id;
+ }
+ else {
+ attribute_id = layer.name;
+ }
+ if (!callback(attribute_id, meta_data)) {
return false;
}
}
@@ -766,21 +816,23 @@ bool GeometryComponent::attribute_is_builtin(const blender::StringRef attribute_
}
blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
- const StringRef attribute_name) const
+ const AttributeIDRef &attribute_id) const
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return {};
}
- const BuiltinAttributeProvider *builtin_provider =
- providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
- if (builtin_provider != nullptr) {
- return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()};
+ if (attribute_id.is_named()) {
+ const BuiltinAttributeProvider *builtin_provider =
+ providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
+ if (builtin_provider != nullptr) {
+ return {builtin_provider->try_get_for_read(*this), builtin_provider->domain()};
+ }
}
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
- ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_name);
+ ReadAttributeLookup attribute = dynamic_provider->try_get_for_read(*this, attribute_id);
if (attribute) {
return attribute;
}
@@ -800,21 +852,23 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_adapt_dom
}
blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_write(
- const StringRef attribute_name)
+ const AttributeIDRef &attribute_id)
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return {};
}
- const BuiltinAttributeProvider *builtin_provider =
- providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
- if (builtin_provider != nullptr) {
- return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()};
+ if (attribute_id.is_named()) {
+ const BuiltinAttributeProvider *builtin_provider =
+ providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
+ if (builtin_provider != nullptr) {
+ return {builtin_provider->try_get_for_write(*this), builtin_provider->domain()};
+ }
}
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
- WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_name);
+ WriteAttributeLookup attribute = dynamic_provider->try_get_for_write(*this, attribute_id);
if (attribute) {
return attribute;
}
@@ -822,53 +876,57 @@ blender::bke::WriteAttributeLookup GeometryComponent::attribute_try_get_for_writ
return {};
}
-bool GeometryComponent::attribute_try_delete(const StringRef attribute_name)
+bool GeometryComponent::attribute_try_delete(const AttributeIDRef &attribute_id)
{
using namespace blender::bke;
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return {};
}
- const BuiltinAttributeProvider *builtin_provider =
- providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
- if (builtin_provider != nullptr) {
- return builtin_provider->try_delete(*this);
+ if (attribute_id.is_named()) {
+ const BuiltinAttributeProvider *builtin_provider =
+ providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
+ if (builtin_provider != nullptr) {
+ return builtin_provider->try_delete(*this);
+ }
}
bool success = false;
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
- success = dynamic_provider->try_delete(*this, attribute_name) || success;
+ success = dynamic_provider->try_delete(*this, attribute_id) || success;
}
return success;
}
-bool GeometryComponent::attribute_try_create(const StringRef attribute_name,
+bool GeometryComponent::attribute_try_create(const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer)
{
using namespace blender::bke;
- if (attribute_name.is_empty()) {
+ if (!attribute_id) {
return false;
}
const ComponentAttributeProviders *providers = this->get_attribute_providers();
if (providers == nullptr) {
return false;
}
- const BuiltinAttributeProvider *builtin_provider =
- providers->builtin_attribute_providers().lookup_default_as(attribute_name, nullptr);
- if (builtin_provider != nullptr) {
- if (builtin_provider->domain() != domain) {
- return false;
- }
- if (builtin_provider->data_type() != data_type) {
- return false;
+ if (attribute_id.is_named()) {
+ const BuiltinAttributeProvider *builtin_provider =
+ providers->builtin_attribute_providers().lookup_default_as(attribute_id.name(), nullptr);
+ if (builtin_provider != nullptr) {
+ if (builtin_provider->domain() != domain) {
+ return false;
+ }
+ if (builtin_provider->data_type() != data_type) {
+ return false;
+ }
+ return builtin_provider->try_create(*this, initializer);
}
- return builtin_provider->try_create(*this, initializer);
}
for (const DynamicAttributesProvider *dynamic_provider :
providers->dynamic_attribute_providers()) {
- if (dynamic_provider->try_create(*this, attribute_name, domain, data_type, initializer)) {
+ if (dynamic_provider->try_create(*this, attribute_id, domain, data_type, initializer)) {
return true;
}
}
@@ -894,13 +952,14 @@ bool GeometryComponent::attribute_try_create_builtin(const blender::StringRef at
return builtin_provider->try_create(*this, initializer);
}
-Set<std::string> GeometryComponent::attribute_names() const
+Set<AttributeIDRef> GeometryComponent::attribute_ids() const
{
- Set<std::string> attributes;
- this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) {
- attributes.add(name);
- return true;
- });
+ Set<AttributeIDRef> attributes;
+ this->attribute_foreach(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
+ attributes.add(attribute_id);
+ return true;
+ });
return attributes;
}
@@ -931,9 +990,9 @@ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac
}
for (const DynamicAttributesProvider *provider : providers->dynamic_attribute_providers()) {
const bool continue_loop = provider->foreach_attribute(
- *this, [&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (handled_attribute_names.add(name)) {
- return callback(name, meta_data);
+ *this, [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (attribute_id.is_anonymous() || handled_attribute_names.add(attribute_id.name())) {
+ return callback(attribute_id, meta_data);
}
return true;
});
@@ -945,9 +1004,9 @@ bool GeometryComponent::attribute_foreach(const AttributeForeachCallback callbac
return true;
}
-bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name) const
+bool GeometryComponent::attribute_exists(const AttributeIDRef &attribute_id) const
{
- blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
+ blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
if (attribute) {
return true;
}
@@ -955,16 +1014,17 @@ bool GeometryComponent::attribute_exists(const blender::StringRef attribute_name
}
std::optional<AttributeMetaData> GeometryComponent::attribute_get_meta_data(
- const StringRef attribute_name) const
+ const AttributeIDRef &attribute_id) const
{
std::optional<AttributeMetaData> result{std::nullopt};
- this->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (attribute_name == name) {
- result = meta_data;
- return false;
- }
- return true;
- });
+ this->attribute_foreach(
+ [&](const AttributeIDRef &current_attribute_id, const AttributeMetaData &meta_data) {
+ if (attribute_id == current_attribute_id) {
+ result = meta_data;
+ return false;
+ }
+ return true;
+ });
return result;
}
@@ -977,11 +1037,11 @@ static std::unique_ptr<blender::fn::GVArray> try_adapt_data_type(
}
std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_read(
- const StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type) const
{
- blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
+ blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
if (!attribute) {
return {};
}
@@ -1007,13 +1067,13 @@ std::unique_ptr<blender::fn::GVArray> GeometryComponent::attribute_try_get_for_r
}
std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_read(
- const StringRef attribute_name, const AttributeDomain domain) const
+ const AttributeIDRef &attribute_id, const AttributeDomain domain) const
{
if (!this->attribute_domain_supported(domain)) {
return {};
}
- blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
+ blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
if (!attribute) {
return {};
}
@@ -1026,9 +1086,9 @@ std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_try_get_for_
}
blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
- const blender::StringRef attribute_name, const CustomDataType data_type) const
+ const AttributeIDRef &attribute_id, const CustomDataType data_type) const
{
- blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_name);
+ blender::bke::ReadAttributeLookup attribute = this->attribute_try_get_for_read(attribute_id);
if (!attribute) {
return {};
}
@@ -1043,13 +1103,13 @@ blender::bke::ReadAttributeLookup GeometryComponent::attribute_try_get_for_read(
}
std::unique_ptr<blender::bke::GVArray> GeometryComponent::attribute_get_for_read(
- const StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const void *default_value) const
{
std::unique_ptr<blender::bke::GVArray> varray = this->attribute_try_get_for_read(
- attribute_name, domain, data_type);
+ attribute_id, domain, data_type);
if (varray) {
return varray;
}
@@ -1065,15 +1125,22 @@ class GVMutableAttribute_For_OutputAttribute
: public blender::fn::GVMutableArray_For_GMutableSpan {
public:
GeometryComponent *component;
- std::string final_name;
+ std::string attribute_name;
+ blender::bke::WeakAnonymousAttributeID anonymous_attribute_id;
GVMutableAttribute_For_OutputAttribute(GMutableSpan data,
GeometryComponent &component,
- std::string final_name)
- : blender::fn::GVMutableArray_For_GMutableSpan(data),
- component(&component),
- final_name(std::move(final_name))
+ const AttributeIDRef &attribute_id)
+ : blender::fn::GVMutableArray_For_GMutableSpan(data), component(&component)
{
+ if (attribute_id.is_named()) {
+ this->attribute_name = attribute_id.name();
+ }
+ else {
+ const AnonymousAttributeID *anonymous_id = &attribute_id.anonymous_id();
+ BKE_anonymous_attribute_id_increment_weak(anonymous_id);
+ this->anonymous_attribute_id = blender::bke::WeakAnonymousAttributeID{anonymous_id};
+ }
}
~GVMutableAttribute_For_OutputAttribute() override
@@ -1083,7 +1150,7 @@ class GVMutableAttribute_For_OutputAttribute
}
};
-static void save_output_attribute(blender::bke::OutputAttribute &output_attribute)
+static void save_output_attribute(OutputAttribute &output_attribute)
{
using namespace blender;
using namespace blender::fn;
@@ -1093,21 +1160,28 @@ static void save_output_attribute(blender::bke::OutputAttribute &output_attribut
dynamic_cast<GVMutableAttribute_For_OutputAttribute &>(output_attribute.varray());
GeometryComponent &component = *varray.component;
- const StringRefNull name = varray.final_name;
+ AttributeIDRef attribute_id;
+ if (!varray.attribute_name.empty()) {
+ attribute_id = varray.attribute_name;
+ }
+ else {
+ attribute_id = varray.anonymous_attribute_id.extract();
+ }
const AttributeDomain domain = output_attribute.domain();
const CustomDataType data_type = output_attribute.custom_data_type();
const CPPType &cpp_type = output_attribute.cpp_type();
- component.attribute_try_delete(name);
- if (!component.attribute_try_create(
- varray.final_name, domain, data_type, AttributeInitDefault())) {
- CLOG_WARN(&LOG,
- "Could not create the '%s' attribute with type '%s'.",
- name.c_str(),
- cpp_type.name().c_str());
+ component.attribute_try_delete(attribute_id);
+ if (!component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault())) {
+ if (!varray.attribute_name.empty()) {
+ CLOG_WARN(&LOG,
+ "Could not create the '%s' attribute with type '%s'.",
+ varray.attribute_name.c_str(),
+ cpp_type.name().c_str());
+ }
return;
}
- WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(name);
+ WriteAttributeLookup write_attribute = component.attribute_try_get_for_write(attribute_id);
BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), buffer);
for (const int i : IndexRange(varray.size())) {
varray.get(i, buffer);
@@ -1115,19 +1189,18 @@ static void save_output_attribute(blender::bke::OutputAttribute &output_attribut
}
}
-static blender::bke::OutputAttribute create_output_attribute(
- GeometryComponent &component,
- const blender::StringRef attribute_name,
- const AttributeDomain domain,
- const CustomDataType data_type,
- const bool ignore_old_values,
- const void *default_value)
+static OutputAttribute create_output_attribute(GeometryComponent &component,
+ const AttributeIDRef &attribute_id,
+ const AttributeDomain domain,
+ const CustomDataType data_type,
+ const bool ignore_old_values,
+ const void *default_value)
{
using namespace blender;
using namespace blender::fn;
using namespace blender::bke;
- if (attribute_name.is_empty()) {
+ if (!attribute_id) {
return {};
}
@@ -1135,7 +1208,8 @@ static blender::bke::OutputAttribute create_output_attribute(
BLI_assert(cpp_type != nullptr);
const nodes::DataTypeConversions &conversions = nodes::get_implicit_type_conversions();
- if (component.attribute_is_builtin(attribute_name)) {
+ if (attribute_id.is_named() && component.attribute_is_builtin(attribute_id.name())) {
+ const StringRef attribute_name = attribute_id.name();
WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name);
if (!attribute) {
if (default_value) {
@@ -1169,18 +1243,18 @@ static blender::bke::OutputAttribute create_output_attribute(
const int domain_size = component.attribute_domain_size(domain);
- WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_name);
+ WriteAttributeLookup attribute = component.attribute_try_get_for_write(attribute_id);
if (!attribute) {
if (default_value) {
const GVArray_For_SingleValueRef default_varray{*cpp_type, domain_size, default_value};
component.attribute_try_create(
- attribute_name, domain, data_type, AttributeInitVArray(&default_varray));
+ attribute_id, domain, data_type, AttributeInitVArray(&default_varray));
}
else {
- component.attribute_try_create(attribute_name, domain, data_type, AttributeInitDefault());
+ component.attribute_try_create(attribute_id, domain, data_type, AttributeInitDefault());
}
- attribute = component.attribute_try_get_for_write(attribute_name);
+ attribute = component.attribute_try_get_for_write(attribute_id);
if (!attribute) {
/* Can't create the attribute. */
return {};
@@ -1202,28 +1276,88 @@ static blender::bke::OutputAttribute create_output_attribute(
else {
/* Fill the temporary array with values from the existing attribute. */
GVArrayPtr old_varray = component.attribute_get_for_read(
- attribute_name, domain, data_type, default_value);
+ attribute_id, domain, data_type, default_value);
old_varray->materialize_to_uninitialized(IndexRange(domain_size), data);
}
GVMutableArrayPtr varray = std::make_unique<GVMutableAttribute_For_OutputAttribute>(
- GMutableSpan{*cpp_type, data, domain_size}, component, attribute_name);
+ GMutableSpan{*cpp_type, data, domain_size}, component, attribute_id);
return OutputAttribute(std::move(varray), domain, save_output_attribute, true);
}
-blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output(
- const StringRef attribute_name,
- const AttributeDomain domain,
- const CustomDataType data_type,
- const void *default_value)
+OutputAttribute GeometryComponent::attribute_try_get_for_output(const AttributeIDRef &attribute_id,
+ const AttributeDomain domain,
+ const CustomDataType data_type,
+ const void *default_value)
{
- return create_output_attribute(*this, attribute_name, domain, data_type, false, default_value);
+ return create_output_attribute(*this, attribute_id, domain, data_type, false, default_value);
}
-blender::bke::OutputAttribute GeometryComponent::attribute_try_get_for_output_only(
- const blender::StringRef attribute_name,
+OutputAttribute GeometryComponent::attribute_try_get_for_output_only(
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type)
{
- return create_output_attribute(*this, attribute_name, domain, data_type, true, nullptr);
+ return create_output_attribute(*this, attribute_id, domain, data_type, true, nullptr);
+}
+
+namespace blender::bke {
+
+const GVArray *AttributeFieldInput::get_varray_for_context(const fn::FieldContext &context,
+ IndexMask UNUSED(mask),
+ ResourceScope &scope) const
+{
+ if (const GeometryComponentFieldContext *geometry_context =
+ dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
+ const GeometryComponent &component = geometry_context->geometry_component();
+ const AttributeDomain domain = geometry_context->domain();
+ const CustomDataType data_type = cpp_type_to_custom_data_type(*type_);
+ GVArrayPtr attribute = component.attribute_try_get_for_read(name_, domain, data_type);
+ return scope.add(std::move(attribute), __func__);
+ }
+ return nullptr;
+}
+
+uint64_t AttributeFieldInput::hash() const
+{
+ return get_default_hash_2(name_, type_);
+}
+
+bool AttributeFieldInput::is_equal_to(const fn::FieldNode &other) const
+{
+ if (const AttributeFieldInput *other_typed = dynamic_cast<const AttributeFieldInput *>(&other)) {
+ return name_ == other_typed->name_ && type_ == other_typed->type_;
+ }
+ return false;
+}
+
+const GVArray *AnonymousAttributeFieldInput::get_varray_for_context(
+ const fn::FieldContext &context, IndexMask UNUSED(mask), ResourceScope &scope) const
+{
+ if (const GeometryComponentFieldContext *geometry_context =
+ dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
+ const GeometryComponent &component = geometry_context->geometry_component();
+ const AttributeDomain domain = geometry_context->domain();
+ const CustomDataType data_type = cpp_type_to_custom_data_type(*type_);
+ GVArrayPtr attribute = component.attribute_try_get_for_read(
+ anonymous_id_.get(), domain, data_type);
+ return scope.add(std::move(attribute), __func__);
+ }
+ return nullptr;
+}
+
+uint64_t AnonymousAttributeFieldInput::hash() const
+{
+ return get_default_hash_2(anonymous_id_.get(), type_);
}
+
+bool AnonymousAttributeFieldInput::is_equal_to(const fn::FieldNode &other) const
+{
+ if (const AnonymousAttributeFieldInput *other_typed =
+ dynamic_cast<const AnonymousAttributeFieldInput *>(&other)) {
+ return anonymous_id_.get() == other_typed->anonymous_id_.get() && type_ == other_typed->type_;
+ }
+ return false;
+}
+
+} // namespace blender::bke
diff --git a/source/blender/blenkernel/intern/attribute_access_intern.hh b/source/blender/blenkernel/intern/attribute_access_intern.hh
index b3a795faa30..261cb26d4e5 100644
--- a/source/blender/blenkernel/intern/attribute_access_intern.hh
+++ b/source/blender/blenkernel/intern/attribute_access_intern.hh
@@ -116,12 +116,13 @@ class BuiltinAttributeProvider {
class DynamicAttributesProvider {
public:
virtual ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
- const StringRef attribute_name) const = 0;
+ const AttributeIDRef &attribute_id) const = 0;
virtual WriteAttributeLookup try_get_for_write(GeometryComponent &component,
- const StringRef attribute_name) const = 0;
- virtual bool try_delete(GeometryComponent &component, const StringRef attribute_name) const = 0;
+ const AttributeIDRef &attribute_id) const = 0;
+ virtual bool try_delete(GeometryComponent &component,
+ const AttributeIDRef &attribute_id) const = 0;
virtual bool try_create(GeometryComponent &UNUSED(component),
- const StringRef UNUSED(attribute_name),
+ const AttributeIDRef &UNUSED(attribute_id),
const AttributeDomain UNUSED(domain),
const CustomDataType UNUSED(data_type),
const AttributeInit &UNUSED(initializer)) const
@@ -154,15 +155,15 @@ class CustomDataAttributeProvider final : public DynamicAttributesProvider {
}
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
- const StringRef attribute_name) const final;
+ const AttributeIDRef &attribute_id) const final;
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
- const StringRef attribute_name) const final;
+ const AttributeIDRef &attribute_id) const final;
- bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final;
+ bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final;
bool try_create(GeometryComponent &component,
- const StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer) const final;
@@ -231,10 +232,10 @@ class NamedLegacyCustomDataProvider final : public DynamicAttributesProvider {
}
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
- const StringRef attribute_name) const final;
+ const AttributeIDRef &attribute_id) const final;
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
- const StringRef attribute_name) const final;
- bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final;
+ const AttributeIDRef &attribute_id) const final;
+ bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final;
bool foreach_attribute(const GeometryComponent &component,
const AttributeForeachCallback callback) const final;
void foreach_domain(const FunctionRef<void(AttributeDomain)> callback) const final;
diff --git a/source/blender/blenkernel/intern/curve_eval.cc b/source/blender/blenkernel/intern/curve_eval.cc
index 5c18f6f3807..1c4f9c5a6ab 100644
--- a/source/blender/blenkernel/intern/curve_eval.cc
+++ b/source/blender/blenkernel/intern/curve_eval.cc
@@ -25,6 +25,7 @@
#include "DNA_curve_types.h"
+#include "BKE_anonymous_attribute.hh"
#include "BKE_curve.h"
#include "BKE_spline.hh"
@@ -37,6 +38,7 @@ using blender::MutableSpan;
using blender::Span;
using blender::StringRefNull;
using blender::Vector;
+using blender::bke::AttributeIDRef;
blender::Span<SplinePtr> CurveEval::splines() const
{
@@ -330,13 +332,13 @@ void CurveEval::assert_valid_point_attributes() const
return;
}
const int layer_len = splines_.first()->attributes.data.totlayer;
- Map<StringRefNull, AttributeMetaData> map;
+ Map<AttributeIDRef, AttributeMetaData> map;
for (const SplinePtr &spline : splines_) {
BLI_assert(spline->attributes.data.totlayer == layer_len);
spline->attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
map.add_or_modify(
- name,
+ attribute_id,
[&](AttributeMetaData *map_data) {
/* All unique attribute names should be added on the first spline. */
BLI_assert(spline == splines_.first());
diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c
index 1a3200a9b6c..ad2d5d267d5 100644
--- a/source/blender/blenkernel/intern/customdata.c
+++ b/source/blender/blenkernel/intern/customdata.c
@@ -46,6 +46,7 @@
#include "BLT_translation.h"
+#include "BKE_anonymous_attribute.h"
#include "BKE_customdata.h"
#include "BKE_customdata_file.h"
#include "BKE_deform.h"
@@ -2127,6 +2128,11 @@ bool CustomData_merge(const struct CustomData *source,
if (flag & CD_FLAG_NOCOPY) {
continue;
}
+ if (layer->anonymous_id &&
+ !BKE_anonymous_attribute_id_has_strong_references(layer->anonymous_id)) {
+ /* This attribute is not referenced anymore, so it can be treated as if it didn't exist. */
+ continue;
+ }
if (!(mask & CD_TYPE_AS_MASK(type))) {
continue;
}
@@ -2166,6 +2172,11 @@ bool CustomData_merge(const struct CustomData *source,
newlayer->active_mask = lastmask;
newlayer->flag |= flag & (CD_FLAG_EXTERNAL | CD_FLAG_IN_MEMORY);
changed = true;
+
+ if (layer->anonymous_id != NULL) {
+ BKE_anonymous_attribute_id_increment_weak(layer->anonymous_id);
+ newlayer->anonymous_id = layer->anonymous_id;
+ }
}
}
@@ -2206,6 +2217,10 @@ static void customData_free_layer__internal(CustomDataLayer *layer, int totelem)
{
const LayerTypeInfo *typeInfo;
+ if (layer->anonymous_id != NULL) {
+ BKE_anonymous_attribute_id_decrement_weak(layer->anonymous_id);
+ layer->anonymous_id = NULL;
+ }
if (!(layer->flag & CD_FLAG_NOFREE) && layer->data) {
typeInfo = layerType_getInfo(layer->type);
@@ -2649,6 +2664,27 @@ void *CustomData_add_layer_named(CustomData *data,
return NULL;
}
+void *CustomData_add_layer_anonymous(struct CustomData *data,
+ int type,
+ eCDAllocType alloctype,
+ void *layerdata,
+ int totelem,
+ const AnonymousAttributeID *anonymous_id)
+{
+ const char *name = BKE_anonymous_attribute_id_internal_name(anonymous_id);
+ CustomDataLayer *layer = customData_add_layer__internal(
+ data, type, alloctype, layerdata, totelem, name);
+ CustomData_update_typemap(data);
+
+ if (layer == NULL) {
+ return NULL;
+ }
+
+ BKE_anonymous_attribute_id_increment_weak(anonymous_id);
+ layer->anonymous_id = anonymous_id;
+ return layer->data;
+}
+
bool CustomData_free_layer(CustomData *data, int type, int totelem, int index)
{
const int index_first = CustomData_get_layer_index(data, type);
@@ -2812,6 +2848,20 @@ 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 UNUSED(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 NULL;
+}
+
void CustomData_duplicate_referenced_layers(CustomData *data, int totelem)
{
for (int i = 0; i < data->totlayer; i++) {
@@ -4244,7 +4294,8 @@ void CustomData_blend_write_prepare(CustomData *data,
for (i = 0, j = 0; i < totlayer; i++) {
CustomDataLayer *layer = &data->layers[i];
- if (layer->flag & CD_FLAG_NOCOPY) { /* Layers with this flag set are not written to file. */
+ /* Layers with this flag set are not written to file. */
+ if ((layer->flag & CD_FLAG_NOCOPY) || layer->anonymous_id != NULL) {
data->totlayer--;
// CLOG_WARN(&LOG, "skipping layer %p (%s)", layer, layer->name);
}
diff --git a/source/blender/blenkernel/intern/geometry_component_curve.cc b/source/blender/blenkernel/intern/geometry_component_curve.cc
index 0b6ba966974..a24b60ee288 100644
--- a/source/blender/blenkernel/intern/geometry_component_curve.cc
+++ b/source/blender/blenkernel/intern/geometry_component_curve.cc
@@ -892,7 +892,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
public:
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
- const StringRef attribute_name) const final
+ const AttributeIDRef &attribute_id) const final
{
const CurveEval *curve = get_curve_from_component_for_read(component);
if (curve == nullptr || curve->splines().size() == 0) {
@@ -902,13 +902,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
Span<SplinePtr> splines = curve->splines();
Vector<GSpan> spans; /* GSpan has no default constructor. */
spans.reserve(splines.size());
- std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_name);
+ std::optional<GSpan> first_span = splines[0]->attributes.get_for_read(attribute_id);
if (!first_span) {
return {};
}
spans.append(*first_span);
for (const int i : IndexRange(1, splines.size() - 1)) {
- std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_name);
+ std::optional<GSpan> span = splines[i]->attributes.get_for_read(attribute_id);
if (!span) {
/* All splines should have the same set of data layers. It would be possible to recover
* here and return partial data instead, but that would add a lot of complexity for a
@@ -945,7 +945,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
/* This function is almost the same as #try_get_for_read, but without const. */
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
- const StringRef attribute_name) const final
+ const AttributeIDRef &attribute_id) const final
{
CurveEval *curve = get_curve_from_component_for_write(component);
if (curve == nullptr || curve->splines().size() == 0) {
@@ -955,13 +955,13 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
MutableSpan<SplinePtr> splines = curve->splines();
Vector<GMutableSpan> spans; /* GMutableSpan has no default constructor. */
spans.reserve(splines.size());
- std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_name);
+ std::optional<GMutableSpan> first_span = splines[0]->attributes.get_for_write(attribute_id);
if (!first_span) {
return {};
}
spans.append(*first_span);
for (const int i : IndexRange(1, splines.size() - 1)) {
- std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_name);
+ std::optional<GMutableSpan> span = splines[i]->attributes.get_for_write(attribute_id);
if (!span) {
/* All splines should have the same set of data layers. It would be possible to recover
* here and return partial data instead, but that would add a lot of complexity for a
@@ -996,7 +996,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
return attribute;
}
- bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final
+ bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final
{
CurveEval *curve = get_curve_from_component_for_write(component);
if (curve == nullptr) {
@@ -1006,7 +1006,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
/* Reuse the boolean for all splines; we expect all splines to have the same attributes. */
bool layer_freed = false;
for (SplinePtr &spline : curve->splines()) {
- layer_freed = spline->attributes.remove(attribute_name);
+ layer_freed = spline->attributes.remove(attribute_id);
}
return layer_freed;
}
@@ -1034,7 +1034,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
}
bool try_create(GeometryComponent &component,
- const StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const AttributeDomain domain,
const CustomDataType data_type,
const AttributeInit &initializer) const final
@@ -1053,7 +1053,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
/* First check the one case that allows us to avoid copying the input data. */
if (splines.size() == 1 && initializer.type == AttributeInit::Type::MoveArray) {
void *source_data = static_cast<const AttributeInitMove &>(initializer).data;
- if (!splines[0]->attributes.create_by_move(attribute_name, data_type, source_data)) {
+ if (!splines[0]->attributes.create_by_move(attribute_id, data_type, source_data)) {
MEM_freeN(source_data);
return false;
}
@@ -1062,7 +1062,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
/* Otherwise just create a custom data layer on each of the splines. */
for (const int i : splines.index_range()) {
- if (!splines[i]->attributes.create(attribute_name, data_type)) {
+ if (!splines[i]->attributes.create(attribute_id, data_type)) {
/* If attribute creation fails on one of the splines, we cannot leave the custom data
* layers in the previous splines around, so delete them before returning. However,
* this is not an expected case. */
@@ -1076,7 +1076,7 @@ class DynamicPointAttributeProvider final : public DynamicAttributesProvider {
return true;
}
- WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_name);
+ WriteAttributeLookup write_attribute = this->try_get_for_write(component, attribute_id);
/* We just created the attribute, it should exist. */
BLI_assert(write_attribute);
diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc
index ef93a3f9b3f..9a4b8f4eb92 100644
--- a/source/blender/blenkernel/intern/geometry_component_mesh.cc
+++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc
@@ -818,16 +818,20 @@ class VArray_For_VertexWeights final : public VArray<float> {
class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
public:
ReadAttributeLookup try_get_for_read(const GeometryComponent &component,
- const StringRef attribute_name) const final
+ const AttributeIDRef &attribute_id) const final
{
BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH);
+ if (!attribute_id.is_named()) {
+ return {};
+ }
const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component);
const Mesh *mesh = mesh_component.get_for_read();
if (mesh == nullptr) {
return {};
}
+ const std::string name = attribute_id.name();
const int vertex_group_index = BLI_findstringindex(
- &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name));
+ &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name));
if (vertex_group_index < 0) {
return {};
}
@@ -843,17 +847,21 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
}
WriteAttributeLookup try_get_for_write(GeometryComponent &component,
- const StringRef attribute_name) const final
+ const AttributeIDRef &attribute_id) const final
{
BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH);
+ if (!attribute_id.is_named()) {
+ return {};
+ }
MeshComponent &mesh_component = static_cast<MeshComponent &>(component);
Mesh *mesh = mesh_component.get_for_write();
if (mesh == nullptr) {
return {};
}
+ const std::string name = attribute_id.name();
const int vertex_group_index = BLI_findstringindex(
- &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name));
+ &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name));
if (vertex_group_index < 0) {
return {};
}
@@ -872,17 +880,21 @@ class VertexGroupsAttributeProvider final : public DynamicAttributesProvider {
ATTR_DOMAIN_POINT};
}
- bool try_delete(GeometryComponent &component, const StringRef attribute_name) const final
+ bool try_delete(GeometryComponent &component, const AttributeIDRef &attribute_id) const final
{
BLI_assert(component.type() == GEO_COMPONENT_TYPE_MESH);
+ if (!attribute_id.is_named()) {
+ return false;
+ }
MeshComponent &mesh_component = static_cast<MeshComponent &>(component);
Mesh *mesh = mesh_component.get_for_write();
if (mesh == nullptr) {
return true;
}
+ const std::string name = attribute_id.name();
const int vertex_group_index = BLI_findstringindex(
- &mesh->vertex_group_names, attribute_name.data(), offsetof(bDeformGroup, name));
+ &mesh->vertex_group_names, name.c_str(), offsetof(bDeformGroup, name));
if (vertex_group_index < 0) {
return false;
}
diff --git a/source/blender/blenkernel/intern/geometry_set_instances.cc b/source/blender/blenkernel/intern/geometry_set_instances.cc
index 376792b9b96..0e19324a3c1 100644
--- a/source/blender/blenkernel/intern/geometry_set_instances.cc
+++ b/source/blender/blenkernel/intern/geometry_set_instances.cc
@@ -331,7 +331,7 @@ void geometry_set_instances_attribute_foreach(const GeometrySet &geometry_set,
void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> set_groups,
Span<GeometryComponentType> component_types,
const Set<std::string> &ignored_attributes,
- Map<std::string, AttributeKind> &r_attributes)
+ Map<AttributeIDRef, AttributeKind> &r_attributes)
{
for (const GeometryInstanceGroup &set_group : set_groups) {
const GeometrySet &set = set_group.geometry_set;
@@ -341,23 +341,24 @@ void geometry_set_gather_instances_attribute_info(Span<GeometryInstanceGroup> se
}
const GeometryComponent &component = *set.get_component_for_read(component_type);
- component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (ignored_attributes.contains(name)) {
- return true;
- }
- auto add_info = [&](AttributeKind *attribute_kind) {
- attribute_kind->domain = meta_data.domain;
- attribute_kind->data_type = meta_data.data_type;
- };
- auto modify_info = [&](AttributeKind *attribute_kind) {
- attribute_kind->domain = meta_data.domain; /* TODO: Use highest priority domain. */
- attribute_kind->data_type = bke::attribute_data_type_highest_complexity(
- {attribute_kind->data_type, meta_data.data_type});
- };
-
- r_attributes.add_or_modify(name, add_info, modify_info);
- return true;
- });
+ component.attribute_foreach(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) {
+ return true;
+ }
+ auto add_info = [&](AttributeKind *attribute_kind) {
+ attribute_kind->domain = meta_data.domain;
+ attribute_kind->data_type = meta_data.data_type;
+ };
+ auto modify_info = [&](AttributeKind *attribute_kind) {
+ attribute_kind->domain = meta_data.domain; /* TODO: Use highest priority domain. */
+ attribute_kind->data_type = bke::attribute_data_type_highest_complexity(
+ {attribute_kind->data_type, meta_data.data_type});
+ };
+
+ r_attributes.add_or_modify(attribute_id, add_info, modify_info);
+ return true;
+ });
}
}
}
@@ -512,11 +513,11 @@ static Mesh *join_mesh_topology_and_builtin_attributes(Span<GeometryInstanceGrou
static void join_attributes(Span<GeometryInstanceGroup> set_groups,
Span<GeometryComponentType> component_types,
- const Map<std::string, AttributeKind> &attribute_info,
+ const Map<AttributeIDRef, AttributeKind> &attribute_info,
GeometryComponent &result)
{
- for (Map<std::string, AttributeKind>::Item entry : attribute_info.items()) {
- StringRef name = entry.key;
+ for (Map<AttributeIDRef, AttributeKind>::Item entry : attribute_info.items()) {
+ const AttributeIDRef attribute_id = entry.key;
const AttributeDomain domain_output = entry.value.domain;
const CustomDataType data_type_output = entry.value.data_type;
const CPPType *cpp_type = bke::custom_data_type_to_cpp_type(data_type_output);
@@ -524,7 +525,7 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups,
result.attribute_try_create(
entry.key, domain_output, data_type_output, AttributeInitDefault());
- WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(name);
+ WriteAttributeLookup write_attribute = result.attribute_try_get_for_write(attribute_id);
if (!write_attribute || &write_attribute.varray->type() != cpp_type ||
write_attribute.domain != domain_output) {
continue;
@@ -543,7 +544,7 @@ static void join_attributes(Span<GeometryInstanceGroup> set_groups,
continue; /* Domain size is 0, so no need to increment the offset. */
}
GVArrayPtr source_attribute = component.attribute_try_get_for_read(
- name, domain_output, data_type_output);
+ attribute_id, domain_output, data_type_output);
if (source_attribute) {
fn::GVArray_GSpan src_span{*source_attribute};
@@ -653,7 +654,7 @@ static void join_instance_groups_mesh(Span<GeometryInstanceGroup> set_groups,
}
/* Don't copy attributes that are stored directly in the mesh data structs. */
- Map<std::string, AttributeKind> attributes;
+ Map<AttributeIDRef, AttributeKind> attributes;
geometry_set_gather_instances_attribute_info(
set_groups,
component_types,
@@ -674,7 +675,7 @@ static void join_instance_groups_pointcloud(Span<GeometryInstanceGroup> set_grou
PointCloudComponent &dst_component = result.get_component_for_write<PointCloudComponent>();
dst_component.replace(new_pointcloud);
- Map<std::string, AttributeKind> attributes;
+ Map<AttributeIDRef, AttributeKind> attributes;
geometry_set_gather_instances_attribute_info(
set_groups, {GEO_COMPONENT_TYPE_POINT_CLOUD}, {"position"}, attributes);
join_attributes(set_groups,
@@ -708,7 +709,7 @@ static void join_instance_groups_curve(Span<GeometryInstanceGroup> set_groups, G
CurveComponent &dst_component = result.get_component_for_write<CurveComponent>();
dst_component.replace(curve);
- Map<std::string, AttributeKind> attributes;
+ Map<AttributeIDRef, AttributeKind> attributes;
geometry_set_gather_instances_attribute_info(
set_groups,
{GEO_COMPONENT_TYPE_CURVE},
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index be4fd7aac4f..950026b9d65 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -5140,6 +5140,7 @@ static void registerGeometryNodes()
register_node_type_geo_attribute_convert();
register_node_type_geo_attribute_curve_map();
register_node_type_geo_attribute_fill();
+ register_node_type_geo_attribute_capture();
register_node_type_geo_attribute_map_range();
register_node_type_geo_attribute_math();
register_node_type_geo_attribute_mix();
@@ -5174,7 +5175,10 @@ static void registerGeometryNodes()
register_node_type_geo_curve_trim();
register_node_type_geo_delete_geometry();
register_node_type_geo_edge_split();
+ register_node_type_geo_input_index();
register_node_type_geo_input_material();
+ register_node_type_geo_input_normal();
+ register_node_type_geo_input_position();
register_node_type_geo_is_viewport();
register_node_type_geo_join_geometry();
register_node_type_geo_material_assign();
@@ -5202,6 +5206,7 @@ static void registerGeometryNodes()
register_node_type_geo_select_by_handle_type();
register_node_type_geo_select_by_material();
register_node_type_geo_separate_components();
+ register_node_type_geo_set_position();
register_node_type_geo_subdivision_surface();
register_node_type_geo_switch();
register_node_type_geo_transform();
diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc
index 12815c2c7e9..f5fef5e4486 100644
--- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc
+++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc
@@ -45,15 +45,20 @@ namespace blender::ed::spreadsheet {
void GeometryDataSource::foreach_default_column_ids(
FunctionRef<void(const SpreadsheetColumnID &)> fn) const
{
- component_->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (meta_data.domain != domain_) {
- return true;
- }
- SpreadsheetColumnID column_id;
- column_id.name = (char *)name.c_str();
- fn(column_id);
- return true;
- });
+ component_->attribute_foreach(
+ [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (meta_data.domain != domain_) {
+ return true;
+ }
+ if (attribute_id.is_anonymous()) {
+ return true;
+ }
+ SpreadsheetColumnID column_id;
+ std::string name = attribute_id.name();
+ column_id.name = (char *)name.c_str();
+ fn(column_id);
+ return true;
+ });
}
std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values(
diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt
index f8d2acc74a8..3c27e9d5e19 100644
--- a/source/blender/functions/CMakeLists.txt
+++ b/source/blender/functions/CMakeLists.txt
@@ -28,14 +28,20 @@ set(INC_SYS
set(SRC
intern/cpp_types.cc
+ intern/field.cc
intern/generic_vector_array.cc
intern/generic_virtual_array.cc
intern/generic_virtual_vector_array.cc
intern/multi_function.cc
intern/multi_function_builder.cc
+ intern/multi_function_procedure.cc
+ intern/multi_function_procedure_builder.cc
+ intern/multi_function_procedure_executor.cc
FN_cpp_type.hh
FN_cpp_type_make.hh
+ FN_field.hh
+ FN_field_cpp_type.hh
FN_generic_pointer.hh
FN_generic_span.hh
FN_generic_value_map.hh
@@ -48,6 +54,9 @@ set(SRC
FN_multi_function_data_type.hh
FN_multi_function_param_type.hh
FN_multi_function_params.hh
+ FN_multi_function_procedure.hh
+ FN_multi_function_procedure_builder.hh
+ FN_multi_function_procedure_executor.hh
FN_multi_function_signature.hh
)
@@ -60,8 +69,10 @@ blender_add_lib(bf_functions "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
set(TEST_SRC
tests/FN_cpp_type_test.cc
+ tests/FN_field_test.cc
tests/FN_generic_span_test.cc
tests/FN_generic_vector_array_test.cc
+ tests/FN_multi_function_procedure_test.cc
tests/FN_multi_function_test.cc
)
set(TEST_LIB
diff --git a/source/blender/functions/FN_field.hh b/source/blender/functions/FN_field.hh
new file mode 100644
index 00000000000..07859565a06
--- /dev/null
+++ b/source/blender/functions/FN_field.hh
@@ -0,0 +1,458 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ *
+ * A #Field represents a function that outputs a value based on an arbitrary number of inputs. The
+ * inputs for a specific field evaluation are provided by a #FieldContext.
+ *
+ * A typical example is a field that computes a displacement vector for every vertex on a mesh
+ * based on its position.
+ *
+ * Fields can be build, composed and evaluated at run-time. They are stored in a directed tree
+ * graph data structure, whereby each node is a #FieldNode and edges are dependencies. A #FieldNode
+ * has an arbitrary number of inputs and at least one output. A #Field references a specific output
+ * of a #FieldNode. The inputs of a #FieldNode are other fields.
+ *
+ * There are two different types of field nodes:
+ * - #FieldInput: Has no input and exactly one output. It represents an input to the entire field
+ * when it is evaluated. During evaluation, the value of this input is based on a #FieldContext.
+ * - #FieldOperation: Has an arbitrary number of field inputs and at least one output. Its main
+ * use is to compose multiple existing fields into new fields.
+ *
+ * When fields are evaluated, they are converted into a multi-function procedure which allows
+ * efficient compution. In the future, we might support different field evaluation mechanisms for
+ * e.g. the following scenarios:
+ * - Latency of a single evaluation is more important than throughput.
+ * - Evaluation should happen on other hardware like GPUs.
+ *
+ * Whenever possible, multiple fields should be evaluated together to avoid duplicate work when
+ * they share common sub-fields and a common context.
+ */
+
+#include "BLI_string_ref.hh"
+#include "BLI_vector.hh"
+
+#include "FN_generic_virtual_array.hh"
+#include "FN_multi_function_builder.hh"
+#include "FN_multi_function_procedure.hh"
+#include "FN_multi_function_procedure_builder.hh"
+#include "FN_multi_function_procedure_executor.hh"
+
+namespace blender::fn {
+
+/**
+ * A node in a field-tree. It has at least one output that can be referenced by fields.
+ */
+class FieldNode {
+ private:
+ bool is_input_;
+
+ public:
+ FieldNode(bool is_input) : is_input_(is_input)
+ {
+ }
+
+ ~FieldNode() = default;
+
+ virtual const CPPType &output_cpp_type(int output_index) const = 0;
+
+ bool is_input() const
+ {
+ return is_input_;
+ }
+
+ bool is_operation() const
+ {
+ return !is_input_;
+ }
+
+ virtual uint64_t hash() const
+ {
+ return get_default_hash(this);
+ }
+
+ friend bool operator==(const FieldNode &a, const FieldNode &b)
+ {
+ return a.is_equal_to(b);
+ }
+
+ virtual bool is_equal_to(const FieldNode &other) const
+ {
+ return this == &other;
+ }
+};
+
+/**
+ * Common base class for fields to avoid declaring the same methods for #GField and #GFieldRef.
+ */
+template<typename NodePtr> class GFieldBase {
+ protected:
+ NodePtr node_ = nullptr;
+ int node_output_index_ = 0;
+
+ GFieldBase(NodePtr node, const int node_output_index)
+ : node_(std::move(node)), node_output_index_(node_output_index)
+ {
+ }
+
+ public:
+ GFieldBase() = default;
+
+ operator bool() const
+ {
+ return node_ != nullptr;
+ }
+
+ friend bool operator==(const GFieldBase &a, const GFieldBase &b)
+ {
+ return &*a.node_ == &*b.node_ && a.node_output_index_ == b.node_output_index_;
+ }
+
+ uint64_t hash() const
+ {
+ return get_default_hash_2(node_, node_output_index_);
+ }
+
+ const fn::CPPType &cpp_type() const
+ {
+ return node_->output_cpp_type(node_output_index_);
+ }
+
+ const FieldNode &node() const
+ {
+ return *node_;
+ }
+
+ int node_output_index() const
+ {
+ return node_output_index_;
+ }
+};
+
+/**
+ * A field whose output type is only known at run-time.
+ */
+class GField : public GFieldBase<std::shared_ptr<FieldNode>> {
+ public:
+ GField() = default;
+
+ GField(std::shared_ptr<FieldNode> node, const int node_output_index = 0)
+ : GFieldBase<std::shared_ptr<FieldNode>>(std::move(node), node_output_index)
+ {
+ }
+};
+
+/**
+ * Same as #GField but is cheaper to copy/move around, because it does not contain a
+ * #std::shared_ptr.
+ */
+class GFieldRef : public GFieldBase<const FieldNode *> {
+ public:
+ GFieldRef() = default;
+
+ GFieldRef(const GField &field)
+ : GFieldBase<const FieldNode *>(&field.node(), field.node_output_index())
+ {
+ }
+
+ GFieldRef(const FieldNode &node, const int node_output_index = 0)
+ : GFieldBase<const FieldNode *>(&node, node_output_index)
+ {
+ }
+};
+
+/**
+ * A typed version of #GField. It has the same memory layout as #GField.
+ */
+template<typename T> class Field : public GField {
+ public:
+ Field() = default;
+
+ Field(GField field) : GField(std::move(field))
+ {
+ BLI_assert(this->cpp_type().template is<T>());
+ }
+
+ Field(std::shared_ptr<FieldNode> node, const int node_output_index = 0)
+ : Field(GField(std::move(node), node_output_index))
+ {
+ }
+};
+
+/**
+ * A #FieldNode that allows composing existing fields into new fields.
+ */
+class FieldOperation : public FieldNode {
+ /**
+ * The multi-function used by this node. It is optionally owned.
+ * Multi-functions with mutable or vector parameters are not supported currently.
+ */
+ std::unique_ptr<const MultiFunction> owned_function_;
+ const MultiFunction *function_;
+
+ /** Inputs to the operation. */
+ blender::Vector<GField> inputs_;
+
+ public:
+ FieldOperation(std::unique_ptr<const MultiFunction> function, Vector<GField> inputs = {})
+ : FieldNode(false), owned_function_(std::move(function)), inputs_(std::move(inputs))
+ {
+ function_ = owned_function_.get();
+ }
+
+ FieldOperation(const MultiFunction &function, Vector<GField> inputs = {})
+ : FieldNode(false), function_(&function), inputs_(std::move(inputs))
+ {
+ }
+
+ Span<GField> inputs() const
+ {
+ return inputs_;
+ }
+
+ const MultiFunction &multi_function() const
+ {
+ return *function_;
+ }
+
+ const CPPType &output_cpp_type(int output_index) const override
+ {
+ int output_counter = 0;
+ for (const int param_index : function_->param_indices()) {
+ MFParamType param_type = function_->param_type(param_index);
+ if (param_type.is_output()) {
+ if (output_counter == output_index) {
+ return param_type.data_type().single_type();
+ }
+ output_counter++;
+ }
+ }
+ BLI_assert_unreachable();
+ return CPPType::get<float>();
+ }
+};
+
+class FieldContext;
+
+/**
+ * A #FieldNode that represents an input to the entire field-tree.
+ */
+class FieldInput : public FieldNode {
+ protected:
+ const CPPType *type_;
+ std::string debug_name_;
+
+ public:
+ FieldInput(const CPPType &type, std::string debug_name = "")
+ : FieldNode(true), type_(&type), debug_name_(std::move(debug_name))
+ {
+ }
+
+ /**
+ * Get the value of this specific input based on the given context. The returned virtual array,
+ * should live at least as long as the passed in #scope. May return null.
+ */
+ virtual const GVArray *get_varray_for_context(const FieldContext &context,
+ IndexMask mask,
+ ResourceScope &scope) const = 0;
+
+ blender::StringRef debug_name() const
+ {
+ return debug_name_;
+ }
+
+ const CPPType &cpp_type() const
+ {
+ return *type_;
+ }
+
+ const CPPType &output_cpp_type(int output_index) const override
+ {
+ BLI_assert(output_index == 0);
+ UNUSED_VARS_NDEBUG(output_index);
+ return *type_;
+ }
+};
+
+/**
+ * Provides inputs for a specific field evaluation.
+ */
+class FieldContext {
+ public:
+ ~FieldContext() = default;
+
+ virtual const GVArray *get_varray_for_input(const FieldInput &field_input,
+ IndexMask mask,
+ ResourceScope &scope) const;
+};
+
+/**
+ * Utility class that makes it easier to evaluate fields.
+ */
+class FieldEvaluator : NonMovable, NonCopyable {
+ private:
+ struct OutputPointerInfo {
+ void *dst = nullptr;
+ /* When a destination virtual array is provided for an input, this is
+ * unnecessary, otherwise this is used to construct the required virtual array. */
+ void (*set)(void *dst, const GVArray &varray, ResourceScope &scope) = nullptr;
+ };
+
+ ResourceScope scope_;
+ const FieldContext &context_;
+ const IndexMask mask_;
+ Vector<GField> fields_to_evaluate_;
+ Vector<GVMutableArray *> dst_hints_;
+ Vector<const GVArray *> evaluated_varrays_;
+ Vector<OutputPointerInfo> output_pointer_infos_;
+ bool is_evaluated_ = false;
+
+ public:
+ /** Takes #mask by pointer because the mask has to live longer than the evaluator. */
+ FieldEvaluator(const FieldContext &context, const IndexMask *mask)
+ : context_(context), mask_(*mask)
+ {
+ }
+
+ /** Construct a field evaluator for all indices less than #size. */
+ FieldEvaluator(const FieldContext &context, const int64_t size) : context_(context), mask_(size)
+ {
+ }
+
+ ~FieldEvaluator()
+ {
+ /* While this assert isn't strictly necessary, and could be replaced with a warning,
+ * it will catch cases where someone forgets to call #evaluate(). */
+ BLI_assert(is_evaluated_);
+ }
+
+ /**
+ * \param field: Field to add to the evaluator.
+ * \param dst: Mutable virtual array that the evaluated result for this field is be written into.
+ */
+ int add_with_destination(GField field, GVMutableArray &dst);
+
+ /** Same as #add_with_destination but typed. */
+ template<typename T> int add_with_destination(Field<T> field, VMutableArray<T> &dst)
+ {
+ GVMutableArray &generic_dst_hint = scope_.construct<GVMutableArray_For_VMutableArray<T>>(
+ __func__, dst);
+ return this->add_with_destination(GField(std::move(field)), generic_dst_hint);
+ }
+
+ /**
+ * \param field: Field to add to the evaluator.
+ * \param dst: Mutable span that the evaluated result for this field is be written into.
+ * \note: When the output may only be used as a single value, the version of this function with
+ * a virtual array result array should be used.
+ */
+ int add_with_destination(GField field, GMutableSpan dst);
+
+ /**
+ * \param field: Field to add to the evaluator.
+ * \param dst: Mutable span that the evaluated result for this field is be written into.
+ * \note: When the output may only be used as a single value, the version of this function with
+ * a virtual array result array should be used.
+ */
+ template<typename T> int add_with_destination(Field<T> field, MutableSpan<T> dst)
+ {
+ GVMutableArray &generic_dst_hint = scope_.construct<GVMutableArray_For_MutableSpan<T>>(
+ __func__, dst);
+ return this->add_with_destination(std::move(field), generic_dst_hint);
+ }
+
+ int add(GField field, const GVArray **varray_ptr);
+
+ /**
+ * \param field: Field to add to the evaluator.
+ * \param varray_ptr: Once #evaluate is called, the resulting virtual array will be will be
+ * assigned to the given position.
+ * \return Index of the field in the evaluator which can be used in the #get_evaluated methods.
+ */
+ template<typename T> int add(Field<T> field, const VArray<T> **varray_ptr)
+ {
+ const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
+ dst_hints_.append(nullptr);
+ output_pointer_infos_.append(OutputPointerInfo{
+ varray_ptr, [](void *dst, const GVArray &varray, ResourceScope &scope) {
+ *(const VArray<T> **)dst = &*scope.construct<GVArray_Typed<T>>(__func__, varray);
+ }});
+ return field_index;
+ }
+
+ /**
+ * \return Index of the field in the evaluator which can be used in the #get_evaluated methods.
+ */
+ int add(GField field);
+
+ /**
+ * Evaluate all fields on the evaluator. This can only be called once.
+ */
+ void evaluate();
+
+ const GVArray &get_evaluated(const int field_index) const
+ {
+ BLI_assert(is_evaluated_);
+ return *evaluated_varrays_[field_index];
+ }
+
+ template<typename T> const VArray<T> &get_evaluated(const int field_index)
+ {
+ const GVArray &varray = this->get_evaluated(field_index);
+ GVArray_Typed<T> &typed_varray = scope_.construct<GVArray_Typed<T>>(__func__, varray);
+ return *typed_varray;
+ }
+
+ /**
+ * Retrieve the output of an evaluated boolean field and convert it to a mask, which can be used
+ * to avoid calculations for unnecessary elements later on. The evaluator will own the indices in
+ * some cases, so it must live at least as long as the returned mask.
+ */
+ IndexMask get_evaluated_as_mask(const int field_index);
+};
+
+Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
+ Span<GFieldRef> fields_to_evaluate,
+ IndexMask mask,
+ const FieldContext &context,
+ Span<GVMutableArray *> dst_hints = {});
+
+/* --------------------------------------------------------------------
+ * Utility functions for simple field creation and evaluation.
+ */
+
+void evaluate_constant_field(const GField &field, void *r_value);
+
+template<typename T> T evaluate_constant_field(const Field<T> &field)
+{
+ T value;
+ value.~T();
+ evaluate_constant_field(field, &value);
+ return value;
+}
+
+template<typename T> Field<T> make_constant_field(T value)
+{
+ auto constant_fn = std::make_unique<fn::CustomMF_Constant<T>>(std::forward<T>(value));
+ auto operation = std::make_shared<FieldOperation>(std::move(constant_fn));
+ return Field<T>{GField{std::move(operation), 0}};
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/FN_field_cpp_type.hh b/source/blender/functions/FN_field_cpp_type.hh
new file mode 100644
index 00000000000..b7eff8f8cf0
--- /dev/null
+++ b/source/blender/functions/FN_field_cpp_type.hh
@@ -0,0 +1,73 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_cpp_type_make.hh"
+#include "FN_field.hh"
+
+namespace blender::fn {
+
+template<typename T> struct FieldCPPTypeParam {
+};
+
+class FieldCPPType : public CPPType {
+ private:
+ const CPPType &field_type_;
+
+ public:
+ template<typename T>
+ FieldCPPType(FieldCPPTypeParam<Field<T>> /* unused */, StringRef debug_name)
+ : CPPType(CPPTypeParam<Field<T>, CPPTypeFlags::None>(), debug_name),
+ field_type_(CPPType::get<T>())
+ {
+ }
+
+ const CPPType &field_type() const
+ {
+ return field_type_;
+ }
+
+ /* Ensure that a #GField and #Field<T> have the same layout, so pointers can be cast between the
+ * two. */
+ static_assert(sizeof(Field<int>) == sizeof(GField));
+ static_assert(sizeof(Field<int>) == sizeof(Field<std::string>));
+
+ const GField &get_gfield(const void *field) const
+ {
+ return *(const GField *)field;
+ }
+
+ void construct_from_gfield(void *r_value, const GField &gfield) const
+ {
+ new (r_value) GField(gfield);
+ }
+};
+
+} // namespace blender::fn
+
+#define MAKE_FIELD_CPP_TYPE(DEBUG_NAME, FIELD_TYPE) \
+ template<> \
+ const blender::fn::CPPType &blender::fn::CPPType::get_impl<blender::fn::Field<FIELD_TYPE>>() \
+ { \
+ static blender::fn::FieldCPPType cpp_type{ \
+ blender::fn::FieldCPPTypeParam<blender::fn::Field<FIELD_TYPE>>(), STRINGIFY(DEBUG_NAME)}; \
+ return cpp_type; \
+ }
diff --git a/source/blender/functions/FN_multi_function_builder.hh b/source/blender/functions/FN_multi_function_builder.hh
index 7a526bb640b..d13615ced07 100644
--- a/source/blender/functions/FN_multi_function_builder.hh
+++ b/source/blender/functions/FN_multi_function_builder.hh
@@ -417,4 +417,13 @@ class CustomMF_DefaultOutput : public MultiFunction {
void call(IndexMask mask, MFParams params, MFContext context) const override;
};
+class CustomMF_GenericCopy : public MultiFunction {
+ private:
+ MFSignature signature_;
+
+ public:
+ CustomMF_GenericCopy(StringRef name, MFDataType data_type);
+ void call(IndexMask mask, MFParams params, MFContext context) const override;
+};
+
} // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_params.hh b/source/blender/functions/FN_multi_function_params.hh
index a480287d578..5af86c7c284 100644
--- a/source/blender/functions/FN_multi_function_params.hh
+++ b/source/blender/functions/FN_multi_function_params.hh
@@ -195,7 +195,7 @@ class MFParams {
template<typename T> const VArray<T> &readonly_single_input(int param_index, StringRef name = "")
{
const GVArray &array = this->readonly_single_input(param_index, name);
- return builder_->scope_.construct<VArray_For_GVArray<T>>(__func__, array);
+ return builder_->scope_.construct<GVArray_Typed<T>>(__func__, array);
}
const GVArray &readonly_single_input(int param_index, StringRef name = "")
{
diff --git a/source/blender/functions/FN_multi_function_procedure.hh b/source/blender/functions/FN_multi_function_procedure.hh
new file mode 100644
index 00000000000..2bf89a38fdd
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_procedure.hh
@@ -0,0 +1,454 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_multi_function.hh"
+
+namespace blender::fn {
+
+class MFVariable;
+class MFInstruction;
+class MFCallInstruction;
+class MFBranchInstruction;
+class MFDestructInstruction;
+class MFDummyInstruction;
+class MFReturnInstruction;
+class MFProcedure;
+
+/** Every instruction has exactly one of these types. */
+enum class MFInstructionType {
+ Call,
+ Branch,
+ Destruct,
+ Dummy,
+ Return,
+};
+
+/**
+ * A variable is similar to a virtual register in other libraries. During evaluation, every is
+ * either uninitialized or contains a value for every index (remember, a multi-function procedure
+ * is always evaluated for many indices at the same time).
+ */
+class MFVariable : NonCopyable, NonMovable {
+ private:
+ MFDataType data_type_;
+ Vector<MFInstruction *> users_;
+ std::string name_;
+ int id_;
+
+ friend MFProcedure;
+ friend MFCallInstruction;
+ friend MFBranchInstruction;
+ friend MFDestructInstruction;
+
+ public:
+ MFDataType data_type() const;
+ Span<MFInstruction *> users();
+
+ StringRefNull name() const;
+ void set_name(std::string name);
+
+ int id() const;
+};
+
+/** Base class for all instruction types. */
+class MFInstruction : NonCopyable, NonMovable {
+ protected:
+ MFInstructionType type_;
+ Vector<MFInstruction *> prev_;
+
+ friend MFProcedure;
+ friend MFCallInstruction;
+ friend MFBranchInstruction;
+ friend MFDestructInstruction;
+ friend MFDummyInstruction;
+ friend MFReturnInstruction;
+
+ public:
+ MFInstructionType type() const;
+
+ /**
+ * Other instructions that come before this instruction. There can be multiple previous
+ * instructions when branching is used in the procedure.
+ */
+ Span<MFInstruction *> prev();
+ Span<const MFInstruction *> prev() const;
+};
+
+/**
+ * References a multi-function that is evaluated when the instruction is executed. It also
+ * references the variables whose data will be passed into the multi-function.
+ */
+class MFCallInstruction : public MFInstruction {
+ private:
+ const MultiFunction *fn_ = nullptr;
+ MFInstruction *next_ = nullptr;
+ MutableSpan<MFVariable *> params_;
+
+ friend MFProcedure;
+
+ public:
+ const MultiFunction &fn() const;
+
+ MFInstruction *next();
+ const MFInstruction *next() const;
+ void set_next(MFInstruction *instruction);
+
+ void set_param_variable(int param_index, MFVariable *variable);
+ void set_params(Span<MFVariable *> variables);
+
+ Span<MFVariable *> params();
+ Span<const MFVariable *> params() const;
+};
+
+/**
+ * What makes a branch instruction special is that it has two successor instructions. One that will
+ * be used when a condition variable was true, and one otherwise.
+ */
+class MFBranchInstruction : public MFInstruction {
+ private:
+ MFVariable *condition_ = nullptr;
+ MFInstruction *branch_true_ = nullptr;
+ MFInstruction *branch_false_ = nullptr;
+
+ friend MFProcedure;
+
+ public:
+ MFVariable *condition();
+ const MFVariable *condition() const;
+ void set_condition(MFVariable *variable);
+
+ MFInstruction *branch_true();
+ const MFInstruction *branch_true() const;
+ void set_branch_true(MFInstruction *instruction);
+
+ MFInstruction *branch_false();
+ const MFInstruction *branch_false() const;
+ void set_branch_false(MFInstruction *instruction);
+};
+
+/**
+ * A destruct instruction destructs a single variable. So the variable value will be uninitialized
+ * after this instruction. All variables that are not output variables of the procedure, have to be
+ * destructed before the procedure ends. Destructing early is generally a good thing, because it
+ * might help with memory buffer reuse, which decreases memory-usage and increases performance.
+ */
+class MFDestructInstruction : public MFInstruction {
+ private:
+ MFVariable *variable_ = nullptr;
+ MFInstruction *next_ = nullptr;
+
+ friend MFProcedure;
+
+ public:
+ MFVariable *variable();
+ const MFVariable *variable() const;
+ void set_variable(MFVariable *variable);
+
+ MFInstruction *next();
+ const MFInstruction *next() const;
+ void set_next(MFInstruction *instruction);
+};
+
+/**
+ * This instruction does nothing, it just exists to building a procedure simpler in some cases.
+ */
+class MFDummyInstruction : public MFInstruction {
+ private:
+ MFInstruction *next_ = nullptr;
+
+ friend MFProcedure;
+
+ public:
+ MFInstruction *next();
+ const MFInstruction *next() const;
+ void set_next(MFInstruction *instruction);
+};
+
+/**
+ * This instruction ends the procedure.
+ */
+class MFReturnInstruction : public MFInstruction {
+};
+
+/**
+ * Inputs and outputs of the entire procedure network.
+ */
+struct MFParameter {
+ MFParamType::InterfaceType type;
+ MFVariable *variable;
+};
+
+struct ConstMFParameter {
+ MFParamType::InterfaceType type;
+ const MFVariable *variable;
+};
+
+/**
+ * A multi-function procedure allows composing multi-functions in arbitrary ways. It consists of
+ * variables and instructions that operate on those variables. Branching and looping within the
+ * procedure is supported as well.
+ *
+ * Typically, a #MFProcedure should be constructed using a #MFProcedureBuilder, which has many more
+ * utility methods for common use cases.
+ */
+class MFProcedure : NonCopyable, NonMovable {
+ private:
+ LinearAllocator<> allocator_;
+ Vector<MFCallInstruction *> call_instructions_;
+ Vector<MFBranchInstruction *> branch_instructions_;
+ Vector<MFDestructInstruction *> destruct_instructions_;
+ Vector<MFDummyInstruction *> dummy_instructions_;
+ Vector<MFReturnInstruction *> return_instructions_;
+ Vector<MFVariable *> variables_;
+ Vector<MFParameter> params_;
+ MFInstruction *entry_ = nullptr;
+
+ friend class MFProcedureDotExport;
+
+ public:
+ MFProcedure() = default;
+ ~MFProcedure();
+
+ MFVariable &new_variable(MFDataType data_type, std::string name = "");
+ MFCallInstruction &new_call_instruction(const MultiFunction &fn);
+ MFBranchInstruction &new_branch_instruction();
+ MFDestructInstruction &new_destruct_instruction();
+ MFDummyInstruction &new_dummy_instruction();
+ MFReturnInstruction &new_return_instruction();
+
+ void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
+
+ Span<ConstMFParameter> params() const;
+
+ MFInstruction *entry();
+ const MFInstruction *entry() const;
+ void set_entry(MFInstruction &entry);
+
+ Span<MFVariable *> variables();
+ Span<const MFVariable *> variables() const;
+
+ void assert_valid() const;
+
+ std::string to_dot() const;
+
+ bool validate() const;
+
+ private:
+ bool validate_all_instruction_pointers_set() const;
+ bool validate_all_params_provided() const;
+ bool validate_same_variables_in_one_call() const;
+ bool validate_parameters() const;
+ bool validate_initialization() const;
+
+ struct InitState {
+ bool can_be_initialized = false;
+ bool can_be_uninitialized = false;
+ };
+
+ InitState find_initialization_state_before_instruction(const MFInstruction &target_instruction,
+ const MFVariable &variable) const;
+};
+
+namespace multi_function_procedure_types {
+using MFVariable = fn::MFVariable;
+using MFInstruction = fn::MFInstruction;
+using MFCallInstruction = fn::MFCallInstruction;
+using MFBranchInstruction = fn::MFBranchInstruction;
+using MFDestructInstruction = fn::MFDestructInstruction;
+using MFProcedure = fn::MFProcedure;
+} // namespace multi_function_procedure_types
+
+/* --------------------------------------------------------------------
+ * MFVariable inline methods.
+ */
+
+inline MFDataType MFVariable::data_type() const
+{
+ return data_type_;
+}
+
+inline Span<MFInstruction *> MFVariable::users()
+{
+ return users_;
+}
+
+inline StringRefNull MFVariable::name() const
+{
+ return name_;
+}
+
+inline int MFVariable::id() const
+{
+ return id_;
+}
+
+/* --------------------------------------------------------------------
+ * MFInstruction inline methods.
+ */
+
+inline MFInstructionType MFInstruction::type() const
+{
+ return type_;
+}
+
+inline Span<MFInstruction *> MFInstruction::prev()
+{
+ return prev_;
+}
+
+inline Span<const MFInstruction *> MFInstruction::prev() const
+{
+ return prev_;
+}
+
+/* --------------------------------------------------------------------
+ * MFCallInstruction inline methods.
+ */
+
+inline const MultiFunction &MFCallInstruction::fn() const
+{
+ return *fn_;
+}
+
+inline MFInstruction *MFCallInstruction::next()
+{
+ return next_;
+}
+
+inline const MFInstruction *MFCallInstruction::next() const
+{
+ return next_;
+}
+
+inline Span<MFVariable *> MFCallInstruction::params()
+{
+ return params_;
+}
+
+inline Span<const MFVariable *> MFCallInstruction::params() const
+{
+ return params_;
+}
+
+/* --------------------------------------------------------------------
+ * MFBranchInstruction inline methods.
+ */
+
+inline MFVariable *MFBranchInstruction::condition()
+{
+ return condition_;
+}
+
+inline const MFVariable *MFBranchInstruction::condition() const
+{
+ return condition_;
+}
+
+inline MFInstruction *MFBranchInstruction::branch_true()
+{
+ return branch_true_;
+}
+
+inline const MFInstruction *MFBranchInstruction::branch_true() const
+{
+ return branch_true_;
+}
+
+inline MFInstruction *MFBranchInstruction::branch_false()
+{
+ return branch_false_;
+}
+
+inline const MFInstruction *MFBranchInstruction::branch_false() const
+{
+ return branch_false_;
+}
+
+/* --------------------------------------------------------------------
+ * MFDestructInstruction inline methods.
+ */
+
+inline MFVariable *MFDestructInstruction::variable()
+{
+ return variable_;
+}
+
+inline const MFVariable *MFDestructInstruction::variable() const
+{
+ return variable_;
+}
+
+inline MFInstruction *MFDestructInstruction::next()
+{
+ return next_;
+}
+
+inline const MFInstruction *MFDestructInstruction::next() const
+{
+ return next_;
+}
+
+/* --------------------------------------------------------------------
+ * MFDummyInstruction inline methods.
+ */
+
+inline MFInstruction *MFDummyInstruction::next()
+{
+ return next_;
+}
+
+inline const MFInstruction *MFDummyInstruction::next() const
+{
+ return next_;
+}
+
+/* --------------------------------------------------------------------
+ * MFProcedure inline methods.
+ */
+
+inline Span<ConstMFParameter> MFProcedure::params() const
+{
+ static_assert(sizeof(MFParameter) == sizeof(ConstMFParameter));
+ return params_.as_span().cast<ConstMFParameter>();
+}
+
+inline MFInstruction *MFProcedure::entry()
+{
+ return entry_;
+}
+
+inline const MFInstruction *MFProcedure::entry() const
+{
+ return entry_;
+}
+
+inline Span<MFVariable *> MFProcedure::variables()
+{
+ return variables_;
+}
+
+inline Span<const MFVariable *> MFProcedure::variables() const
+{
+ return variables_;
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_procedure_builder.hh b/source/blender/functions/FN_multi_function_procedure_builder.hh
new file mode 100644
index 00000000000..d5e45470a0e
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_procedure_builder.hh
@@ -0,0 +1,260 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_multi_function_procedure.hh"
+
+namespace blender::fn {
+
+/**
+ * An #MFInstructionCursor points to a position in a multi-function procedure, where an instruction
+ * can be inserted.
+ */
+class MFInstructionCursor {
+ private:
+ MFInstruction *instruction_ = nullptr;
+ /* Only used when it is a branch instruction. */
+ bool branch_output_ = false;
+ /* Only used when instruction is null. */
+ bool is_entry_ = false;
+
+ public:
+ MFInstructionCursor() = default;
+
+ MFInstructionCursor(MFCallInstruction &instruction);
+ MFInstructionCursor(MFDestructInstruction &instruction);
+ MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output);
+ MFInstructionCursor(MFDummyInstruction &instruction);
+
+ static MFInstructionCursor Entry();
+
+ void insert(MFProcedure &procedure, MFInstruction *new_instruction);
+};
+
+/**
+ * Utility class to build a #MFProcedure.
+ */
+class MFProcedureBuilder {
+ private:
+ /** Procedure that is being build. */
+ MFProcedure *procedure_ = nullptr;
+ /** Cursors where the next instruction should be inserted. */
+ Vector<MFInstructionCursor> cursors_;
+
+ public:
+ struct Branch;
+ struct Loop;
+
+ MFProcedureBuilder(MFProcedure &procedure,
+ MFInstructionCursor initial_cursor = MFInstructionCursor::Entry());
+
+ MFProcedureBuilder(Span<MFProcedureBuilder *> builders);
+
+ MFProcedureBuilder(Branch &branch);
+
+ void set_cursor(const MFInstructionCursor &cursor);
+ void set_cursor(Span<MFInstructionCursor> cursors);
+ void set_cursor(Span<MFProcedureBuilder *> builders);
+ void set_cursor_after_branch(Branch &branch);
+ void set_cursor_after_loop(Loop &loop);
+
+ void add_destruct(MFVariable &variable);
+ void add_destruct(Span<MFVariable *> variables);
+
+ MFReturnInstruction &add_return();
+
+ Branch add_branch(MFVariable &condition);
+
+ Loop add_loop();
+ void add_loop_continue(Loop &loop);
+ void add_loop_break(Loop &loop);
+
+ MFCallInstruction &add_call_with_no_variables(const MultiFunction &fn);
+ MFCallInstruction &add_call_with_all_variables(const MultiFunction &fn,
+ Span<MFVariable *> param_variables);
+
+ Vector<MFVariable *> add_call(const MultiFunction &fn,
+ Span<MFVariable *> input_and_mutable_variables = {});
+
+ template<int OutputN>
+ std::array<MFVariable *, OutputN> add_call(const MultiFunction &fn,
+ Span<MFVariable *> input_and_mutable_variables = {});
+
+ void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
+ MFVariable &add_parameter(MFParamType param_type, std::string name = "");
+
+ MFVariable &add_input_parameter(MFDataType data_type, std::string name = "");
+ template<typename T> MFVariable &add_single_input_parameter(std::string name = "");
+ template<typename T> MFVariable &add_single_mutable_parameter(std::string name = "");
+
+ void add_output_parameter(MFVariable &variable);
+
+ private:
+ void link_to_cursors(MFInstruction *instruction);
+};
+
+struct MFProcedureBuilder::Branch {
+ MFProcedureBuilder branch_true;
+ MFProcedureBuilder branch_false;
+};
+
+struct MFProcedureBuilder::Loop {
+ MFInstruction *begin = nullptr;
+ MFDummyInstruction *end = nullptr;
+};
+
+/* --------------------------------------------------------------------
+ * MFInstructionCursor inline methods.
+ */
+
+inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction)
+ : instruction_(&instruction)
+{
+}
+
+inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction)
+ : instruction_(&instruction)
+{
+}
+
+inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction,
+ bool branch_output)
+ : instruction_(&instruction), branch_output_(branch_output)
+{
+}
+
+inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction)
+ : instruction_(&instruction)
+{
+}
+
+inline MFInstructionCursor MFInstructionCursor::Entry()
+{
+ MFInstructionCursor cursor;
+ cursor.is_entry_ = true;
+ return cursor;
+}
+
+/* --------------------------------------------------------------------
+ * MFProcedureBuilder inline methods.
+ */
+
+inline MFProcedureBuilder::MFProcedureBuilder(Branch &branch)
+ : MFProcedureBuilder(*branch.branch_true.procedure_)
+{
+ this->set_cursor_after_branch(branch);
+}
+
+inline MFProcedureBuilder::MFProcedureBuilder(MFProcedure &procedure,
+ MFInstructionCursor initial_cursor)
+ : procedure_(&procedure), cursors_({initial_cursor})
+{
+}
+
+inline MFProcedureBuilder::MFProcedureBuilder(Span<MFProcedureBuilder *> builders)
+ : MFProcedureBuilder(*builders[0]->procedure_)
+{
+ this->set_cursor(builders);
+}
+
+inline void MFProcedureBuilder::set_cursor(const MFInstructionCursor &cursor)
+{
+ cursors_ = {cursor};
+}
+
+inline void MFProcedureBuilder::set_cursor(Span<MFInstructionCursor> cursors)
+{
+ cursors_ = cursors;
+}
+
+inline void MFProcedureBuilder::set_cursor_after_branch(Branch &branch)
+{
+ this->set_cursor({&branch.branch_false, &branch.branch_true});
+}
+
+inline void MFProcedureBuilder::set_cursor_after_loop(Loop &loop)
+{
+ this->set_cursor(MFInstructionCursor{*loop.end});
+}
+
+inline void MFProcedureBuilder::set_cursor(Span<MFProcedureBuilder *> builders)
+{
+ cursors_.clear();
+ for (MFProcedureBuilder *builder : builders) {
+ cursors_.extend(builder->cursors_);
+ }
+}
+
+template<int OutputN>
+inline std::array<MFVariable *, OutputN> MFProcedureBuilder::add_call(
+ const MultiFunction &fn, Span<MFVariable *> input_and_mutable_variables)
+{
+ Vector<MFVariable *> output_variables = this->add_call(fn, input_and_mutable_variables);
+ BLI_assert(output_variables.size() == OutputN);
+
+ std::array<MFVariable *, OutputN> output_array;
+ initialized_copy_n(output_variables.data(), OutputN, output_array.data());
+ return output_array;
+}
+
+inline void MFProcedureBuilder::add_parameter(MFParamType::InterfaceType interface_type,
+ MFVariable &variable)
+{
+ procedure_->add_parameter(interface_type, variable);
+}
+
+inline MFVariable &MFProcedureBuilder::add_parameter(MFParamType param_type, std::string name)
+{
+ MFVariable &variable = procedure_->new_variable(param_type.data_type(), std::move(name));
+ this->add_parameter(param_type.interface_type(), variable);
+ return variable;
+}
+
+inline MFVariable &MFProcedureBuilder::add_input_parameter(MFDataType data_type, std::string name)
+{
+ return this->add_parameter(MFParamType(MFParamType::Input, data_type), std::move(name));
+}
+
+template<typename T>
+inline MFVariable &MFProcedureBuilder::add_single_input_parameter(std::string name)
+{
+ return this->add_parameter(MFParamType::ForSingleInput(CPPType::get<T>()), std::move(name));
+}
+
+template<typename T>
+inline MFVariable &MFProcedureBuilder::add_single_mutable_parameter(std::string name)
+{
+ return this->add_parameter(MFParamType::ForMutableSingle(CPPType::get<T>()), std::move(name));
+}
+
+inline void MFProcedureBuilder::add_output_parameter(MFVariable &variable)
+{
+ this->add_parameter(MFParamType::Output, variable);
+}
+
+inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction)
+{
+ for (MFInstructionCursor &cursor : cursors_) {
+ cursor.insert(*procedure_, instruction);
+ }
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_procedure_executor.hh b/source/blender/functions/FN_multi_function_procedure_executor.hh
new file mode 100644
index 00000000000..9c8b59739b8
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_procedure_executor.hh
@@ -0,0 +1,39 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_multi_function_procedure.hh"
+
+namespace blender::fn {
+
+/** A multi-function that executes a procedure internally. */
+class MFProcedureExecutor : public MultiFunction {
+ private:
+ MFSignature signature_;
+ const MFProcedure &procedure_;
+
+ public:
+ MFProcedureExecutor(std::string name, const MFProcedure &procedure);
+
+ void call(IndexMask mask, MFParams params, MFContext context) const override;
+};
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/cpp_types.cc b/source/blender/functions/intern/cpp_types.cc
index 7be34d2a1bf..058fb76af2b 100644
--- a/source/blender/functions/intern/cpp_types.cc
+++ b/source/blender/functions/intern/cpp_types.cc
@@ -15,6 +15,7 @@
*/
#include "FN_cpp_type_make.hh"
+#include "FN_field_cpp_type.hh"
#include "BLI_color.hh"
#include "BLI_float2.hh"
@@ -39,4 +40,12 @@ MAKE_CPP_TYPE(ColorGeometry4b, blender::ColorGeometry4b, CPPTypeFlags::BasicType
MAKE_CPP_TYPE(string, std::string, CPPTypeFlags::BasicType)
+MAKE_FIELD_CPP_TYPE(FloatField, float);
+MAKE_FIELD_CPP_TYPE(Float2Field, float2);
+MAKE_FIELD_CPP_TYPE(Float3Field, float3);
+MAKE_FIELD_CPP_TYPE(ColorGeometry4fField, blender::ColorGeometry4f);
+MAKE_FIELD_CPP_TYPE(BoolField, bool);
+MAKE_FIELD_CPP_TYPE(Int32Field, int32_t);
+MAKE_FIELD_CPP_TYPE(StringField, std::string);
+
} // namespace blender::fn
diff --git a/source/blender/functions/intern/field.cc b/source/blender/functions/intern/field.cc
new file mode 100644
index 00000000000..a27c5e4e3dc
--- /dev/null
+++ b/source/blender/functions/intern/field.cc
@@ -0,0 +1,569 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "BLI_map.hh"
+#include "BLI_multi_value_map.hh"
+#include "BLI_set.hh"
+#include "BLI_stack.hh"
+#include "BLI_vector_set.hh"
+
+#include "FN_field.hh"
+
+namespace blender::fn {
+
+/* --------------------------------------------------------------------
+ * Field Evaluation.
+ */
+
+struct FieldTreeInfo {
+ /**
+ * When fields are build, they only have references to the fields that they depend on. This map
+ * allows traversal of fields in the opposite direction. So for every field it stores what other
+ * fields directly depend on it.
+ */
+ MultiValueMap<GFieldRef, GFieldRef> field_users;
+ /**
+ * The same field input may exist in the field tree as as separate nodes due to the way
+ * the tree is constructed. This set contains every different input only once.
+ */
+ VectorSet<std::reference_wrapper<const FieldInput>> deduplicated_field_inputs;
+};
+
+/**
+ * Collects some information from the field tree that is required by later steps.
+ */
+static FieldTreeInfo preprocess_field_tree(Span<GFieldRef> entry_fields)
+{
+ FieldTreeInfo field_tree_info;
+
+ Stack<GFieldRef> fields_to_check;
+ Set<GFieldRef> handled_fields;
+
+ for (GFieldRef field : entry_fields) {
+ if (handled_fields.add(field)) {
+ fields_to_check.push(field);
+ }
+ }
+
+ while (!fields_to_check.is_empty()) {
+ GFieldRef field = fields_to_check.pop();
+ if (field.node().is_input()) {
+ const FieldInput &field_input = static_cast<const FieldInput &>(field.node());
+ field_tree_info.deduplicated_field_inputs.add(field_input);
+ continue;
+ }
+ BLI_assert(field.node().is_operation());
+ const FieldOperation &operation = static_cast<const FieldOperation &>(field.node());
+ for (const GFieldRef operation_input : operation.inputs()) {
+ field_tree_info.field_users.add(operation_input, field);
+ if (handled_fields.add(operation_input)) {
+ fields_to_check.push(operation_input);
+ }
+ }
+ }
+ return field_tree_info;
+}
+
+/**
+ * Retrieves the data from the context that is passed as input into the field.
+ */
+static Vector<const GVArray *> get_field_context_inputs(
+ ResourceScope &scope,
+ const IndexMask mask,
+ const FieldContext &context,
+ const Span<std::reference_wrapper<const FieldInput>> field_inputs)
+{
+ Vector<const GVArray *> field_context_inputs;
+ for (const FieldInput &field_input : field_inputs) {
+ const GVArray *varray = context.get_varray_for_input(field_input, mask, scope);
+ if (varray == nullptr) {
+ const CPPType &type = field_input.cpp_type();
+ varray = &scope.construct<GVArray_For_SingleValueRef>(
+ __func__, type, mask.min_array_size(), type.default_value());
+ }
+ field_context_inputs.append(varray);
+ }
+ return field_context_inputs;
+}
+
+/**
+ * \return A set that contains all fields from the field tree that depend on an input that varies
+ * for different indices.
+ */
+static Set<GFieldRef> find_varying_fields(const FieldTreeInfo &field_tree_info,
+ Span<const GVArray *> field_context_inputs)
+{
+ Set<GFieldRef> found_fields;
+ Stack<GFieldRef> fields_to_check;
+
+ /* The varying fields are the ones that depend on inputs that are not constant. Therefore we
+ * start the tree search at the non-constant input fields and traverse through all fields that
+ * depend on those. */
+ for (const int i : field_context_inputs.index_range()) {
+ const GVArray *varray = field_context_inputs[i];
+ if (varray->is_single()) {
+ continue;
+ }
+ const FieldInput &field_input = field_tree_info.deduplicated_field_inputs[i];
+ const GFieldRef field_input_field{field_input, 0};
+ const Span<GFieldRef> users = field_tree_info.field_users.lookup(field_input_field);
+ for (const GFieldRef &field : users) {
+ if (found_fields.add(field)) {
+ fields_to_check.push(field);
+ }
+ }
+ }
+ while (!fields_to_check.is_empty()) {
+ GFieldRef field = fields_to_check.pop();
+ const Span<GFieldRef> users = field_tree_info.field_users.lookup(field);
+ for (GFieldRef field : users) {
+ if (found_fields.add(field)) {
+ fields_to_check.push(field);
+ }
+ }
+ }
+ return found_fields;
+}
+
+/**
+ * Builds the #procedure so that it computes the the fields.
+ */
+static void build_multi_function_procedure_for_fields(MFProcedure &procedure,
+ ResourceScope &scope,
+ const FieldTreeInfo &field_tree_info,
+ Span<GFieldRef> output_fields)
+{
+ MFProcedureBuilder builder{procedure};
+ /* Every input, intermediate and output field corresponds to a variable in the procedure. */
+ Map<GFieldRef, MFVariable *> variable_by_field;
+
+ /* Start by adding the field inputs as parameters to the procedure. */
+ for (const FieldInput &field_input : field_tree_info.deduplicated_field_inputs) {
+ MFVariable &variable = builder.add_input_parameter(
+ MFDataType::ForSingle(field_input.cpp_type()), field_input.debug_name());
+ variable_by_field.add_new({field_input, 0}, &variable);
+ }
+
+ /* Utility struct that is used to do proper depth first search traversal of the tree below. */
+ struct FieldWithIndex {
+ GFieldRef field;
+ int current_input_index = 0;
+ };
+
+ for (GFieldRef field : output_fields) {
+ /* We start a new stack for each output field to make sure that a field pushed later to the
+ * stack does never depend on a field that was pushed before. */
+ Stack<FieldWithIndex> fields_to_check;
+ fields_to_check.push({field, 0});
+ while (!fields_to_check.is_empty()) {
+ FieldWithIndex &field_with_index = fields_to_check.peek();
+ const GFieldRef &field = field_with_index.field;
+ if (variable_by_field.contains(field)) {
+ /* The field has been handled already. */
+ fields_to_check.pop();
+ continue;
+ }
+ /* Field inputs should already be handled above. */
+ BLI_assert(field.node().is_operation());
+
+ const FieldOperation &operation = static_cast<const FieldOperation &>(field.node());
+ const Span<GField> operation_inputs = operation.inputs();
+
+ if (field_with_index.current_input_index < operation_inputs.size()) {
+ /* Not all inputs are handled yet. Push the next input field to the stack and increment the
+ * input index. */
+ fields_to_check.push({operation_inputs[field_with_index.current_input_index]});
+ field_with_index.current_input_index++;
+ }
+ else {
+ /* All inputs variables are ready, now add the function call. */
+ Vector<MFVariable *> input_variables;
+ for (const GField &field : operation_inputs) {
+ input_variables.append(variable_by_field.lookup(field));
+ }
+ const MultiFunction &multi_function = operation.multi_function();
+ Vector<MFVariable *> output_variables = builder.add_call(multi_function, input_variables);
+ /* Add newly created variables to the map. */
+ for (const int i : output_variables.index_range()) {
+ variable_by_field.add_new({operation, i}, output_variables[i]);
+ }
+ }
+ }
+ }
+
+ /* Add output parameters to the procedure. */
+ Set<MFVariable *> already_output_variables;
+ for (const GFieldRef &field : output_fields) {
+ MFVariable *variable = variable_by_field.lookup(field);
+ if (!already_output_variables.add(variable)) {
+ /* One variable can be output at most once. To output the same value twice, we have to make
+ * a copy first. */
+ const MultiFunction &copy_fn = scope.construct<CustomMF_GenericCopy>(
+ __func__, "copy", variable->data_type());
+ variable = builder.add_call<1>(copy_fn, {variable})[0];
+ }
+ builder.add_output_parameter(*variable);
+ }
+
+ /* Remove the variables that should not be destructed from the map. */
+ for (const GFieldRef &field : output_fields) {
+ variable_by_field.remove(field);
+ }
+ /* Add destructor calls for the remaining variables. */
+ for (MFVariable *variable : variable_by_field.values()) {
+ builder.add_destruct(*variable);
+ }
+
+ builder.add_return();
+
+ // std::cout << procedure.to_dot() << "\n";
+ BLI_assert(procedure.validate());
+}
+
+/**
+ * Utility class that destructs elements from a partially initialized array.
+ */
+struct PartiallyInitializedArray : NonCopyable, NonMovable {
+ void *buffer;
+ IndexMask mask;
+ const CPPType *type;
+
+ ~PartiallyInitializedArray()
+ {
+ this->type->destruct_indices(this->buffer, this->mask);
+ }
+};
+
+/**
+ * Evaluate fields in the given context. If possible, multiple fields should be evaluated together,
+ * because that can be more efficient when they share common sub-fields.
+ *
+ * \param scope: The resource scope that owns data that makes up the output virtual arrays. Make
+ * sure the scope is not destructed when the output virtual arrays are still used.
+ * \param fields_to_evaluate: The fields that should be evaluated together.
+ * \param mask: Determines which indices are computed. The mask may be referenced by the returned
+ * virtual arrays. So the underlying indices (if applicable) should live longer then #scope.
+ * \param context: The context that the field is evaluated in. Used to retrieve data from each
+ * #FieldInput in the field network.
+ * \param dst_hints: If provided, the computed data will be written into those virtual arrays
+ * instead of into newly created ones. That allows making the computed data live longer than
+ * #scope and is more efficient when the data will be written into those virtual arrays
+ * later anyway.
+ * \return The computed virtual arrays for each provided field. If #dst_hints is passed, the
+ * provided virtual arrays are returned.
+ */
+Vector<const GVArray *> evaluate_fields(ResourceScope &scope,
+ Span<GFieldRef> fields_to_evaluate,
+ IndexMask mask,
+ const FieldContext &context,
+ Span<GVMutableArray *> dst_hints)
+{
+ Vector<const GVArray *> r_varrays(fields_to_evaluate.size(), nullptr);
+
+ /* Destination hints are optional. Create a small utility method to access them. */
+ auto get_dst_hint_if_available = [&](int index) -> GVMutableArray * {
+ if (dst_hints.is_empty()) {
+ return nullptr;
+ }
+ return dst_hints[index];
+ };
+
+ /* Traverse the field tree and prepare some data that is used in later steps. */
+ FieldTreeInfo field_tree_info = preprocess_field_tree(fields_to_evaluate);
+
+ /* Get inputs that will be passed into the field when evaluated. */
+ Vector<const GVArray *> field_context_inputs = get_field_context_inputs(
+ scope, mask, context, field_tree_info.deduplicated_field_inputs);
+
+ /* Finish fields that output an input varray directly. For those we don't have to do any further
+ * processing. */
+ for (const int out_index : fields_to_evaluate.index_range()) {
+ const GFieldRef &field = fields_to_evaluate[out_index];
+ if (!field.node().is_input()) {
+ continue;
+ }
+ const FieldInput &field_input = static_cast<const FieldInput &>(field.node());
+ const int field_input_index = field_tree_info.deduplicated_field_inputs.index_of(field_input);
+ const GVArray *varray = field_context_inputs[field_input_index];
+ r_varrays[out_index] = varray;
+ }
+
+ Set<GFieldRef> varying_fields = find_varying_fields(field_tree_info, field_context_inputs);
+
+ /* Separate fields into two categories. Those that are constant and need to be evaluated only
+ * once, and those that need to be evaluated for every index. */
+ Vector<GFieldRef> varying_fields_to_evaluate;
+ Vector<int> varying_field_indices;
+ Vector<GFieldRef> constant_fields_to_evaluate;
+ Vector<int> constant_field_indices;
+ for (const int i : fields_to_evaluate.index_range()) {
+ if (r_varrays[i] != nullptr) {
+ /* Already done. */
+ continue;
+ }
+ GFieldRef field = fields_to_evaluate[i];
+ if (varying_fields.contains(field)) {
+ varying_fields_to_evaluate.append(field);
+ varying_field_indices.append(i);
+ }
+ else {
+ constant_fields_to_evaluate.append(field);
+ constant_field_indices.append(i);
+ }
+ }
+
+ const int array_size = mask.min_array_size();
+
+ /* Evaluate varying fields if necessary. */
+ if (!varying_fields_to_evaluate.is_empty()) {
+ /* Build the procedure for those fields. */
+ MFProcedure procedure;
+ build_multi_function_procedure_for_fields(
+ procedure, scope, field_tree_info, varying_fields_to_evaluate);
+ MFProcedureExecutor procedure_executor{"Procedure", procedure};
+ MFParamsBuilder mf_params{procedure_executor, array_size};
+ MFContextBuilder mf_context;
+
+ /* Provide inputs to the procedure executor. */
+ for (const GVArray *varray : field_context_inputs) {
+ mf_params.add_readonly_single_input(*varray);
+ }
+
+ for (const int i : varying_fields_to_evaluate.index_range()) {
+ const GFieldRef &field = varying_fields_to_evaluate[i];
+ const CPPType &type = field.cpp_type();
+ const int out_index = varying_field_indices[i];
+
+ /* Try to get an existing virtual array that the result should be written into. */
+ GVMutableArray *output_varray = get_dst_hint_if_available(out_index);
+ void *buffer;
+ if (output_varray == nullptr || !output_varray->is_span()) {
+ /* Allocate a new buffer for the computed result. */
+ buffer = scope.linear_allocator().allocate(type.size() * array_size, type.alignment());
+
+ /* Make sure that elements in the buffer will be destructed. */
+ PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>(
+ __func__);
+ destruct_helper.buffer = buffer;
+ destruct_helper.mask = mask;
+ destruct_helper.type = &type;
+
+ r_varrays[out_index] = &scope.construct<GVArray_For_GSpan>(
+ __func__, GSpan{type, buffer, array_size});
+ }
+ else {
+ /* Write the result into the existing span. */
+ buffer = output_varray->get_internal_span().data();
+
+ r_varrays[out_index] = output_varray;
+ }
+
+ /* Pass output buffer to the procedure executor. */
+ const GMutableSpan span{type, buffer, array_size};
+ mf_params.add_uninitialized_single_output(span);
+ }
+
+ procedure_executor.call(mask, mf_params, mf_context);
+ }
+
+ /* Evaluate constant fields if necessary. */
+ if (!constant_fields_to_evaluate.is_empty()) {
+ /* Build the procedure for those fields. */
+ MFProcedure procedure;
+ build_multi_function_procedure_for_fields(
+ procedure, scope, field_tree_info, constant_fields_to_evaluate);
+ MFProcedureExecutor procedure_executor{"Procedure", procedure};
+ MFParamsBuilder mf_params{procedure_executor, 1};
+ MFContextBuilder mf_context;
+
+ /* Provide inputs to the procedure executor. */
+ for (const GVArray *varray : field_context_inputs) {
+ mf_params.add_readonly_single_input(*varray);
+ }
+
+ for (const int i : constant_fields_to_evaluate.index_range()) {
+ const GFieldRef &field = constant_fields_to_evaluate[i];
+ const CPPType &type = field.cpp_type();
+ /* Allocate memory where the computed value will be stored in. */
+ void *buffer = scope.linear_allocator().allocate(type.size(), type.alignment());
+
+ /* Use this to make sure that the value is destructed in the end. */
+ PartiallyInitializedArray &destruct_helper = scope.construct<PartiallyInitializedArray>(
+ __func__);
+ destruct_helper.buffer = buffer;
+ destruct_helper.mask = IndexRange(1);
+ destruct_helper.type = &type;
+
+ /* Pass output buffer to the procedure executor. */
+ mf_params.add_uninitialized_single_output({type, buffer, 1});
+
+ /* Create virtual array that can be used after the procedure has been executed below. */
+ const int out_index = constant_field_indices[i];
+ r_varrays[out_index] = &scope.construct<GVArray_For_SingleValueRef>(
+ __func__, type, array_size, buffer);
+ }
+
+ procedure_executor.call(IndexRange(1), mf_params, mf_context);
+ }
+
+ /* Copy data to destination hints if still necessary. In some cases the evaluation above has
+ * written the computed data in the right place already. */
+ if (!dst_hints.is_empty()) {
+ for (const int out_index : fields_to_evaluate.index_range()) {
+ GVMutableArray *output_varray = get_dst_hint_if_available(out_index);
+ if (output_varray == nullptr) {
+ /* Caller did not provide a destination for this output. */
+ continue;
+ }
+ const GVArray *computed_varray = r_varrays[out_index];
+ BLI_assert(computed_varray->type() == output_varray->type());
+ if (output_varray == computed_varray) {
+ /* The result has been written into the destination provided by the caller already. */
+ continue;
+ }
+ /* Still have to copy over the data in the destination provided by the caller. */
+ if (output_varray->is_span()) {
+ /* Materialize into a span. */
+ computed_varray->materialize_to_uninitialized(output_varray->get_internal_span().data());
+ }
+ else {
+ /* Slower materialize into a different structure. */
+ const CPPType &type = computed_varray->type();
+ BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
+ for (const int i : mask) {
+ computed_varray->get_to_uninitialized(i, buffer);
+ output_varray->set_by_relocate(i, buffer);
+ }
+ }
+ r_varrays[out_index] = output_varray;
+ }
+ }
+ return r_varrays;
+}
+
+void evaluate_constant_field(const GField &field, void *r_value)
+{
+ ResourceScope scope;
+ FieldContext context;
+ Vector<const GVArray *> varrays = evaluate_fields(scope, {field}, IndexRange(1), context);
+ varrays[0]->get_to_uninitialized(0, r_value);
+}
+
+const GVArray *FieldContext::get_varray_for_input(const FieldInput &field_input,
+ IndexMask mask,
+ ResourceScope &scope) const
+{
+ /* By default ask the field input to create the varray. Another field context might overwrite
+ * the context here. */
+ return field_input.get_varray_for_context(*this, mask, scope);
+}
+
+/* --------------------------------------------------------------------
+ * FieldEvaluator.
+ */
+
+static Vector<int64_t> indices_from_selection(const VArray<bool> &selection)
+{
+ /* If the selection is just a single value, it's best to avoid calling this
+ * function when constructing an IndexMask and use an IndexRange instead. */
+ BLI_assert(!selection.is_single());
+ Vector<int64_t> indices;
+ if (selection.is_span()) {
+ Span<bool> span = selection.get_internal_span();
+ for (const int64_t i : span.index_range()) {
+ if (span[i]) {
+ indices.append(i);
+ }
+ }
+ }
+ else {
+ for (const int i : selection.index_range()) {
+ if (selection[i]) {
+ indices.append(i);
+ }
+ }
+ }
+ return indices;
+}
+
+int FieldEvaluator::add_with_destination(GField field, GVMutableArray &dst)
+{
+ const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
+ dst_hints_.append(&dst);
+ output_pointer_infos_.append({});
+ return field_index;
+}
+
+int FieldEvaluator::add_with_destination(GField field, GMutableSpan dst)
+{
+ GVMutableArray &varray_dst_hint = scope_.construct<GVMutableArray_For_GMutableSpan>(__func__,
+ dst);
+ return this->add_with_destination(std::move(field), varray_dst_hint);
+}
+
+int FieldEvaluator::add(GField field, const GVArray **varray_ptr)
+{
+ const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
+ dst_hints_.append(nullptr);
+ output_pointer_infos_.append(OutputPointerInfo{
+ varray_ptr, [](void *dst, const GVArray &varray, ResourceScope &UNUSED(scope)) {
+ *(const GVArray **)dst = &varray;
+ }});
+ return field_index;
+}
+
+int FieldEvaluator::add(GField field)
+{
+ const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
+ dst_hints_.append(nullptr);
+ output_pointer_infos_.append({});
+ return field_index;
+}
+
+void FieldEvaluator::evaluate()
+{
+ BLI_assert_msg(!is_evaluated_, "Cannot evaluate fields twice.");
+ Array<GFieldRef> fields(fields_to_evaluate_.size());
+ for (const int i : fields_to_evaluate_.index_range()) {
+ fields[i] = fields_to_evaluate_[i];
+ }
+ evaluated_varrays_ = evaluate_fields(scope_, fields, mask_, context_, dst_hints_);
+ BLI_assert(fields_to_evaluate_.size() == evaluated_varrays_.size());
+ for (const int i : fields_to_evaluate_.index_range()) {
+ OutputPointerInfo &info = output_pointer_infos_[i];
+ if (info.dst != nullptr) {
+ info.set(info.dst, *evaluated_varrays_[i], scope_);
+ }
+ }
+ is_evaluated_ = true;
+}
+
+IndexMask FieldEvaluator::get_evaluated_as_mask(const int field_index)
+{
+ const GVArray &varray = this->get_evaluated(field_index);
+ GVArray_Typed<bool> typed_varray{varray};
+
+ if (typed_varray->is_single()) {
+ if (typed_varray->get_internal_single()) {
+ return IndexRange(typed_varray.size());
+ }
+ return IndexRange(0);
+ }
+
+ return scope_.add_value(indices_from_selection(*typed_varray), __func__).as_span();
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_builder.cc b/source/blender/functions/intern/multi_function_builder.cc
index c6b3b808130..180d1f17a54 100644
--- a/source/blender/functions/intern/multi_function_builder.cc
+++ b/source/blender/functions/intern/multi_function_builder.cc
@@ -123,4 +123,32 @@ void CustomMF_DefaultOutput::call(IndexMask mask, MFParams params, MFContext UNU
}
}
+CustomMF_GenericCopy::CustomMF_GenericCopy(StringRef name, MFDataType data_type)
+{
+ MFSignatureBuilder signature{name};
+ signature.input("Input", data_type);
+ signature.output("Output", data_type);
+ signature_ = signature.build();
+ this->set_signature(&signature_);
+}
+
+void CustomMF_GenericCopy::call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const
+{
+ const MFDataType data_type = this->param_type(0).data_type();
+ switch (data_type.category()) {
+ case MFDataType::Single: {
+ const GVArray &inputs = params.readonly_single_input(0, "Input");
+ GMutableSpan outputs = params.uninitialized_single_output(1, "Output");
+ inputs.materialize_to_uninitialized(mask, outputs.data());
+ break;
+ }
+ case MFDataType::Vector: {
+ const GVVectorArray &inputs = params.readonly_vector_input(0, "Input");
+ GVectorArray &outputs = params.vector_output(1, "Output");
+ outputs.extend(mask, inputs);
+ break;
+ }
+ }
+}
+
} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_procedure.cc b/source/blender/functions/intern/multi_function_procedure.cc
new file mode 100644
index 00000000000..7d96491f5e0
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_procedure.cc
@@ -0,0 +1,808 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FN_multi_function_procedure.hh"
+
+#include "BLI_dot_export.hh"
+#include "BLI_stack.hh"
+
+namespace blender::fn {
+
+void MFVariable::set_name(std::string name)
+{
+ name_ = std::move(name);
+}
+
+void MFCallInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ next_ = instruction;
+}
+
+void MFCallInstruction::set_param_variable(int param_index, MFVariable *variable)
+{
+ if (params_[param_index] != nullptr) {
+ params_[param_index]->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ BLI_assert(fn_->param_type(param_index).data_type() == variable->data_type());
+ variable->users_.append(this);
+ }
+ params_[param_index] = variable;
+}
+
+void MFCallInstruction::set_params(Span<MFVariable *> variables)
+{
+ BLI_assert(variables.size() == params_.size());
+ for (const int i : variables.index_range()) {
+ this->set_param_variable(i, variables[i]);
+ }
+}
+
+void MFBranchInstruction::set_condition(MFVariable *variable)
+{
+ if (condition_ != nullptr) {
+ condition_->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ variable->users_.append(this);
+ }
+ condition_ = variable;
+}
+
+void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
+{
+ if (branch_true_ != nullptr) {
+ branch_true_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ branch_true_ = instruction;
+}
+
+void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
+{
+ if (branch_false_ != nullptr) {
+ branch_false_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ branch_false_ = instruction;
+}
+
+void MFDestructInstruction::set_variable(MFVariable *variable)
+{
+ if (variable_ != nullptr) {
+ variable_->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ variable->users_.append(this);
+ }
+ variable_ = variable;
+}
+
+void MFDestructInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ next_ = instruction;
+}
+
+void MFDummyInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ next_ = instruction;
+}
+
+MFVariable &MFProcedure::new_variable(MFDataType data_type, std::string name)
+{
+ MFVariable &variable = *allocator_.construct<MFVariable>().release();
+ variable.name_ = std::move(name);
+ variable.data_type_ = data_type;
+ variable.id_ = variables_.size();
+ variables_.append(&variable);
+ return variable;
+}
+
+MFCallInstruction &MFProcedure::new_call_instruction(const MultiFunction &fn)
+{
+ MFCallInstruction &instruction = *allocator_.construct<MFCallInstruction>().release();
+ instruction.type_ = MFInstructionType::Call;
+ instruction.fn_ = &fn;
+ instruction.params_ = allocator_.allocate_array<MFVariable *>(fn.param_amount());
+ instruction.params_.fill(nullptr);
+ call_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFBranchInstruction &MFProcedure::new_branch_instruction()
+{
+ MFBranchInstruction &instruction = *allocator_.construct<MFBranchInstruction>().release();
+ instruction.type_ = MFInstructionType::Branch;
+ branch_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFDestructInstruction &MFProcedure::new_destruct_instruction()
+{
+ MFDestructInstruction &instruction = *allocator_.construct<MFDestructInstruction>().release();
+ instruction.type_ = MFInstructionType::Destruct;
+ destruct_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFDummyInstruction &MFProcedure::new_dummy_instruction()
+{
+ MFDummyInstruction &instruction = *allocator_.construct<MFDummyInstruction>().release();
+ instruction.type_ = MFInstructionType::Dummy;
+ dummy_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFReturnInstruction &MFProcedure::new_return_instruction()
+{
+ MFReturnInstruction &instruction = *allocator_.construct<MFReturnInstruction>().release();
+ instruction.type_ = MFInstructionType::Return;
+ return_instructions_.append(&instruction);
+ return instruction;
+}
+
+void MFProcedure::add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable)
+{
+ params_.append({interface_type, &variable});
+}
+
+void MFProcedure::set_entry(MFInstruction &entry)
+{
+ entry_ = &entry;
+}
+
+void MFProcedure::assert_valid() const
+{
+ /**
+ * - Non parameter variables are destructed.
+ * - At every instruction, every variable is either initialized or uninitialized.
+ * - Input and mutable parameters of call instructions are initialized.
+ * - Condition of branch instruction is initialized.
+ * - Output parameters of call instructions are not initialized.
+ * - Input parameters are never destructed.
+ * - Mutable and output parameteres are initialized on every exit.
+ * - No aliasing issues in call instructions (can happen when variable is used more than once).
+ */
+}
+
+MFProcedure::~MFProcedure()
+{
+ for (MFCallInstruction *instruction : call_instructions_) {
+ instruction->~MFCallInstruction();
+ }
+ for (MFBranchInstruction *instruction : branch_instructions_) {
+ instruction->~MFBranchInstruction();
+ }
+ for (MFDestructInstruction *instruction : destruct_instructions_) {
+ instruction->~MFDestructInstruction();
+ }
+ for (MFDummyInstruction *instruction : dummy_instructions_) {
+ instruction->~MFDummyInstruction();
+ }
+ for (MFReturnInstruction *instruction : return_instructions_) {
+ instruction->~MFReturnInstruction();
+ }
+ for (MFVariable *variable : variables_) {
+ variable->~MFVariable();
+ }
+}
+
+bool MFProcedure::validate() const
+{
+ if (entry_ == nullptr) {
+ return false;
+ }
+ if (!this->validate_all_instruction_pointers_set()) {
+ return false;
+ }
+ if (!this->validate_all_params_provided()) {
+ return false;
+ }
+ if (!this->validate_same_variables_in_one_call()) {
+ return false;
+ }
+ if (!this->validate_parameters()) {
+ return false;
+ }
+ if (!this->validate_initialization()) {
+ return false;
+ }
+ return true;
+}
+
+bool MFProcedure::validate_all_instruction_pointers_set() const
+{
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ if (instruction->next_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFDestructInstruction *instruction : destruct_instructions_) {
+ if (instruction->next_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFBranchInstruction *instruction : branch_instructions_) {
+ if (instruction->branch_true_ == nullptr) {
+ return false;
+ }
+ if (instruction->branch_false_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFDummyInstruction *instruction : dummy_instructions_) {
+ if (instruction->next_ == nullptr) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_all_params_provided() const
+{
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ for (const MFVariable *variable : instruction->params_) {
+ if (variable == nullptr) {
+ return false;
+ }
+ }
+ }
+ for (const MFBranchInstruction *instruction : branch_instructions_) {
+ if (instruction->condition_ == nullptr) {
+ return false;
+ }
+ }
+ for (const MFDestructInstruction *instruction : destruct_instructions_) {
+ if (instruction->variable_ == nullptr) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_same_variables_in_one_call() const
+{
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ const MultiFunction &fn = *instruction->fn_;
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ const MFVariable *variable = instruction->params_[param_index];
+ for (const int other_param_index : fn.param_indices()) {
+ if (other_param_index == param_index) {
+ continue;
+ }
+ const MFVariable *other_variable = instruction->params_[other_param_index];
+ if (other_variable != variable) {
+ continue;
+ }
+ if (ELEM(param_type.interface_type(), MFParamType::Mutable, MFParamType::Output)) {
+ /* When a variable is used as mutable or output parameter, it can only be used once. */
+ return false;
+ }
+ const MFParamType other_param_type = fn.param_type(other_param_index);
+ /* A variable is allowed to be used as input more than once. */
+ if (other_param_type.interface_type() != MFParamType::Input) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_parameters() const
+{
+ Set<const MFVariable *> variables;
+ for (const MFParameter &param : params_) {
+ /* One variable cannot be used as multiple parameters. */
+ if (!variables.add(param.variable)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool MFProcedure::validate_initialization() const
+{
+ /* TODO: Issue warning when it maybe wrongly initialized. */
+ for (const MFDestructInstruction *instruction : destruct_instructions_) {
+ const MFVariable &variable = *instruction->variable_;
+ const InitState state = this->find_initialization_state_before_instruction(*instruction,
+ variable);
+ if (!state.can_be_initialized) {
+ return false;
+ }
+ }
+ for (const MFBranchInstruction *instruction : branch_instructions_) {
+ const MFVariable &variable = *instruction->condition_;
+ const InitState state = this->find_initialization_state_before_instruction(*instruction,
+ variable);
+ if (!state.can_be_initialized) {
+ return false;
+ }
+ }
+ for (const MFCallInstruction *instruction : call_instructions_) {
+ const MultiFunction &fn = *instruction->fn_;
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ const MFVariable &variable = *instruction->params_[param_index];
+ const InitState state = this->find_initialization_state_before_instruction(*instruction,
+ variable);
+ switch (param_type.interface_type()) {
+ case MFParamType::Input:
+ case MFParamType::Mutable: {
+ if (!state.can_be_initialized) {
+ return false;
+ }
+ break;
+ }
+ case MFParamType::Output: {
+ if (!state.can_be_uninitialized) {
+ return false;
+ }
+ break;
+ }
+ }
+ }
+ }
+ Set<const MFVariable *> variables_that_should_be_initialized_on_return;
+ for (const MFParameter &param : params_) {
+ if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
+ variables_that_should_be_initialized_on_return.add_new(param.variable);
+ }
+ }
+ for (const MFReturnInstruction *instruction : return_instructions_) {
+ for (const MFVariable *variable : variables_) {
+ const InitState init_state = this->find_initialization_state_before_instruction(*instruction,
+ *variable);
+ if (variables_that_should_be_initialized_on_return.contains(variable)) {
+ if (!init_state.can_be_initialized) {
+ return false;
+ }
+ }
+ else {
+ if (!init_state.can_be_uninitialized) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction(
+ const MFInstruction &target_instruction, const MFVariable &target_variable) const
+{
+ InitState state;
+
+ auto check_entry_instruction = [&]() {
+ bool caller_initialized_variable = false;
+ for (const MFParameter &param : params_) {
+ if (param.variable == &target_variable) {
+ if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
+ caller_initialized_variable = true;
+ break;
+ }
+ }
+ }
+ if (caller_initialized_variable) {
+ state.can_be_initialized = true;
+ }
+ else {
+ state.can_be_uninitialized = true;
+ }
+ };
+
+ if (&target_instruction == entry_) {
+ check_entry_instruction();
+ }
+
+ Set<const MFInstruction *> checked_instructions;
+ Stack<const MFInstruction *> instructions_to_check;
+ instructions_to_check.push_multiple(target_instruction.prev_);
+
+ while (!instructions_to_check.is_empty()) {
+ const MFInstruction &instruction = *instructions_to_check.pop();
+ if (!checked_instructions.add(&instruction)) {
+ /* Skip if the instruction has been checked already. */
+ continue;
+ }
+ bool state_modified = false;
+ switch (instruction.type_) {
+ case MFInstructionType::Call: {
+ const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>(
+ instruction);
+ const MultiFunction &fn = *call_instruction.fn_;
+ for (const int param_index : fn.param_indices()) {
+ if (call_instruction.params_[param_index] == &target_variable) {
+ const MFParamType param_type = fn.param_type(param_index);
+ if (param_type.interface_type() == MFParamType::Output) {
+ state.can_be_initialized = true;
+ state_modified = true;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ const MFDestructInstruction &destruct_instruction =
+ static_cast<const MFDestructInstruction &>(instruction);
+ if (destruct_instruction.variable_ == &target_variable) {
+ state.can_be_uninitialized = true;
+ state_modified = true;
+ }
+ break;
+ }
+ case MFInstructionType::Branch:
+ case MFInstructionType::Dummy:
+ case MFInstructionType::Return: {
+ /* These instruction types don't change the initialization state of variables. */
+ break;
+ }
+ }
+
+ if (!state_modified) {
+ if (&instruction == entry_) {
+ check_entry_instruction();
+ }
+ instructions_to_check.push_multiple(instruction.prev_);
+ }
+ }
+
+ return state;
+}
+
+class MFProcedureDotExport {
+ private:
+ const MFProcedure &procedure_;
+ dot::DirectedGraph digraph_;
+ Map<const MFInstruction *, dot::Node *> dot_nodes_by_begin_;
+ Map<const MFInstruction *, dot::Node *> dot_nodes_by_end_;
+
+ public:
+ MFProcedureDotExport(const MFProcedure &procedure) : procedure_(procedure)
+ {
+ }
+
+ std::string generate()
+ {
+ this->create_nodes();
+ this->create_edges();
+ return digraph_.to_dot_string();
+ }
+
+ void create_nodes()
+ {
+ Vector<const MFInstruction *> all_instructions;
+ auto add_instructions = [&](auto instructions) {
+ all_instructions.extend(instructions.begin(), instructions.end());
+ };
+ add_instructions(procedure_.call_instructions_);
+ add_instructions(procedure_.branch_instructions_);
+ add_instructions(procedure_.destruct_instructions_);
+ add_instructions(procedure_.dummy_instructions_);
+ add_instructions(procedure_.return_instructions_);
+
+ Set<const MFInstruction *> handled_instructions;
+
+ for (const MFInstruction *representative : all_instructions) {
+ if (handled_instructions.contains(representative)) {
+ continue;
+ }
+ Vector<const MFInstruction *> block_instructions = this->get_instructions_in_block(
+ *representative);
+ std::stringstream ss;
+ ss << "<";
+
+ for (const MFInstruction *current : block_instructions) {
+ handled_instructions.add_new(current);
+ switch (current->type()) {
+ case MFInstructionType::Call: {
+ this->instruction_to_string(*static_cast<const MFCallInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ this->instruction_to_string(*static_cast<const MFDestructInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ this->instruction_to_string(*static_cast<const MFDummyInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Return: {
+ this->instruction_to_string(*static_cast<const MFReturnInstruction *>(current), ss);
+ break;
+ }
+ case MFInstructionType::Branch: {
+ this->instruction_to_string(*static_cast<const MFBranchInstruction *>(current), ss);
+ break;
+ }
+ }
+ ss << R"(<br align="left" />)";
+ }
+ ss << ">";
+
+ dot::Node &dot_node = digraph_.new_node(ss.str());
+ dot_node.set_shape(dot::Attr_shape::Rectangle);
+ dot_nodes_by_begin_.add_new(block_instructions.first(), &dot_node);
+ dot_nodes_by_end_.add_new(block_instructions.last(), &dot_node);
+ }
+ }
+
+ void create_edges()
+ {
+ auto create_edge = [&](dot::Node &from_node,
+ const MFInstruction *to_instruction) -> dot::DirectedEdge & {
+ if (to_instruction == nullptr) {
+ dot::Node &to_node = digraph_.new_node("missing");
+ to_node.set_shape(dot::Attr_shape::Diamond);
+ return digraph_.new_edge(from_node, to_node);
+ }
+ dot::Node &to_node = *dot_nodes_by_begin_.lookup(to_instruction);
+ return digraph_.new_edge(from_node, to_node);
+ };
+
+ for (auto item : dot_nodes_by_end_.items()) {
+ const MFInstruction &from_instruction = *item.key;
+ dot::Node &from_node = *item.value;
+ switch (from_instruction.type()) {
+ case MFInstructionType::Call: {
+ const MFInstruction *to_instruction =
+ static_cast<const MFCallInstruction &>(from_instruction).next();
+ create_edge(from_node, to_instruction);
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ const MFInstruction *to_instruction =
+ static_cast<const MFDestructInstruction &>(from_instruction).next();
+ create_edge(from_node, to_instruction);
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ const MFInstruction *to_instruction =
+ static_cast<const MFDummyInstruction &>(from_instruction).next();
+ create_edge(from_node, to_instruction);
+ break;
+ }
+ case MFInstructionType::Return: {
+ break;
+ }
+ case MFInstructionType::Branch: {
+ const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>(
+ from_instruction);
+ const MFInstruction *to_true_instruction = branch_instruction.branch_true();
+ const MFInstruction *to_false_instruction = branch_instruction.branch_false();
+ create_edge(from_node, to_true_instruction).attributes.set("color", "#118811");
+ create_edge(from_node, to_false_instruction).attributes.set("color", "#881111");
+ break;
+ }
+ }
+ }
+
+ dot::Node &entry_node = this->create_entry_node();
+ create_edge(entry_node, procedure_.entry());
+ }
+
+ bool has_to_be_block_begin(const MFInstruction &instruction)
+ {
+ if (procedure_.entry() == &instruction) {
+ return true;
+ }
+ if (instruction.prev().size() != 1) {
+ return true;
+ }
+ if (instruction.prev()[0]->type() == MFInstructionType::Branch) {
+ return true;
+ }
+ return false;
+ }
+
+ const MFInstruction &get_first_instruction_in_block(const MFInstruction &representative)
+ {
+ const MFInstruction *current = &representative;
+ while (!this->has_to_be_block_begin(*current)) {
+ current = current->prev()[0];
+ if (current == &representative) {
+ /* There is a loop without entry or exit, just break it up here. */
+ break;
+ }
+ }
+ return *current;
+ }
+
+ const MFInstruction *get_next_instruction_in_block(const MFInstruction &instruction,
+ const MFInstruction &block_begin)
+ {
+ const MFInstruction *next = nullptr;
+ switch (instruction.type()) {
+ case MFInstructionType::Call: {
+ next = static_cast<const MFCallInstruction &>(instruction).next();
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ next = static_cast<const MFDestructInstruction &>(instruction).next();
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ next = static_cast<const MFDummyInstruction &>(instruction).next();
+ break;
+ }
+ case MFInstructionType::Return:
+ case MFInstructionType::Branch: {
+ break;
+ }
+ }
+ if (next == nullptr) {
+ return nullptr;
+ }
+ if (next == &block_begin) {
+ return nullptr;
+ }
+ if (this->has_to_be_block_begin(*next)) {
+ return nullptr;
+ }
+ return next;
+ }
+
+ Vector<const MFInstruction *> get_instructions_in_block(const MFInstruction &representative)
+ {
+ Vector<const MFInstruction *> instructions;
+ const MFInstruction &begin = this->get_first_instruction_in_block(representative);
+ for (const MFInstruction *current = &begin; current != nullptr;
+ current = this->get_next_instruction_in_block(*current, begin)) {
+ instructions.append(current);
+ }
+ return instructions;
+ }
+
+ void variable_to_string(const MFVariable *variable, std::stringstream &ss)
+ {
+ if (variable == nullptr) {
+ ss << "null";
+ }
+ else {
+ ss << "$" << variable->id();
+ if (!variable->name().is_empty()) {
+ ss << "(" << variable->name() << ")";
+ }
+ }
+ }
+
+ void instruction_name_format(StringRef name, std::stringstream &ss)
+ {
+ ss << name;
+ }
+
+ void instruction_to_string(const MFCallInstruction &instruction, std::stringstream &ss)
+ {
+ const MultiFunction &fn = instruction.fn();
+ this->instruction_name_format(fn.name() + ": ", ss);
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ const MFVariable *variable = instruction.params()[param_index];
+ ss << R"(<font color="grey30">)";
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ ss << "in";
+ break;
+ }
+ case MFParamType::Mutable: {
+ ss << "mut";
+ break;
+ }
+ case MFParamType::Output: {
+ ss << "out";
+ break;
+ }
+ }
+ ss << " </font> ";
+ variable_to_string(variable, ss);
+ if (param_index < fn.param_amount() - 1) {
+ ss << ", ";
+ }
+ }
+ }
+
+ void instruction_to_string(const MFDestructInstruction &instruction, std::stringstream &ss)
+ {
+ instruction_name_format("Destruct ", ss);
+ variable_to_string(instruction.variable(), ss);
+ }
+
+ void instruction_to_string(const MFDummyInstruction &UNUSED(instruction), std::stringstream &ss)
+ {
+ instruction_name_format("Dummy ", ss);
+ }
+
+ void instruction_to_string(const MFReturnInstruction &UNUSED(instruction), std::stringstream &ss)
+ {
+ instruction_name_format("Return ", ss);
+
+ Vector<ConstMFParameter> outgoing_parameters;
+ for (const ConstMFParameter &param : procedure_.params()) {
+ if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
+ outgoing_parameters.append(param);
+ }
+ }
+ for (const int param_index : outgoing_parameters.index_range()) {
+ const ConstMFParameter &param = outgoing_parameters[param_index];
+ variable_to_string(param.variable, ss);
+ if (param_index < outgoing_parameters.size() - 1) {
+ ss << ", ";
+ }
+ }
+ }
+
+ void instruction_to_string(const MFBranchInstruction &instruction, std::stringstream &ss)
+ {
+ instruction_name_format("Branch ", ss);
+ variable_to_string(instruction.condition(), ss);
+ }
+
+ dot::Node &create_entry_node()
+ {
+ std::stringstream ss;
+ ss << "Entry: ";
+ Vector<ConstMFParameter> incoming_parameters;
+ for (const ConstMFParameter &param : procedure_.params()) {
+ if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
+ incoming_parameters.append(param);
+ }
+ }
+ for (const int param_index : incoming_parameters.index_range()) {
+ const ConstMFParameter &param = incoming_parameters[param_index];
+ variable_to_string(param.variable, ss);
+ if (param_index < incoming_parameters.size() - 1) {
+ ss << ", ";
+ }
+ }
+
+ dot::Node &node = digraph_.new_node(ss.str());
+ node.set_shape(dot::Attr_shape::Ellipse);
+ return node;
+ }
+};
+
+std::string MFProcedure::to_dot() const
+{
+ MFProcedureDotExport dot_export{*this};
+ return dot_export.generate();
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_procedure_builder.cc b/source/blender/functions/intern/multi_function_procedure_builder.cc
new file mode 100644
index 00000000000..3c088776bea
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_procedure_builder.cc
@@ -0,0 +1,175 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FN_multi_function_procedure_builder.hh"
+
+namespace blender::fn {
+
+void MFInstructionCursor::insert(MFProcedure &procedure, MFInstruction *new_instruction)
+{
+ if (instruction_ == nullptr) {
+ if (is_entry_) {
+ procedure.set_entry(*new_instruction);
+ }
+ else {
+ /* The cursors points at nothing, nothing to do. */
+ }
+ }
+ else {
+ switch (instruction_->type()) {
+ case MFInstructionType::Call: {
+ static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction);
+ break;
+ }
+ case MFInstructionType::Branch: {
+ MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(
+ instruction_);
+ if (branch_output_) {
+ branch_instruction.set_branch_true(new_instruction);
+ }
+ else {
+ branch_instruction.set_branch_false(new_instruction);
+ }
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction);
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction);
+ break;
+ }
+ case MFInstructionType::Return: {
+ /* It shouldn't be possible to build a cursor that points to a return instruction. */
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+}
+
+void MFProcedureBuilder::add_destruct(MFVariable &variable)
+{
+ MFDestructInstruction &instruction = procedure_->new_destruct_instruction();
+ instruction.set_variable(&variable);
+ this->link_to_cursors(&instruction);
+ cursors_ = {MFInstructionCursor{instruction}};
+}
+
+void MFProcedureBuilder::add_destruct(Span<MFVariable *> variables)
+{
+ for (MFVariable *variable : variables) {
+ this->add_destruct(*variable);
+ }
+}
+
+MFReturnInstruction &MFProcedureBuilder::add_return()
+{
+ MFReturnInstruction &instruction = procedure_->new_return_instruction();
+ this->link_to_cursors(&instruction);
+ cursors_ = {};
+ return instruction;
+}
+
+MFCallInstruction &MFProcedureBuilder::add_call_with_no_variables(const MultiFunction &fn)
+{
+ MFCallInstruction &instruction = procedure_->new_call_instruction(fn);
+ this->link_to_cursors(&instruction);
+ cursors_ = {MFInstructionCursor{instruction}};
+ return instruction;
+}
+
+MFCallInstruction &MFProcedureBuilder::add_call_with_all_variables(
+ const MultiFunction &fn, Span<MFVariable *> param_variables)
+{
+ MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
+ instruction.set_params(param_variables);
+ return instruction;
+}
+
+Vector<MFVariable *> MFProcedureBuilder::add_call(const MultiFunction &fn,
+ Span<MFVariable *> input_and_mutable_variables)
+{
+ Vector<MFVariable *> output_variables;
+ MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ switch (param_type.interface_type()) {
+ case MFParamType::Input:
+ case MFParamType::Mutable: {
+ MFVariable *variable = input_and_mutable_variables.first();
+ instruction.set_param_variable(param_index, variable);
+ input_and_mutable_variables = input_and_mutable_variables.drop_front(1);
+ break;
+ }
+ case MFParamType::Output: {
+ MFVariable &variable = procedure_->new_variable(param_type.data_type(),
+ fn.param_name(param_index));
+ instruction.set_param_variable(param_index, &variable);
+ output_variables.append(&variable);
+ break;
+ }
+ }
+ }
+ /* All passed in variables should have been dropped in the loop above. */
+ BLI_assert(input_and_mutable_variables.is_empty());
+ return output_variables;
+}
+
+MFProcedureBuilder::Branch MFProcedureBuilder::add_branch(MFVariable &condition)
+{
+ MFBranchInstruction &instruction = procedure_->new_branch_instruction();
+ instruction.set_condition(&condition);
+ this->link_to_cursors(&instruction);
+ /* Clear cursors because this builder ends here. */
+ cursors_.clear();
+
+ Branch branch{*procedure_, *procedure_};
+ branch.branch_true.set_cursor(MFInstructionCursor{instruction, true});
+ branch.branch_false.set_cursor(MFInstructionCursor{instruction, false});
+ return branch;
+}
+
+MFProcedureBuilder::Loop MFProcedureBuilder::add_loop()
+{
+ MFDummyInstruction &loop_begin = procedure_->new_dummy_instruction();
+ MFDummyInstruction &loop_end = procedure_->new_dummy_instruction();
+ this->link_to_cursors(&loop_begin);
+ cursors_ = {MFInstructionCursor{loop_begin}};
+
+ Loop loop;
+ loop.begin = &loop_begin;
+ loop.end = &loop_end;
+
+ return loop;
+}
+
+void MFProcedureBuilder::add_loop_continue(Loop &loop)
+{
+ this->link_to_cursors(loop.begin);
+ /* Clear cursors because this builder ends here. */
+ cursors_.clear();
+}
+
+void MFProcedureBuilder::add_loop_break(Loop &loop)
+{
+ this->link_to_cursors(loop.end);
+ /* Clear cursors because this builder ends here. */
+ cursors_.clear();
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_procedure_executor.cc b/source/blender/functions/intern/multi_function_procedure_executor.cc
new file mode 100644
index 00000000000..38b26415779
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_procedure_executor.cc
@@ -0,0 +1,1212 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FN_multi_function_procedure_executor.hh"
+
+#include "BLI_stack.hh"
+
+namespace blender::fn {
+
+MFProcedureExecutor::MFProcedureExecutor(std::string name, const MFProcedure &procedure)
+ : procedure_(procedure)
+{
+ MFSignatureBuilder signature(std::move(name));
+
+ for (const ConstMFParameter &param : procedure.params()) {
+ signature.add(param.variable->name(), MFParamType(param.type, param.variable->data_type()));
+ }
+
+ signature_ = signature.build();
+ this->set_signature(&signature_);
+}
+
+using IndicesSplitVectors = std::array<Vector<int64_t>, 2>;
+
+namespace {
+enum class ValueType {
+ GVArray = 0,
+ Span = 1,
+ GVVectorArray = 2,
+ GVectorArray = 3,
+ OneSingle = 4,
+ OneVector = 5,
+};
+constexpr int tot_variable_value_types = 6;
+} // namespace
+
+/**
+ * During evaluation, a variable may be stored in various different forms, depending on what
+ * instructions do with the variables.
+ */
+struct VariableValue {
+ ValueType type;
+
+ VariableValue(ValueType type) : type(type)
+ {
+ }
+};
+
+/* This variable is the unmodified virtual array from the caller. */
+struct VariableValue_GVArray : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::GVArray;
+ const GVArray &data;
+
+ VariableValue_GVArray(const GVArray &data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+/* This variable has a different value for every index. Some values may be uninitialized. The span
+ * may be owned by the caller. */
+struct VariableValue_Span : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::Span;
+ void *data;
+ bool owned;
+
+ VariableValue_Span(void *data, bool owned) : VariableValue(static_type), data(data), owned(owned)
+ {
+ }
+};
+
+/* This variable is the unmodified virtual vector array from the caller. */
+struct VariableValue_GVVectorArray : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::GVVectorArray;
+ const GVVectorArray &data;
+
+ VariableValue_GVVectorArray(const GVVectorArray &data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+/* This variable has a different vector for every index. */
+struct VariableValue_GVectorArray : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::GVectorArray;
+ GVectorArray &data;
+ bool owned;
+
+ VariableValue_GVectorArray(GVectorArray &data, bool owned)
+ : VariableValue(static_type), data(data), owned(owned)
+ {
+ }
+};
+
+/* This variable has the same value for every index. */
+struct VariableValue_OneSingle : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::OneSingle;
+ void *data;
+ bool is_initialized = false;
+
+ VariableValue_OneSingle(void *data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+/* This variable has the same vector for every index. */
+struct VariableValue_OneVector : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::OneVector;
+ GVectorArray &data;
+
+ VariableValue_OneVector(GVectorArray &data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+static_assert(std::is_trivially_destructible_v<VariableValue_GVArray>);
+static_assert(std::is_trivially_destructible_v<VariableValue_Span>);
+static_assert(std::is_trivially_destructible_v<VariableValue_GVVectorArray>);
+static_assert(std::is_trivially_destructible_v<VariableValue_GVectorArray>);
+static_assert(std::is_trivially_destructible_v<VariableValue_OneSingle>);
+static_assert(std::is_trivially_destructible_v<VariableValue_OneVector>);
+
+class VariableState;
+
+/**
+ * The #ValueAllocator is responsible for providing memory for variables and their values. It also
+ * manages the reuse of buffers to improve performance.
+ */
+class ValueAllocator : NonCopyable, NonMovable {
+ private:
+ /* Allocate with 64 byte alignment for better reusability of buffers and improved cache
+ * performance. */
+ static constexpr inline int min_alignment = 64;
+
+ /* Use stacks so that the most recently used buffers are reused first. This improves cache
+ * efficiency. */
+ std::array<Stack<VariableValue *>, tot_variable_value_types> values_free_lists_;
+ /* The integer key is the size of one element (e.g. 4 for an integer buffer). All buffers are
+ * aligned to #min_alignment bytes. */
+ Map<int, Stack<void *>> span_buffers_free_list_;
+
+ public:
+ ValueAllocator() = default;
+
+ ~ValueAllocator()
+ {
+ for (Stack<VariableValue *> &stack : values_free_lists_) {
+ while (!stack.is_empty()) {
+ MEM_freeN(stack.pop());
+ }
+ }
+ for (Stack<void *> &stack : span_buffers_free_list_.values()) {
+ while (!stack.is_empty()) {
+ MEM_freeN(stack.pop());
+ }
+ }
+ }
+
+ template<typename... Args> VariableState *obtain_variable_state(Args &&...args);
+
+ void release_variable_state(VariableState *state);
+
+ VariableValue_GVArray *obtain_GVArray(const GVArray &varray)
+ {
+ return this->obtain<VariableValue_GVArray>(varray);
+ }
+
+ VariableValue_GVVectorArray *obtain_GVVectorArray(const GVVectorArray &varray)
+ {
+ return this->obtain<VariableValue_GVVectorArray>(varray);
+ }
+
+ VariableValue_Span *obtain_Span_not_owned(void *buffer)
+ {
+ return this->obtain<VariableValue_Span>(buffer, false);
+ }
+
+ VariableValue_Span *obtain_Span(const CPPType &type, int size)
+ {
+ void *buffer = nullptr;
+
+ const int element_size = type.size();
+ const int alignment = type.alignment();
+
+ if (alignment > min_alignment) {
+ /* In this rare case we fallback to not reusing existing buffers. */
+ buffer = MEM_mallocN_aligned(element_size * size, alignment, __func__);
+ }
+ else {
+ Stack<void *> *stack = span_buffers_free_list_.lookup_ptr(element_size);
+ if (stack == nullptr || stack->is_empty()) {
+ buffer = MEM_mallocN_aligned(element_size * size, min_alignment, __func__);
+ }
+ else {
+ /* Reuse existing buffer. */
+ buffer = stack->pop();
+ }
+ }
+
+ return this->obtain<VariableValue_Span>(buffer, true);
+ }
+
+ VariableValue_GVectorArray *obtain_GVectorArray_not_owned(GVectorArray &data)
+ {
+ return this->obtain<VariableValue_GVectorArray>(data, false);
+ }
+
+ VariableValue_GVectorArray *obtain_GVectorArray(const CPPType &type, int size)
+ {
+ GVectorArray *vector_array = new GVectorArray(type, size);
+ return this->obtain<VariableValue_GVectorArray>(*vector_array, true);
+ }
+
+ VariableValue_OneSingle *obtain_OneSingle(const CPPType &type)
+ {
+ void *buffer = MEM_mallocN_aligned(type.size(), type.alignment(), __func__);
+ return this->obtain<VariableValue_OneSingle>(buffer);
+ }
+
+ VariableValue_OneVector *obtain_OneVector(const CPPType &type)
+ {
+ GVectorArray *vector_array = new GVectorArray(type, 1);
+ return this->obtain<VariableValue_OneVector>(*vector_array);
+ }
+
+ void release_value(VariableValue *value, const MFDataType &data_type)
+ {
+ switch (value->type) {
+ case ValueType::GVArray: {
+ break;
+ }
+ case ValueType::Span: {
+ auto *value_typed = static_cast<VariableValue_Span *>(value);
+ if (value_typed->owned) {
+ const CPPType &type = data_type.single_type();
+ /* Assumes all values in the buffer are uninitialized already. */
+ Stack<void *> &buffers = span_buffers_free_list_.lookup_or_add_default(type.size());
+ buffers.push(value_typed->data);
+ }
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ break;
+ }
+ case ValueType::GVectorArray: {
+ auto *value_typed = static_cast<VariableValue_GVectorArray *>(value);
+ if (value_typed->owned) {
+ delete &value_typed->data;
+ }
+ break;
+ }
+ case ValueType::OneSingle: {
+ auto *value_typed = static_cast<VariableValue_OneSingle *>(value);
+ if (value_typed->is_initialized) {
+ const CPPType &type = data_type.single_type();
+ type.destruct(value_typed->data);
+ }
+ MEM_freeN(value_typed->data);
+ break;
+ }
+ case ValueType::OneVector: {
+ auto *value_typed = static_cast<VariableValue_OneVector *>(value);
+ delete &value_typed->data;
+ break;
+ }
+ }
+
+ Stack<VariableValue *> &stack = values_free_lists_[(int)value->type];
+ stack.push(value);
+ }
+
+ private:
+ template<typename T, typename... Args> T *obtain(Args &&...args)
+ {
+ static_assert(std::is_base_of_v<VariableValue, T>);
+ Stack<VariableValue *> &stack = values_free_lists_[(int)T::static_type];
+ if (stack.is_empty()) {
+ void *buffer = MEM_mallocN(sizeof(T), __func__);
+ return new (buffer) T(std::forward<Args>(args)...);
+ }
+ return new (stack.pop()) T(std::forward<Args>(args)...);
+ }
+};
+
+/**
+ * This class keeps track of a single variable during evaluation.
+ */
+class VariableState : NonCopyable, NonMovable {
+ private:
+ /** The current value of the variable. The storage format may change over time. */
+ VariableValue *value_;
+ /** Number of indices that are currently initialized in this variable. */
+ int tot_initialized_;
+ /* This a non-owning pointer to either span buffer or #GVectorArray or null. */
+ void *caller_provided_storage_ = nullptr;
+
+ public:
+ VariableState(VariableValue &value, int tot_initialized, void *caller_provided_storage = nullptr)
+ : value_(&value),
+ tot_initialized_(tot_initialized),
+ caller_provided_storage_(caller_provided_storage)
+ {
+ }
+
+ void destruct_self(ValueAllocator &value_allocator, const MFDataType &data_type)
+ {
+ value_allocator.release_value(value_, data_type);
+ value_allocator.release_variable_state(this);
+ }
+
+ /* True if this contains only one value for all indices, i.e. the value for all indices is
+ * the same. */
+ bool is_one() const
+ {
+ switch (value_->type) {
+ case ValueType::GVArray:
+ return this->value_as<VariableValue_GVArray>()->data.is_single();
+ case ValueType::Span:
+ return tot_initialized_ == 0;
+ case ValueType::GVVectorArray:
+ return this->value_as<VariableValue_GVVectorArray>()->data.is_single_vector();
+ case ValueType::GVectorArray:
+ return tot_initialized_ == 0;
+ case ValueType::OneSingle:
+ return true;
+ case ValueType::OneVector:
+ return true;
+ }
+ BLI_assert_unreachable();
+ return false;
+ }
+
+ bool is_fully_initialized(const IndexMask full_mask)
+ {
+ return tot_initialized_ == full_mask.size();
+ }
+
+ bool is_fully_uninitialized(const IndexMask full_mask)
+ {
+ UNUSED_VARS(full_mask);
+ return tot_initialized_ == 0;
+ }
+
+ void add_as_input(MFParamsBuilder &params, IndexMask mask, const MFDataType &data_type) const
+ {
+ /* Sanity check to make sure that enough values are initialized. */
+ BLI_assert(mask.size() <= tot_initialized_);
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ params.add_readonly_single_input(this->value_as<VariableValue_GVArray>()->data);
+ break;
+ }
+ case ValueType::Span: {
+ const void *data = this->value_as<VariableValue_Span>()->data;
+ const GSpan span{data_type.single_type(), data, mask.min_array_size()};
+ params.add_readonly_single_input(span);
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_GVVectorArray>()->data);
+ break;
+ }
+ case ValueType::GVectorArray: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_GVectorArray>()->data);
+ break;
+ }
+ case ValueType::OneSingle: {
+ const auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ const GPointer gpointer{data_type.single_type(), value_typed->data};
+ params.add_readonly_single_input(gpointer);
+ break;
+ }
+ case ValueType::OneVector: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_OneVector>()->data[0]);
+ break;
+ }
+ }
+ }
+
+ void ensure_is_mutable(IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ if (ELEM(value_->type, ValueType::Span, ValueType::GVectorArray)) {
+ return;
+ }
+
+ const int array_size = full_mask.min_array_size();
+
+ switch (data_type.category()) {
+ case MFDataType::Single: {
+ const CPPType &type = data_type.single_type();
+ VariableValue_Span *new_value = nullptr;
+ if (caller_provided_storage_ == nullptr) {
+ new_value = value_allocator.obtain_Span(type, array_size);
+ }
+ else {
+ /* Reuse the storage provided caller when possible. */
+ new_value = value_allocator.obtain_Span_not_owned(caller_provided_storage_);
+ }
+ if (value_->type == ValueType::GVArray) {
+ /* Fill new buffer with data from virtual array. */
+ this->value_as<VariableValue_GVArray>()->data.materialize_to_uninitialized(
+ full_mask, new_value->data);
+ }
+ else if (value_->type == ValueType::OneSingle) {
+ auto *old_value_typed_ = this->value_as<VariableValue_OneSingle>();
+ if (old_value_typed_->is_initialized) {
+ /* Fill the buffer with a single value. */
+ type.fill_construct_indices(old_value_typed_->data, new_value->data, full_mask);
+ }
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ case MFDataType::Vector: {
+ const CPPType &type = data_type.vector_base_type();
+ VariableValue_GVectorArray *new_value = nullptr;
+ if (caller_provided_storage_ == nullptr) {
+ new_value = value_allocator.obtain_GVectorArray(type, array_size);
+ }
+ else {
+ new_value = value_allocator.obtain_GVectorArray_not_owned(
+ *(GVectorArray *)caller_provided_storage_);
+ }
+ if (value_->type == ValueType::GVVectorArray) {
+ /* Fill new vector array with data from virtual vector array. */
+ new_value->data.extend(full_mask, this->value_as<VariableValue_GVVectorArray>()->data);
+ }
+ else if (value_->type == ValueType::OneVector) {
+ /* Fill all indices with the same value. */
+ const GSpan vector = this->value_as<VariableValue_OneVector>()->data[0];
+ new_value->data.extend(full_mask, GVVectorArray_For_SingleGSpan{vector, array_size});
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ }
+ }
+
+ void add_as_mutable(MFParamsBuilder &params,
+ IndexMask mask,
+ IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ /* Sanity check to make sure that enough values are initialized. */
+ BLI_assert(mask.size() <= tot_initialized_);
+
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::Span: {
+ void *data = this->value_as<VariableValue_Span>()->data;
+ const GMutableSpan span{data_type.single_type(), data, mask.min_array_size()};
+ params.add_single_mutable(span);
+ break;
+ }
+ case ValueType::GVectorArray: {
+ params.add_vector_mutable(this->value_as<VariableValue_GVectorArray>()->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::GVVectorArray:
+ case ValueType::OneSingle:
+ case ValueType::OneVector: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ void add_as_output(MFParamsBuilder &params,
+ IndexMask mask,
+ IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ /* Sanity check to make sure that enough values are not initialized. */
+ BLI_assert(mask.size() <= full_mask.size() - tot_initialized_);
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::Span: {
+ void *data = this->value_as<VariableValue_Span>()->data;
+ const GMutableSpan span{data_type.single_type(), data, mask.min_array_size()};
+ params.add_uninitialized_single_output(span);
+ break;
+ }
+ case ValueType::GVectorArray: {
+ params.add_vector_output(this->value_as<VariableValue_GVectorArray>()->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::GVVectorArray:
+ case ValueType::OneSingle:
+ case ValueType::OneVector: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+
+ tot_initialized_ += mask.size();
+ }
+
+ void add_as_input__one(MFParamsBuilder &params, const MFDataType &data_type) const
+ {
+ BLI_assert(this->is_one());
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ params.add_readonly_single_input(this->value_as<VariableValue_GVArray>()->data);
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_GVVectorArray>()->data);
+ break;
+ }
+ case ValueType::OneSingle: {
+ const auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ GPointer ptr{data_type.single_type(), value_typed->data};
+ params.add_readonly_single_input(ptr);
+ break;
+ }
+ case ValueType::OneVector: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_OneVector>()->data);
+ break;
+ }
+ case ValueType::Span:
+ case ValueType::GVectorArray: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ void ensure_is_mutable__one(const MFDataType &data_type, ValueAllocator &value_allocator)
+ {
+ BLI_assert(this->is_one());
+ if (ELEM(value_->type, ValueType::OneSingle, ValueType::OneVector)) {
+ return;
+ }
+
+ switch (data_type.category()) {
+ case MFDataType::Single: {
+ const CPPType &type = data_type.single_type();
+ VariableValue_OneSingle *new_value = value_allocator.obtain_OneSingle(type);
+ if (value_->type == ValueType::GVArray) {
+ this->value_as<VariableValue_GVArray>()->data.get_internal_single_to_uninitialized(
+ new_value->data);
+ new_value->is_initialized = true;
+ }
+ else if (value_->type == ValueType::Span) {
+ BLI_assert(tot_initialized_ == 0);
+ /* Nothing to do, the single value is uninitialized already. */
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ case MFDataType::Vector: {
+ const CPPType &type = data_type.vector_base_type();
+ VariableValue_OneVector *new_value = value_allocator.obtain_OneVector(type);
+ if (value_->type == ValueType::GVVectorArray) {
+ const GVVectorArray &old_vector_array =
+ this->value_as<VariableValue_GVVectorArray>()->data;
+ new_value->data.extend(IndexRange(1), old_vector_array);
+ }
+ else if (value_->type == ValueType::GVectorArray) {
+ BLI_assert(tot_initialized_ == 0);
+ /* Nothing to do. */
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ }
+ }
+
+ void add_as_mutable__one(MFParamsBuilder &params,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ BLI_assert(this->is_one());
+ this->ensure_is_mutable__one(data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ params.add_single_mutable(GMutableSpan{data_type.single_type(), value_typed->data, 1});
+ break;
+ }
+ case ValueType::OneVector: {
+ params.add_vector_mutable(this->value_as<VariableValue_OneVector>()->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::Span:
+ case ValueType::GVVectorArray:
+ case ValueType::GVectorArray: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ void add_as_output__one(MFParamsBuilder &params,
+ IndexMask mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ BLI_assert(this->is_one());
+ this->ensure_is_mutable__one(data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(!value_typed->is_initialized);
+ params.add_uninitialized_single_output(
+ GMutableSpan{data_type.single_type(), value_typed->data, 1});
+ /* It becomes initialized when the multi-function is called. */
+ value_typed->is_initialized = true;
+ break;
+ }
+ case ValueType::OneVector: {
+ auto *value_typed = this->value_as<VariableValue_OneVector>();
+ BLI_assert(value_typed->data[0].is_empty());
+ params.add_vector_output(value_typed->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::Span:
+ case ValueType::GVVectorArray:
+ case ValueType::GVectorArray: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+
+ tot_initialized_ += mask.size();
+ }
+
+ void destruct(IndexMask mask,
+ IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ int new_tot_initialized = tot_initialized_ - mask.size();
+
+ /* Sanity check to make sure that enough indices can be destructed. */
+ BLI_assert(new_tot_initialized >= 0);
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ if (mask.size() == full_mask.size()) {
+ /* All elements are destructed. The elements are owned by the caller, so we don't
+ * actually destruct them. */
+ value_allocator.release_value(value_, data_type);
+ value_ = value_allocator.obtain_OneSingle(data_type.single_type());
+ }
+ else {
+ /* Not all elements are destructed. Since we can't work on the original array, we have to
+ * create a copy first. */
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+ BLI_assert(value_->type == ValueType::Span);
+ const CPPType &type = data_type.single_type();
+ type.destruct_indices(this->value_as<VariableValue_Span>()->data, mask);
+ }
+ break;
+ }
+ case ValueType::Span: {
+ const CPPType &type = data_type.single_type();
+ type.destruct_indices(this->value_as<VariableValue_Span>()->data, mask);
+ if (new_tot_initialized == 0) {
+ /* Release span when all values are initialized. */
+ value_allocator.release_value(value_, data_type);
+ value_ = value_allocator.obtain_OneSingle(data_type.single_type());
+ }
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ if (mask.size() == full_mask.size()) {
+ /* All elements are cleared. The elements are owned by the caller, so don't actually
+ * destruct them. */
+ value_allocator.release_value(value_, data_type);
+ value_ = value_allocator.obtain_OneVector(data_type.vector_base_type());
+ }
+ else {
+ /* Not all elements are cleared. Since we can't work on the original vector array, we
+ * have to create a copy first. A possible future optimization is to create the partial
+ * copy directly. */
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+ BLI_assert(value_->type == ValueType::GVectorArray);
+ this->value_as<VariableValue_GVectorArray>()->data.clear(mask);
+ }
+ break;
+ }
+ case ValueType::GVectorArray: {
+ this->value_as<VariableValue_GVectorArray>()->data.clear(mask);
+ break;
+ }
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ if (mask.size() == tot_initialized_) {
+ const CPPType &type = data_type.single_type();
+ type.destruct(value_typed->data);
+ value_typed->is_initialized = false;
+ }
+ break;
+ }
+ case ValueType::OneVector: {
+ auto *value_typed = this->value_as<VariableValue_OneVector>();
+ if (mask.size() == tot_initialized_) {
+ value_typed->data.clear({0});
+ }
+ break;
+ }
+ }
+
+ tot_initialized_ = new_tot_initialized;
+ }
+
+ void indices_split(IndexMask mask, IndicesSplitVectors &r_indices)
+ {
+ BLI_assert(mask.size() <= tot_initialized_);
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ const GVArray_Typed<bool> varray{this->value_as<VariableValue_GVArray>()->data};
+ for (const int i : mask) {
+ r_indices[varray[i]].append(i);
+ }
+ break;
+ }
+ case ValueType::Span: {
+ const Span<bool> span((bool *)this->value_as<VariableValue_Span>()->data,
+ mask.min_array_size());
+ for (const int i : mask) {
+ r_indices[span[i]].append(i);
+ }
+ break;
+ }
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ const bool condition = *(bool *)value_typed->data;
+ r_indices[condition].extend(mask);
+ break;
+ }
+ case ValueType::GVVectorArray:
+ case ValueType::GVectorArray:
+ case ValueType::OneVector: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ template<typename T> T *value_as()
+ {
+ BLI_assert(value_->type == T::static_type);
+ return static_cast<T *>(value_);
+ }
+
+ template<typename T> const T *value_as() const
+ {
+ BLI_assert(value_->type == T::static_type);
+ return static_cast<T *>(value_);
+ }
+};
+
+template<typename... Args> VariableState *ValueAllocator::obtain_variable_state(Args &&...args)
+{
+ return new VariableState(std::forward<Args>(args)...);
+}
+
+void ValueAllocator::release_variable_state(VariableState *state)
+{
+ delete state;
+}
+
+/** Keeps track of the states of all variables during evaluation. */
+class VariableStates {
+ private:
+ ValueAllocator value_allocator_;
+ Map<const MFVariable *, VariableState *> variable_states_;
+ IndexMask full_mask_;
+
+ public:
+ VariableStates(IndexMask full_mask) : full_mask_(full_mask)
+ {
+ }
+
+ ~VariableStates()
+ {
+ for (auto &&item : variable_states_.items()) {
+ const MFVariable *variable = item.key;
+ VariableState *state = item.value;
+ state->destruct_self(value_allocator_, variable->data_type());
+ }
+ }
+
+ ValueAllocator &value_allocator()
+ {
+ return value_allocator_;
+ }
+
+ const IndexMask &full_mask() const
+ {
+ return full_mask_;
+ }
+
+ void add_initial_variable_states(const MFProcedureExecutor &fn,
+ const MFProcedure &procedure,
+ MFParams &params)
+ {
+ for (const int param_index : fn.param_indices()) {
+ MFParamType param_type = fn.param_type(param_index);
+ const MFVariable *variable = procedure.params()[param_index].variable;
+
+ auto add_state = [&](VariableValue *value,
+ bool input_is_initialized,
+ void *caller_provided_storage = nullptr) {
+ const int tot_initialized = input_is_initialized ? full_mask_.size() : 0;
+ variable_states_.add_new(variable,
+ value_allocator_.obtain_variable_state(
+ *value, tot_initialized, caller_provided_storage));
+ };
+
+ switch (param_type.category()) {
+ case MFParamType::SingleInput: {
+ const GVArray &data = params.readonly_single_input(param_index);
+ add_state(value_allocator_.obtain_GVArray(data), true);
+ break;
+ }
+ case MFParamType::VectorInput: {
+ const GVVectorArray &data = params.readonly_vector_input(param_index);
+ add_state(value_allocator_.obtain_GVVectorArray(data), true);
+ break;
+ }
+ case MFParamType::SingleOutput: {
+ GMutableSpan data = params.uninitialized_single_output(param_index);
+ add_state(value_allocator_.obtain_Span_not_owned(data.data()), false, data.data());
+ break;
+ }
+ case MFParamType::VectorOutput: {
+ GVectorArray &data = params.vector_output(param_index);
+ add_state(value_allocator_.obtain_GVectorArray_not_owned(data), false, &data);
+ break;
+ }
+ case MFParamType::SingleMutable: {
+ GMutableSpan data = params.single_mutable(param_index);
+ add_state(value_allocator_.obtain_Span_not_owned(data.data()), true, data.data());
+ break;
+ }
+ case MFParamType::VectorMutable: {
+ GVectorArray &data = params.vector_mutable(param_index);
+ add_state(value_allocator_.obtain_GVectorArray_not_owned(data), true, &data);
+ break;
+ }
+ }
+ }
+ }
+
+ void add_as_param(VariableState &variable_state,
+ MFParamsBuilder &params,
+ const MFParamType &param_type,
+ const IndexMask &mask)
+ {
+ const MFDataType data_type = param_type.data_type();
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ variable_state.add_as_input(params, mask, data_type);
+ break;
+ }
+ case MFParamType::Mutable: {
+ variable_state.add_as_mutable(params, mask, full_mask_, data_type, value_allocator_);
+ break;
+ }
+ case MFParamType::Output: {
+ variable_state.add_as_output(params, mask, full_mask_, data_type, value_allocator_);
+ break;
+ }
+ }
+ }
+
+ void add_as_param__one(VariableState &variable_state,
+ MFParamsBuilder &params,
+ const MFParamType &param_type,
+ const IndexMask &mask)
+ {
+ const MFDataType data_type = param_type.data_type();
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ variable_state.add_as_input__one(params, data_type);
+ break;
+ }
+ case MFParamType::Mutable: {
+ variable_state.add_as_mutable__one(params, data_type, value_allocator_);
+ break;
+ }
+ case MFParamType::Output: {
+ variable_state.add_as_output__one(params, mask, data_type, value_allocator_);
+ break;
+ }
+ }
+ }
+
+ void destruct(const MFVariable &variable, const IndexMask &mask)
+ {
+ VariableState &variable_state = this->get_variable_state(variable);
+ variable_state.destruct(mask, full_mask_, variable.data_type(), value_allocator_);
+ }
+
+ VariableState &get_variable_state(const MFVariable &variable)
+ {
+ return *variable_states_.lookup_or_add_cb(
+ &variable, [&]() { return this->create_new_state_for_variable(variable); });
+ }
+
+ VariableState *create_new_state_for_variable(const MFVariable &variable)
+ {
+ MFDataType data_type = variable.data_type();
+ switch (data_type.category()) {
+ case MFDataType::Single: {
+ const CPPType &type = data_type.single_type();
+ return value_allocator_.obtain_variable_state(*value_allocator_.obtain_OneSingle(type), 0);
+ }
+ case MFDataType::Vector: {
+ const CPPType &type = data_type.vector_base_type();
+ return value_allocator_.obtain_variable_state(*value_allocator_.obtain_OneVector(type), 0);
+ }
+ }
+ BLI_assert_unreachable();
+ return nullptr;
+ }
+};
+
+static bool evaluate_as_one(const MultiFunction &fn,
+ Span<VariableState *> param_variable_states,
+ const IndexMask &mask,
+ const IndexMask &full_mask)
+{
+ if (fn.depends_on_context()) {
+ return false;
+ }
+ if (mask.size() < full_mask.size()) {
+ return false;
+ }
+ for (VariableState *state : param_variable_states) {
+ if (!state->is_one()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void execute_call_instruction(const MFCallInstruction &instruction,
+ IndexMask mask,
+ VariableStates &variable_states,
+ const MFContext &context)
+{
+ const MultiFunction &fn = instruction.fn();
+
+ Vector<VariableState *> param_variable_states;
+ param_variable_states.resize(fn.param_amount());
+
+ for (const int param_index : fn.param_indices()) {
+ const MFVariable *variable = instruction.params()[param_index];
+ VariableState &variable_state = variable_states.get_variable_state(*variable);
+ param_variable_states[param_index] = &variable_state;
+ }
+
+ /* If all inputs to the function are constant, it's enough to call the function only once instead
+ * of for every index. */
+ if (evaluate_as_one(fn, param_variable_states, mask, variable_states.full_mask())) {
+ MFParamsBuilder params(fn, 1);
+
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ VariableState &variable_state = *param_variable_states[param_index];
+ variable_states.add_as_param__one(variable_state, params, param_type, mask);
+ }
+
+ fn.call(IndexRange(1), params, context);
+ }
+ else {
+ MFParamsBuilder params(fn, mask.min_array_size());
+
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ VariableState &variable_state = *param_variable_states[param_index];
+ variable_states.add_as_param(variable_state, params, param_type, mask);
+ }
+
+ fn.call(mask, params, context);
+ }
+}
+
+/** An index mask, that might own the indices if necessary. */
+struct InstructionIndices {
+ bool is_owned;
+ Vector<int64_t> owned_indices;
+ IndexMask referenced_indices;
+
+ IndexMask mask() const
+ {
+ if (this->is_owned) {
+ return this->owned_indices.as_span();
+ }
+ return this->referenced_indices;
+ }
+};
+
+/** Contains information about the next instruction that should be executed. */
+struct NextInstructionInfo {
+ const MFInstruction *instruction = nullptr;
+ InstructionIndices indices;
+
+ IndexMask mask() const
+ {
+ return this->indices.mask();
+ }
+
+ operator bool() const
+ {
+ return this->instruction != nullptr;
+ }
+};
+
+/**
+ * Keeps track of the next instruction for all indices and decides in which order instructions are
+ * evaluated.
+ */
+class InstructionScheduler {
+ private:
+ Map<const MFInstruction *, Vector<InstructionIndices>> indices_by_instruction_;
+
+ public:
+ InstructionScheduler() = default;
+
+ void add_referenced_indices(const MFInstruction &instruction, IndexMask mask)
+ {
+ if (mask.is_empty()) {
+ return;
+ }
+ InstructionIndices new_indices;
+ new_indices.is_owned = false;
+ new_indices.referenced_indices = mask;
+ indices_by_instruction_.lookup_or_add_default(&instruction).append(std::move(new_indices));
+ }
+
+ void add_owned_indices(const MFInstruction &instruction, Vector<int64_t> indices)
+ {
+ if (indices.is_empty()) {
+ return;
+ }
+ BLI_assert(IndexMask::indices_are_valid_index_mask(indices));
+
+ InstructionIndices new_indices;
+ new_indices.is_owned = true;
+ new_indices.owned_indices = std::move(indices);
+ indices_by_instruction_.lookup_or_add_default(&instruction).append(std::move(new_indices));
+ }
+
+ void add_previous_instruction_indices(const MFInstruction &instruction,
+ NextInstructionInfo &instr_info)
+ {
+ indices_by_instruction_.lookup_or_add_default(&instruction)
+ .append(std::move(instr_info.indices));
+ }
+
+ NextInstructionInfo pop_next()
+ {
+ if (indices_by_instruction_.is_empty()) {
+ return {};
+ }
+ /* TODO: Implement better mechanism to determine next instruction. */
+ const MFInstruction *instruction = *indices_by_instruction_.keys().begin();
+
+ NextInstructionInfo next_instruction_info;
+ next_instruction_info.instruction = instruction;
+ next_instruction_info.indices = this->pop_indices_array(instruction);
+ return next_instruction_info;
+ }
+
+ private:
+ InstructionIndices pop_indices_array(const MFInstruction *instruction)
+ {
+ Vector<InstructionIndices> *indices = indices_by_instruction_.lookup_ptr(instruction);
+ if (indices == nullptr) {
+ return {};
+ }
+ InstructionIndices r_indices = (*indices).pop_last();
+ BLI_assert(!r_indices.mask().is_empty());
+ if (indices->is_empty()) {
+ indices_by_instruction_.remove_contained(instruction);
+ }
+ return r_indices;
+ }
+};
+
+void MFProcedureExecutor::call(IndexMask full_mask, MFParams params, MFContext context) const
+{
+ BLI_assert(procedure_.validate());
+
+ LinearAllocator<> allocator;
+
+ VariableStates variable_states{full_mask};
+ variable_states.add_initial_variable_states(*this, procedure_, params);
+
+ InstructionScheduler scheduler;
+ scheduler.add_referenced_indices(*procedure_.entry(), full_mask);
+
+ /* Loop until all indices got to a return instruction. */
+ while (NextInstructionInfo instr_info = scheduler.pop_next()) {
+ const MFInstruction &instruction = *instr_info.instruction;
+ switch (instruction.type()) {
+ case MFInstructionType::Call: {
+ const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>(
+ instruction);
+ execute_call_instruction(call_instruction, instr_info.mask(), variable_states, context);
+ scheduler.add_previous_instruction_indices(*call_instruction.next(), instr_info);
+ break;
+ }
+ case MFInstructionType::Branch: {
+ const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>(
+ instruction);
+ const MFVariable *condition_var = branch_instruction.condition();
+ VariableState &variable_state = variable_states.get_variable_state(*condition_var);
+
+ IndicesSplitVectors new_indices;
+ variable_state.indices_split(instr_info.mask(), new_indices);
+ scheduler.add_owned_indices(*branch_instruction.branch_false(), new_indices[false]);
+ scheduler.add_owned_indices(*branch_instruction.branch_true(), new_indices[true]);
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ const MFDestructInstruction &destruct_instruction =
+ static_cast<const MFDestructInstruction &>(instruction);
+ const MFVariable *variable = destruct_instruction.variable();
+ variable_states.destruct(*variable, instr_info.mask());
+ scheduler.add_previous_instruction_indices(*destruct_instruction.next(), instr_info);
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ const MFDummyInstruction &dummy_instruction = static_cast<const MFDummyInstruction &>(
+ instruction);
+ scheduler.add_previous_instruction_indices(*dummy_instruction.next(), instr_info);
+ break;
+ }
+ case MFInstructionType::Return: {
+ /* Don't insert the indices back into the scheduler. */
+ break;
+ }
+ }
+ }
+
+ for (const int param_index : this->param_indices()) {
+ const MFParamType param_type = this->param_type(param_index);
+ const MFVariable *variable = procedure_.params()[param_index].variable;
+ VariableState &variable_state = variable_states.get_variable_state(*variable);
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ /* Input variables must be destructed in the end. */
+ BLI_assert(variable_state.is_fully_uninitialized(full_mask));
+ break;
+ }
+ case MFParamType::Mutable:
+ case MFParamType::Output: {
+ /* Mutable and output variables must be initialized in the end. */
+ BLI_assert(variable_state.is_fully_initialized(full_mask));
+ /* Make sure that the data is in the memory provided by the caller. */
+ variable_state.ensure_is_mutable(
+ full_mask, param_type.data_type(), variable_states.value_allocator());
+ break;
+ }
+ }
+ }
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/tests/FN_field_test.cc b/source/blender/functions/tests/FN_field_test.cc
new file mode 100644
index 00000000000..212b79e75d3
--- /dev/null
+++ b/source/blender/functions/tests/FN_field_test.cc
@@ -0,0 +1,278 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "FN_cpp_type.hh"
+#include "FN_field.hh"
+#include "FN_multi_function_builder.hh"
+
+namespace blender::fn::tests {
+
+TEST(field, ConstantFunction)
+{
+ /* TODO: Figure out how to not use another "FieldOperation(" inside of std::make_shared. */
+ GField constant_field{std::make_shared<FieldOperation>(
+ FieldOperation(std::make_unique<CustomMF_Constant<int>>(10), {})),
+ 0};
+
+ Array<int> result(4);
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, 4};
+ evaluator.add_with_destination(constant_field, result.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result[0], 10);
+ EXPECT_EQ(result[1], 10);
+ EXPECT_EQ(result[2], 10);
+ EXPECT_EQ(result[3], 10);
+}
+
+class IndexFieldInput final : public FieldInput {
+ public:
+ IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index")
+ {
+ }
+
+ const GVArray *get_varray_for_context(const FieldContext &UNUSED(context),
+ IndexMask mask,
+ ResourceScope &scope) const final
+ {
+ auto index_func = [](int i) { return i; };
+ return &scope.construct<
+ GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>(
+ __func__, mask.min_array_size(), mask.min_array_size(), index_func);
+ }
+};
+
+TEST(field, VArrayInput)
+{
+ GField index_field{std::make_shared<IndexFieldInput>()};
+
+ Array<int> result_1(4);
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, 4};
+ evaluator.add_with_destination(index_field, result_1.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result_1[0], 0);
+ EXPECT_EQ(result_1[1], 1);
+ EXPECT_EQ(result_1[2], 2);
+ EXPECT_EQ(result_1[3], 3);
+
+ /* Evaluate a second time, just to test that the first didn't break anything. */
+ Array<int> result_2(10);
+
+ const Array<int64_t> indices = {2, 4, 6, 8};
+ const IndexMask mask{indices};
+
+ FieldEvaluator evaluator_2{context, &mask};
+ evaluator_2.add_with_destination(index_field, result_2.as_mutable_span());
+ evaluator_2.evaluate();
+ EXPECT_EQ(result_2[2], 2);
+ EXPECT_EQ(result_2[4], 4);
+ EXPECT_EQ(result_2[6], 6);
+ EXPECT_EQ(result_2[8], 8);
+}
+
+TEST(field, VArrayInputMultipleOutputs)
+{
+ std::shared_ptr<FieldInput> index_input = std::make_shared<IndexFieldInput>();
+ GField field_1{index_input};
+ GField field_2{index_input};
+
+ Array<int> result_1(10);
+ Array<int> result_2(10);
+
+ const Array<int64_t> indices = {2, 4, 6, 8};
+ const IndexMask mask{indices};
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, &mask};
+ evaluator.add_with_destination(field_1, result_1.as_mutable_span());
+ evaluator.add_with_destination(field_2, result_2.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result_1[2], 2);
+ EXPECT_EQ(result_1[4], 4);
+ EXPECT_EQ(result_1[6], 6);
+ EXPECT_EQ(result_1[8], 8);
+ EXPECT_EQ(result_2[2], 2);
+ EXPECT_EQ(result_2[4], 4);
+ EXPECT_EQ(result_2[6], 6);
+ EXPECT_EQ(result_2[8], 8);
+}
+
+TEST(field, InputAndFunction)
+{
+ GField index_field{std::make_shared<IndexFieldInput>()};
+
+ std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>(
+ "add", [](int a, int b) { return a + b; });
+ GField output_field{std::make_shared<FieldOperation>(
+ FieldOperation(std::move(add_fn), {index_field, index_field})),
+ 0};
+
+ Array<int> result(10);
+
+ const Array<int64_t> indices = {2, 4, 6, 8};
+ const IndexMask mask{indices};
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, &mask};
+ evaluator.add_with_destination(output_field, result.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result[2], 4);
+ EXPECT_EQ(result[4], 8);
+ EXPECT_EQ(result[6], 12);
+ EXPECT_EQ(result[8], 16);
+}
+
+TEST(field, TwoFunctions)
+{
+ GField index_field{std::make_shared<IndexFieldInput>()};
+
+ std::unique_ptr<MultiFunction> add_fn = std::make_unique<CustomMF_SI_SI_SO<int, int, int>>(
+ "add", [](int a, int b) { return a + b; });
+ GField add_field{std::make_shared<FieldOperation>(
+ FieldOperation(std::move(add_fn), {index_field, index_field})),
+ 0};
+
+ std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>(
+ "add_10", [](int a) { return a + 10; });
+ GField result_field{
+ std::make_shared<FieldOperation>(FieldOperation(std::move(add_10_fn), {add_field})), 0};
+
+ Array<int> result(10);
+
+ const Array<int64_t> indices = {2, 4, 6, 8};
+ const IndexMask mask{indices};
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, &mask};
+ evaluator.add_with_destination(result_field, result.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result[2], 14);
+ EXPECT_EQ(result[4], 18);
+ EXPECT_EQ(result[6], 22);
+ EXPECT_EQ(result[8], 26);
+}
+
+class TwoOutputFunction : public MultiFunction {
+ private:
+ MFSignature signature_;
+
+ public:
+ TwoOutputFunction(StringRef name)
+ {
+ MFSignatureBuilder signature{name};
+ signature.single_input<int>("In1");
+ signature.single_input<int>("In2");
+ signature.single_output<int>("Add");
+ signature.single_output<int>("Add10");
+ signature_ = signature.build();
+ this->set_signature(&signature_);
+ }
+
+ void call(IndexMask mask, MFParams params, MFContext UNUSED(context)) const override
+ {
+ const VArray<int> &in1 = params.readonly_single_input<int>(0, "In1");
+ const VArray<int> &in2 = params.readonly_single_input<int>(1, "In2");
+ MutableSpan<int> add = params.uninitialized_single_output<int>(2, "Add");
+ MutableSpan<int> add_10 = params.uninitialized_single_output<int>(3, "Add10");
+ mask.foreach_index([&](const int64_t i) {
+ add[i] = in1[i] + in2[i];
+ add_10[i] = add[i] + 10;
+ });
+ }
+};
+
+TEST(field, FunctionTwoOutputs)
+{
+ /* Also use two separate input fields, why not. */
+ GField index_field_1{std::make_shared<IndexFieldInput>()};
+ GField index_field_2{std::make_shared<IndexFieldInput>()};
+
+ std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(FieldOperation(
+ std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field_1, index_field_2}));
+
+ GField result_field_1{fn, 0};
+ GField result_field_2{fn, 1};
+
+ Array<int> result_1(10);
+ Array<int> result_2(10);
+
+ const Array<int64_t> indices = {2, 4, 6, 8};
+ const IndexMask mask{indices};
+
+ FieldContext context;
+ FieldEvaluator evaluator{context, &mask};
+ evaluator.add_with_destination(result_field_1, result_1.as_mutable_span());
+ evaluator.add_with_destination(result_field_2, result_2.as_mutable_span());
+ evaluator.evaluate();
+ EXPECT_EQ(result_1[2], 4);
+ EXPECT_EQ(result_1[4], 8);
+ EXPECT_EQ(result_1[6], 12);
+ EXPECT_EQ(result_1[8], 16);
+ EXPECT_EQ(result_2[2], 14);
+ EXPECT_EQ(result_2[4], 18);
+ EXPECT_EQ(result_2[6], 22);
+ EXPECT_EQ(result_2[8], 26);
+}
+
+TEST(field, TwoFunctionsTwoOutputs)
+{
+ GField index_field{std::make_shared<IndexFieldInput>()};
+
+ std::shared_ptr<FieldOperation> fn = std::make_shared<FieldOperation>(FieldOperation(
+ std::make_unique<TwoOutputFunction>("SI_SI_SO_SO"), {index_field, index_field}));
+
+ Array<int64_t> mask_indices = {2, 4, 6, 8};
+ IndexMask mask = mask_indices.as_span();
+
+ Field<int> result_field_1{fn, 0};
+ Field<int> intermediate_field{fn, 1};
+
+ std::unique_ptr<MultiFunction> add_10_fn = std::make_unique<CustomMF_SI_SO<int, int>>(
+ "add_10", [](int a) { return a + 10; });
+ Field<int> result_field_2{
+ std::make_shared<FieldOperation>(FieldOperation(std::move(add_10_fn), {intermediate_field})),
+ 0};
+
+ FieldContext field_context;
+ FieldEvaluator field_evaluator{field_context, &mask};
+ const VArray<int> *result_1 = nullptr;
+ const VArray<int> *result_2 = nullptr;
+ field_evaluator.add(result_field_1, &result_1);
+ field_evaluator.add(result_field_2, &result_2);
+ field_evaluator.evaluate();
+
+ EXPECT_EQ(result_1->get(2), 4);
+ EXPECT_EQ(result_1->get(4), 8);
+ EXPECT_EQ(result_1->get(6), 12);
+ EXPECT_EQ(result_1->get(8), 16);
+ EXPECT_EQ(result_2->get(2), 24);
+ EXPECT_EQ(result_2->get(4), 28);
+ EXPECT_EQ(result_2->get(6), 32);
+ EXPECT_EQ(result_2->get(8), 36);
+}
+
+TEST(field, SameFieldTwice)
+{
+ GField constant_field{
+ std::make_shared<FieldOperation>(std::make_unique<CustomMF_Constant<int>>(10)), 0};
+
+ FieldContext field_context;
+ IndexMask mask{IndexRange(2)};
+ ResourceScope scope;
+ Vector<const GVArray *> results = evaluate_fields(
+ scope, {constant_field, constant_field}, mask, field_context);
+
+ GVArray_Typed<int> varray1{*results[0]};
+ GVArray_Typed<int> varray2{*results[1]};
+
+ EXPECT_EQ(varray1->get(0), 10);
+ EXPECT_EQ(varray1->get(1), 10);
+ EXPECT_EQ(varray2->get(0), 10);
+ EXPECT_EQ(varray2->get(1), 10);
+}
+
+} // namespace blender::fn::tests
diff --git a/source/blender/functions/tests/FN_multi_function_procedure_test.cc b/source/blender/functions/tests/FN_multi_function_procedure_test.cc
new file mode 100644
index 00000000000..0b4b88fba3d
--- /dev/null
+++ b/source/blender/functions/tests/FN_multi_function_procedure_test.cc
@@ -0,0 +1,344 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "FN_multi_function_builder.hh"
+#include "FN_multi_function_procedure_builder.hh"
+#include "FN_multi_function_procedure_executor.hh"
+#include "FN_multi_function_test_common.hh"
+
+namespace blender::fn::tests {
+
+TEST(multi_function_procedure, SimpleTest)
+{
+ /**
+ * procedure(int var1, int var2, int *var4) {
+ * int var3 = var1 + var2;
+ * var4 = var2 + var3;
+ * var4 += 10;
+ * }
+ */
+
+ CustomMF_SI_SI_SO<int, int, int> add_fn{"add", [](int a, int b) { return a + b; }};
+ CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var1 = &builder.add_single_input_parameter<int>();
+ MFVariable *var2 = &builder.add_single_input_parameter<int>();
+ auto [var3] = builder.add_call<1>(add_fn, {var1, var2});
+ auto [var4] = builder.add_call<1>(add_fn, {var2, var3});
+ builder.add_call(add_10_fn, {var4});
+ builder.add_destruct({var1, var2, var3});
+ builder.add_return();
+ builder.add_output_parameter(*var4);
+
+ EXPECT_TRUE(procedure.validate());
+
+ MFProcedureExecutor executor{"My Procedure", procedure};
+
+ MFParamsBuilder params{executor, 3};
+ MFContextBuilder context;
+
+ Array<int> input_array = {1, 2, 3};
+ params.add_readonly_single_input(input_array.as_span());
+ params.add_readonly_single_input_value(3);
+
+ Array<int> output_array(3);
+ params.add_uninitialized_single_output(output_array.as_mutable_span());
+
+ executor.call(IndexRange(3), params, context);
+
+ EXPECT_EQ(output_array[0], 17);
+ EXPECT_EQ(output_array[1], 18);
+ EXPECT_EQ(output_array[2], 19);
+}
+
+TEST(multi_function_procedure, BranchTest)
+{
+ /**
+ * procedure(int &var1, bool var2) {
+ * if (var2) {
+ * var1 += 100;
+ * }
+ * else {
+ * var1 += 10;
+ * }
+ * var1 += 10;
+ * }
+ */
+
+ CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
+ CustomMF_SM<int> add_100_fn{"add_100", [](int &a) { a += 100; }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var1 = &builder.add_single_mutable_parameter<int>();
+ MFVariable *var2 = &builder.add_single_input_parameter<bool>();
+
+ MFProcedureBuilder::Branch branch = builder.add_branch(*var2);
+ branch.branch_false.add_call(add_10_fn, {var1});
+ branch.branch_true.add_call(add_100_fn, {var1});
+ builder.set_cursor_after_branch(branch);
+ builder.add_call(add_10_fn, {var1});
+ builder.add_destruct({var2});
+ builder.add_return();
+
+ EXPECT_TRUE(procedure.validate());
+
+ MFProcedureExecutor procedure_fn{"Condition Test", procedure};
+ MFParamsBuilder params(procedure_fn, 5);
+
+ Array<int> values_a = {1, 5, 3, 6, 2};
+ Array<bool> values_cond = {true, false, true, true, false};
+
+ params.add_single_mutable(values_a.as_mutable_span());
+ params.add_readonly_single_input(values_cond.as_span());
+
+ MFContextBuilder context;
+ procedure_fn.call({1, 2, 3, 4}, params, context);
+
+ EXPECT_EQ(values_a[0], 1);
+ EXPECT_EQ(values_a[1], 25);
+ EXPECT_EQ(values_a[2], 113);
+ EXPECT_EQ(values_a[3], 116);
+ EXPECT_EQ(values_a[4], 22);
+}
+
+TEST(multi_function_procedure, EvaluateOne)
+{
+ /**
+ * procedure(int var1, int *var2) {
+ * var2 = var1 + 10;
+ * }
+ */
+
+ int tot_evaluations = 0;
+ CustomMF_SI_SO<int, int> add_10_fn{"add_10", [&](int a) {
+ tot_evaluations++;
+ return a + 10;
+ }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var1 = &builder.add_single_input_parameter<int>();
+ auto [var2] = builder.add_call<1>(add_10_fn, {var1});
+ builder.add_destruct(*var1);
+ builder.add_return();
+ builder.add_output_parameter(*var2);
+
+ MFProcedureExecutor procedure_fn{"Evaluate One", procedure};
+ MFParamsBuilder params{procedure_fn, 5};
+
+ Array<int> values_out = {1, 2, 3, 4, 5};
+ params.add_readonly_single_input_value(1);
+ params.add_uninitialized_single_output(values_out.as_mutable_span());
+
+ MFContextBuilder context;
+ procedure_fn.call({0, 1, 3, 4}, params, context);
+
+ EXPECT_EQ(values_out[0], 11);
+ EXPECT_EQ(values_out[1], 11);
+ EXPECT_EQ(values_out[2], 3);
+ EXPECT_EQ(values_out[3], 11);
+ EXPECT_EQ(values_out[4], 11);
+ /* We expect only one evaluation, because the input is constant. */
+ EXPECT_EQ(tot_evaluations, 1);
+}
+
+TEST(multi_function_procedure, SimpleLoop)
+{
+ /**
+ * procedure(int count, int *out) {
+ * out = 1;
+ * int index = 0'
+ * loop {
+ * if (index >= count) {
+ * break;
+ * }
+ * out *= 2;
+ * index += 1;
+ * }
+ * out += 1000;
+ * }
+ */
+
+ CustomMF_Constant<int> const_1_fn{1};
+ CustomMF_Constant<int> const_0_fn{0};
+ CustomMF_SI_SI_SO<int, int, bool> greater_or_equal_fn{"greater or equal",
+ [](int a, int b) { return a >= b; }};
+ CustomMF_SM<int> double_fn{"double", [](int &a) { a *= 2; }};
+ CustomMF_SM<int> add_1000_fn{"add 1000", [](int &a) { a += 1000; }};
+ CustomMF_SM<int> add_1_fn{"add 1", [](int &a) { a += 1; }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var_count = &builder.add_single_input_parameter<int>("count");
+ auto [var_out] = builder.add_call<1>(const_1_fn);
+ var_out->set_name("out");
+ auto [var_index] = builder.add_call<1>(const_0_fn);
+ var_index->set_name("index");
+
+ MFProcedureBuilder::Loop loop = builder.add_loop();
+ auto [var_condition] = builder.add_call<1>(greater_or_equal_fn, {var_index, var_count});
+ var_condition->set_name("condition");
+ MFProcedureBuilder::Branch branch = builder.add_branch(*var_condition);
+ branch.branch_true.add_destruct(*var_condition);
+ branch.branch_true.add_loop_break(loop);
+ branch.branch_false.add_destruct(*var_condition);
+ builder.set_cursor_after_branch(branch);
+ builder.add_call(double_fn, {var_out});
+ builder.add_call(add_1_fn, {var_index});
+ builder.add_loop_continue(loop);
+ builder.set_cursor_after_loop(loop);
+ builder.add_call(add_1000_fn, {var_out});
+ builder.add_destruct({var_count, var_index});
+ builder.add_return();
+ builder.add_output_parameter(*var_out);
+
+ EXPECT_TRUE(procedure.validate());
+
+ MFProcedureExecutor procedure_fn{"Simple Loop", procedure};
+ MFParamsBuilder params{procedure_fn, 5};
+
+ Array<int> counts = {4, 3, 7, 6, 4};
+ Array<int> results(5, -1);
+
+ params.add_readonly_single_input(counts.as_span());
+ params.add_uninitialized_single_output(results.as_mutable_span());
+
+ MFContextBuilder context;
+ procedure_fn.call({0, 1, 3, 4}, params, context);
+
+ EXPECT_EQ(results[0], 1016);
+ EXPECT_EQ(results[1], 1008);
+ EXPECT_EQ(results[2], -1);
+ EXPECT_EQ(results[3], 1064);
+ EXPECT_EQ(results[4], 1016);
+}
+
+TEST(multi_function_procedure, Vectors)
+{
+ /**
+ * procedure(vector<int> v1, vector<int> &v2, vector<int> *v3) {
+ * v1.extend(v2);
+ * int constant = 5;
+ * v2.append(constant);
+ * v2.extend(v1);
+ * int len = sum(v2);
+ * v3 = range(len);
+ * }
+ */
+
+ CreateRangeFunction create_range_fn;
+ ConcatVectorsFunction extend_fn;
+ GenericAppendFunction append_fn{CPPType::get<int>()};
+ SumVectorFunction sum_elements_fn;
+ CustomMF_Constant<int> constant_5_fn{5};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var_v1 = &builder.add_input_parameter(MFDataType::ForVector<int>());
+ MFVariable *var_v2 = &builder.add_parameter(MFParamType::ForMutableVector(CPPType::get<int>()));
+ builder.add_call(extend_fn, {var_v1, var_v2});
+ auto [var_constant] = builder.add_call<1>(constant_5_fn);
+ builder.add_call(append_fn, {var_v2, var_constant});
+ builder.add_destruct(*var_constant);
+ builder.add_call(extend_fn, {var_v2, var_v1});
+ auto [var_len] = builder.add_call<1>(sum_elements_fn, {var_v2});
+ auto [var_v3] = builder.add_call<1>(create_range_fn, {var_len});
+ builder.add_destruct({var_v1, var_len});
+ builder.add_return();
+ builder.add_output_parameter(*var_v3);
+
+ EXPECT_TRUE(procedure.validate());
+
+ MFProcedureExecutor procedure_fn{"Vectors", procedure};
+ MFParamsBuilder params{procedure_fn, 5};
+
+ Array<int> v1 = {5, 2, 3};
+ GVectorArray v2{CPPType::get<int>(), 5};
+ GVectorArray v3{CPPType::get<int>(), 5};
+
+ int value_10 = 10;
+ v2.append(0, &value_10);
+ v2.append(4, &value_10);
+
+ params.add_readonly_vector_input(v1.as_span());
+ params.add_vector_mutable(v2);
+ params.add_vector_output(v3);
+
+ MFContextBuilder context;
+ procedure_fn.call({0, 1, 3, 4}, params, context);
+
+ EXPECT_EQ(v2[0].size(), 6);
+ EXPECT_EQ(v2[1].size(), 4);
+ EXPECT_EQ(v2[2].size(), 0);
+ EXPECT_EQ(v2[3].size(), 4);
+ EXPECT_EQ(v2[4].size(), 6);
+
+ EXPECT_EQ(v3[0].size(), 35);
+ EXPECT_EQ(v3[1].size(), 15);
+ EXPECT_EQ(v3[2].size(), 0);
+ EXPECT_EQ(v3[3].size(), 15);
+ EXPECT_EQ(v3[4].size(), 35);
+}
+
+TEST(multi_function_procedure, BufferReuse)
+{
+ /**
+ * procedure(int a, int *out) {
+ * int b = a + 10;
+ * int c = c + 10;
+ * int d = d + 10;
+ * int e = d + 10;
+ * out = e + 10;
+ * }
+ */
+
+ CustomMF_SI_SO<int, int> add_10_fn{"add 10", [](int a) { return a + 10; }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var_a = &builder.add_single_input_parameter<int>();
+ auto [var_b] = builder.add_call<1>(add_10_fn, {var_a});
+ builder.add_destruct(*var_a);
+ auto [var_c] = builder.add_call<1>(add_10_fn, {var_b});
+ builder.add_destruct(*var_b);
+ auto [var_d] = builder.add_call<1>(add_10_fn, {var_c});
+ builder.add_destruct(*var_c);
+ auto [var_e] = builder.add_call<1>(add_10_fn, {var_d});
+ builder.add_destruct(*var_d);
+ auto [var_out] = builder.add_call<1>(add_10_fn, {var_e});
+ builder.add_destruct(*var_e);
+ builder.add_return();
+ builder.add_output_parameter(*var_out);
+
+ EXPECT_TRUE(procedure.validate());
+
+ MFProcedureExecutor procedure_fn{"Buffer Reuse", procedure};
+
+ Array<int> inputs = {4, 1, 6, 2, 3};
+ Array<int> results(5, -1);
+
+ MFParamsBuilder params{procedure_fn, 5};
+ params.add_readonly_single_input(inputs.as_span());
+ params.add_uninitialized_single_output(results.as_mutable_span());
+
+ MFContextBuilder context;
+ procedure_fn.call({0, 2, 3, 4}, params, context);
+
+ EXPECT_EQ(results[0], 54);
+ EXPECT_EQ(results[1], -1);
+ EXPECT_EQ(results[2], 56);
+ EXPECT_EQ(results[3], 52);
+ EXPECT_EQ(results[4], 53);
+}
+
+} // namespace blender::fn::tests
diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h
index e5282409329..6acea8da15f 100644
--- a/source/blender/makesdna/DNA_customdata_types.h
+++ b/source/blender/makesdna/DNA_customdata_types.h
@@ -31,6 +31,8 @@
extern "C" {
#endif
+struct AnonymousAttributeID;
+
/** Descriptor and storage for a custom data layer. */
typedef struct CustomDataLayer {
/** Type of data in layer. */
@@ -53,6 +55,13 @@ typedef struct CustomDataLayer {
char name[64];
/** 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.
+ */
+ const struct AnonymousAttributeID *anonymous_id;
} CustomDataLayer;
#define MAX_CUSTOMDATA_LAYER_NAME 64
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index fd67150811d..82eabf6995c 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -1441,6 +1441,13 @@ typedef struct NodeGeometryCurveFill {
uint8_t mode;
} NodeGeometryCurveFill;
+typedef struct NodeGeometryAttributeCapture {
+ /* CustomDataType. */
+ int8_t data_type;
+ /* AttributeDomain. */
+ int8_t domain;
+} NodeGeometryAttributeCapture;
+
/* script node mode */
#define NODE_SCRIPT_INTERNAL 0
#define NODE_SCRIPT_EXTERNAL 1
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index a7c0372f309..aa9fb410a14 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -10284,6 +10284,26 @@ static void def_geo_curve_fill(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
+static void def_geo_attribute_capture(StructRNA *srna)
+{
+ PropertyRNA *prop;
+
+ RNA_def_struct_sdna_from(srna, "NodeGeometryAttributeCapture", "storage");
+
+ prop = RNA_def_property(srna, "data_type", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, rna_enum_attribute_type_items);
+ RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_GeometryNodeAttributeFill_type_itemf");
+ RNA_def_property_enum_default(prop, CD_PROP_FLOAT);
+ RNA_def_property_ui_text(prop, "Data Type", "Type of data stored in attribute");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_GeometryNode_socket_update");
+
+ prop = RNA_def_property(srna, "domain", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, rna_enum_attribute_domain_items);
+ RNA_def_property_enum_default(prop, ATTR_DOMAIN_POINT);
+ RNA_def_property_ui_text(prop, "Domain", "");
+ RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+}
+
/* -------------------------------------------------------------------------- */
static void rna_def_shader_node(BlenderRNA *brna)
diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc
index 9d8630b21e7..b2e2e97a961 100644
--- a/source/blender/modifiers/intern/MOD_nodes.cc
+++ b/source/blender/modifiers/intern/MOD_nodes.cc
@@ -85,6 +85,7 @@
#include "NOD_geometry.h"
#include "NOD_geometry_nodes_eval_log.hh"
+#include "FN_field.hh"
#include "FN_multi_function.hh"
using blender::destruct_ptr;
@@ -410,24 +411,30 @@ static void init_socket_cpp_value_from_property(const IDProperty &property,
{
switch (socket_value_type) {
case SOCK_FLOAT: {
+ float value = 0.0f;
if (property.type == IDP_FLOAT) {
- *(float *)r_value = IDP_Float(&property);
+ value = IDP_Float(&property);
}
else if (property.type == IDP_DOUBLE) {
- *(float *)r_value = (float)IDP_Double(&property);
+ value = (float)IDP_Double(&property);
}
+ new (r_value) blender::fn::Field<float>(blender::fn::make_constant_field(value));
break;
}
case SOCK_INT: {
- *(int *)r_value = IDP_Int(&property);
+ int value = IDP_Int(&property);
+ new (r_value) blender::fn::Field<int>(blender::fn::make_constant_field(value));
break;
}
case SOCK_VECTOR: {
- copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property));
+ float3 value;
+ copy_v3_v3(value, (const float *)IDP_Array(&property));
+ new (r_value) blender::fn::Field<float3>(blender::fn::make_constant_field(value));
break;
}
case SOCK_BOOLEAN: {
- *(bool *)r_value = IDP_Int(&property) != 0;
+ bool value = IDP_Int(&property) != 0;
+ new (r_value) blender::fn::Field<bool>(blender::fn::make_constant_field(value));
break;
}
case SOCK_STRING: {
diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
index 5646e37707c..4f21924619b 100644
--- a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
+++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc
@@ -21,6 +21,8 @@
#include "DEG_depsgraph_query.h"
+#include "FN_field.hh"
+#include "FN_field_cpp_type.hh"
#include "FN_generic_value_map.hh"
#include "FN_multi_function.hh"
@@ -33,6 +35,9 @@
namespace blender::modifiers::geometry_nodes {
using fn::CPPType;
+using fn::Field;
+using fn::FieldCPPType;
+using fn::GField;
using fn::GValueMap;
using nodes::GeoNodeExecParams;
using namespace fn::multi_function_types;
@@ -858,11 +863,10 @@ class GeometryNodesEvaluator {
const MultiFunction &fn,
NodeState &node_state)
{
- MFContextBuilder fn_context;
- MFParamsBuilder fn_params{fn, 1};
LinearAllocator<> &allocator = local_allocators_.local();
/* Prepare the inputs for the multi function. */
+ Vector<GField> input_fields;
for (const int i : node->inputs().index_range()) {
const InputSocketRef &socket_ref = node->input(i);
if (!socket_ref.is_available()) {
@@ -873,24 +877,12 @@ class GeometryNodesEvaluator {
BLI_assert(input_state.was_ready_for_execution);
SingleInputValue &single_value = *input_state.value.single;
BLI_assert(single_value.value != nullptr);
- fn_params.add_readonly_single_input(GPointer{*input_state.type, single_value.value});
- }
- /* Prepare the outputs for the multi function. */
- Vector<GMutablePointer> outputs;
- for (const int i : node->outputs().index_range()) {
- const OutputSocketRef &socket_ref = node->output(i);
- if (!socket_ref.is_available()) {
- continue;
- }
- const CPPType &type = *get_socket_cpp_type(socket_ref);
- void *buffer = allocator.allocate(type.size(), type.alignment());
- fn_params.add_uninitialized_single_output(GMutableSpan{type, buffer, 1});
- outputs.append({type, buffer});
+ input_fields.append(std::move(*(GField *)single_value.value));
}
- fn.call(IndexRange(1), fn_params, fn_context);
+ auto operation = std::make_shared<fn::FieldOperation>(fn, std::move(input_fields));
- /* Forward the computed outputs. */
+ /* Forward outputs. */
int output_index = 0;
for (const int i : node->outputs().index_range()) {
const OutputSocketRef &socket_ref = node->output(i);
@@ -899,8 +891,9 @@ class GeometryNodesEvaluator {
}
OutputState &output_state = node_state.outputs[i];
const DOutputSocket socket{node.context(), &socket_ref};
- GMutablePointer value = outputs[output_index];
- this->forward_output(socket, value);
+ const CPPType *cpp_type = get_socket_cpp_type(socket_ref);
+ GField &field = *allocator.construct<GField>(operation, output_index).release();
+ this->forward_output(socket, {cpp_type, &field});
output_state.has_been_computed = true;
output_index++;
}
@@ -922,7 +915,7 @@ class GeometryNodesEvaluator {
OutputState &output_state = node_state.outputs[socket->index()];
output_state.has_been_computed = true;
void *buffer = allocator.allocate(type->size(), type->alignment());
- type->copy_construct(type->default_value(), buffer);
+ this->construct_default_value(*type, buffer);
this->forward_output({node.context(), socket}, {*type, buffer});
}
}
@@ -1389,14 +1382,42 @@ class GeometryNodesEvaluator {
return;
}
+ const FieldCPPType *from_field_type = dynamic_cast<const FieldCPPType *>(&from_type);
+ const FieldCPPType *to_field_type = dynamic_cast<const FieldCPPType *>(&to_type);
+
+ if (from_field_type != nullptr && to_field_type != nullptr) {
+ const CPPType &from_base_type = from_field_type->field_type();
+ const CPPType &to_base_type = to_field_type->field_type();
+ if (conversions_.is_convertible(from_base_type, to_base_type)) {
+ const MultiFunction &fn = *conversions_.get_conversion_multi_function(
+ MFDataType::ForSingle(from_base_type), MFDataType::ForSingle(to_base_type));
+ const GField &from_field = *(const GField *)from_value;
+ auto operation = std::make_shared<fn::FieldOperation>(fn, Vector<GField>{from_field});
+ new (to_value) GField(std::move(operation), 0);
+ return;
+ }
+ }
if (conversions_.is_convertible(from_type, to_type)) {
/* Do the conversion if possible. */
conversions_.convert_to_uninitialized(from_type, to_type, from_value, to_value);
}
else {
/* Cannot convert, use default value instead. */
- to_type.copy_construct(to_type.default_value(), to_value);
+ this->construct_default_value(to_type, to_value);
+ }
+ }
+
+ void construct_default_value(const CPPType &type, void *r_value)
+ {
+ if (const FieldCPPType *field_cpp_type = dynamic_cast<const FieldCPPType *>(&type)) {
+ const CPPType &base_type = field_cpp_type->field_type();
+ auto constant_fn = std::make_unique<fn::CustomMF_GenericConstant>(base_type,
+ base_type.default_value());
+ auto operation = std::make_shared<fn::FieldOperation>(std::move(constant_fn));
+ new (r_value) GField(std::move(operation), 0);
+ return;
}
+ type.copy_construct(type.default_value(), r_value);
}
NodeState &get_node_state(const DNode node)
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index 1e7ac4b92f6..ac2200d496b 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -142,6 +142,7 @@ set(SRC
function/node_function_util.cc
geometry/nodes/node_geo_align_rotation_to_vector.cc
+ geometry/nodes/node_geo_attribute_capture.cc
geometry/nodes/node_geo_attribute_clamp.cc
geometry/nodes/node_geo_attribute_color_ramp.cc
geometry/nodes/node_geo_attribute_combine_xyz.cc
@@ -187,6 +188,9 @@ set(SRC
geometry/nodes/node_geo_delete_geometry.cc
geometry/nodes/node_geo_edge_split.cc
geometry/nodes/node_geo_input_material.cc
+ geometry/nodes/node_geo_input_normal.cc
+ geometry/nodes/node_geo_input_position.cc
+ geometry/nodes/node_geo_input_index.cc
geometry/nodes/node_geo_is_viewport.cc
geometry/nodes/node_geo_join_geometry.cc
geometry/nodes/node_geo_material_assign.cc
@@ -212,6 +216,7 @@ set(SRC
geometry/nodes/node_geo_raycast.cc
geometry/nodes/node_geo_select_by_material.cc
geometry/nodes/node_geo_separate_components.cc
+ geometry/nodes/node_geo_set_position.cc
geometry/nodes/node_geo_subdivision_surface.cc
geometry/nodes/node_geo_switch.cc
geometry/nodes/node_geo_transform.cc
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index 00062400eee..e9fb4ad123c 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -37,6 +37,7 @@ void register_node_type_geo_attribute_compare(void);
void register_node_type_geo_attribute_convert(void);
void register_node_type_geo_attribute_curve_map(void);
void register_node_type_geo_attribute_fill(void);
+void register_node_type_geo_attribute_capture(void);
void register_node_type_geo_attribute_map_range(void);
void register_node_type_geo_attribute_math(void);
void register_node_type_geo_attribute_mix(void);
@@ -71,7 +72,10 @@ void register_node_type_geo_curve_to_points(void);
void register_node_type_geo_curve_trim(void);
void register_node_type_geo_delete_geometry(void);
void register_node_type_geo_edge_split(void);
+void register_node_type_geo_input_index(void);
void register_node_type_geo_input_material(void);
+void register_node_type_geo_input_normal(void);
+void register_node_type_geo_input_position(void);
void register_node_type_geo_is_viewport(void);
void register_node_type_geo_join_geometry(void);
void register_node_type_geo_material_assign(void);
@@ -99,6 +103,7 @@ void register_node_type_geo_sample_texture(void);
void register_node_type_geo_select_by_handle_type(void);
void register_node_type_geo_select_by_material(void);
void register_node_type_geo_separate_components(void);
+void register_node_type_geo_set_position(void);
void register_node_type_geo_subdivision_surface(void);
void register_node_type_geo_switch(void);
void register_node_type_geo_transform(void);
diff --git a/source/blender/nodes/NOD_geometry_exec.hh b/source/blender/nodes/NOD_geometry_exec.hh
index d6a23051c0b..e0972e40a64 100644
--- a/source/blender/nodes/NOD_geometry_exec.hh
+++ b/source/blender/nodes/NOD_geometry_exec.hh
@@ -16,7 +16,8 @@
#pragma once
-#include "FN_generic_value_map.hh"
+#include "FN_field.hh"
+#include "FN_multi_function_builder.hh"
#include "BKE_attribute_access.hh"
#include "BKE_geometry_set.hh"
@@ -32,17 +33,24 @@ struct ModifierData;
namespace blender::nodes {
+using bke::AttributeIDRef;
using bke::geometry_set_realize_instances;
+using bke::GeometryComponentFieldContext;
using bke::OutputAttribute;
using bke::OutputAttribute_Typed;
using bke::ReadAttributeLookup;
+using bke::StrongAnonymousAttributeID;
+using bke::WeakAnonymousAttributeID;
using bke::WriteAttributeLookup;
using fn::CPPType;
+using fn::Field;
+using fn::FieldInput;
+using fn::FieldOperation;
+using fn::GField;
using fn::GMutablePointer;
using fn::GMutableSpan;
using fn::GPointer;
using fn::GSpan;
-using fn::GValueMap;
using fn::GVArray;
using fn::GVArray_GSpan;
using fn::GVArray_Span;
@@ -121,6 +129,14 @@ class GeoNodeExecParams {
{
}
+ template<typename T>
+ static inline constexpr bool is_stored_as_field_v = std::is_same_v<T, float> ||
+ std::is_same_v<T, int> ||
+ std::is_same_v<T, bool> ||
+ std::is_same_v<T, ColorGeometry4f> ||
+ std::is_same_v<T, float3> ||
+ std::is_same_v<T, std::string>;
+
/**
* Get the input value for the input socket with the given identifier.
*
@@ -142,11 +158,17 @@ class GeoNodeExecParams {
*/
template<typename T> T extract_input(StringRef identifier)
{
+ if constexpr (is_stored_as_field_v<T>) {
+ Field<T> field = this->extract_input<Field<T>>(identifier);
+ return fn::evaluate_constant_field(field);
+ }
+ else {
#ifdef DEBUG
- this->check_input_access(identifier, &CPPType::get<T>());
+ this->check_input_access(identifier, &CPPType::get<T>());
#endif
- GMutablePointer gvalue = this->extract_input(identifier);
- return gvalue.relocate_out<T>();
+ GMutablePointer gvalue = this->extract_input(identifier);
+ return gvalue.relocate_out<T>();
+ }
}
/**
@@ -159,7 +181,13 @@ class GeoNodeExecParams {
Vector<GMutablePointer> gvalues = provider_->extract_multi_input(identifier);
Vector<T> values;
for (GMutablePointer gvalue : gvalues) {
- values.append(gvalue.relocate_out<T>());
+ if constexpr (is_stored_as_field_v<T>) {
+ const Field<T> &field = *gvalue.get<Field<T>>();
+ values.append(fn::evaluate_constant_field(field));
+ }
+ else {
+ values.append(gvalue.relocate_out<T>());
+ }
}
return values;
}
@@ -167,14 +195,20 @@ class GeoNodeExecParams {
/**
* Get the input value for the input socket with the given identifier.
*/
- template<typename T> const T &get_input(StringRef identifier) const
+ template<typename T> const T get_input(StringRef identifier) const
{
+ if constexpr (is_stored_as_field_v<T>) {
+ const Field<T> &field = this->get_input<Field<T>>(identifier);
+ return fn::evaluate_constant_field(field);
+ }
+ else {
#ifdef DEBUG
- this->check_input_access(identifier, &CPPType::get<T>());
+ this->check_input_access(identifier, &CPPType::get<T>());
#endif
- GPointer gvalue = provider_->get_input(identifier);
- BLI_assert(gvalue.is_type<T>());
- return *(const T *)gvalue.get();
+ GPointer gvalue = provider_->get_input(identifier);
+ BLI_assert(gvalue.is_type<T>());
+ return *(const T *)gvalue.get();
+ }
}
/**
@@ -183,13 +217,19 @@ class GeoNodeExecParams {
template<typename T> void set_output(StringRef identifier, T &&value)
{
using StoredT = std::decay_t<T>;
- const CPPType &type = CPPType::get<std::decay_t<T>>();
+ if constexpr (is_stored_as_field_v<StoredT>) {
+ this->set_output<Field<StoredT>>(identifier,
+ fn::make_constant_field<StoredT>(std::forward<T>(value)));
+ }
+ else {
+ const CPPType &type = CPPType::get<StoredT>();
#ifdef DEBUG
- this->check_output_access(identifier, type);
+ this->check_output_access(identifier, type);
#endif
- GMutablePointer gvalue = provider_->alloc_output_value(type);
- new (gvalue.get()) StoredT(std::forward<T>(value));
- provider_->set_output(identifier, gvalue);
+ GMutablePointer gvalue = provider_->alloc_output_value(type);
+ new (gvalue.get()) StoredT(std::forward<T>(value));
+ provider_->set_output(identifier, gvalue);
+ }
}
/**
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index 2e9f186de02..670cde4470e 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -276,6 +276,7 @@ DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMBINE_XYZ, def_geo_attribute_combine_
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_COMPARE, def_geo_attribute_attribute_compare, "ATTRIBUTE_COMPARE", AttributeCompare, "Attribute Compare", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CONVERT, def_geo_attribute_convert, "ATTRIBUTE_CONVERT", AttributeConvert, "Attribute Convert", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CURVE_MAP, def_geo_attribute_curve_map, "ATTRIBUTE_CURVE_MAP", AttributeCurveMap, "Attribute Curve Map", "")
+DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_CAPTURE, def_geo_attribute_capture, "ATTRIBUTE_CAPTURE", AttributeCapture, "Attribute Capture", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_FILL, def_geo_attribute_fill, "ATTRIBUTE_FILL", AttributeFill, "Attribute Fill", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MAP_RANGE, def_geo_attribute_map_range, "ATTRIBUTE_MAP_RANGE", AttributeMapRange, "Attribute Map Range", "")
DefNode(GeometryNode, GEO_NODE_ATTRIBUTE_MATH, def_geo_attribute_math, "ATTRIBUTE_MATH", AttributeMath, "Attribute Math", "")
@@ -304,8 +305,8 @@ DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_SPIRAL, 0, "CURVE_PRIMITIVE_SPIRA
DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_STAR, 0, "CURVE_PRIMITIVE_STAR", CurveStar, "Star", "")
DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "")
DefNode(GeometryNode, GEO_NODE_CURVE_REVERSE, 0, "CURVE_REVERSE", CurveReverse, "Curve Reverse", "")
-DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLES, def_geo_curve_set_handles, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "")
DefNode(GeometryNode, GEO_NODE_CURVE_SELECT_HANDLES, def_geo_curve_select_handles, "CURVE_SELECT_HANDLES", CurveSelectHandles, "Select by Handle Type", "")
+DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLES, def_geo_curve_set_handles, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "")
DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "")
DefNode(GeometryNode, GEO_NODE_CURVE_SUBDIVIDE, def_geo_curve_subdivide, "CURVE_SUBDIVIDE", CurveSubdivide, "Curve Subdivide", "")
DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "")
@@ -313,7 +314,10 @@ DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_
DefNode(GeometryNode, GEO_NODE_CURVE_TRIM, def_geo_curve_trim, "CURVE_TRIM", CurveTrim, "Curve Trim", "")
DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, 0, "DELETE_GEOMETRY", DeleteGeometry, "Delete Geometry", "")
DefNode(GeometryNode, GEO_NODE_EDGE_SPLIT, 0, "EDGE_SPLIT", EdgeSplit, "Edge Split", "")
+DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "")
+DefNode(GeometryNode, GEO_NODE_INPUT_NORMAL, 0, "INPUT_NORMAL", InputNormal, "Normal", "")
+DefNode(GeometryNode, GEO_NODE_INPUT_POSITION, 0, "POSITION", InputPosition, "Position", "")
DefNode(GeometryNode, GEO_NODE_IS_VIEWPORT, 0, "IS_VIEWPORT", IsViewport, "Is Viewport", "")
DefNode(GeometryNode, GEO_NODE_JOIN_GEOMETRY, 0, "JOIN_GEOMETRY", JoinGeometry, "Join Geometry", "")
DefNode(GeometryNode, GEO_NODE_MATERIAL_ASSIGN, 0, "MATERIAL_ASSIGN", MaterialAssign, "Material Assign", "")
@@ -339,6 +343,7 @@ DefNode(GeometryNode, GEO_NODE_POINTS_TO_VOLUME, def_geo_points_to_volume, "POIN
DefNode(GeometryNode, GEO_NODE_RAYCAST, def_geo_raycast, "RAYCAST", Raycast, "Raycast", "")
DefNode(GeometryNode, GEO_NODE_SELECT_BY_MATERIAL, 0, "SELECT_BY_MATERIAL", SelectByMaterial, "Select by Material", "")
DefNode(GeometryNode, GEO_NODE_SEPARATE_COMPONENTS, 0, "SEPARATE_COMPONENTS", SeparateComponents, "Separate Components", "")
+DefNode(GeometryNode, GEO_NODE_SET_POSITION, 0, "SET_POSITION", SetPosition, "Set Position", "")
DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, def_geo_subdivision_surface, "SUBDIVISION_SURFACE", SubdivisionSurface, "Subdivision Surface", "")
DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "")
DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "")
diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh
index 6a69c3d24ec..015ac0de002 100644
--- a/source/blender/nodes/geometry/node_geometry_util.hh
+++ b/source/blender/nodes/geometry/node_geometry_util.hh
@@ -84,7 +84,7 @@ struct CurveToPointsResults {
MutableSpan<float> radii;
MutableSpan<float> tilts;
- Map<std::string, GMutableSpan> point_attributes;
+ Map<AttributeIDRef, GMutableSpan> point_attributes;
MutableSpan<float3> tangents;
MutableSpan<float3> normals;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc
new file mode 100644
index 00000000000..1fa71d3f57d
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_attribute_capture.cc
@@ -0,0 +1,210 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "BKE_attribute_math.hh"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_attribute_capture_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Geometry");
+ b.add_input<decl::Vector>("Value");
+ b.add_input<decl::Float>("Value", "Value_001");
+ b.add_input<decl::Color>("Value", "Value_002");
+ b.add_input<decl::Bool>("Value", "Value_003");
+ b.add_input<decl::Int>("Value", "Value_004");
+
+ b.add_output<decl::Geometry>("Geometry");
+ b.add_output<decl::Vector>("Attribute");
+ b.add_output<decl::Float>("Attribute", "Attribute_001");
+ b.add_output<decl::Color>("Attribute", "Attribute_002");
+ b.add_output<decl::Bool>("Attribute", "Attribute_003");
+ b.add_output<decl::Int>("Attribute", "Attribute_004");
+}
+
+static void geo_node_attribute_capture_layout(uiLayout *layout,
+ bContext *UNUSED(C),
+ PointerRNA *ptr)
+{
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetPropDecorate(layout, false);
+ uiItemR(layout, ptr, "domain", 0, "", ICON_NONE);
+ uiItemR(layout, ptr, "data_type", 0, "", ICON_NONE);
+}
+
+static void geo_node_attribute_capture_init(bNodeTree *UNUSED(tree), bNode *node)
+{
+ NodeGeometryAttributeCapture *data = (NodeGeometryAttributeCapture *)MEM_callocN(
+ sizeof(NodeGeometryAttributeCapture), __func__);
+ data->data_type = CD_PROP_FLOAT;
+ data->domain = ATTR_DOMAIN_POINT;
+
+ node->storage = data;
+}
+
+static void geo_node_attribute_capture_update(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *)
+ node->storage;
+ const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type);
+
+ bNodeSocket *socket_value_attribute_name = (bNodeSocket *)node->inputs.first;
+ bNodeSocket *socket_value_vector = socket_value_attribute_name->next;
+ bNodeSocket *socket_value_float = socket_value_vector->next;
+ bNodeSocket *socket_value_color4f = socket_value_float->next;
+ bNodeSocket *socket_value_boolean = socket_value_color4f->next;
+ bNodeSocket *socket_value_int32 = socket_value_boolean->next;
+
+ nodeSetSocketAvailability(socket_value_vector, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(socket_value_float, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(socket_value_color4f, data_type == CD_PROP_COLOR);
+ nodeSetSocketAvailability(socket_value_boolean, data_type == CD_PROP_BOOL);
+ nodeSetSocketAvailability(socket_value_int32, data_type == CD_PROP_INT32);
+
+ bNodeSocket *out_socket_value_attribute_name = (bNodeSocket *)node->outputs.first;
+ bNodeSocket *out_socket_value_vector = out_socket_value_attribute_name->next;
+ bNodeSocket *out_socket_value_float = out_socket_value_vector->next;
+ bNodeSocket *out_socket_value_color4f = out_socket_value_float->next;
+ bNodeSocket *out_socket_value_boolean = out_socket_value_color4f->next;
+ bNodeSocket *out_socket_value_int32 = out_socket_value_boolean->next;
+
+ nodeSetSocketAvailability(out_socket_value_vector, data_type == CD_PROP_FLOAT3);
+ nodeSetSocketAvailability(out_socket_value_float, data_type == CD_PROP_FLOAT);
+ nodeSetSocketAvailability(out_socket_value_color4f, data_type == CD_PROP_COLOR);
+ nodeSetSocketAvailability(out_socket_value_boolean, data_type == CD_PROP_BOOL);
+ nodeSetSocketAvailability(out_socket_value_int32, data_type == CD_PROP_INT32);
+}
+
+static void try_capture_field_on_geometry(GeometryComponent &component,
+ const AttributeIDRef &attribute_id,
+ const AttributeDomain domain,
+ const GField &field)
+{
+ GeometryComponentFieldContext field_context{component, domain};
+ const int domain_size = component.attribute_domain_size(domain);
+ const IndexMask mask{IndexMask(domain_size)};
+
+ const CustomDataType data_type = bke::cpp_type_to_custom_data_type(field.cpp_type());
+ OutputAttribute output_attribute = component.attribute_try_get_for_output_only(
+ attribute_id, domain, data_type);
+
+ fn::FieldEvaluator evaluator{field_context, &mask};
+ evaluator.add_with_destination(field, output_attribute.varray());
+ evaluator.evaluate();
+
+ output_attribute.save();
+}
+
+static void geo_node_attribute_capture_exec(GeoNodeExecParams params)
+{
+ GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
+
+ geometry_set = bke::geometry_set_realize_instances(geometry_set);
+
+ const bNode &node = params.node();
+ const NodeGeometryAttributeCapture &storage = *(const NodeGeometryAttributeCapture *)
+ node.storage;
+ const CustomDataType data_type = static_cast<CustomDataType>(storage.data_type);
+ const AttributeDomain domain = static_cast<AttributeDomain>(storage.domain);
+
+ GField field;
+ switch (data_type) {
+ case CD_PROP_FLOAT:
+ field = params.get_input<Field<float>>("Value_001");
+ break;
+ case CD_PROP_FLOAT3:
+ field = params.get_input<Field<float3>>("Value");
+ break;
+ case CD_PROP_COLOR:
+ field = params.get_input<Field<ColorGeometry4f>>("Value_002");
+ break;
+ case CD_PROP_BOOL:
+ field = params.get_input<Field<bool>>("Value_003");
+ break;
+ case CD_PROP_INT32:
+ field = params.get_input<Field<int>>("Value_004");
+ break;
+ default:
+ break;
+ }
+
+ WeakAnonymousAttributeID anonymous_id{"Attribute Capture"};
+ const CPPType &type = field.cpp_type();
+
+ static const Array<GeometryComponentType> types = {
+ GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE};
+ for (const GeometryComponentType type : types) {
+ if (geometry_set.has(type)) {
+ GeometryComponent &component = geometry_set.get_component_for_write(type);
+ try_capture_field_on_geometry(component, anonymous_id.get(), domain, field);
+ }
+ }
+
+ GField output_field{
+ std::make_shared<bke::AnonymousAttributeFieldInput>(std::move(anonymous_id), type)};
+
+ switch (data_type) {
+ case CD_PROP_FLOAT: {
+ params.set_output("Attribute_001", Field<float>(output_field));
+ break;
+ }
+ case CD_PROP_FLOAT3: {
+ params.set_output("Attribute", Field<float3>(output_field));
+ break;
+ }
+ case CD_PROP_COLOR: {
+ params.set_output("Attribute_002", Field<ColorGeometry4f>(output_field));
+ break;
+ }
+ case CD_PROP_BOOL: {
+ params.set_output("Attribute_003", Field<bool>(output_field));
+ break;
+ }
+ case CD_PROP_INT32: {
+ params.set_output("Attribute_004", Field<int>(output_field));
+ break;
+ }
+ default:
+ break;
+ }
+
+ params.set_output("Geometry", geometry_set);
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_attribute_capture()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(
+ &ntype, GEO_NODE_ATTRIBUTE_CAPTURE, "Attribute Capture", NODE_CLASS_ATTRIBUTE, 0);
+ node_type_storage(&ntype,
+ "NodeGeometryAttributeCapture",
+ node_free_standard_storage,
+ node_copy_standard_storage);
+ node_type_init(&ntype, blender::nodes::geo_node_attribute_capture_init);
+ node_type_update(&ntype, blender::nodes::geo_node_attribute_capture_update);
+ ntype.declare = blender::nodes::geo_node_attribute_capture_declare;
+ ntype.geometry_node_execute = blender::nodes::geo_node_attribute_capture_exec;
+ ntype.draw_buttons = blender::nodes::geo_node_attribute_capture_layout;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc
index 699fcc5e983..7853c5aa04a 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_endpoints.cc
@@ -56,25 +56,26 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component,
Span<int> offsets,
PointCloudComponent &points)
{
- curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (meta_data.domain != ATTR_DOMAIN_CURVE) {
- return true;
- }
- GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
- name, ATTR_DOMAIN_CURVE, meta_data.data_type);
-
- OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
- name, ATTR_DOMAIN_POINT, meta_data.data_type);
- GMutableSpan result = result_attribute.as_span();
-
- /* Only copy the attributes of splines in the offsets. */
- for (const int i : offsets.index_range()) {
- spline_attribute->get(offsets[i], result[i]);
- }
-
- result_attribute.save();
- return true;
- });
+ curve_component.attribute_foreach(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (meta_data.domain != ATTR_DOMAIN_CURVE) {
+ return true;
+ }
+ GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
+ attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type);
+
+ OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
+ attribute_id, ATTR_DOMAIN_POINT, meta_data.data_type);
+ GMutableSpan result = result_attribute.as_span();
+
+ /* Only copy the attributes of splines in the offsets. */
+ for (const int i : offsets.index_range()) {
+ spline_attribute->get(offsets[i], result[i]);
+ }
+
+ result_attribute.save();
+ return true;
+ });
}
/**
@@ -124,20 +125,20 @@ static void copy_endpoint_attributes(Span<SplinePtr> splines,
/* Copy the point attribute data over. */
for (const auto &item : start_data.point_attributes.items()) {
- const StringRef name = item.key;
+ const AttributeIDRef attribute_id = item.key;
GMutableSpan point_span = item.value;
- BLI_assert(spline.attributes.get_for_read(name));
- GSpan spline_span = *spline.attributes.get_for_read(name);
+ BLI_assert(spline.attributes.get_for_read(attribute_id));
+ GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
blender::fn::GVArray_For_GSpan(spline_span).get(0, point_span[i]);
}
for (const auto &item : end_data.point_attributes.items()) {
- const StringRef name = item.key;
+ const AttributeIDRef attribute_id = item.key;
GMutableSpan point_span = item.value;
- BLI_assert(spline.attributes.get_for_read(name));
- GSpan spline_span = *spline.attributes.get_for_read(name);
+ BLI_assert(spline.attributes.get_for_read(attribute_id));
+ GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
blender::fn::GVArray_For_GSpan(spline_span).get(spline.size() - 1, point_span[i]);
}
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc
index 0b71a8cb03a..11c8f0c0767 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_resample.cc
@@ -84,14 +84,14 @@ static SplinePtr resample_spline(const Spline &input_spline, const int count)
input_spline.radii().first());
output_spline->attributes.reallocate(1);
input_spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> src = input_spline.attributes.get_for_read(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src = input_spline.attributes.get_for_read(attribute_id);
BLI_assert(src);
- if (!output_spline->attributes.create(name, meta_data.data_type)) {
+ if (!output_spline->attributes.create(attribute_id, meta_data.data_type)) {
BLI_assert_unreachable();
return false;
}
- std::optional<GMutableSpan> dst = output_spline->attributes.get_for_write(name);
+ std::optional<GMutableSpan> dst = output_spline->attributes.get_for_write(attribute_id);
if (!dst) {
BLI_assert_unreachable();
return false;
@@ -122,15 +122,15 @@ static SplinePtr resample_spline(const Spline &input_spline, const int count)
output_spline->attributes.reallocate(count);
input_spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> input_attribute = input_spline.attributes.get_for_read(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> input_attribute = input_spline.attributes.get_for_read(attribute_id);
BLI_assert(input_attribute);
- if (!output_spline->attributes.create(name, meta_data.data_type)) {
+ if (!output_spline->attributes.create(attribute_id, meta_data.data_type)) {
BLI_assert_unreachable();
return false;
}
std::optional<GMutableSpan> output_attribute = output_spline->attributes.get_for_write(
- name);
+ attribute_id);
if (!output_attribute) {
BLI_assert_unreachable();
return false;
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc
index b826e1c6510..720af50b795 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_reverse.cc
@@ -83,9 +83,9 @@ static void geo_node_curve_reverse_exec(GeoNodeExecParams params)
reverse_data<float>(splines[i]->tilts());
splines[i]->attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
std::optional<blender::fn::GMutableSpan> output_attribute =
- splines[i]->attributes.get_for_write(name);
+ splines[i]->attributes.get_for_write(attribute_id);
if (!output_attribute) {
BLI_assert_unreachable();
return false;
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 6a093f442cb..6380873fbcb 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
@@ -74,14 +74,14 @@ template<typename CopyFn>
static void copy_attributes(const Spline &input_spline, Spline &output_spline, CopyFn copy_fn)
{
input_spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> src = input_spline.attributes.get_for_read(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src = input_spline.attributes.get_for_read(attribute_id);
BLI_assert(src);
- if (!output_spline.attributes.create(name, meta_data.data_type)) {
+ if (!output_spline.attributes.create(attribute_id, meta_data.data_type)) {
BLI_assert_unreachable();
return false;
}
- std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(name);
+ std::optional<GMutableSpan> dst = output_spline.attributes.get_for_write(attribute_id);
if (!dst) {
BLI_assert_unreachable();
return false;
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 958d83e2967..2809543a73d 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_subdivide.cc
@@ -283,16 +283,16 @@ static void subdivide_dynamic_attributes(const Spline &src_spline,
{
const bool is_cyclic = src_spline.is_cyclic();
src_spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> src = src_spline.attributes.get_for_read(name);
+ [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src = src_spline.attributes.get_for_read(attribute_id);
BLI_assert(src);
- if (!dst_spline.attributes.create(name, meta_data.data_type)) {
+ if (!dst_spline.attributes.create(attribute_id, meta_data.data_type)) {
/* Since the source spline of the same type had the attribute, adding it should work. */
BLI_assert_unreachable();
}
- std::optional<GMutableSpan> dst = dst_spline.attributes.get_for_write(name);
+ std::optional<GMutableSpan> dst = dst_spline.attributes.get_for_write(attribute_id);
BLI_assert(dst);
attribute_math::convert_to_static_type(dst->type(), [&](auto dummy) {
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 052eb92d269..74f43fc0cec 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
@@ -115,21 +115,21 @@ static Array<int> calculate_spline_point_offsets(GeoNodeExecParams &params,
}
static GMutableSpan create_attribute_and_retrieve_span(PointCloudComponent &points,
- const StringRef name,
+ const AttributeIDRef &attribute_id,
const CustomDataType data_type)
{
- points.attribute_try_create(name, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault());
- WriteAttributeLookup attribute = points.attribute_try_get_for_write(name);
+ points.attribute_try_create(attribute_id, ATTR_DOMAIN_POINT, data_type, AttributeInitDefault());
+ WriteAttributeLookup attribute = points.attribute_try_get_for_write(attribute_id);
BLI_assert(attribute);
return attribute.varray->get_internal_span();
}
template<typename T>
static MutableSpan<T> create_attribute_and_retrieve_span(PointCloudComponent &points,
- const StringRef name)
+ const AttributeIDRef &attribute_id)
{
GMutableSpan attribute = create_attribute_and_retrieve_span(
- points, name, bke::cpp_type_to_custom_data_type(CPPType::get<T>()));
+ points, attribute_id, bke::cpp_type_to_custom_data_type(CPPType::get<T>()));
return attribute.typed<T>();
}
@@ -147,9 +147,10 @@ CurveToPointsResults curve_to_points_create_result_attributes(PointCloudComponen
/* Because of the invariants of the curve component, we use the attributes of the
* first spline as a representative for the attribute meta data all splines. */
curve.splines().first()->attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
attributes.point_attributes.add_new(
- name, create_attribute_and_retrieve_span(points, name, meta_data.data_type));
+ attribute_id,
+ create_attribute_and_retrieve_span(points, attribute_id, meta_data.data_type));
return true;
},
ATTR_DOMAIN_POINT);
@@ -179,12 +180,12 @@ static void copy_evaluated_point_attributes(Span<SplinePtr> splines,
spline.interpolate_to_evaluated(spline.radii())->materialize(data.radii.slice(offset, size));
spline.interpolate_to_evaluated(spline.tilts())->materialize(data.tilts.slice(offset, size));
- for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) {
- const StringRef name = item.key;
+ for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) {
+ const AttributeIDRef attribute_id = item.key;
GMutableSpan point_span = item.value;
- BLI_assert(spline.attributes.get_for_read(name));
- GSpan spline_span = *spline.attributes.get_for_read(name);
+ BLI_assert(spline.attributes.get_for_read(attribute_id));
+ GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
spline.interpolate_to_evaluated(spline_span)
->materialize(point_span.slice(offset, size).data());
@@ -222,12 +223,12 @@ static void copy_uniform_sample_point_attributes(Span<SplinePtr> splines,
uniform_samples,
data.tilts.slice(offset, size));
- for (const Map<std::string, GMutableSpan>::Item &item : data.point_attributes.items()) {
- const StringRef name = item.key;
+ for (const Map<AttributeIDRef, GMutableSpan>::Item &item : data.point_attributes.items()) {
+ const AttributeIDRef attribute_id = item.key;
GMutableSpan point_span = item.value;
- BLI_assert(spline.attributes.get_for_read(name));
- GSpan spline_span = *spline.attributes.get_for_read(name);
+ BLI_assert(spline.attributes.get_for_read(attribute_id));
+ GSpan spline_span = *spline.attributes.get_for_read(attribute_id);
spline.sample_with_index_factors(*spline.interpolate_to_evaluated(spline_span),
uniform_samples,
@@ -257,31 +258,32 @@ static void copy_spline_domain_attributes(const CurveComponent &curve_component,
Span<int> offsets,
PointCloudComponent &points)
{
- curve_component.attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (meta_data.domain != ATTR_DOMAIN_CURVE) {
- return true;
- }
- GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
- name, ATTR_DOMAIN_CURVE, meta_data.data_type);
- const CPPType &type = spline_attribute->type();
-
- OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
- name, ATTR_DOMAIN_POINT, meta_data.data_type);
- GMutableSpan result = result_attribute.as_span();
-
- for (const int i : IndexRange(spline_attribute->size())) {
- const int offset = offsets[i];
- const int size = offsets[i + 1] - offsets[i];
- if (size != 0) {
- BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
- spline_attribute->get(i, buffer);
- type.fill_assign_n(buffer, result[offset], size);
- }
- }
-
- result_attribute.save();
- return true;
- });
+ curve_component.attribute_foreach(
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (meta_data.domain != ATTR_DOMAIN_CURVE) {
+ return true;
+ }
+ GVArrayPtr spline_attribute = curve_component.attribute_get_for_read(
+ attribute_id, ATTR_DOMAIN_CURVE, meta_data.data_type);
+ const CPPType &type = spline_attribute->type();
+
+ OutputAttribute result_attribute = points.attribute_try_get_for_output_only(
+ attribute_id, ATTR_DOMAIN_POINT, meta_data.data_type);
+ GMutableSpan result = result_attribute.as_span();
+
+ for (const int i : IndexRange(spline_attribute->size())) {
+ const int offset = offsets[i];
+ const int size = offsets[i + 1] - offsets[i];
+ if (size != 0) {
+ BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
+ spline_attribute->get(i, buffer);
+ type.fill_assign_n(buffer, result[offset], size);
+ }
+ }
+
+ result_attribute.save();
+ return true;
+ });
}
void curve_create_default_rotation_attribute(Span<float3> tangents,
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 578f0298a19..4ad311d63c5 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_trim.cc
@@ -158,8 +158,8 @@ static void trim_poly_spline(Spline &spline,
linear_trim_data<float>(start, end, spline.tilts());
spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) {
- std::optional<GMutableSpan> src = spline.attributes.get_for_write(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
+ std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id);
BLI_assert(src);
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
using T = decltype(dummy);
@@ -193,14 +193,14 @@ static PolySpline trim_nurbs_spline(const Spline &spline,
/* Copy generic attribute data. */
spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> src = spline.attributes.get_for_read(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src = spline.attributes.get_for_read(attribute_id);
BLI_assert(src);
- if (!new_spline.attributes.create(name, meta_data.data_type)) {
+ if (!new_spline.attributes.create(attribute_id, meta_data.data_type)) {
BLI_assert_unreachable();
return false;
}
- std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(name);
+ std::optional<GMutableSpan> dst = new_spline.attributes.get_for_write(attribute_id);
BLI_assert(dst);
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
@@ -249,8 +249,8 @@ static void trim_bezier_spline(Spline &spline,
linear_trim_data<float>(start, end, bezier_spline.radii());
linear_trim_data<float>(start, end, bezier_spline.tilts());
spline.attributes.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &UNUSED(meta_data)) {
- std::optional<GMutableSpan> src = spline.attributes.get_for_write(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &UNUSED(meta_data)) {
+ std::optional<GMutableSpan> src = spline.attributes.get_for_write(attribute_id);
BLI_assert(src);
attribute_math::convert_to_static_type(src->type(), [&](auto dummy) {
using T = decltype(dummy);
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 6bc0ab49959..b36b12d8cf5 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_delete_geometry.cc
@@ -93,17 +93,17 @@ static void copy_dynamic_attributes(const CustomDataAttributes &src,
const IndexMask mask)
{
src.foreach_attribute(
- [&](StringRefNull name, const AttributeMetaData &meta_data) {
- std::optional<GSpan> src_attribute = src.get_for_read(name);
+ [&](const AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ std::optional<GSpan> src_attribute = src.get_for_read(attribute_id);
BLI_assert(src_attribute);
- if (!dst.create(name, meta_data.data_type)) {
+ if (!dst.create(attribute_id, meta_data.data_type)) {
/* Since the source spline of the same type had the attribute, adding it should work.
*/
BLI_assert_unreachable();
}
- std::optional<GMutableSpan> new_attribute = dst.get_for_write(name);
+ std::optional<GMutableSpan> new_attribute = dst.get_for_write(attribute_id);
BLI_assert(new_attribute);
attribute_math::convert_to_static_type(new_attribute->type(), [&](auto dummy) {
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_index.cc b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc
new file mode 100644
index 00000000000..e2287abe56c
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_index.cc
@@ -0,0 +1,60 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_input_index_declare(NodeDeclarationBuilder &b)
+{
+ b.add_output<decl::Int>("Index");
+}
+
+class IndexFieldInput final : public fn::FieldInput {
+ public:
+ IndexFieldInput() : FieldInput(CPPType::get<int>(), "Index")
+ {
+ }
+
+ const GVArray *get_varray_for_context(const fn::FieldContext &UNUSED(context),
+ IndexMask mask,
+ ResourceScope &scope) const final
+ {
+ /* TODO: Investigate a similar method to IndexRange::as_span() */
+ auto index_func = [](int i) { return i; };
+ return &scope.construct<
+ fn::GVArray_For_EmbeddedVArray<int, VArray_For_Func<int, decltype(index_func)>>>(
+ __func__, mask.min_array_size(), mask.min_array_size(), index_func);
+ }
+};
+
+static void geo_node_input_index_exec(GeoNodeExecParams params)
+{
+ Field<int> index_field{std::make_shared<IndexFieldInput>()};
+ params.set_output("Index", std::move(index_field));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_input_index()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_INPUT_INDEX, "Index", NODE_CLASS_INPUT, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_input_index_exec;
+ ntype.declare = blender::nodes::geo_node_input_index_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc
new file mode 100644
index 00000000000..e1e14eb367d
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_normal.cc
@@ -0,0 +1,211 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_mesh.h"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_input_normal_declare(NodeDeclarationBuilder &b)
+{
+ b.add_output<decl::Vector>("Normal");
+}
+
+static GVArrayPtr mesh_face_normals(const Mesh &mesh,
+ const Span<MVert> verts,
+ const Span<MPoly> polys,
+ const Span<MLoop> loops,
+ const IndexMask mask)
+{
+ /* Use existing normals to avoid unnecessarily recalculating them, if possible. */
+ if (!(mesh.runtime.cd_dirty_poly & CD_MASK_NORMAL) &&
+ CustomData_has_layer(&mesh.pdata, CD_NORMAL)) {
+ const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL);
+
+ return std::make_unique<fn::GVArray_For_Span<float3>>(
+ Span<float3>((const float3 *)data, polys.size()));
+ }
+
+ auto normal_fn = [verts, polys, loops](const int i) -> float3 {
+ float3 normal;
+ const MPoly &poly = polys[i];
+ BKE_mesh_calc_poly_normal(&poly, &loops[poly.loopstart], verts.data(), normal);
+ return normal;
+ };
+
+ return std::make_unique<
+ fn::GVArray_For_EmbeddedVArray<float3, VArray_For_Func<float3, decltype(normal_fn)>>>(
+ mask.min_array_size(), mask.min_array_size(), normal_fn);
+}
+
+static GVArrayPtr mesh_vertex_normals(const Mesh &mesh,
+ const Span<MVert> verts,
+ const Span<MPoly> polys,
+ const Span<MLoop> loops,
+ const IndexMask mask)
+{
+ /* Use existing normals to avoid unnecessarily recalculating them, if possible. */
+ if (!(mesh.runtime.cd_dirty_vert & CD_MASK_NORMAL) &&
+ CustomData_has_layer(&mesh.vdata, CD_NORMAL)) {
+ const void *data = CustomData_get_layer(&mesh.pdata, CD_NORMAL);
+
+ return std::make_unique<fn::GVArray_For_Span<float3>>(
+ Span<float3>((const float3 *)data, mesh.totvert));
+ }
+
+ /* If the normals are dirty, they must be recalculated for the output of this node's field
+ * source. Ideally vertex normals could be calculated lazily on a const mesh. But that's not
+ * possible at the moment, so we take ownership of the results. Sadly we must also create a copy
+ * of MVert to use the mesh normals API. This can be improved by adding mutex-protected lazy
+ * calculation of normals on meshes.
+ *
+ * Use mask.min_array_size() to avoid calculating a final chunk of data if possible. */
+ Array<MVert> temp_verts(verts);
+ Array<float3> normals(verts.size()); /* Use full size for accumulation from faces. */
+ BKE_mesh_calc_normals_poly_and_vertex(temp_verts.data(),
+ mask.min_array_size(),
+ loops.data(),
+ loops.size(),
+ polys.data(),
+ polys.size(),
+ nullptr,
+ (float(*)[3])normals.data());
+
+ return std::make_unique<fn::GVArray_For_ArrayContainer<Array<float3>>>(std::move(normals));
+}
+
+static const GVArray *construct_mesh_normals_gvarray(const MeshComponent &mesh_component,
+ const Mesh &mesh,
+ const IndexMask mask,
+ const AttributeDomain domain,
+ ResourceScope &scope)
+{
+ Span<MVert> verts{mesh.mvert, mesh.totvert};
+ Span<MEdge> edges{mesh.medge, mesh.totedge};
+ Span<MPoly> polys{mesh.mpoly, mesh.totpoly};
+ Span<MLoop> loops{mesh.mloop, mesh.totloop};
+
+ switch (domain) {
+ case ATTR_DOMAIN_FACE: {
+ return scope.add_value(mesh_face_normals(mesh, verts, polys, loops, mask), __func__).get();
+ }
+ case ATTR_DOMAIN_POINT: {
+ return scope.add_value(mesh_vertex_normals(mesh, verts, polys, loops, mask), __func__).get();
+ }
+ case ATTR_DOMAIN_EDGE: {
+ /* In this case, start with vertex normals and convert to the edge domain, since the
+ * conversion from edges to vertices is very simple. Use the full mask since the edges
+ * might use the vertex normal from any index. */
+ GVArrayPtr vert_normals = mesh_vertex_normals(
+ mesh, verts, polys, loops, IndexRange(verts.size()));
+ Span<float3> vert_normals_span = vert_normals->get_internal_span().typed<float3>();
+ Array<float3> edge_normals(mask.min_array_size());
+
+ /* Use "manual" domain interpolation instead of the GeometryComponent API to avoid
+ * calculating unnecessary values and to allow normalizing the result much more simply. */
+ for (const int i : mask) {
+ const MEdge &edge = edges[i];
+ edge_normals[i] = float3::interpolate(
+ vert_normals_span[edge.v1], vert_normals_span[edge.v2], 0.5f)
+ .normalized();
+ }
+
+ return &scope.construct<fn::GVArray_For_ArrayContainer<Array<float3>>>(
+ __func__, std::move(edge_normals));
+ }
+ case ATTR_DOMAIN_CORNER: {
+ /* The normals on corners are just the mesh's face normals, so start with the face normal
+ * array and copy the face normal for each of its corners. */
+ GVArrayPtr face_normals = mesh_face_normals(
+ mesh, verts, polys, loops, IndexRange(polys.size()));
+
+ /* In this case using the mesh component's generic domain interpolation is fine,
+ * since the face normal is just copied to every corner. */
+ GVArrayPtr loop_normals = mesh_component.attribute_try_adapt_domain(
+ std::move(face_normals), ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER);
+ return scope.add_value(std::move(loop_normals), __func__).get();
+ }
+ default:
+ return nullptr;
+ }
+}
+
+class NormalFieldInput final : public fn::FieldInput {
+ public:
+ NormalFieldInput() : fn::FieldInput(CPPType::get<float3>(), "Normal")
+ {
+ }
+
+ const GVArray *get_varray_for_context(const fn::FieldContext &context,
+ IndexMask mask,
+ ResourceScope &scope) const final
+ {
+ if (const GeometryComponentFieldContext *geometry_context =
+ dynamic_cast<const GeometryComponentFieldContext *>(&context)) {
+
+ const GeometryComponent &component = geometry_context->geometry_component();
+ const AttributeDomain domain = geometry_context->domain();
+
+ if (component.type() == GEO_COMPONENT_TYPE_MESH) {
+ const MeshComponent &mesh_component = static_cast<const MeshComponent &>(component);
+ const Mesh *mesh = mesh_component.get_for_read();
+ if (mesh == nullptr) {
+ return nullptr;
+ }
+
+ return construct_mesh_normals_gvarray(mesh_component, *mesh, mask, domain, scope);
+ }
+ if (component.type() == GEO_COMPONENT_TYPE_CURVE) {
+ /* TODO: Add curve normals support. */
+ return nullptr;
+ }
+ }
+ return nullptr;
+ }
+
+ uint64_t hash() const override
+ {
+ /* Some random constant hash. */
+ return 669605641;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const override
+ {
+ return dynamic_cast<const NormalFieldInput *>(&other) != nullptr;
+ }
+};
+
+static void geo_node_input_normal_exec(GeoNodeExecParams params)
+{
+ Field<float3> normal_field{std::make_shared<NormalFieldInput>()};
+ params.set_output("Normal", std::move(normal_field));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_input_normal()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_INPUT_NORMAL, "Normal", NODE_CLASS_INPUT, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_input_normal_exec;
+ ntype.declare = blender::nodes::geo_node_input_normal_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_position.cc b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc
new file mode 100644
index 00000000000..c6365bf6809
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_position.cc
@@ -0,0 +1,43 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_input_position_declare(NodeDeclarationBuilder &b)
+{
+ b.add_output<decl::Vector>("Position");
+}
+
+static void geo_node_input_position_exec(GeoNodeExecParams params)
+{
+ Field<float3> position_field{
+ std::make_shared<bke::AttributeFieldInput>("position", CPPType::get<float3>())};
+ params.set_output("Position", std::move(position_field));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_input_position()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_INPUT_POSITION, "Position", NODE_CLASS_INPUT, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_input_position_exec;
+ ntype.declare = blender::nodes::geo_node_input_position_declare;
+ nodeRegisterType(&ntype);
+}
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 730cf08feaa..5792ee1485a 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
@@ -161,34 +161,35 @@ static Array<const GeometryComponent *> to_base_components(Span<const Component
return components;
}
-static Map<std::string, AttributeMetaData> get_final_attribute_info(
+static Map<AttributeIDRef, AttributeMetaData> get_final_attribute_info(
Span<const GeometryComponent *> components, Span<StringRef> ignored_attributes)
{
- Map<std::string, AttributeMetaData> info;
+ Map<AttributeIDRef, AttributeMetaData> info;
for (const GeometryComponent *component : components) {
- component->attribute_foreach([&](StringRefNull name, const AttributeMetaData &meta_data) {
- if (ignored_attributes.contains(name)) {
- return true;
- }
- info.add_or_modify(
- name,
- [&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; },
- [&](AttributeMetaData *meta_data_final) {
- meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity(
- {meta_data_final->data_type, meta_data.data_type});
- meta_data_final->domain = blender::bke::attribute_domain_highest_priority(
- {meta_data_final->domain, meta_data.domain});
- });
- return true;
- });
+ component->attribute_foreach(
+ [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (attribute_id.is_named() && ignored_attributes.contains(attribute_id.name())) {
+ return true;
+ }
+ info.add_or_modify(
+ attribute_id,
+ [&](AttributeMetaData *meta_data_final) { *meta_data_final = meta_data; },
+ [&](AttributeMetaData *meta_data_final) {
+ meta_data_final->data_type = blender::bke::attribute_data_type_highest_complexity(
+ {meta_data_final->data_type, meta_data.data_type});
+ meta_data_final->domain = blender::bke::attribute_domain_highest_priority(
+ {meta_data_final->domain, meta_data.domain});
+ });
+ return true;
+ });
}
return info;
}
static void fill_new_attribute(Span<const GeometryComponent *> src_components,
- StringRef attribute_name,
+ const AttributeIDRef &attribute_id,
const CustomDataType data_type,
const AttributeDomain domain,
GMutableSpan dst_span)
@@ -203,7 +204,7 @@ static void fill_new_attribute(Span<const GeometryComponent *> src_components,
continue;
}
GVArrayPtr read_attribute = component->attribute_get_for_read(
- attribute_name, domain, data_type, nullptr);
+ attribute_id, domain, data_type, nullptr);
GVArray_GSpan src_span{*read_attribute};
const void *src_buffer = src_span.data();
@@ -218,20 +219,21 @@ static void join_attributes(Span<const GeometryComponent *> src_components,
GeometryComponent &result,
Span<StringRef> ignored_attributes = {})
{
- const Map<std::string, AttributeMetaData> info = get_final_attribute_info(src_components,
- ignored_attributes);
+ const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info(src_components,
+ ignored_attributes);
- for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) {
- const StringRef name = item.key;
+ for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) {
+ const AttributeIDRef attribute_id = item.key;
const AttributeMetaData &meta_data = item.value;
OutputAttribute write_attribute = result.attribute_try_get_for_output_only(
- name, meta_data.domain, meta_data.data_type);
+ attribute_id, meta_data.domain, meta_data.data_type);
if (!write_attribute) {
continue;
}
GMutableSpan dst_span = write_attribute.as_span();
- fill_new_attribute(src_components, name, meta_data.data_type, meta_data.domain, dst_span);
+ fill_new_attribute(
+ src_components, attribute_id, meta_data.data_type, meta_data.domain, dst_span);
write_attribute.save();
}
}
@@ -306,7 +308,7 @@ static void join_components(Span<const VolumeComponent *> src_components, Geomet
* \note This takes advantage of the fact that creating attributes on joined curves never
* changes a point attribute into a spline attribute; it is always the other way around.
*/
-static void ensure_control_point_attribute(const StringRef name,
+static void ensure_control_point_attribute(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
Span<CurveComponent *> src_components,
CurveEval &result)
@@ -321,7 +323,7 @@ static void ensure_control_point_attribute(const StringRef name,
const CurveEval *current_curve = src_components[src_component_index]->get_for_read();
for (SplinePtr &spline : splines) {
- std::optional<GSpan> attribute = spline->attributes.get_for_read(name);
+ std::optional<GSpan> attribute = spline->attributes.get_for_read(attribute_id);
if (attribute) {
if (attribute->type() != type) {
@@ -334,22 +336,22 @@ static void ensure_control_point_attribute(const StringRef name,
conversions.try_convert(std::make_unique<GVArray_For_GSpan>(*attribute), type)
->materialize(converted_buffer);
- spline->attributes.remove(name);
- spline->attributes.create_by_move(name, data_type, converted_buffer);
+ spline->attributes.remove(attribute_id);
+ spline->attributes.create_by_move(attribute_id, data_type, converted_buffer);
}
}
else {
- spline->attributes.create(name, data_type);
+ spline->attributes.create(attribute_id, data_type);
- if (current_curve->attributes.get_for_read(name)) {
+ if (current_curve->attributes.get_for_read(attribute_id)) {
/* In this case the attribute did not exist, but there is a spline domain attribute
* we can retrieve a value from, as a spline to point domain conversion. So fill the
* new attribute with the value for this spline. */
GVArrayPtr current_curve_attribute = current_curve->attributes.get_for_read(
- name, data_type, nullptr);
+ attribute_id, data_type, nullptr);
- BLI_assert(spline->attributes.get_for_read(name));
- std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(name);
+ BLI_assert(spline->attributes.get_for_read(attribute_id));
+ std::optional<GMutableSpan> new_attribute = spline->attributes.get_for_write(attribute_id);
BUFFER_FOR_CPP_TYPE_VALUE(type, buffer);
current_curve_attribute->get(spline_index_in_component, buffer);
@@ -371,15 +373,15 @@ static void ensure_control_point_attribute(const StringRef name,
/**
* Fill data for an attribute on the new curve based on all source curves.
*/
-static void ensure_spline_attribute(const StringRef name,
+static void ensure_spline_attribute(const AttributeIDRef &attribute_id,
const CustomDataType data_type,
Span<CurveComponent *> src_components,
CurveEval &result)
{
const CPPType &type = *bke::custom_data_type_to_cpp_type(data_type);
- result.attributes.create(name, data_type);
- GMutableSpan result_attribute = *result.attributes.get_for_write(name);
+ result.attributes.create(attribute_id, data_type);
+ GMutableSpan result_attribute = *result.attributes.get_for_write(attribute_id);
int offset = 0;
for (const CurveComponent *component : src_components) {
@@ -388,7 +390,7 @@ static void ensure_spline_attribute(const StringRef name,
if (size == 0) {
continue;
}
- GVArrayPtr read_attribute = curve.attributes.get_for_read(name, data_type, nullptr);
+ GVArrayPtr read_attribute = curve.attributes.get_for_read(attribute_id, data_type, nullptr);
GVArray_GSpan src_span{*read_attribute};
const void *src_buffer = src_span.data();
@@ -406,19 +408,19 @@ static void ensure_spline_attribute(const StringRef name,
* \warning Splines have been moved out of the source components at this point, so it
* is important to only read curve-level data (spline domain attributes) from them.
*/
-static void join_curve_attributes(const Map<std::string, AttributeMetaData> &info,
+static void join_curve_attributes(const Map<AttributeIDRef, AttributeMetaData> &info,
Span<CurveComponent *> src_components,
CurveEval &result)
{
- for (const Map<std::string, AttributeMetaData>::Item &item : info.items()) {
- const StringRef name = item.key;
+ for (const Map<AttributeIDRef, AttributeMetaData>::Item &item : info.items()) {
+ const AttributeIDRef attribute_id = item.key;
const AttributeMetaData meta_data = item.value;
if (meta_data.domain == ATTR_DOMAIN_CURVE) {
- ensure_spline_attribute(name, meta_data.data_type, src_components, result);
+ ensure_spline_attribute(attribute_id, meta_data.data_type, src_components, result);
}
else {
- ensure_control_point_attribute(name, meta_data.data_type, src_components, result);
+ ensure_control_point_attribute(attribute_id, meta_data.data_type, src_components, result);
}
}
}
@@ -446,7 +448,7 @@ static void join_curve_components(MutableSpan<GeometrySet> src_geometry_sets, Ge
}
/* Retrieve attribute info before moving the splines out of the input components. */
- const Map<std::string, AttributeMetaData> info = get_final_attribute_info(
+ const Map<AttributeIDRef, AttributeMetaData> info = get_final_attribute_info(
{(const GeometryComponent **)src_components.data(), src_components.size()},
{"position", "radius", "tilt", "cyclic", "resolution"});
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 2cea60ea112..298c6fc1ffe 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
@@ -52,10 +52,10 @@ static void copy_attributes_to_points(CurveEval &curve,
Span<Vector<int>> point_to_vert_maps)
{
MutableSpan<SplinePtr> splines = curve.splines();
- Set<std::string> source_attribute_names = mesh_component.attribute_names();
+ Set<AttributeIDRef> source_attribute_ids = mesh_component.attribute_ids();
/* Copy builtin control point attributes. */
- if (source_attribute_names.contains_as("tilt")) {
+ if (source_attribute_ids.contains("tilt")) {
const GVArray_Typed<float> tilt_attribute = mesh_component.attribute_get_for_read<float>(
"tilt", ATTR_DOMAIN_POINT, 0.0f);
threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) {
@@ -64,9 +64,9 @@ static void copy_attributes_to_points(CurveEval &curve,
*tilt_attribute, point_to_vert_maps[i], splines[i]->tilts());
}
});
- source_attribute_names.remove_contained_as("tilt");
+ source_attribute_ids.remove_contained("tilt");
}
- if (source_attribute_names.contains_as("radius")) {
+ if (source_attribute_ids.contains("radius")) {
const GVArray_Typed<float> radius_attribute = mesh_component.attribute_get_for_read<float>(
"radius", ATTR_DOMAIN_POINT, 1.0f);
threading::parallel_for(splines.index_range(), 256, [&](IndexRange range) {
@@ -75,15 +75,15 @@ static void copy_attributes_to_points(CurveEval &curve,
*radius_attribute, point_to_vert_maps[i], splines[i]->radii());
}
});
- source_attribute_names.remove_contained_as("radius");
+ source_attribute_ids.remove_contained("radius");
}
/* Don't copy other builtin control point attributes. */
- source_attribute_names.remove_as("position");
+ source_attribute_ids.remove("position");
/* Copy dynamic control point attributes. */
- for (const StringRef name : source_attribute_names) {
- const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(name,
+ for (const AttributeIDRef &attribute_id : source_attribute_ids) {
+ const GVArrayPtr mesh_attribute = mesh_component.attribute_try_get_for_read(attribute_id,
ATTR_DOMAIN_POINT);
/* Some attributes might not exist if they were builtin attribute on domains that don't
* have any elements, i.e. a face attribute on the output of the line primitive node. */
@@ -96,8 +96,9 @@ static void copy_attributes_to_points(CurveEval &curve,
threading::parallel_for(splines.index_range(), 128, [&](IndexRange range) {
for (const int i : range) {
/* Create attribute on the spline points. */
- splines[i]->attributes.create(name, data_type);
- std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write(name);
+ splines[i]->attributes.create(attribute_id, data_type);
+ std::optional<GMutableSpan> spline_attribute = splines[i]->attributes.get_for_write(
+ attribute_id);
BLI_assert(spline_attribute);
/* Copy attribute based on the map for this spline. */
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc
index cf874bea718..dcab1ea3fb2 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc
@@ -277,17 +277,17 @@ BLI_NOINLINE static void interpolate_attribute(const Mesh &mesh,
BLI_NOINLINE static void interpolate_existing_attributes(
Span<GeometryInstanceGroup> set_groups,
Span<int> instance_start_offsets,
- const Map<std::string, AttributeKind> &attributes,
+ const Map<AttributeIDRef, AttributeKind> &attributes,
GeometryComponent &component,
Span<Vector<float3>> bary_coords_array,
Span<Vector<int>> looptri_indices_array)
{
- for (Map<std::string, AttributeKind>::Item entry : attributes.items()) {
- StringRef attribute_name = entry.key;
+ for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
+ const AttributeIDRef attribute_id = entry.key;
const CustomDataType output_data_type = entry.value.data_type;
/* The output domain is always #ATTR_DOMAIN_POINT, since we are creating a point cloud. */
OutputAttribute attribute_out = component.attribute_try_get_for_output_only(
- attribute_name, ATTR_DOMAIN_POINT, output_data_type);
+ attribute_id, ATTR_DOMAIN_POINT, output_data_type);
if (!attribute_out) {
continue;
}
@@ -301,7 +301,7 @@ BLI_NOINLINE static void interpolate_existing_attributes(
const Mesh &mesh = *source_component.get_for_read();
std::optional<AttributeMetaData> attribute_info = component.attribute_get_meta_data(
- attribute_name);
+ attribute_id);
if (!attribute_info) {
i_instance += set_group.transforms.size();
continue;
@@ -309,7 +309,7 @@ BLI_NOINLINE static void interpolate_existing_attributes(
const AttributeDomain source_domain = attribute_info->domain;
GVArrayPtr source_attribute = source_component.attribute_get_for_read(
- attribute_name, source_domain, output_data_type, nullptr);
+ attribute_id, source_domain, output_data_type, nullptr);
if (!source_attribute) {
i_instance += set_group.transforms.size();
continue;
@@ -406,7 +406,7 @@ BLI_NOINLINE static void compute_special_attributes(Span<GeometryInstanceGroup>
BLI_NOINLINE static void add_remaining_point_attributes(
Span<GeometryInstanceGroup> set_groups,
Span<int> instance_start_offsets,
- const Map<std::string, AttributeKind> &attributes,
+ const Map<AttributeIDRef, AttributeKind> &attributes,
GeometryComponent &component,
Span<Vector<float3>> bary_coords_array,
Span<Vector<int>> looptri_indices_array)
@@ -629,7 +629,7 @@ static void geo_node_point_distribute_exec(GeoNodeExecParams params)
PointCloudComponent &point_component =
geometry_set_out.get_component_for_write<PointCloudComponent>();
- Map<std::string, AttributeKind> attributes;
+ Map<AttributeIDRef, AttributeKind> attributes;
bke::geometry_set_gather_instances_attribute_info(
set_groups, {GEO_COMPONENT_TYPE_MESH}, {"position", "normal", "id"}, attributes);
add_remaining_point_attributes(set_groups,
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc
index 1b0061346c4..8752c1c669e 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_separate.cc
@@ -53,8 +53,8 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component,
Span<bool> masks,
const bool invert)
{
- for (const std::string &name : in_component.attribute_names()) {
- ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(name);
+ for (const AttributeIDRef &attribute_id : in_component.attribute_ids()) {
+ ReadAttributeLookup attribute = in_component.attribute_try_get_for_read(attribute_id);
const CustomDataType data_type = bke::cpp_type_to_custom_data_type(attribute.varray->type());
/* Only copy point attributes. Theoretically this could interpolate attributes on other
@@ -65,7 +65,7 @@ void copy_point_attributes_based_on_mask(const GeometryComponent &in_component,
}
OutputAttribute result_attribute = result_component.attribute_try_get_for_output_only(
- name, ATTR_DOMAIN_POINT, data_type);
+ attribute_id, ATTR_DOMAIN_POINT, data_type);
attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
using T = decltype(dummy);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc
index d187bf0fa71..ad91cb2d377 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_point_translate.cc
@@ -17,6 +17,8 @@
#include "UI_interface.h"
#include "UI_resources.h"
+#include "FN_multi_function_builder.hh"
+
#include "node_geometry_util.hh"
namespace blender::nodes {
@@ -26,6 +28,7 @@ static void geo_node_point_translate_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Geometry>("Geometry");
b.add_input<decl::String>("Translation");
b.add_input<decl::Vector>("Translation", "Translation_001").subtype(PROP_TRANSLATION);
+ b.add_input<decl::Bool>("Selection").default_value(true);
b.add_output<decl::Geometry>("Geometry");
}
@@ -36,7 +39,7 @@ static void geo_node_point_translate_layout(uiLayout *layout, bContext *UNUSED(C
uiItemR(layout, ptr, "input_type", 0, IFACE_("Type"), ICON_NONE);
}
-static void execute_on_component(GeoNodeExecParams params, GeometryComponent &component)
+static void execute_on_component_legacy(GeoNodeExecParams params, GeometryComponent &component)
{
OutputAttribute_Typed<float3> position_attribute =
component.attribute_try_get_for_output<float3>("position", ATTR_DOMAIN_POINT, {0, 0, 0});
@@ -53,20 +56,85 @@ static void execute_on_component(GeoNodeExecParams params, GeometryComponent &co
position_attribute.save();
}
+class SpanFieldInput final : public fn::FieldInput {
+ GSpan span_;
+
+ public:
+ SpanFieldInput(GSpan span) : FieldInput(CPPType::get<float3>(), "Span"), span_(span)
+ {
+ }
+ const GVArray *get_varray_for_context(const fn::FieldContext &UNUSED(context),
+ IndexMask UNUSED(mask),
+ ResourceScope &scope) const final
+ {
+ return &scope.construct<fn::GVArray_For_GSpan>(__func__, span_);
+ }
+};
+
+static void execute_on_component(GeometryComponent &component,
+ const Field<bool> &selection_field,
+ const Field<float3> &translation_field)
+{
+ GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT};
+ const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+ fn::FieldEvaluator selection_evaluator{field_context, domain_size};
+ selection_evaluator.add(selection_field);
+ selection_evaluator.evaluate();
+ const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0);
+
+ OutputAttribute_Typed<float3> positions = component.attribute_try_get_for_output<float3>(
+ "position", ATTR_DOMAIN_POINT, {0, 0, 0});
+ MutableSpan<float3> position_span = positions.as_span();
+ fn::Field<float3> position_field{std::make_shared<SpanFieldInput>(position_span.as_span())};
+
+ /* Add an add operation field on top of the provided translation field, which can be evaluated
+ * directly into the position virtual array. That way, any optimizations can be done more
+ * generally for the whole evaluation system. */
+ static const fn::CustomMF_SI_SI_SO<float3, float3, float3> add_fn = {
+ "Add", [](float3 a, float3 b) { return a + b; }};
+ std::shared_ptr<fn::FieldOperation> add_operation = std::make_shared<fn::FieldOperation>(
+ fn::FieldOperation(add_fn, {position_field, translation_field}));
+
+ fn::FieldEvaluator position_evaluator{field_context, &selection};
+ position_evaluator.add_with_destination({add_operation}, position_span);
+ position_evaluator.evaluate();
+
+ positions.save();
+}
+
static void geo_node_point_translate_exec(GeoNodeExecParams params)
{
GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
geometry_set = geometry_set_realize_instances(geometry_set);
- if (geometry_set.has<MeshComponent>()) {
- execute_on_component(params, geometry_set.get_component_for_write<MeshComponent>());
- }
- if (geometry_set.has<PointCloudComponent>()) {
- execute_on_component(params, geometry_set.get_component_for_write<PointCloudComponent>());
+ const NodeGeometryPointTranslate &storage =
+ *(const NodeGeometryPointTranslate *)params.node().storage;
+
+ static const Array<GeometryComponentType> types{
+ GEO_COMPONENT_TYPE_MESH,
+ GEO_COMPONENT_TYPE_POINT_CLOUD,
+ GEO_COMPONENT_TYPE_CURVE,
+ };
+
+ /* TODO: Remove legacy string input and add versioning. */
+ if (storage.input_type == GEO_NODE_ATTRIBUTE_INPUT_ATTRIBUTE) {
+ for (const GeometryComponentType type : types) {
+ if (geometry_set.has(type)) {
+ execute_on_component_legacy(params, geometry_set.get_component_for_write(type));
+ }
+ }
+ params.error_message_add(NodeWarningType::Info, "Selection not supported in legacy mode");
}
- if (geometry_set.has<CurveComponent>()) {
- execute_on_component(params, geometry_set.get_component_for_write<CurveComponent>());
+ else {
+ Field<bool> selection = params.extract_input<Field<bool>>("Selection");
+ Field<float3> translation = params.extract_input<Field<float3>>("Translation_001");
+ for (const GeometryComponentType type : types) {
+ if (geometry_set.has(type)) {
+ execute_on_component(geometry_set.get_component_for_write(type), selection, translation);
+ }
+ }
}
params.set_output("Geometry", std::move(geometry_set));
diff --git a/source/blender/nodes/geometry/nodes/node_geo_set_position.cc b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc
new file mode 100644
index 00000000000..e8591616f55
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_set_position.cc
@@ -0,0 +1,79 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "DEG_depsgraph_query.h"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes {
+
+static void geo_node_set_position_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Geometry>("Geometry");
+ b.add_input<decl::Vector>("Position");
+ b.add_input<decl::Bool>("Selection").default_value(true);
+ b.add_output<decl::Geometry>("Geometry");
+}
+
+static void set_position_in_component(GeometryComponent &component,
+ const Field<bool> &selection_field,
+ const Field<float3> &position_field)
+{
+ GeometryComponentFieldContext field_context{component, ATTR_DOMAIN_POINT};
+ const int domain_size = component.attribute_domain_size(ATTR_DOMAIN_POINT);
+
+ fn::FieldEvaluator selection_evaluator{field_context, domain_size};
+ selection_evaluator.add(selection_field);
+ selection_evaluator.evaluate();
+ const IndexMask selection = selection_evaluator.get_evaluated_as_mask(0);
+
+ OutputAttribute_Typed<float3> positions = component.attribute_try_get_for_output<float3>(
+ "position", ATTR_DOMAIN_POINT, {0, 0, 0});
+ fn::FieldEvaluator position_evaluator{field_context, &selection};
+ position_evaluator.add_with_destination(position_field, positions.varray());
+ position_evaluator.evaluate();
+ positions.save();
+}
+
+static void geo_node_set_position_exec(GeoNodeExecParams params)
+{
+ GeometrySet geometry = params.extract_input<GeometrySet>("Geometry");
+ geometry = geometry_set_realize_instances(geometry);
+ Field<bool> selection_field = params.extract_input<Field<bool>>("Selection");
+ Field<float3> position_field = params.extract_input<Field<float3>>("Position");
+
+ for (const GeometryComponentType type :
+ {GEO_COMPONENT_TYPE_MESH, GEO_COMPONENT_TYPE_POINT_CLOUD, GEO_COMPONENT_TYPE_CURVE}) {
+ if (geometry.has(type)) {
+ set_position_in_component(
+ geometry.get_component_for_write(type), selection_field, position_field);
+ }
+ }
+
+ params.set_output("Geometry", std::move(geometry));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_set_position()
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_SET_POSITION, "Set Position", NODE_CLASS_GEOMETRY, 0);
+ ntype.geometry_node_execute = blender::nodes::geo_node_set_position_exec;
+ ntype.declare = blender::nodes::geo_node_set_position_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/intern/geometry_nodes_eval_log.cc b/source/blender/nodes/intern/geometry_nodes_eval_log.cc
index 7487f11d77d..3b3b643d0ae 100644
--- a/source/blender/nodes/intern/geometry_nodes_eval_log.cc
+++ b/source/blender/nodes/intern/geometry_nodes_eval_log.cc
@@ -161,8 +161,10 @@ GeometryValueLog::GeometryValueLog(const GeometrySet &geometry_set, bool log_ful
{
bke::geometry_set_instances_attribute_foreach(
geometry_set,
- [&](StringRefNull attribute_name, const AttributeMetaData &meta_data) {
- this->attributes_.append({attribute_name, meta_data.domain, meta_data.data_type});
+ [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) {
+ if (attribute_id.is_named()) {
+ this->attributes_.append({attribute_id.name(), meta_data.domain, meta_data.data_type});
+ }
return true;
},
8);
diff --git a/source/blender/nodes/intern/node_socket.cc b/source/blender/nodes/intern/node_socket.cc
index d386781e3ce..8efd6c55459 100644
--- a/source/blender/nodes/intern/node_socket.cc
+++ b/source/blender/nodes/intern/node_socket.cc
@@ -48,6 +48,7 @@
#include "NOD_socket.h"
#include "FN_cpp_type_make.hh"
+#include "FN_field.hh"
using namespace blender;
using blender::nodes::SocketDeclarationPtr;
@@ -701,8 +702,14 @@ static bNodeSocketType *make_socket_type_bool()
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
*(bool *)r_value = ((bNodeSocketValueBoolean *)socket.default_value)->value;
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<bool>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ bool value;
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value) blender::fn::Field<bool>(blender::fn::make_constant_field(value));
+ };
return socktype;
}
@@ -713,8 +720,14 @@ static bNodeSocketType *make_socket_type_float(PropertySubType subtype)
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
*(float *)r_value = ((bNodeSocketValueFloat *)socket.default_value)->value;
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<float>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ float value;
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value) blender::fn::Field<float>(blender::fn::make_constant_field(value));
+ };
return socktype;
}
@@ -725,8 +738,14 @@ static bNodeSocketType *make_socket_type_int(PropertySubType subtype)
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
*(int *)r_value = ((bNodeSocketValueInt *)socket.default_value)->value;
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<int>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ int value;
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value) blender::fn::Field<int>(blender::fn::make_constant_field(value));
+ };
return socktype;
}
@@ -737,8 +756,14 @@ static bNodeSocketType *make_socket_type_vector(PropertySubType subtype)
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
*(blender::float3 *)r_value = ((bNodeSocketValueVector *)socket.default_value)->value;
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<blender::float3>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ blender::float3 value;
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value) blender::fn::Field<blender::float3>(blender::fn::make_constant_field(value));
+ };
return socktype;
}
@@ -751,8 +776,15 @@ static bNodeSocketType *make_socket_type_rgba()
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
*(blender::ColorGeometry4f *)r_value = ((bNodeSocketValueRGBA *)socket.default_value)->value;
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<blender::ColorGeometry4f>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ blender::ColorGeometry4f value;
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value)
+ blender::fn::Field<blender::ColorGeometry4f>(blender::fn::make_constant_field(value));
+ };
return socktype;
}
@@ -763,8 +795,15 @@ static bNodeSocketType *make_socket_type_string()
socktype->get_base_cpp_value = [](const bNodeSocket &socket, void *r_value) {
new (r_value) std::string(((bNodeSocketValueString *)socket.default_value)->value);
};
- socktype->get_geometry_nodes_cpp_type = socktype->get_base_cpp_type;
- socktype->get_geometry_nodes_cpp_value = socktype->get_base_cpp_value;
+ socktype->get_geometry_nodes_cpp_type = []() {
+ return &blender::fn::CPPType::get<blender::fn::Field<std::string>>();
+ };
+ socktype->get_geometry_nodes_cpp_value = [](const bNodeSocket &socket, void *r_value) {
+ std::string value;
+ value.~basic_string();
+ socket.typeinfo->get_base_cpp_value(socket, &value);
+ new (r_value) blender::fn::Field<std::string>(blender::fn::make_constant_field(value));
+ };
return socktype;
}