Mesh: Use shared cache for derived triangulation

Use the shared cache system introduced in e8f4010611 for the
"looptris" triangulation cache. This avoids recalculation when meshes
are copied but the positions or topology don't change. The most obvious
improvement is for cases like a large meshes being adjusted slightly
with a simple geometry nodes modifier. In a basic test with a transform
node with a 1 million point grid I observed an improvement of 13%, from
9.75 to 11 FPS, which shows that we avoid spending 6ms recalculating
the triangulation of every update.

This also makes the thread safety for the triangulation data use a
more standard double-checked lock pattern, which is nice because we
can avoid holding a lock whenever the cached data is retrieved.

Split from https://developer.blender.org/D16530
This commit is contained in:
Hans Goudey 2022-11-18 17:29:24 -06:00
parent 12d7994a48
commit 511ac66dab
Notes: blender-bot 2023-04-03 12:23:24 +02:00
Referenced by issue #105164, QuadriFlow Remesh Crash
Referenced by issue #105953, Regression: QuadriFlow Remesh Crash
Referenced by issue #106469, Regression: Cycles: unstable triangulation of mesh in viewport
3 changed files with 30 additions and 104 deletions

View File

@ -13,6 +13,7 @@
# include "MEM_guardedalloc.h"
# include "BLI_array.hh"
# include "BLI_bit_vector.hh"
# include "BLI_bounds_types.hh"
# include "BLI_math_vec_types.hh"
@ -20,6 +21,7 @@
# include "BLI_span.hh"
# include "DNA_customdata_types.h"
# include "DNA_meshdata_types.h"
struct BVHCache;
struct EditMeshData;
@ -78,19 +80,6 @@ struct LooseEdgeCache {
int count = -1;
};
/**
* \warning Typical access is done via #Mesh::looptris().
*/
struct MLoopTri_Store {
/* WARNING! swapping between array (ready-to-be-used data) and array_wip
* (where data is actually computed)
* shall always be protected by same lock as one used for looptris computing. */
MLoopTri *array = nullptr;
MLoopTri *array_wip = nullptr;
int len = 0;
int len_alloc = 0;
};
struct MeshRuntime {
/* Evaluated mesh for objects which do not have effective modifiers.
* This mesh is used as a result of modifier stack evaluation.
@ -120,8 +109,8 @@ struct MeshRuntime {
*/
void *batch_cache = nullptr;
/** Cache for derived triangulation of the mesh. */
MLoopTri_Store looptris;
/** Cache for derived triangulation of the mesh, accessed with #Mesh::looptris(). */
SharedCache<Array<MLoopTri>> looptris_cache;
/** Cache for BVH trees generated for the mesh. Defined in 'BKE_bvhutil.c' */
BVHCache *bvh_cache = nullptr;

View File

@ -129,6 +129,7 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int
* Caches will be "un-shared" as necessary later on. */
mesh_dst->runtime->bounds_cache = mesh_src->runtime->bounds_cache;
mesh_dst->runtime->loose_edges_cache = mesh_src->runtime->loose_edges_cache;
mesh_dst->runtime->looptris_cache = mesh_src->runtime->looptris_cache;
/* Only do tessface if we have no polys. */
const bool do_tessface = ((mesh_src->totface != 0) && (mesh_src->totpoly == 0));

View File

@ -105,23 +105,14 @@ MeshRuntime::~MeshRuntime()
BKE_shrinkwrap_boundary_data_free(this->shrinkwrap_data);
}
MEM_SAFE_FREE(this->subsurf_face_dot_tags);
MEM_SAFE_FREE(this->looptris.array);
}
} // namespace blender::bke
blender::Span<MLoopTri> Mesh::looptris() const
{
const MLoopTri *looptris = BKE_mesh_runtime_looptri_ensure(this);
const int num_looptris = BKE_mesh_runtime_looptri_len(this);
return {looptris, num_looptris};
}
const blender::bke::LooseEdgeCache &Mesh::loose_edges() const
{
using namespace blender::bke;
this->runtime->loose_edges_cache.ensure([&](LooseEdgeCache &r_data) {
SCOPED_TIMER("loose_edges");
blender::BitVector<> &loose_edges = r_data.is_loose_bits;
loose_edges.resize(0);
loose_edges.resize(this->totedge, true);
@ -149,97 +140,42 @@ void Mesh::loose_edges_tag_none() const
});
}
/**
* Ensure the array is large enough
*
* \note This function must always be thread-protected by caller.
* It should only be used by internal code.
*/
static void mesh_ensure_looptri_data(Mesh *mesh)
blender::Span<MLoopTri> Mesh::looptris() const
{
/* This is a ported copy of `DM_ensure_looptri_data(dm)`. */
const uint totpoly = mesh->totpoly;
const int looptris_len = poly_to_tri_count(totpoly, mesh->totloop);
this->runtime->looptris_cache.ensure([&](blender::Array<MLoopTri> &r_data) {
const Span<MVert> verts = this->verts();
const Span<MPoly> polys = this->polys();
const Span<MLoop> loops = this->loops();
BLI_assert(mesh->runtime->looptris.array_wip == nullptr);
r_data.reinitialize(poly_to_tri_count(polys.size(), loops.size()));
SWAP(MLoopTri *, mesh->runtime->looptris.array, mesh->runtime->looptris.array_wip);
if ((looptris_len > mesh->runtime->looptris.len_alloc) ||
(looptris_len < mesh->runtime->looptris.len_alloc * 2) || (totpoly == 0)) {
MEM_SAFE_FREE(mesh->runtime->looptris.array_wip);
mesh->runtime->looptris.len_alloc = 0;
mesh->runtime->looptris.len = 0;
}
if (totpoly) {
if (mesh->runtime->looptris.array_wip == nullptr) {
mesh->runtime->looptris.array_wip = static_cast<MLoopTri *>(
MEM_malloc_arrayN(looptris_len, sizeof(*mesh->runtime->looptris.array_wip), __func__));
mesh->runtime->looptris.len_alloc = looptris_len;
if (BKE_mesh_poly_normals_are_dirty(this)) {
BKE_mesh_recalc_looptri(
loops.data(), polys.data(), verts.data(), loops.size(), polys.size(), r_data.data());
}
else {
BKE_mesh_recalc_looptri_with_normals(loops.data(),
polys.data(),
verts.data(),
loops.size(),
polys.size(),
r_data.data(),
BKE_mesh_poly_normals_ensure(this));
}
});
mesh->runtime->looptris.len = looptris_len;
}
}
static void recalc_loopris(Mesh *mesh)
{
mesh_ensure_looptri_data(mesh);
BLI_assert(mesh->totpoly == 0 || mesh->runtime->looptris.array_wip != nullptr);
const Span<MVert> verts = mesh->verts();
const Span<MPoly> polys = mesh->polys();
const Span<MLoop> loops = mesh->loops();
if (!BKE_mesh_poly_normals_are_dirty(mesh)) {
BKE_mesh_recalc_looptri_with_normals(loops.data(),
polys.data(),
verts.data(),
mesh->totloop,
mesh->totpoly,
mesh->runtime->looptris.array_wip,
BKE_mesh_poly_normals_ensure(mesh));
}
else {
BKE_mesh_recalc_looptri(loops.data(),
polys.data(),
verts.data(),
mesh->totloop,
mesh->totpoly,
mesh->runtime->looptris.array_wip);
}
BLI_assert(mesh->runtime->looptris.array == nullptr);
atomic_cas_ptr((void **)&mesh->runtime->looptris.array,
mesh->runtime->looptris.array,
mesh->runtime->looptris.array_wip);
mesh->runtime->looptris.array_wip = nullptr;
return this->runtime->looptris_cache.data();
}
int BKE_mesh_runtime_looptri_len(const Mesh *mesh)
{
/* This is a ported copy of `dm_getNumLoopTri(dm)`. */
const int looptri_len = poly_to_tri_count(mesh->totpoly, mesh->totloop);
BLI_assert(ELEM(mesh->runtime->looptris.len, 0, looptri_len));
return looptri_len;
/* Allow returning the size without calculating the cache. */
return poly_to_tri_count(mesh->totpoly, mesh->totloop);
}
const MLoopTri *BKE_mesh_runtime_looptri_ensure(const Mesh *mesh)
{
std::lock_guard lock{mesh->runtime->eval_mutex};
MLoopTri *looptri = mesh->runtime->looptris.array;
if (looptri != nullptr) {
BLI_assert(BKE_mesh_runtime_looptri_len(mesh) == mesh->runtime->looptris.len);
}
else {
/* Must isolate multithreaded tasks while holding a mutex lock. */
blender::threading::isolate_task([&]() { recalc_loopris(const_cast<Mesh *>(mesh)); });
looptri = mesh->runtime->looptris.array;
}
return looptri;
return mesh->looptris().data();
}
void BKE_mesh_runtime_verttri_from_looptri(MVertTri *r_verttri,
@ -288,18 +224,18 @@ void BKE_mesh_runtime_clear_geometry(Mesh *mesh)
free_subdiv_ccg(*mesh->runtime);
mesh->runtime->bounds_cache.tag_dirty();
mesh->runtime->loose_edges_cache.tag_dirty();
mesh->runtime->looptris_cache.tag_dirty();
if (mesh->runtime->shrinkwrap_data) {
BKE_shrinkwrap_boundary_data_free(mesh->runtime->shrinkwrap_data);
}
MEM_SAFE_FREE(mesh->runtime->subsurf_face_dot_tags);
MEM_SAFE_FREE(mesh->runtime->looptris.array);
}
void BKE_mesh_tag_coords_changed(Mesh *mesh)
{
BKE_mesh_normals_tag_dirty(mesh);
free_bvh_cache(*mesh->runtime);
MEM_SAFE_FREE(mesh->runtime->looptris.array);
mesh->runtime->looptris_cache.tag_dirty();
mesh->runtime->bounds_cache.tag_dirty();
}