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:
authorJacques Lucke <jacques@blender.org>2021-09-09 13:54:20 +0300
committerJacques Lucke <jacques@blender.org>2021-09-09 13:54:20 +0300
commitbf47fb40fd6f0ee9386e9936cf213a1049c55b61 (patch)
treec8bbe7c00b27ac845e4adbc214b7f29ec670a9f3
parent0f6be4e1520087bfe6d1dc98b61d65686ae09b3f (diff)
Geometry Nodes: fields and anonymous attributes
This implements the initial core framework for fields and anonymous attributes (also see T91274). The new functionality is hidden behind the "Geometry Nodes Fields" feature flag. When enabled in the user preferences, the following new nodes become available: `Position`, `Index`, `Normal`, `Set Position` and `Attribute Capture`. Socket inspection has not been updated to work with fields yet. Besides these changes at the user level, this patch contains the ground work for: * building and evaluating fields at run-time (`FN_fields.hh`) and * creating and accessing anonymous attributes on geometry (`BKE_anonymous_attribute.h`). For evaluating fields we use a new so called multi-function procedure (`FN_multi_function_procedure.hh`). It allows composing multi-functions in arbitrary ways and supports efficient evaluation as is required by fields. See `FN_multi_function_procedure.hh` for more details on how this evaluation mechanism can be used. A new `AttributeIDRef` has been added which allows handling named and anonymous attributes in the same way in many places. Hans and I worked on this patch together. Differential Revision: https://developer.blender.org/D12414
-rw-r--r--release/scripts/startup/bl_ui/space_userpref.py1
-rw-r--r--release/scripts/startup/nodeitems_builtins.py9
-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.hh456
-rw-r--r--source/blender/functions/FN_field_cpp_type.hh72
-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.hh452
-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.cc794
-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/makesdna/DNA_userdef_types.h3
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c20
-rw-r--r--source/blender/makesrna/intern/rna_userdef.c4
-rw-r--r--source/blender/modifiers/intern/MOD_nodes.cc21
-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_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
67 files changed, 6674 insertions, 468 deletions
diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py
index 1ac19d020d8..0d1cbc424d7 100644
--- a/release/scripts/startup/bl_ui/space_userpref.py
+++ b/release/scripts/startup/bl_ui/space_userpref.py
@@ -2254,6 +2254,7 @@ class USERPREF_PT_experimental_new_features(ExperimentalPanel, Panel):
({"property": "use_sculpt_tools_tilt"}, "T82877"),
({"property": "use_extended_asset_browser"}, ("project/view/130/", "Project Page")),
({"property": "use_override_templates"}, ("T73318", "Milestone 4")),
+ ({"property": "use_geometry_nodes_fields"}, "T91274"),
),
)
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index 668ea4bfae5..114e654e22b 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -180,6 +180,10 @@ def object_eevee_cycles_shader_nodes_poll(context):
eevee_cycles_shader_nodes_poll(context))
+def geometry_nodes_fields_poll(context):
+ return context.preferences.experimental.use_geometry_nodes_fields
+
+
# All standard node categories currently used in nodes.
shader_node_categories = [
@@ -478,6 +482,7 @@ geometry_node_categories = [
GeometryNodeCategory("GEO_ATTRIBUTE", "Attribute", items=[
NodeItem("GeometryNodeAttributeRandomize"),
NodeItem("GeometryNodeAttributeMath"),
+ NodeItem("GeometryNodeAttributeCapture", poll=geometry_nodes_fields_poll),
NodeItem("GeometryNodeAttributeClamp"),
NodeItem("GeometryNodeAttributeCompare"),
NodeItem("GeometryNodeAttributeConvert"),
@@ -534,6 +539,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeJoinGeometry"),
NodeItem("GeometryNodeSeparateComponents"),
NodeItem("GeometryNodeRaycast"),
+ NodeItem("GeometryNodeSetPosition", poll=geometry_nodes_fields_poll),
]),
GeometryNodeCategory("GEO_INPUT", "Input", items=[
NodeItem("GeometryNodeObjectInfo"),
@@ -544,6 +550,9 @@ geometry_node_categories = [
NodeItem("FunctionNodeInputVector"),
NodeItem("GeometryNodeInputMaterial"),
NodeItem("GeometryNodeIsViewport"),
+ NodeItem("GeometryNodeInputPosition", poll=geometry_nodes_fields_poll),
+ NodeItem("GeometryNodeInputIndex", poll=geometry_nodes_fields_poll),
+ NodeItem("GeometryNodeInputNormal", poll=geometry_nodes_fields_poll),
]),
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..25188531580
--- /dev/null
+++ b/source/blender/functions/FN_field.hh
@@ -0,0 +1,456 @@
+/*
+ * 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 and 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_varrays_;
+ 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 &varray = scope_.construct<GVMutableArray_For_VMutableArray<T>>(__func__, dst);
+ return this->add_with_destination(GField(std::move(field)), varray);
+ }
+
+ /**
+ * \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 &varray = scope_.construct<GVMutableArray_For_MutableSpan<T>>(__func__, dst);
+ return this->add_with_destination(std::move(field), varray);
+ }
+
+ 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_varrays_.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_varrays = {});
+
+/* --------------------------------------------------------------------
+ * 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..5e6f1b5a585
--- /dev/null
+++ b/source/blender/functions/FN_field_cpp_type.hh
@@ -0,0 +1,72 @@
+/*
+ * 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 #GField and #Field<T> have the same layout, to enable casting 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..62f2292c1d9
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_procedure.hh
@@ -0,0 +1,452 @@
+/*
+ * 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;
+
+ 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..fa7dea97b7f
--- /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 built, 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 the other
+ * fields that depend on it directly.
+ */
+ 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 them. */
+ 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_varrays: 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_varrays 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_varrays)
+{
+ Vector<const GVArray *> r_varrays(fields_to_evaluate.size(), nullptr);
+ const int array_size = mask.min_array_size();
+
+ /* Destination arrays are optional. Create a small utility method to access them. */
+ auto get_dst_varray_if_available = [&](int index) -> GVMutableArray * {
+ if (dst_varrays.is_empty()) {
+ return nullptr;
+ }
+ BLI_assert(dst_varrays[index] == nullptr || dst_varrays[index]->size() >= array_size);
+ return dst_varrays[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);
+ }
+ }
+
+ /* 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_varray_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 supplied destination arrays if necessary. In some cases the evaluation above has
+ * written the computed data in the right place already. */
+ if (!dst_varrays.is_empty()) {
+ for (const int out_index : fields_to_evaluate.index_range()) {
+ GVMutableArray *output_varray = get_dst_varray_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_varrays_.append(&dst);
+ output_pointer_infos_.append({});
+ return field_index;
+}
+
+int FieldEvaluator::add_with_destination(GField field, GMutableSpan dst)
+{
+ GVMutableArray &varray = scope_.construct<GVMutableArray_For_GMutableSpan>(__func__, dst);
+ return this->add_with_destination(std::move(field), varray);
+}
+
+int FieldEvaluator::add(GField field, const GVArray **varray_ptr)
+{
+ const int field_index = fields_to_evaluate_.append_and_get_index(std::move(field));
+ dst_varrays_.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_varrays_.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_varrays_);
+ 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..6eff7bc09f8
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_procedure.cc
@@ -0,0 +1,794 @@
+/*
+ * 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;
+}
+
+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/makesdna/DNA_userdef_types.h b/source/blender/makesdna/DNA_userdef_types.h
index 1cb561ddd9f..7160d2c3751 100644
--- a/source/blender/makesdna/DNA_userdef_types.h
+++ b/source/blender/makesdna/DNA_userdef_types.h
@@ -646,7 +646,8 @@ typedef struct UserDef_Experimental {
char use_sculpt_tools_tilt;
char use_extended_asset_browser;
char use_override_templates;
- char _pad[5];
+ char use_geometry_nodes_fields;
+ char _pad[4];
/** `makesdna` does not allow empty structs. */
} UserDef_Experimental;
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index a7c0372f309..b294e93310c 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", "Which domain to store the data in");
+ 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/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c
index fe1b0757690..0a328c3b068 100644
--- a/source/blender/makesrna/intern/rna_userdef.c
+++ b/source/blender/makesrna/intern/rna_userdef.c
@@ -6300,6 +6300,10 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
RNA_def_property_boolean_sdna(prop, NULL, "use_override_templates", 1);
RNA_def_property_ui_text(
prop, "Override Templates", "Enable library override template in the python API");
+
+ prop = RNA_def_property(srna, "use_geometry_nodes_fields", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "use_geometry_nodes_fields", 1);
+ RNA_def_property_ui_text(prop, "Geometry Nodes Fields", "Enable field nodes in geometry nodes");
}
static void rna_def_userdef_addon_collection(BlenderRNA *brna, PropertyRNA *cprop)
diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc
index 47fb6826ca3..ba557c3976c 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,28 +411,36 @@ 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: {
- new (r_value) std::string(IDP_String(&property));
+ std::string value = IDP_String(&property);
+ new (r_value)
+ blender::fn::Field<std::string>(blender::fn::make_constant_field(std::move(value)));
break;
}
case SOCK_OBJECT: {
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 1382efa025b..6f40687540c 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 17cd8e987a7..74740ba244f 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..b8f126ef1db
--- /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, the data
+ * will still be normalized, 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_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;
}