Mesh: Parallelize corner to point domain adaptation
This commit parallelizes attribute domain interpolation for meshes from the face corner to vertex domain. Parallel iteration is implemented using the vertex to face topology map. The same map is used for mesh normal computations and benefits from potential cost amortization in its shared cache. This approach then tries to maximize cache usage while minimizing potential memory consumption by avoiding caching multiple topology maps. The performance gain is dependent on the attribute type and domain size. For a mesh with 16M vertices and 4x4 matrix attribute interpolation, a 4.5x performance improvement was observed (from 10.7s to 2.38s). Pull Request: https://projects.blender.org/blender/blender/pulls/135372
This commit is contained in:
committed by
Hans Goudey
parent
299a581b1b
commit
eae60bc3e6
@@ -23,36 +23,47 @@ namespace blender::bke {
|
||||
|
||||
template<typename T>
|
||||
static void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh,
|
||||
const VArray<T> &old_values,
|
||||
MutableSpan<T> r_values)
|
||||
const VArray<T> &src,
|
||||
MutableSpan<T> r_dst)
|
||||
{
|
||||
BLI_assert(r_values.size() == mesh.verts_num);
|
||||
BLI_assert(r_dst.size() == mesh.verts_num);
|
||||
const GroupedSpan<int> vert_to_face_map = mesh.vert_to_face_map();
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
const OffsetIndices<int> faces = mesh.faces();
|
||||
|
||||
attribute_math::DefaultMixer<T> mixer(r_values);
|
||||
for (const int corner : IndexRange(mesh.corners_num)) {
|
||||
mixer.mix_in(corner_verts[corner], old_values[corner]);
|
||||
}
|
||||
mixer.finalize();
|
||||
threading::parallel_for(vert_to_face_map.index_range(), 2048, [&](const IndexRange sub_range) {
|
||||
for (const int64_t vert : sub_range) {
|
||||
const Span<int> vert_faces = vert_to_face_map[vert];
|
||||
|
||||
attribute_math::DefaultMixer<T> mixer({&r_dst[vert], 1});
|
||||
for (const int face : vert_faces) {
|
||||
const int corner = mesh::face_find_corner_from_vert(faces[face], corner_verts, int(vert));
|
||||
mixer.mix_in(0, src[corner]);
|
||||
}
|
||||
mixer.finalize();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* A vertex is selected if all connected face corners were selected and it is not loose. */
|
||||
template<>
|
||||
void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh,
|
||||
const VArray<bool> &old_values,
|
||||
MutableSpan<bool> r_values)
|
||||
const VArray<bool> &src,
|
||||
MutableSpan<bool> r_dst)
|
||||
{
|
||||
BLI_assert(r_values.size() == mesh.verts_num);
|
||||
BLI_assert(r_dst.size() == mesh.verts_num);
|
||||
const Span<int> corner_verts = mesh.corner_verts();
|
||||
|
||||
r_values.fill(true);
|
||||
for (const int corner : IndexRange(mesh.corners_num)) {
|
||||
const int point_index = corner_verts[corner];
|
||||
r_dst.fill(true);
|
||||
threading::parallel_for(IndexRange(mesh.corners_num), 4096, [&](const IndexRange sub_range) {
|
||||
for (const int64_t corner : sub_range) {
|
||||
const int vert = corner_verts[corner];
|
||||
|
||||
if (!old_values[corner]) {
|
||||
r_values[point_index] = false;
|
||||
if (!src[corner]) {
|
||||
r_dst[vert] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Deselect loose vertices without corners that are still selected from the 'true' default. */
|
||||
const LooseVertCache &loose_verts = mesh.verts_no_face();
|
||||
@@ -61,7 +72,7 @@ void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh,
|
||||
threading::parallel_for(bits.index_range(), 2048, [&](const IndexRange range) {
|
||||
for (const int vert_index : range) {
|
||||
if (bits[vert_index]) {
|
||||
r_values[vert_index] = false;
|
||||
r_dst[vert_index] = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user