Geometry Nodes: Use implicit sharing in point/mesh conversion nodes #106917

Closed
Hans Goudey wants to merge 1 commits from HooglyBoogly:points-nodes-sharing into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
3 changed files with 113 additions and 35 deletions

View File

@ -80,6 +80,13 @@ Bounds<float3> 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

View File

@ -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)
Review

I'm not quite convinced we should start using custom data here yet. Better integrate implicit sharing with the attribute api and later field evaluation + maybe virtual arrays. I started doing that a bit in my branch. See e.g. GAttributeReader in https://projects.blender.org/JacquesLucke/blender/src/branch/sim-bake/source/blender/blenkernel/BKE_attribute.hh.

I'm not quite convinced we should start using custom data here yet. Better integrate implicit sharing with the attribute api and later field evaluation + maybe virtual arrays. I started doing that a bit in my branch. See e.g. `GAttributeReader` in https://projects.blender.org/JacquesLucke/blender/src/branch/sim-bake/source/blender/blenkernel/BKE_attribute.hh.
{
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<float3> &position_field,
Field<float> &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<float3> positions_eval = evaluator.get_evaluated<float3>(0);
const VArray<float> radii_eval = evaluator.get_evaluated<float>(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();
Review

This does a deep comparison of the positions leading to some significant new overhead.

This does a deep comparison of the positions leading to some significant new overhead.
Map<AttributeIDRef, AttributeKind> 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);
Review

move one line up

move one line up
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<AttributeIDRef, AttributeKind>::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});
}

View File

@ -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<decl::Geometry>(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<bool> &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);
Review

This should probably be moved one line up.

This should probably be moved one line up.
const AttributeAccessor src_attributes = points->attributes();
MutableAttributeAccessor dst_attributes = mesh->attributes_for_write();
for (Map<AttributeIDRef, AttributeKind>::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<AttributeIDRef, AttributeKind>::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<AttributeIDRef, AttributeKind>::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});
}