[Bf-blender-cvs] [5cf6f570c65] master: Geometry Nodes: use virtual arrays in internal attribute api

Jacques Lucke noreply at git.blender.org
Sat Apr 17 16:46:09 CEST 2021


Commit: 5cf6f570c65daa3325055e54bb07fa864f269960
Author: Jacques Lucke
Date:   Sat Apr 17 16:41:03 2021 +0200
Branches: master
https://developer.blender.org/rB5cf6f570c65daa3325055e54bb07fa864f269960

Geometry Nodes: use virtual arrays in internal attribute api

A virtual array is a data structure that is similar to a normal array
in that its elements can be accessed by an index. However, a virtual
array does not have to be a contiguous array internally. Instead, its
elements can be layed out arbitrarily while element access happens
through a virtual function call. However, the virtual array data
structures are designed so that the virtual function call can be avoided
in cases where it could become a bottleneck.

Most commonly, a virtual array is backed by an actual array/span or
is a single value internally, that is the same for every index.
Besides those, there are many more specialized virtual arrays like the
ones that provides vertex positions based on the `MVert` struct or
vertex group weights.

Not all attributes used by geometry nodes are stored in simple contiguous
arrays. To provide uniform access to all kinds of attributes, the attribute
API has to provide virtual array functionality that hides the implementation
details of attributes.

Before this refactor, the attribute API provided its own virtual array
implementation as part of the `ReadAttribute` and `WriteAttribute` types.
That resulted in unnecessary code duplication with the virtual array system.
Even worse, it bound many algorithms used by geometry nodes to the specifics
of the attribute API, even though they could also use different data sources
(such as data from sockets, default values, later results of expressions, ...).

This refactor removes the `ReadAttribute` and `WriteAttribute` types and
replaces them with `GVArray` and `GVMutableArray` respectively. The `GV`
stands for "generic virtual". The "generic" means that the data type contained
in those virtual arrays is only known at run-time. There are the corresponding
statically typed types `VArray<T>` and `VMutableArray<T>` as well.

No regressions are expected from this refactor. It does come with one
improvement for users. The attribute API can convert the data type
on write now. This is especially useful when writing to builtin attributes
like `material_index` with e.g. the Attribute Math node (which usually
just writes to float attributes, while `material_index` is an integer attribute).

Differential Revision: https://developer.blender.org/D10994

===================================================================

M	source/blender/blenkernel/BKE_attribute_access.hh
M	source/blender/blenkernel/BKE_geometry_set.hh
M	source/blender/blenkernel/intern/attribute_access.cc
M	source/blender/blenkernel/intern/attribute_access_intern.hh
M	source/blender/blenkernel/intern/geometry_component_mesh.cc
M	source/blender/blenkernel/intern/geometry_component_pointcloud.cc
M	source/blender/blenkernel/intern/geometry_set_instances.cc
M	source/blender/blenlib/BLI_virtual_array.hh
M	source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc
M	source/blender/functions/FN_generic_virtual_array.hh
M	source/blender/nodes/NOD_geometry_exec.hh
M	source/blender/nodes/NOD_type_conversions.hh
M	source/blender/nodes/geometry/nodes/node_geo_align_rotation_to_vector.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_clamp.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_color_ramp.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_combine_xyz.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_compare.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_convert.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_fill.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_map_range.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_math.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_mix.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_proximity.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_randomize.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_sample_texture.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_separate_xyz.cc
M	source/blender/nodes/geometry/nodes/node_geo_attribute_vector_math.cc
M	source/blender/nodes/geometry/nodes/node_geo_bounding_box.cc
M	source/blender/nodes/geometry/nodes/node_geo_join_geometry.cc
M	source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc
M	source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc
M	source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc
M	source/blender/nodes/geometry/nodes/node_geo_point_distribute.cc
M	source/blender/nodes/geometry/nodes/node_geo_point_instance.cc
M	source/blender/nodes/geometry/nodes/node_geo_point_rotate.cc
M	source/blender/nodes/geometry/nodes/node_geo_point_scale.cc
M	source/blender/nodes/geometry/nodes/node_geo_point_separate.cc
M	source/blender/nodes/geometry/nodes/node_geo_point_translate.cc
M	source/blender/nodes/geometry/nodes/node_geo_points_to_volume.cc
M	source/blender/nodes/intern/node_geometry_exec.cc
M	source/blender/nodes/intern/type_conversions.cc

===================================================================

diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh
index 120b4e08b9c..f6a6de04b70 100644
--- a/source/blender/blenkernel/BKE_attribute_access.hh
+++ b/source/blender/blenkernel/BKE_attribute_access.hh
@@ -20,6 +20,7 @@
 
 #include "FN_cpp_type.hh"
 #include "FN_generic_span.hh"
+#include "FN_generic_virtual_array.hh"
 
 #include "BKE_attribute.h"
 
