[Bf-blender-cvs] [3f5dfbf6814] master: Geometry Nodes: Modify existing mesh in split edges node

Hans Goudey noreply at git.blender.org
Sun Nov 27 01:12:36 CET 2022


Commit: 3f5dfbf6814298bea15bcd0766b6748c9256feaa
Author: Hans Goudey
Date:   Sat Nov 26 17:54:05 2022 -0600
Branches: master
https://developer.blender.org/rB3f5dfbf6814298bea15bcd0766b6748c9256feaa

Geometry Nodes: Modify existing mesh in split edges node

Instead of creating a new mesh from scratch, modify an existing mesh.
This allows us to keep derived caches for triangulation and bounds
alive more easily, and allows keeping materials and non-generic
attributes like vertex groups alive on the mesh.

It also has other performance benefits, since face and face corner
attributes aren't affected at all, and because of reduced overhead
from not allocating a new mesh.

Updating edge attributes is a bit more complicated now, since we
have to completely replace the arrays but keep the existing attribute
IDs around. The new mesh update tag is also slightly too specific IMO.
But I think both of those things will improve in the future because
of existing plans for further refactoring these areas:
- New attribute storage that gives pointer stability
- Further use and granularity of mesh update tagging that will
  make the correct API more clear

Fixes T102711

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

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

M	source/blender/blenkernel/BKE_mesh.h
M	source/blender/blenkernel/intern/mesh_runtime.cc
M	source/blender/nodes/geometry/nodes/node_geo_edge_split.cc

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

diff --git a/source/blender/blenkernel/BKE_mesh.h b/source/blender/blenkernel/BKE_mesh.h
index 1b327e11a53..577107149ee 100644
--- a/source/blender/blenkernel/BKE_mesh.h
+++ b/source/blender/blenkernel/BKE_mesh.h
@@ -72,6 +72,11 @@ void BKE_mesh_tag_coords_changed_uniformly(struct Mesh *mesh);
 
 void BKE_mesh_tag_topology_changed(struct Mesh *mesh);
 
+/**
+ * Call when new edges and vertices have been created but positions and faces haven't changed.
+ */
+void BKE_mesh_tag_edges_split(struct Mesh *mesh);
+
 /* *** mesh.c *** */
 
 struct BMesh *BKE_mesh_to_bmesh_ex(const struct Mesh *me,
diff --git a/source/blender/blenkernel/intern/mesh_runtime.cc b/source/blender/blenkernel/intern/mesh_runtime.cc
index 51c98f6aa6b..8f71b9eee71 100644
--- a/source/blender/blenkernel/intern/mesh_runtime.cc
+++ b/source/blender/blenkernel/intern/mesh_runtime.cc
@@ -231,6 +231,21 @@ void BKE_mesh_runtime_clear_geometry(Mesh *mesh)
   MEM_SAFE_FREE(mesh->runtime->subsurf_face_dot_tags);
 }
 
+void BKE_mesh_tag_edges_split(struct Mesh *mesh)
+{
+  /* Triangulation didn't change because vertex positions and loop vertex indices didn't change.
+   * Face normals didn't change either, but tag those anyway, since there is no API function to
+   * only tag vertex normals dirty. */
+  free_bvh_cache(*mesh->runtime);
+  free_normals(*mesh->runtime);
+  free_subdiv_ccg(*mesh->runtime);
+  mesh->runtime->loose_edges_cache.tag_dirty();
+  if (mesh->runtime->shrinkwrap_data) {
+    BKE_shrinkwrap_boundary_data_free(mesh->runtime->shrinkwrap_data);
+  }
+  MEM_SAFE_FREE(mesh->runtime->subsurf_face_dot_tags);
+}
+
 void BKE_mesh_tag_coords_changed(Mesh *mesh)
 {
   BKE_mesh_normals_tag_dirty(mesh);
diff --git a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc
index 1a19897a148..21e7dc092e3 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_edge_split.cc
@@ -27,48 +27,105 @@ static inline bool naive_edges_equal(const MEdge &edge1, const MEdge &edge2)
   return edge1.v1 == edge2.v1 && edge1.v2 == edge2.v2;
 }
 
