Geometry Nodes: Avoid extra attribute copy in Sort Elements node

Implicit sharing dramatically reduces the cost of coping geometry
which is extremely helpful for read-only and slight modification.
But case of Sort Elements node implies a total rewrite of whole domain,
so implicit sharing only adds redundant cost for write access. This fix
uses kernel API to create domains without attributes, manually forcing
implicit sharing for offset indices and uses kernel attribute copy
functions to force sharing for all attributes when this is possible.
In all other cases new attributes are created from scratch and copied
as re-ordered only once.

In attached example file shown the difference depend on the number of
attributes on the mesh, benchmark result:

| Attributes | Before | After |
| ---------- | ------ | ----- |
| 1          | 2 ms  | 1.5 ms |
| 5          | 5 ms  | 4 ms   |
| 10         | 9 ms  | 6 ms   |
| 50         | 45 ms | 30 ms  |
| 100        | 89 ms | 59 ms  |

Pull Request: https://projects.blender.org/blender/blender/pulls/117910
This commit is contained in:
Iliya Katueshenock
2025-01-08 23:06:22 +01:00
committed by Hans Goudey
parent 9945563fac
commit 9d13e39585

View File

@@ -4,6 +4,7 @@
#include "BKE_anonymous_attribute_id.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute_filters.hh"
#include "BKE_attribute_math.hh"
#include "BKE_curves.hh"
#include "BKE_geometry_set.hh"
@@ -46,6 +47,7 @@ static void reorder_attributes_group_to_group(const bke::AttributeAccessor src_a
const OffsetIndices<int> src_offsets,
const OffsetIndices<int> dst_offsets,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
bke::MutableAttributeAccessor dst_attributes)
{
src_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
@@ -55,6 +57,9 @@ static void reorder_attributes_group_to_group(const bke::AttributeAccessor src_a
if (iter.data_type == CD_PROP_STRING) {
return;
}
if (attribute_filter.allow_skip(iter.name)) {
return;
}
const GVArray src = *iter.get(domain);
bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
iter.name, domain, iter.data_type);
@@ -84,75 +89,160 @@ static Array<int> invert_permutation(const Span<int> permutation)
return data;
}
static void reorder_mesh_verts_exec(const Mesh &src_mesh,
const Span<int> old_by_new_map,
Mesh &dst_mesh)
static void copy_and_reorder_mesh_verts(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
bke::gather_attributes(src_mesh.attributes(),
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
bke::gather_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
{},
attribute_filter,
old_by_new_map,
dst_mesh.attributes_for_write());
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
bke::attribute_filter_with_skip_ref(attribute_filter, {".edge_verts"}),
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
attribute_filter,
dst_attributes);
implicit_sharing::free_shared_data(&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
implicit_sharing::copy_shared_pointer(src_mesh.face_offset_indices,
src_mesh.runtime->face_offsets_sharing_info,
&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Corner,
bke::AttrDomain::Corner,
bke::attribute_filter_with_skip_ref(attribute_filter, {".corner_vert"}),
dst_attributes);
const Array<int> new_by_old_map = invert_permutation(old_by_new_map);
dst_attributes.add<int2>(".edge_verts", bke::AttrDomain::Edge, bke::AttributeInitConstruct());
array_utils::gather(new_by_old_map.as_span(),
dst_mesh.edges().cast<int>(),
src_mesh.edges().cast<int>(),
dst_mesh.edges_for_write().cast<int>());
dst_attributes.add<int>(".corner_vert", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
array_utils::gather(
new_by_old_map.as_span(), dst_mesh.corner_verts(), dst_mesh.corner_verts_for_write());
new_by_old_map.as_span(), src_mesh.corner_verts(), dst_mesh.corner_verts_for_write());
}
static void reorder_mesh_edges_exec(const Mesh &src_mesh,
const Span<int> old_by_new_map,
Mesh &dst_mesh)
static void copy_and_reorder_mesh_edges(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
bke::gather_attributes(src_mesh.attributes(),
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
bke::copy_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
{},
attribute_filter,
old_by_new_map,
dst_mesh.attributes_for_write());
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
attribute_filter,
dst_attributes);
implicit_sharing::free_shared_data(&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
implicit_sharing::copy_shared_pointer(src_mesh.face_offset_indices,
src_mesh.runtime->face_offsets_sharing_info,
&dst_mesh.face_offset_indices,
&dst_mesh.runtime->face_offsets_sharing_info);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Corner,
bke::AttrDomain::Corner,
bke::attribute_filter_with_skip_ref(attribute_filter, {".corner_edge"}),
dst_attributes);
const Array<int> new_by_old_map = invert_permutation(old_by_new_map);
dst_attributes.add<int>(".corner_edge", bke::AttrDomain::Corner, bke::AttributeInitConstruct());
array_utils::gather(
new_by_old_map.as_span(), dst_mesh.corner_edges(), dst_mesh.corner_edges_for_write());
new_by_old_map.as_span(), src_mesh.corner_edges(), dst_mesh.corner_edges_for_write());
}
static void reorder_mesh_faces_exec(const Mesh &src_mesh,
const Span<int> old_by_new_map,
Mesh &dst_mesh)
static void copy_and_reorder_mesh_faces(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
bke::gather_attributes(src_mesh.attributes(),
const bke::AttributeAccessor src_attributes = src_mesh.attributes();
bke::MutableAttributeAccessor dst_attributes = dst_mesh.attributes_for_write();
bke::copy_attributes(src_attributes,
bke::AttrDomain::Point,
bke::AttrDomain::Point,
attribute_filter,
dst_attributes);
bke::copy_attributes(src_attributes,
bke::AttrDomain::Edge,
bke::AttrDomain::Edge,
attribute_filter,
dst_attributes);
bke::gather_attributes(src_attributes,
bke::AttrDomain::Face,
bke::AttrDomain::Face,
{},
attribute_filter,
old_by_new_map,
dst_mesh.attributes_for_write());
dst_attributes);
const Span<int> old_offsets = src_mesh.face_offsets();
MutableSpan<int> new_offsets = dst_mesh.face_offsets_for_write();
offset_indices::gather_group_sizes(old_offsets, old_by_new_map, new_offsets);
offset_indices::accumulate_counts_to_offsets(new_offsets);
reorder_attributes_group_to_group(src_mesh.attributes(),
reorder_attributes_group_to_group(src_attributes,
bke::AttrDomain::Corner,
old_offsets,
new_offsets.as_span(),
old_by_new_map,
dst_mesh.attributes_for_write());
attribute_filter,
dst_attributes);
}
static void reorder_mesh_exec(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttrDomain domain,
Mesh &dst_mesh)
static void copy_and_reorder_mesh(const Mesh &src_mesh,
const Span<int> old_by_new_map,
const bke::AttrDomain domain,
const bke::AttributeFilter &attribute_filter,
Mesh &dst_mesh)
{
switch (domain) {
case bke::AttrDomain::Point:
reorder_mesh_verts_exec(src_mesh, old_by_new_map, dst_mesh);
copy_and_reorder_mesh_verts(src_mesh, old_by_new_map, attribute_filter, dst_mesh);
break;
case bke::AttrDomain::Edge:
reorder_mesh_edges_exec(src_mesh, old_by_new_map, dst_mesh);
copy_and_reorder_mesh_edges(src_mesh, old_by_new_map, attribute_filter, dst_mesh);
break;
case bke::AttrDomain::Face:
reorder_mesh_faces_exec(src_mesh, old_by_new_map, dst_mesh);
copy_and_reorder_mesh_faces(src_mesh, old_by_new_map, attribute_filter, dst_mesh);
break;
default:
break;
@@ -161,28 +251,30 @@ static void reorder_mesh_exec(const Mesh &src_mesh,
dst_mesh.tag_topology_changed();
}
static void reorder_points_exec(const PointCloud &src_pointcloud,
const Span<int> old_by_new_map,
PointCloud &dst_pointcloud)
static void copy_and_reorder_points(const PointCloud &src_pointcloud,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
PointCloud &dst_pointcloud)
{
bke::gather_attributes(src_pointcloud.attributes(),
bke::AttrDomain::Point,
bke::AttrDomain::Point,
{},
attribute_filter,
old_by_new_map,
dst_pointcloud.attributes_for_write());
dst_pointcloud.tag_positions_changed();
dst_pointcloud.tag_radii_changed();
}
static void reorder_curves_exec(const bke::CurvesGeometry &src_curves,
const Span<int> old_by_new_map,
bke::CurvesGeometry &dst_curves)
static void copy_and_reorder_curves(const bke::CurvesGeometry &src_curves,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
bke::CurvesGeometry &dst_curves)
{
bke::gather_attributes(src_curves.attributes(),
bke::AttrDomain::Curve,
bke::AttrDomain::Curve,
{},
attribute_filter,
old_by_new_map,
dst_curves.attributes_for_write());
@@ -196,42 +288,33 @@ static void reorder_curves_exec(const bke::CurvesGeometry &src_curves,
old_offsets,
new_offsets.as_span(),
old_by_new_map,
attribute_filter,
dst_curves.attributes_for_write());
dst_curves.tag_topology_changed();
}
static void reorder_instaces_exec(const bke::Instances &src_instances,
const Span<int> old_by_new_map,
bke::Instances &dst_instances)
static void copy_and_reorder_instaces(const bke::Instances &src_instances,
const Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter,
bke::Instances &dst_instances)
{
dst_instances.resize(src_instances.instances_num());
bke::gather_attributes(src_instances.attributes(),
bke::AttrDomain::Instance,
bke::AttrDomain::Instance,
{},
attribute_filter,
old_by_new_map,
dst_instances.attributes_for_write());
}
static void clean_unused_attributes(const bke::AttributeFilter &attribute_filter,
bke::MutableAttributeAccessor attributes)
{
Vector<std::string> unused_ids;
attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
if (!bke::attribute_name_is_anonymous(iter.name)) {
return;
}
if (iter.data_type == CD_PROP_STRING) {
return;
}
if (!attribute_filter.allow_skip(iter.name)) {
return;
}
unused_ids.append(iter.name);
});
for (const std::string &unused_id : unused_ids) {
attributes.remove(unused_id);
for (const bke::InstanceReference &reference : src_instances.references()) {
dst_instances.add_reference(reference);
}
BLI_assert(src_instances.references() == dst_instances.references());
const Span<float4x4> old_transforms = src_instances.transforms();
MutableSpan<float4x4> new_transforms = dst_instances.transforms_for_write();
array_utils::gather(old_transforms, old_by_new_map, new_transforms);
}
Mesh *reorder_mesh(const Mesh &src_mesh,
@@ -239,9 +322,10 @@ Mesh *reorder_mesh(const Mesh &src_mesh,
bke::AttrDomain domain,
const bke::AttributeFilter &attribute_filter)
{
Mesh *dst_mesh = BKE_mesh_copy_for_eval(src_mesh);
clean_unused_attributes(attribute_filter, dst_mesh->attributes_for_write());
reorder_mesh_exec(src_mesh, old_by_new_map, domain, *dst_mesh);
Mesh *dst_mesh = bke::mesh_new_no_attributes(
src_mesh.verts_num, src_mesh.edges_num, src_mesh.faces_num, src_mesh.corners_num);
BKE_mesh_copy_parameters_for_eval(dst_mesh, &src_mesh);
copy_and_reorder_mesh(src_mesh, old_by_new_map, domain, attribute_filter, *dst_mesh);
return dst_mesh;
}
@@ -249,9 +333,8 @@ PointCloud *reorder_points(const PointCloud &src_pointcloud,
Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter)
{
PointCloud *dst_pointcloud = BKE_pointcloud_copy_for_eval(&src_pointcloud);
clean_unused_attributes(attribute_filter, dst_pointcloud->attributes_for_write());
reorder_points_exec(src_pointcloud, old_by_new_map, *dst_pointcloud);
PointCloud *dst_pointcloud = bke::pointcloud_new_no_attributes(src_pointcloud.totpoint);
copy_and_reorder_points(src_pointcloud, old_by_new_map, attribute_filter, *dst_pointcloud);
return dst_pointcloud;
}
@@ -259,9 +342,9 @@ bke::CurvesGeometry reorder_curves_geometry(const bke::CurvesGeometry &src_curve
Span<int> old_by_new_map,
const bke::AttributeFilter &attribute_filter)
{
bke::CurvesGeometry dst_curves = bke::CurvesGeometry(src_curves);
clean_unused_attributes(attribute_filter, dst_curves.attributes_for_write());
reorder_curves_exec(src_curves, old_by_new_map, dst_curves);
bke::CurvesGeometry dst_curves = bke::curves_new_no_attributes(src_curves.points_num(),
src_curves.curves_num());
copy_and_reorder_curves(src_curves, old_by_new_map, attribute_filter, dst_curves);
return dst_curves;
}
@@ -270,7 +353,7 @@ Curves *reorder_curves(const Curves &src_curves,
const bke::AttributeFilter &attribute_filter)
{
const bke::CurvesGeometry src_curve_geometry = src_curves.geometry.wrap();
Curves *dst_curves = BKE_curves_copy_for_eval(&src_curves);
Curves *dst_curves = bke::curves_new_nomain(0, 0);
dst_curves->geometry.wrap() = reorder_curves_geometry(
src_curve_geometry, old_by_new_map, attribute_filter);
return dst_curves;
@@ -281,8 +364,7 @@ bke::Instances *reorder_instaces(const bke::Instances &src_instances,
const bke::AttributeFilter &attribute_filter)
{
bke::Instances *dst_instances = new bke::Instances(src_instances);
clean_unused_attributes(attribute_filter, dst_instances->attributes_for_write());
reorder_instaces_exec(src_instances, old_by_new_map, *dst_instances);
copy_and_reorder_instaces(src_instances, old_by_new_map, attribute_filter, *dst_instances);
return dst_instances;
}