@@ -30,6 +31,10 @@
 namespace blender::bke {
 
 using fn::CPPType;
+using fn::GVArray;
+using fn::GVArrayPtr;
+using fn::GVMutableArray;
+using fn::GVMutableArrayPtr;
 
 const CPPType *custom_data_type_to_cpp_type(const CustomDataType type);
 CustomDataType cpp_type_to_custom_data_type(const CPPType &type);
@@ -37,113 +42,93 @@ CustomDataType attribute_data_type_highest_complexity(Span<CustomDataType> data_
 AttributeDomain attribute_domain_highest_priority(Span<AttributeDomain> domains);
 
 /**
- * This class offers an indirection for reading an attribute.
- * This is useful for the following reasons:
- * - Blender does not store all attributes the same way.
- *   The simplest case are custom data layers with primitive types.
- *   A bit more complex are mesh attributes like the position of vertices,
- *   which are embedded into the MVert struct.
- *   Even more complex to access are vertex weights.
- * - Sometimes attributes are stored on one domain, but we want to access
- *   the attribute on a different domain. Therefore, we have to interpolate
- *   between the domains.
+ * Used when looking up a "plain attribute" based on a name for reading from it.
  */
-class ReadAttribute {
- protected:
-  const AttributeDomain domain_;
-  const CPPType &cpp_type_;
-  const CustomDataType custom_data_type_;
-  const int64_t size_;
-
-  /* Protects the span below, so that no two threads initialize it at the same time. */
-  mutable std::mutex span_mutex_;
-  /* When it is not null, it points to the attribute array or a temporary array that contains all
-   * the attribute values. */
-  mutable void *array_buffer_ = nullptr;
-  /* Is true when the buffer above is owned by the attribute accessor. */
-  mutable bool array_is_temporary_ = false;
+struct ReadAttributeLookup {
+  /* The virtual array that is used to read from this attribute. */
+  GVArrayPtr varray;
+  /* Domain the attribute lives on in the geometry. */
+  AttributeDomain domain;
 
- public:
-  ReadAttribute(AttributeDomain domain, const CPPType &cpp_type, const int64_t size)
-      : domain_(domain),
-        cpp_type_(cpp_type),
-        custom_data_type_(cpp_type_to_custom_data_type(cpp_type)),
-        size_(size)
+  /* Convenience function to check if the attribute has been found. */
+  operator bool() const
   {
+    return this->varray.get() != nullptr;
   }
+};
 
-  virtual ~ReadAttribute();
+/**
+ * Used when looking up a "plain attribute" based on a name for reading from it and writing to it.
+ */
+struct WriteAttributeLookup {
+  /* The virtual array that is used to read from and write to the attribute. */
+  GVMutableArrayPtr varray;
+  /* Domain the attributes lives on in the geometry. */
+  AttributeDomain domain;
 
-  AttributeDomain domain() const
+  /* Convenience function to check if the attribute has been found. */
+  operator bool() const
   {
-    return domain_;
+    return this->varray.get() != nullptr;
   }
+};
 
-  const CPPType &cpp_type() const
-  {
-    return cpp_type_;
-  }
+/**
+ * An output attribute allows writing to an attribute (and optionally reading as well). It adds
+ * some convenience features on top of `GVMutableArray` that are very commonly used.
+ *
+ * Supported convenience features:
+ * - Implicit type conversion when writing to builtin attributes.
+ * - Supports simple access to a span containing the attribute values (that avoids the use of
+ *   VMutableArray_Span in many cases).
+ * - An output attribute can live side by side with an existing attribute with a different domain
+ *   or data type. The old attribute will only be overwritten when the #save function is called.
+ */
+class OutputAttribute {
+ public:
+  using SaveFn = std::function<void(OutputAttribute &)>;
 
-  CustomDataType custom_data_type() const
+ private:
+  GVMutableArrayPtr varray_;
+  AttributeDomain domain_;
+  SaveFn save_;
+  std::optional<fn::GVMutableArray_GSpan> optional_span_varray_;
+  bool ignore_old_values_ = false;
+
+ public:
+  OutputAttribute() = default;
+
+  OutputAttribute(GVMutableArrayPtr varray,
+                  AttributeDomain domain,
+                  SaveFn save,
+                  const bool ignore_old_values)
+      : varray_(std::move(varray)),
+        domain_(domain),
+        save_(std::move(save)),
+        ignore_old_values_(ignore_old_values)
   {
-    return custom_data_type_;
   }
 
-  int64_t size() const
+  operator bool() const
   {
-    return size_;
+    return varray_.get() != nullptr;
   }
 
-  void get(const int64_t index, void *r_value) const
+  GVMutableArray &operator*()
   {
-    BLI_assert(index < size_);
-    this->get_internal(index, r_value);
+    return *varray_;
   }
 
-  /* Get a span that contains all attribute values. */
-  fn::GSpan get_span() const;
-
-  template<typename T> Span<T> get_span() const
+  GVMutableArray *operator->()
   {
-    return this->get_span().typed<T>();
+    return varray_.get();
   }
 
- protected:
-  /* r_value is expected to be uninitialized. */
-  virtual void get_internal(const int64_t index, void *r_value) const = 0;
-
-  virtual void initialize_span() const;
-};
-
-/**
- * This exists for similar reasons as the ReadAttribute class, except that
- * it does not deal with interpolation between domains.
- */
-class WriteAttribute {
- protected:
-  const AttributeDomain domain_;
-  const CPPType &cpp_type_;
-  const CustomDataType custom_data_type_;
-  const int64_t size_;
-
-  /* When not null, this points either to the attribute array or to a temporary array. */
-  void *array_buffer_ = nullptr;
-  /* True, when the buffer points to a temporary array. */
-  bool array_is_temporary_ = false;
-  /* This helps to protect against forgetting to apply changes done to the array. */
-  bool array_should_be_applied_ = false;
-
- public:
-  WriteAttribute(AttributeDomain domain, const CPPType &cpp_type, const int64_t size)
-      : domain_(domain),
-        cpp_type_(cpp_type),
-        custom_data_type_(cpp_type_to_custom_data_type(cpp_type)),
-        size_(size)
+  GVMutableArray &varray()
   {
+    return *varray_;
   }
 
-  virtual ~WriteAttribute();
-
   AttributeDomain domain() const
   {
     return domain_;
@@ -151,168 +136,94 @@ class WriteAttribute {
 
   const CPPType &cpp_type() const
   {
-    return cpp_type_;
+    return varray_->type();
   }
 
   CustomDataType custom_data_type() const
   {
-    return custom_data_type_;
-  }
-
-  int64_t size() const
-  {
-    return size_;
-  }
-
-  void get(const int64_t index, void *r_value) const
-  {
-    BLI_assert(index < size_);
-    this->get_internal(index, r_value);
+    return cpp_type_to_custom_data_type(this->cpp_type());
   }
 
-  void set(const int64_t index, const void *value)
+  fn::GMutableSpan as_span()
   {
-    BLI_assert(index < size_);
-    this->set_internal(index, value);
+    if (!optional_span_varray_.has_value()) {
+      const bool materialize_old_values = !ignore_old_values_;
+      optional_span_varray_.emplace(*varray_, materialize_old_values);
+    }
+    fn::GVMutableArray_GSpan &span_varray = *optional_span_varray_;
+    return span_varray;
   }
 
-  /* Get a span that new attribute values can be written into. When all values have been changed,
-   * #apply_span has to be called. */
-  fn::GMutableSpan get_span();
-  /* The span returned by this method might not contain the current attribute values. */
-  fn::GMutableSpan get_span_for_write_only();
-  /* Write the changes to the span into the actual attribute, if they aren't already. */
-  void apply_span();
-
-  template<typename T> MutableSpan<T> get_span()
+  template<typename T> MutableSpan<T> as_span()
   {
-    return this->get_span().typed<T>();
+    return this->as_span().typed<T>();
   }
 
-  template<typename T> MutableSpan<T> get_span_for_write_only()
-  {
-    return this->get_span_for_write_only().typed<T>();
-  }
-
- protected:
-  virtual void get_internal(const int64_t index, void *r_value) const = 0;
-  virtual void set_internal(const int64_t index, const void *value) = 0;
-
-  virtual void initialize_span(const bool write_only);
-  virtual void apply_span_if_necessary();
+  void save();
 };
 
-using ReadAttributePtr = std::unique_ptr<ReadAttribute>;
-using WriteAttributePtr = std::unique_ptr<WriteAttribute>;
-
-/* This provides type safe access to an attribute.
- * The underlying ReadAttribute is owned optionally. */
-template<typename T> class TypedReadAttribute {
+/**
+ * Same as OutputAttribute, but should be used when the data type is known at compile time.
+ */
+template<typename T> class OutputAttribute_Typed {
  private:
-  std::unique_ptr<const ReadAttribute> owned_attribute_;
-  const ReadAttribute *attribute_;
+  OutputAttribute attribute_;
+  std::optional<fn::GVMutableArray_Typed<T>> optional_varray_;
+  VMutableArray<T> *varray_ = nullptr;
 
  public:
-  TypedReadAttribute(ReadAttributePtr attribute) : TypedReadAttribute(*attribute)
+  OutputAttribute_Typed(OutputAttribute attribute) : attribute_(std::move(attribute))
   {
-    owned_attribute_ = std::move(attribute);
-    BLI_assert(owned_attribute_);
+    if (attribute_) {
+      optional_varray_.emplace(attribute_.varray());
+      varray_ = &**optional_varray_;
+    }
   }
 
-  TypedReadAttribute(const ReadAttribute &attribute) : attribute_(&attribute)
+  operator bool() const
   {
-    BLI_assert(attribute_->cpp_type().is<T>());
+    return varray_ != nullptr;
   }
 
-  int64_t size() const
+  VMutableArray<T> &operator*()
   {
-    return attribute_->size();
+    return *varray_;
   }
 
-  T operator[](const int64_t index) const
+  VMutableArray<T> *operator->()
   {
-    BLI_assert(index < attribute_->size());
-    T value;
-    value.~T();
-    attribute_->get(index, &value);
-    return value;
+    return varray_;
   }
 
-  /* Get a span to that contains all attrib

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list