From 5df70a41f9129852aeeb27da27296aeb6bd8b05b Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 13 Apr 2023 13:12:43 -0400 Subject: [PATCH] Geometry Nodes: Use implicit sharing in point/mesh conversion nodes Reduces memory usage and time spent copying data in the "Points to Vertices" and "Mesh to Points" nodes. An arbitrary speedup can be observed by just adding more data to the source geometries. For example, In a simple test I saw a 70x speedup. The sharing in the "Mesh to Points" node is more limited though, since it can copy from multiple domains. A new utility function is added to share a custom data layer. I'd imagine this being part of a more general "attribute interpolation" abstraction in the future. --- .../nodes/geometry/node_geometry_util.hh | 7 ++ .../geometry/nodes/node_geo_mesh_to_points.cc | 75 ++++++++++++++----- .../nodes/node_geo_points_to_vertices.cc | 66 ++++++++++++---- 3 files changed, 113 insertions(+), 35 deletions(-) diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh index cfef1bcaae7..3f24d565a9e 100644 --- a/source/blender/nodes/geometry/node_geometry_util.hh +++ b/source/blender/nodes/geometry/node_geometry_util.hh @@ -80,6 +80,13 @@ Bounds calculate_bounds_radial_primitive(float radius_top, int segments, float height); +/** + * Add a layer to \a dst, sharing the data from \a src with its implicit sharing info if available. + * Assumes that the layer exists in the source and doesn't exist in the destination. + */ +void share_custom_data_layer( + const CustomData &src, CustomData &dst, AttributeIDRef id, eCustomDataType type, int size); + /** * Returns the parts of the geometry that are on the selection for the given domain. If the domain * is not applicable for the component, e.g. face domain for point cloud, nothing happens to that diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc index f6ed43d3e20..ac6e175ed5f 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_to_points.cc @@ -45,6 +45,23 @@ static void node_init(bNodeTree * /*tree*/, bNode *node) node->storage = data; } +static const CustomData &mesh_custom_data_for_domain(const Mesh &mesh, const eAttrDomain domain) +{ + switch (domain) { + case ATTR_DOMAIN_POINT: + return mesh.vdata; + case ATTR_DOMAIN_EDGE: + return mesh.edata; + case ATTR_DOMAIN_FACE: + return mesh.pdata; + case ATTR_DOMAIN_CORNER: + return mesh.ldata; + default: + BLI_assert_unreachable(); + return mesh.vdata; + } +} + static void geometry_set_mesh_to_points(GeometrySet &geometry_set, Field &position_field, Field &radius_field, @@ -72,20 +89,13 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, evaluator.add(radius_field); evaluator.evaluate(); const IndexMask selection = evaluator.get_evaluated_selection_as_mask(); + const VArray positions_eval = evaluator.get_evaluated(0); + const VArray radii_eval = evaluator.get_evaluated(1); - PointCloud *pointcloud = BKE_pointcloud_new_nomain(selection.size()); - geometry_set.replace_pointcloud(pointcloud); - MutableAttributeAccessor dst_attributes = pointcloud->attributes_for_write(); - - GSpanAttributeWriter position = dst_attributes.lookup_or_add_for_write_only_span( - "position", ATTR_DOMAIN_POINT, CD_PROP_FLOAT3); - array_utils::gather(evaluator.get_evaluated(0), selection, position.span); - position.finish(); - - GSpanAttributeWriter radius = dst_attributes.lookup_or_add_for_write_only_span( - "radius", ATTR_DOMAIN_POINT, CD_PROP_FLOAT); - array_utils::gather(evaluator.get_evaluated(1), selection, radius.span); - radius.finish(); + const bool share_arrays = selection.size() == domain_size; + const bool share_position = share_arrays && domain == ATTR_DOMAIN_POINT && + positions_eval.is_span() && + positions_eval.get_internal_span() == mesh->vert_positions(); Map attributes; geometry_set.gather_attributes_for_propagation({GEO_COMPONENT_TYPE_MESH}, @@ -95,20 +105,47 @@ static void geometry_set_mesh_to_points(GeometrySet &geometry_set, attributes); attributes.remove("position"); + const CustomData &mesh_data = mesh_custom_data_for_domain(*mesh, domain); const AttributeAccessor src_attributes = mesh->attributes(); + PointCloud *pointcloud; + if (share_position) { + /* Create an empty point cloud so the positions can be easily shared with a generic utility. */ + pointcloud = BKE_pointcloud_new_nomain(0); + pointcloud->totpoint = mesh->totvert; + CustomData_free_layer_named(&pointcloud->pdata, "position", pointcloud->totpoint); + share_custom_data_layer( + mesh_data, pointcloud->pdata, "position", CD_PROP_FLOAT3, mesh->totvert); + } + else { + pointcloud = BKE_pointcloud_new_nomain(selection.size()); + array_utils::gather(positions_eval, selection, pointcloud->positions_for_write()); + } + + MutableAttributeAccessor dst_attributes = pointcloud->attributes_for_write(); + GSpanAttributeWriter radius = dst_attributes.lookup_or_add_for_write_only_span( + "radius", ATTR_DOMAIN_POINT, CD_PROP_FLOAT); + array_utils::gather(radii_eval, selection, radius.span); + radius.finish(); + for (Map::Item entry : attributes.items()) { const AttributeIDRef attribute_id = entry.key; const eCustomDataType data_type = entry.value.data_type; - GVArray src = src_attributes.lookup_or_default(attribute_id, domain, data_type); - GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( - attribute_id, ATTR_DOMAIN_POINT, data_type); - if (dst && src) { - array_utils::gather(src, selection, dst.span); - dst.finish(); + if (share_arrays && entry.value.domain == domain) { + share_custom_data_layer(mesh_data, pointcloud->pdata, entry.key, data_type, mesh->totvert); + } + else { + GVArray src = src_attributes.lookup_or_default(attribute_id, domain, data_type); + GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( + attribute_id, ATTR_DOMAIN_POINT, data_type); + if (dst && src) { + array_utils::gather(src, selection, dst.span); + dst.finish(); + } } } + geometry_set.replace_pointcloud(pointcloud); geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_POINT_CLOUD}); } diff --git a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc index 92ac14046ec..a4493f3fe3e 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_points_to_vertices.cc @@ -9,6 +9,29 @@ #include "node_geometry_util.hh" +namespace blender::nodes { + +void share_custom_data_layer(const CustomData &src, + CustomData &dst, + const AttributeIDRef id, + const eCustomDataType type, + const int size) +{ + const std::string name = id.name(); + const int index = CustomData_get_named_layer_index(&src, type, name.c_str()); + const CustomDataLayer &src_layer = src.layers[index]; + if (id.is_anonymous()) { + CustomData_add_layer_anonymous_with_data( + &dst, type, &id.anonymous_id(), size, src_layer.data, src_layer.sharing_info); + } + else { + CustomData_add_layer_named_with_data( + &dst, type, src_layer.data, size, name.c_str(), src_layer.sharing_info); + } +} + +} // namespace blender::nodes + namespace blender::nodes::node_geo_points_to_vertices_cc { using blender::Array; @@ -20,7 +43,6 @@ static void node_declare(NodeDeclarationBuilder &b) b.add_output(N_("Mesh")).propagate_all(); } -/* One improvement would be to move the attribute arrays directly to the mesh when possible. */ static void geometry_set_points_to_vertices( GeometrySet &geometry_set, Field &selection_field, @@ -49,25 +71,37 @@ static void geometry_set_points_to_vertices( propagation_info, attributes); - Mesh *mesh = BKE_mesh_new_nomain(selection.size(), 0, 0, 0); - geometry_set.replace_mesh(mesh); + if (selection.size() == points->totpoint) { + /* Create an empty mesh so the positions can be easily shared with a generic utility. */ + Mesh *mesh = BKE_mesh_new_nomain(0, 0, 0, 0); + geometry_set.replace_mesh(mesh); + mesh->totvert = points->totpoint; + CustomData_free_layer_named(&mesh->vdata, "position", mesh->totvert); - const AttributeAccessor src_attributes = points->attributes(); - MutableAttributeAccessor dst_attributes = mesh->attributes_for_write(); - - for (Map::Item entry : attributes.items()) { - const AttributeIDRef attribute_id = entry.key; - const eCustomDataType data_type = entry.value.data_type; - GVArray src = src_attributes.lookup_or_default(attribute_id, ATTR_DOMAIN_POINT, data_type); - GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( - attribute_id, ATTR_DOMAIN_POINT, data_type); - if (dst && src) { - src.materialize_compressed_to_uninitialized(selection, dst.span.data()); - dst.finish(); + for (Map::Item entry : attributes.items()) { + share_custom_data_layer( + points->pdata, mesh->vdata, entry.key, entry.value.data_type, mesh->totvert); } } + else { + Mesh *mesh = BKE_mesh_new_nomain(selection.size(), 0, 0, 0); + geometry_set.replace_mesh(mesh); - mesh->loose_edges_tag_none(); + const AttributeAccessor src_attributes = points->attributes(); + MutableAttributeAccessor dst_attributes = mesh->attributes_for_write(); + + for (Map::Item entry : attributes.items()) { + const AttributeIDRef attribute_id = entry.key; + const eCustomDataType data_type = entry.value.data_type; + GVArray src = src_attributes.lookup_or_default(attribute_id, ATTR_DOMAIN_POINT, data_type); + GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( + attribute_id, ATTR_DOMAIN_POINT, data_type); + if (dst && src) { + src.materialize_compressed_to_uninitialized(selection, dst.span.data()); + dst.finish(); + } + } + } geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH}); } -- 2.30.2