[Bf-blender-cvs] [ac257864a67] geometry-nodes: Geometry Nodes: support accessing attributes with spans

Jacques Lucke noreply at git.blender.org
Thu Nov 26 18:22:50 CET 2020


Commit: ac257864a67450da29694ba7fbdc8461aea26a97
Author: Jacques Lucke
Date:   Thu Nov 26 18:11:03 2020 +0100
Branches: geometry-nodes
https://developer.blender.org/rBac257864a67450da29694ba7fbdc8461aea26a97

Geometry Nodes: support accessing attributes with spans

Before, attributes could only be accessed via setter and getter functions.
While this works well, it can be cumbersome and slow.
It would be better if most code could just operate on the underlying
attribute array directly. The issue is that some attributes are currently
not stored in plain arrays (e.g. vertex positions, vertex groups, ...).

This patch implements a solution where these "special" attributes are
converted into a temporary array for easy access and then copied
back into their correct form after all modifications are done.

Attribute accessors now have a `get_span` and `apply_span` method,
which can be used exactly for that purpose. In the case of attributes
that are plain arrays internally, only small constant overhead is added.

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

M	source/blender/blenkernel/BKE_attribute_access.hh
M	source/blender/blenkernel/intern/attribute_access.cc

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

diff --git a/source/blender/blenkernel/BKE_attribute_access.hh b/source/blender/blenkernel/BKE_attribute_access.hh
index db1c4a3f043..4dc5708ac70 100644
--- a/source/blender/blenkernel/BKE_attribute_access.hh
+++ b/source/blender/blenkernel/BKE_attribute_access.hh
@@ -16,7 +16,10 @@
 
 #pragma once
 
+#include <mutex>
+
 #include "FN_cpp_type.hh"
+#include "FN_spans.hh"
 
 #include "BKE_attribute.h"
 
@@ -46,6 +49,14 @@ class ReadAttribute {
   const CPPType &cpp_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;
+
  public:
   ReadAttribute(AttributeDomain domain, const CPPType &cpp_type, const int64_t size)
       : domain_(domain), cpp_type_(cpp_type), size_(size)
@@ -75,9 +86,14 @@ class ReadAttribute {
     this->get_internal(index, r_value);
   }
 
+  /* Get a span that contains all attribute values. */
+  fn::GSpan get_span() const;
+
  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;
 };
 
 /**
@@ -90,6 +106,13 @@ class WriteAttribute {
   const CPPType &cpp_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 agains 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), size_(size)
@@ -125,9 +148,18 @@ class WriteAttribute {
     this->set_internal(index, value);
   }
 
+  /* Get a span that new attribute values can be written into. When all values have been changed,
+   * #apply_span has to be called. The span might not contain the original attribute values. */
+  fn::GMutableSpan get_span();
+  /* Write the changes to the span into the actual attribute, if they aren't already. */
+  void apply_span();
+
  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();
+  virtual void apply_span_if_necessary();
 };
 
 using ReadAttributePtr = std::unique_ptr<ReadAttribute>;
@@ -158,6 +190,12 @@ template<typename T> class TypedReadAttribute {
     attribute_->get(index, &value);
     return value;
   }
+
+  /* Get a span to that contains all attribute values for faster and more convenient access. */
+  Span<T> get_span() const
+  {
+    return attribute_->get_span().typed<T>();
+  }
 };
 
 /* This provides type safe access to an attribute. */
@@ -190,6 +228,20 @@ template<typename T> class TypedWriteAttribute {
   {
     attribute_->set(index, &value);
   }
+
+  /* Get a span that new values can be written into. Once all values have been updated #apply_span
+   * has to be called. The span might *not* contain the initial attribute values, so one should
+   * generally only write to the span. */
+  MutableSpan<T> get_span()
+  {
+    return attribute_->get_span().typed<T>();
+  }
+
+  /* Write back all changes to the actual attribute, if necessary. */
+  void apply_span()
+  {
+    attribute_->apply_span();
+  }
 };
 
 using FloatReadAttribute = TypedReadAttribute<float>;
diff --git a/source/blender/blenkernel/intern/attribute_access.cc b/source/blender/blenkernel/intern/attribute_access.cc
index ce56118e9ba..46ddef720a5 100644
--- a/source/blender/blenkernel/intern/attribute_access.cc
+++ b/source/blender/blenkernel/intern/attribute_access.cc
@@ -31,8 +31,12 @@
 #include "BLI_float2.hh"
 #include "BLI_span.hh"
 
+#include "CLG_log.h"
+
 #include "NOD_node_tree_multi_function.hh"
 
+static CLG_LogRef LOG = {"bke.attribute_access"};
+
 using blender::float3;
 using blender::Set;
 using blender::StringRef;
