Sculpt: Reuse existing mesh triangles cache in sculpt mode
Addresses #121240. Instead of allocating a new array and recalculating mesh triangulation every time the user enters sculpt mode, reuse the mesh's existing cache. Currently in order to avoid recalculating triangulation on every brush update (which would typically be necessary because the triangulation direction depends on vertex positions), add a mechanism to "freeze" the cache to skip recalculations until the user exits sculpt mode. That even avoids recalculation if vertex positions aren't affected. This is necessary because we can't use the cache in a dirty state; tagging the cache dirty frees the triangulation array. Removing the duplicate triangles array reduces memory usage by 384 MB in a 16 million vertex sculpt, and makes entering sculpt mode 125ms faster (tested on a Ryzen 7840u). In the long term, I hope we find a different solution that's a bit more transparent and hopefully more integrated with the caching system in general. In the meantime, this is a relatively safe low impact change that helps document the needs for such a system anyway. Pull Request: https://projects.blender.org/blender/blender/pulls/123638
This commit is contained in:
@@ -94,6 +94,19 @@ struct LooseEdgeCache : public LooseGeomCache {};
|
||||
*/
|
||||
struct LooseVertCache : public LooseGeomCache {};
|
||||
|
||||
struct TrianglesCache {
|
||||
SharedCache<Array<int3>> data;
|
||||
bool frozen = false;
|
||||
bool dirty_while_frozen = false;
|
||||
|
||||
/** Delay applying dirty tags from #tag_dirty() until #unfreeze is called. */
|
||||
void freeze();
|
||||
/** Apply dirty tags from after #freeze, and make future dirty tags apply immediately. */
|
||||
void unfreeze();
|
||||
/** Call instead of `data.tag_dirty()`. */
|
||||
void tag_dirty();
|
||||
};
|
||||
|
||||
struct MeshRuntime {
|
||||
/**
|
||||
* "Evaluated" mesh owned by this mesh. Used for objects which don't have effective modifiers, so
|
||||
@@ -141,7 +154,7 @@ struct MeshRuntime {
|
||||
void *batch_cache = nullptr;
|
||||
|
||||
/** Cache for derived triangulation of the mesh, accessed with #Mesh::corner_tris(). */
|
||||
SharedCache<Array<int3>> corner_tris_cache;
|
||||
TrianglesCache corner_tris_cache;
|
||||
/** Cache for triangle to original face index map, accessed with #Mesh::corner_tri_faces(). */
|
||||
SharedCache<Array<int>> corner_tri_faces_cache;
|
||||
|
||||
|
||||
@@ -226,9 +226,38 @@ void Mesh::tag_overlapping_none()
|
||||
this->flag |= ME_NO_OVERLAPPING_TOPOLOGY;
|
||||
}
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
void TrianglesCache::freeze()
|
||||
{
|
||||
this->frozen = true;
|
||||
this->dirty_while_frozen = false;
|
||||
}
|
||||
|
||||
void TrianglesCache::unfreeze()
|
||||
{
|
||||
this->frozen = false;
|
||||
if (this->dirty_while_frozen) {
|
||||
this->data.tag_dirty();
|
||||
}
|
||||
this->dirty_while_frozen = false;
|
||||
}
|
||||
|
||||
void TrianglesCache::tag_dirty()
|
||||
{
|
||||
if (this->frozen) {
|
||||
this->dirty_while_frozen = true;
|
||||
}
|
||||
else {
|
||||
this->data.tag_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
blender::Span<blender::int3> Mesh::corner_tris() const
|
||||
{
|
||||
this->runtime->corner_tris_cache.ensure([&](blender::Array<blender::int3> &r_data) {
|
||||
this->runtime->corner_tris_cache.data.ensure([&](blender::Array<blender::int3> &r_data) {
|
||||
const Span<float3> positions = this->vert_positions();
|
||||
const blender::OffsetIndices faces = this->faces();
|
||||
const Span<int> corner_verts = this->corner_verts();
|
||||
@@ -244,7 +273,7 @@ blender::Span<blender::int3> Mesh::corner_tris() const
|
||||
}
|
||||
});
|
||||
|
||||
return this->runtime->corner_tris_cache.data();
|
||||
return this->runtime->corner_tris_cache.data.data();
|
||||
}
|
||||
|
||||
blender::Span<int> Mesh::corner_tri_faces() const
|
||||
@@ -296,7 +325,7 @@ void BKE_mesh_runtime_clear_geometry(Mesh *mesh)
|
||||
mesh->runtime->loose_edges_cache.tag_dirty();
|
||||
mesh->runtime->loose_verts_cache.tag_dirty();
|
||||
mesh->runtime->verts_no_face_cache.tag_dirty();
|
||||
mesh->runtime->corner_tris_cache.tag_dirty();
|
||||
mesh->runtime->corner_tris_cache.data.tag_dirty();
|
||||
mesh->runtime->corner_tri_faces_cache.tag_dirty();
|
||||
mesh->runtime->shrinkwrap_boundary_cache.tag_dirty();
|
||||
mesh->runtime->subsurf_face_dot_tags.clear_and_shrink();
|
||||
|
||||
@@ -654,14 +654,10 @@ std::unique_ptr<PBVH> build_mesh(Mesh *mesh)
|
||||
pbvh->header.type = PBVH_FACES;
|
||||
|
||||
const int totvert = mesh->verts_num;
|
||||
const int corner_tris_num = poly_to_tri_count(mesh->faces_num, mesh->corners_num);
|
||||
MutableSpan<float3> vert_positions = mesh->vert_positions_for_write();
|
||||
const OffsetIndices<int> faces = mesh->faces();
|
||||
const Span<int> corner_verts = mesh->corner_verts();
|
||||
|
||||
pbvh->corner_tris.reinitialize(corner_tris_num);
|
||||
mesh::corner_tris_calc(vert_positions, faces, corner_verts, pbvh->corner_tris);
|
||||
const Span<int3> corner_tris = pbvh->corner_tris;
|
||||
const Span<int3> corner_tris = mesh->corner_tris();
|
||||
pbvh->corner_tris = corner_tris;
|
||||
|
||||
pbvh->mesh = mesh;
|
||||
|
||||
@@ -681,7 +677,7 @@ std::unique_ptr<PBVH> build_mesh(Mesh *mesh)
|
||||
#endif
|
||||
|
||||
/* For each face, store the AABB and the AABB centroid */
|
||||
Array<Bounds<float3>> prim_bounds(corner_tris_num);
|
||||
Array<Bounds<float3>> prim_bounds(corner_tris.size());
|
||||
const Bounds<float3> cb = threading::parallel_reduce(
|
||||
corner_tris.index_range(),
|
||||
1024,
|
||||
@@ -701,7 +697,7 @@ std::unique_ptr<PBVH> build_mesh(Mesh *mesh)
|
||||
},
|
||||
[](const Bounds<float3> &a, const Bounds<float3> &b) { return bounds::merge(a, b); });
|
||||
|
||||
if (corner_tris_num) {
|
||||
if (!corner_tris.is_empty()) {
|
||||
const AttributeAccessor attributes = mesh->attributes();
|
||||
const VArraySpan hide_poly = *attributes.lookup<bool>(".hide_poly", AttrDomain::Face);
|
||||
const VArraySpan material_index = *attributes.lookup<int>("material_index", AttrDomain::Face);
|
||||
@@ -716,7 +712,7 @@ std::unique_ptr<PBVH> build_mesh(Mesh *mesh)
|
||||
vert_bitmap,
|
||||
&cb,
|
||||
prim_bounds,
|
||||
corner_tris_num);
|
||||
corner_tris.size());
|
||||
|
||||
#ifdef TEST_PBVH_FACE_SPLIT
|
||||
test_face_boundaries(pbvh, tri_faces);
|
||||
|
||||
@@ -154,8 +154,7 @@ struct PBVH {
|
||||
/** Only valid for polygon meshes. */
|
||||
blender::OffsetIndices<int> faces;
|
||||
blender::Span<int> corner_verts;
|
||||
/* Owned by the #PBVH, because after deformations they have to be recomputed. */
|
||||
blender::Array<blender::int3> corner_tris;
|
||||
blender::Span<blender::int3> corner_tris;
|
||||
|
||||
/* Grid Data */
|
||||
CCGKey gridkey;
|
||||
|
||||
@@ -355,6 +355,10 @@ void ED_object_sculptmode_enter_ex(Main &bmain,
|
||||
const int mode_flag = OB_MODE_SCULPT;
|
||||
Mesh *mesh = BKE_mesh_from_object(&ob);
|
||||
|
||||
/* Re-triangulating the mesh for position changes in sculpt mode isn't worth the performance
|
||||
* impact, so delay triangulation updates until the user exits sculpt mode. */
|
||||
mesh->runtime->corner_tris_cache.freeze();
|
||||
|
||||
/* Enter sculpt mode. */
|
||||
ob.mode |= mode_flag;
|
||||
|
||||
@@ -450,6 +454,8 @@ void ED_object_sculptmode_exit_ex(Main &bmain, Depsgraph &depsgraph, Scene &scen
|
||||
const int mode_flag = OB_MODE_SCULPT;
|
||||
Mesh *mesh = BKE_mesh_from_object(&ob);
|
||||
|
||||
mesh->runtime->corner_tris_cache.unfreeze();
|
||||
|
||||
multires_flush_sculpt_updates(&ob);
|
||||
|
||||
/* Not needed for now. */
|
||||
|
||||
Reference in New Issue
Block a user