-static void transfer_attributes(const Map<AttributeIDRef, AttributeKind> &attributes,
-                                const Span<int> new_to_old_verts_map,
-                                const Span<int> new_to_old_edges_map,
-                                const AttributeAccessor src_attributes,
-                                MutableAttributeAccessor dst_attributes)
+static void add_new_vertices(Mesh &mesh, const Span<int> new_to_old_verts_map)
 {
-  for (Map<AttributeIDRef, AttributeKind>::Item entry : attributes.items()) {
-    const AttributeIDRef attribute_id = entry.key;
-    GAttributeReader src_attribute = src_attributes.lookup(attribute_id);
-    if (!src_attribute) {
+  CustomData_realloc(&mesh.vdata, mesh.totvert, mesh.totvert + new_to_old_verts_map.size());
+  mesh.totvert += new_to_old_verts_map.size();
+
+  MutableAttributeAccessor attributes = mesh.attributes_for_write();
+  for (const AttributeIDRef &id : attributes.all_ids()) {
+    if (attributes.lookup_meta_data(id)->domain != ATTR_DOMAIN_POINT) {
       continue;
     }
-    const eCustomDataType data_type = bke::cpp_type_to_custom_data_type(
-        src_attribute.varray.type());
-    GSpanAttributeWriter dst_attribute = dst_attributes.lookup_or_add_for_write_only_span(
-        attribute_id, src_attribute.domain, data_type);
-    if (!dst_attribute) {
+    GSpanAttributeWriter attribute = attributes.lookup_for_write_span(id);
+    if (!attribute) {
       continue;
     }
 
-    attribute_math::convert_to_static_type(data_type, [&](auto dummy) {
+    attribute_math::convert_to_static_type(attribute.span.type(), [&](auto dummy) {
       using T = decltype(dummy);
-      VArraySpan<T> span{src_attribute.varray.typed<T>()};
-      MutableSpan<T> dst_span = dst_attribute.span.typed<T>();
-      switch (src_attribute.domain) {
-        case ATTR_DOMAIN_POINT:
-          array_utils::gather(span, new_to_old_verts_map, dst_span);
-          break;
-        case ATTR_DOMAIN_EDGE:
-          array_utils::gather(span, new_to_old_edges_map, dst_span);
-          break;
-        case ATTR_DOMAIN_FACE:
-          dst_span.copy_from(span);
-          break;
-        case ATTR_DOMAIN_CORNER:
-          dst_span.copy_from(span);
-          break;
-        default:
-          BLI_assert_unreachable();
-      }
+      MutableSpan<T> span = attribute.span.typed<T>();
+      const Span<T> old_data = span.drop_back(new_to_old_verts_map.size());
+      MutableSpan<T> new_data = span.take_back(new_to_old_verts_map.size());
+      array_utils::gather(old_data, new_to_old_verts_map, new_data);
+    });
+
+    attribute.finish();
+  }
+}
+
+static void add_new_edges(Mesh &mesh,
+                          const Span<MEdge> new_edges,
+                          const Span<int> new_to_old_edges_map)
+{
+  MutableAttributeAccessor attributes = mesh.attributes_for_write();
+
+  /* Store a copy of the IDs locally since we will remove the existing attributes which
+   * can also free the names, since the API does not provide pointer stability. */
+  Vector<std::string> named_ids;
+  Vector<WeakAnonymousAttributeID> anonymous_ids;
+  for (const AttributeIDRef &id : attributes.all_ids()) {
+    if (attributes.lookup_meta_data(id)->domain != ATTR_DOMAIN_EDGE) {
+      continue;
+    }
+    if (!id.should_be_kept()) {
+      continue;
+    }
+    if (id.is_named()) {
+      named_ids.append(id.name());
+    }
+    else {
+      anonymous_ids.append(WeakAnonymousAttributeID(&id.anonymous_id()));
+    }
+  }
+  Vector<AttributeIDRef> local_edge_ids;
+  for (const StringRef name : named_ids) {
+    local_edge_ids.append(name);
+  }
+  for (const WeakAnonymousAttributeID &id : anonymous_ids) {
+    local_edge_ids.append(id.get());
+  }
+
+  /* Build new arrays for the copied edge attributes. Unlike vertices, new edges aren't all at the
+   * end of the array, so just copying to the new edges would overwrite old values when they were
+   * still needed. */
+  struct NewAttributeData {
+    const AttributeIDRef &local_id;
+    const CPPType &type;
+    void *array;
+  };
+  Vector<NewAttributeData> dst_attributes;
+  for (const AttributeIDRef &id : local_edge_ids) {
+    GAttributeReader attribute = attributes.lookup(id);
+    if (!attribute) {
+      continue;
+    }
+
+    const CPPType &type = attribute.varray.type();
+    void *new_data = MEM_malloc_arrayN(new_edges.size(), type.size(), __func__);
+
+    attribute_math::convert_to_static_type(type, [&](auto dummy) {
+      using T = decltype(dummy);
+      const VArray<T> src = attribute.varray.typed<T>();
+      MutableSpan<T> dst(static_cast<T *>(new_data), new_edges.size());
+      array_utils::gather(src, new_to_old_edges_map, dst);
     });
-    dst_attribute.finish();
+
+    /* Free the original attribute as soon as possible to lower peak memory usage. */
+    attributes.remove(id);
+    dst_attributes.append({id, type, new_data});
+  }
+
+  CustomData_free(&mesh.edata, mesh.totedge);
+  mesh.totedge = new_edges.size();
+  CustomData_add_layer(&mesh.edata, CD_MEDGE, CD_CONSTRUCT, nullptr, mesh.totedge);
+  mesh.edges_for_write().copy_from(new_edges);
+
+  for (NewAttributeData &new_data : dst_attributes) {
+    attributes.add(new_data.local_id,
+                   ATTR_DOMAIN_EDGE,
+                   bke::cpp_type_to_custom_data_type(new_data.type),
+                   bke::AttributeInitMoveArray(new_data.array));
   }
 }
 
@@ -139,6 +196,7 @@ static void swap_vertex_of_edge(MEdge &edge,
 /** Split the vertex into duplicates so that each fan has a different vertex. */
 static void split_vertex_per_fan(const int vertex,
                                  const int start_offset,
+                                 const int orig_verts_num,
                                  const Span<int> fans,
                                  const Span<int> fan_sizes,
                                  const Span<Vector<int>> edge_to_loop_map,
@@ -151,7 +209,7 @@ static void split_vertex_per_fan(const int vertex,
    * original vertex. */
   for (const int i : fan_sizes.index_range().drop_back(1)) {
     const int new_vert_i = start_offset + i;
-    new_to_old_verts_map[new_vert_i] = vertex;
+    new_to_old_verts_map[new_vert_i - orig_verts_num] = vertex;
 
     for (const int edge_i : fans.slice(fan_start, fan_sizes[i])) {
       swap_vertex_of_edge(
@@ -272,9 +330,7 @@ static void split_edge_per_poly(const int edge_i,
   edge_to_loop_map[edge_i].resize(1);
 }
 
-static Mesh *mesh_edge_split(const Mesh &mesh,
-                             const IndexMask mask,
-                             const Map<AttributeIDRef, AttributeKind> &attributes)
+static void mesh_edge_split(Mesh &mesh, const IndexMask mask)
 {
   /* Flag vertices that need to be split. */
   Array<bool> should_split_vert(mesh.totvert, false);
@@ -308,26 +364,21 @@ static Mesh *mesh_edge_split(const Mesh &mesh,
 
   const Span<MPoly> polys = mesh.polys();
 
-  Array<MLoop> new_loops(mesh.loops());
+  MutableSpan<MLoop> loops = mesh.loops_for_write();
   Vector<MEdge> new_edges(new_edges_size);
   new_edges.as_mutable_span().take_front(edges.size()).copy_from(edges);
 
   edge_to_loop_map.resize(new_edges_size);
 
   /* Used for transferring attributes. */
-  Vector<int> new_to_old_verts_map(IndexRange(mesh.totvert).as_span());
   Vector<int> new_to_old_edges_map(IndexRange(new_edges.size()).as_span());
 
   /* Step 1: Split the edges. */
   threading::parallel_for(mask.index_range(), 512, [&](IndexRange range) {
     for (const int mask_i : range) {
       const int edge_i = mask[mask_i];
-      split_edge_per_poly(edge_i,
-                          edge_offsets[edge_i],
-                          edge_to_loop_map,
-                          new_loops,
-                          new_edges,
-                          new_to_old_edges_map);
+      split_edge_per_poly(
+          edge_i, edge_offsets[edge_i], edge_to_loop_map, loops, new_edges, new_to_old_edges_map);
     }
   });
 
@@ -348,7 +399,7 @@ static Mesh *mesh_edge_split(const Mesh &mesh,
         continue;
       }
       calc_vertex

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list