@@ -45,8 +49,89 @@ namespace blender::bke {
 /** \name Attribute Accessor implementations
  * \{ */
 
-ReadAttribute::~ReadAttribute() = default;
-WriteAttribute::~WriteAttribute() = default;
+ReadAttribute::~ReadAttribute()
+{
+  if (array_is_temporary_ && array_buffer_ != nullptr) {
+    cpp_type_.destruct_n(array_buffer_, size_);
+    MEM_freeN(array_buffer_);
+  }
+}
+
+fn::GSpan ReadAttribute::get_span() const
+{
+  if (size_ == 0) {
+    return fn::GSpan(cpp_type_);
+  }
+  if (array_buffer_ == nullptr) {
+    std::lock_guard lock{span_mutex_};
+    if (array_buffer_ == nullptr) {
+      this->initialize_span();
+    }
+  }
+  return fn::GSpan(cpp_type_, array_buffer_, size_);
+}
+
+void ReadAttribute::initialize_span() const
+{
+  const int element_size = cpp_type_.size();
+  array_buffer_ = MEM_mallocN_aligned(size_ * element_size, cpp_type_.alignment(), __func__);
+  array_is_temporary_ = true;
+  for (const int i : IndexRange(size_)) {
+    this->get_internal(i, POINTER_OFFSET(array_buffer_, i * element_size));
+  }
+}
+
+WriteAttribute::~WriteAttribute()
+{
+  if (array_should_be_applied_) {
+    CLOG_ERROR(&LOG, "Forgot to call apply_span_if_necessary.");
+  }
+  if (array_is_temporary_ && array_buffer_ != nullptr) {
+    cpp_type_.destruct_n(array_buffer_, size_);
+    MEM_freeN(array_buffer_);
+  }
+}
+
+/**
+ * Get a mutable span that can be modified. When all modifications to the attribute are done,
+ * #apply_span_if_necessary should be called.
+ */
+fn::GMutableSpan WriteAttribute::get_span()
+{
+  if (size_ == 0) {
+    return fn::GMutableSpan(cpp_type_);
+  }
+  if (array_buffer_ == nullptr) {
+    this->initialize_span();
+  }
+  array_should_be_applied_ = true;
+  return fn::GMutableSpan(cpp_type_, array_buffer_, size_);
+}
+
+void WriteAttribute::initialize_span()
+{
+  array_buffer_ = MEM_mallocN_aligned(cpp_type_.size() * size_, cpp_type_.alignment(), __func__);
+  array_is_temporary_ = true;
+  /* This does nothing for trivial types, but is necessary for general correctness. */
+  cpp_type_.construct_default_n(array_buffer_, size_);
+}
+
+void WriteAttribute::apply_span()
+{
+  this->apply_span_if_necessary();
+  array_should_be_applied_ = false;
+}
+
+void WriteAttribute::apply_span_if_necessary()
+{
+  /* Only works when the span has been initialized beforehand. */
+  BLI_assert(array_buffer_ != nullptr);
+
+  const int element_size = cpp_type_.size();
+  for (const int i : IndexRange(size_)) {
+    this->set_internal(i, POINTER_OFFSET(array_buffer_, i * element_size));
+  }
+}
 
 class VertexWeightWriteAttribute final : public WriteAttribute {
  private:
@@ -126,6 +211,17 @@ template<typename T> class ArrayWriteAttribute final : public WriteAttribute {
   {
     data_[index] = *reinterpret_cast<const T *>(value);
   }
+
+  void initialize_span() override
+  {
+    array_buffer_ = data_.data();
+    array_is_temporary_ = false;
+  }
+
+  void apply_span_if_necessary() override
+  {
+    /* Do nothing, because the span contains the attribute itself already. */
+  }
 };
 
 template<typename T> class ArrayReadAttribute final : public ReadAttribute {
@@ -142,6 +238,13 @@ template<typename T> class ArrayReadAttribute final : public ReadAttribute {
   {
     new (r_value) T(data_[index]);
   }
+
+  void initialize_span() const override
+  {
+    /* The data will not be modified, so this const_cast is fine. */
+    array_buffer_ = const_cast<T *>(data_.data());
+    array_is_temporary_ = false;
+  }
 };
 
 template<typename StructT, typename ElemT, typename GetFuncT, typename SetFuncT>
@@ -225,6 +328,14 @@ class ConstantReadAttribute final : public ReadAttribute {
   {
     this->cpp_type_.copy_to_uninitialized(value_, r_value);
   }
+
+  void initialize_span() const override
+  {
+    const int element_size = cpp_type_.size();
+    array_buffer_ = MEM_mallocN_aligned(size_ * element_size, cpp_type_.alignment(), __func__);
+    array_is_temporary_ = true;
+    cpp_type_.fill_uninitialized(value_, array_buffer_, size_);
+  }
 };
 
 class ConvertedReadAttribute final : public ReadAttribute {



More information about the Bf-blender-cvs mailing list