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
504 lines
15 KiB
C++
504 lines
15 KiB
C++
/* SPDX-FileCopyrightText: 2005 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_array_utils.hh"
|
|
#include "BLI_math_geom.h"
|
|
#include "BLI_task.hh"
|
|
|
|
#include "BKE_bake_data_block_id.hh"
|
|
#include "BKE_bvhutils.hh"
|
|
#include "BKE_customdata.hh"
|
|
#include "BKE_editmesh_cache.hh"
|
|
#include "BKE_lib_id.hh"
|
|
#include "BKE_mesh.hh"
|
|
#include "BKE_mesh_mapping.hh"
|
|
#include "BKE_mesh_runtime.hh"
|
|
#include "BKE_shrinkwrap.hh"
|
|
#include "BKE_subdiv_ccg.hh"
|
|
|
|
using blender::float3;
|
|
using blender::MutableSpan;
|
|
using blender::Span;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Mesh Runtime Struct Utils
|
|
* \{ */
|
|
|
|
namespace blender::bke {
|
|
|
|
static void free_mesh_eval(MeshRuntime &mesh_runtime)
|
|
{
|
|
if (mesh_runtime.mesh_eval != nullptr) {
|
|
BKE_id_free(nullptr, mesh_runtime.mesh_eval);
|
|
mesh_runtime.mesh_eval = nullptr;
|
|
}
|
|
}
|
|
|
|
static void free_bvh_cache(MeshRuntime &mesh_runtime)
|
|
{
|
|
if (mesh_runtime.bvh_cache) {
|
|
bvhcache_free(mesh_runtime.bvh_cache);
|
|
mesh_runtime.bvh_cache = nullptr;
|
|
}
|
|
}
|
|
|
|
static void free_batch_cache(MeshRuntime &mesh_runtime)
|
|
{
|
|
if (mesh_runtime.batch_cache) {
|
|
BKE_mesh_batch_cache_free(mesh_runtime.batch_cache);
|
|
mesh_runtime.batch_cache = nullptr;
|
|
}
|
|
}
|
|
|
|
MeshRuntime::MeshRuntime() = default;
|
|
|
|
MeshRuntime::~MeshRuntime()
|
|
{
|
|
free_mesh_eval(*this);
|
|
free_bvh_cache(*this);
|
|
free_batch_cache(*this);
|
|
}
|
|
|
|
static int reset_bits_and_count(MutableBitSpan bits, const Span<int> indices_to_reset)
|
|
{
|
|
int count = bits.size();
|
|
for (const int i : indices_to_reset) {
|
|
if (bits[i]) {
|
|
bits[i].reset();
|
|
count--;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static void bit_vector_with_reset_bits_or_empty(const Span<int> indices_to_reset,
|
|
const int indexed_elems_num,
|
|
BitVector<> &r_bits,
|
|
int &r_count)
|
|
{
|
|
r_bits.resize(0);
|
|
r_bits.resize(indexed_elems_num, true);
|
|
r_count = reset_bits_and_count(r_bits, indices_to_reset);
|
|
if (r_count == 0) {
|
|
r_bits.clear_and_shrink();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If there are no loose edges and no loose vertices, all vertices are used by faces.
|
|
*/
|
|
static void try_tag_verts_no_face_none(const Mesh &mesh)
|
|
{
|
|
if (!mesh.runtime->loose_edges_cache.is_cached() || mesh.loose_edges().count > 0) {
|
|
return;
|
|
}
|
|
if (!mesh.runtime->loose_verts_cache.is_cached() || mesh.loose_verts().count > 0) {
|
|
return;
|
|
}
|
|
mesh.runtime->verts_no_face_cache.ensure([&](LooseVertCache &r_data) {
|
|
r_data.is_loose_bits.clear_and_shrink();
|
|
r_data.count = 0;
|
|
});
|
|
}
|
|
|
|
} // namespace blender::bke
|
|
|
|
blender::Span<int> Mesh::corner_to_face_map() const
|
|
{
|
|
using namespace blender;
|
|
this->runtime->corner_to_face_map_cache.ensure([&](Array<int> &r_data) {
|
|
const OffsetIndices faces = this->faces();
|
|
r_data = bke::mesh::build_corner_to_face_map(faces);
|
|
});
|
|
return this->runtime->corner_to_face_map_cache.data();
|
|
}
|
|
|
|
blender::OffsetIndices<int> Mesh::vert_to_face_map_offsets() const
|
|
{
|
|
using namespace blender;
|
|
this->runtime->vert_to_face_offset_cache.ensure([&](Array<int> &r_data) {
|
|
r_data = Array<int>(this->verts_num + 1, 0);
|
|
offset_indices::build_reverse_offsets(this->corner_verts(), r_data);
|
|
});
|
|
return OffsetIndices<int>(this->runtime->vert_to_face_offset_cache.data());
|
|
}
|
|
|
|
blender::GroupedSpan<int> Mesh::vert_to_face_map() const
|
|
{
|
|
using namespace blender;
|
|
const OffsetIndices offsets = this->vert_to_face_map_offsets();
|
|
this->runtime->vert_to_face_map_cache.ensure([&](Array<int> &r_data) {
|
|
r_data.reinitialize(this->corners_num);
|
|
if (this->runtime->vert_to_corner_map_cache.is_cached() &&
|
|
this->runtime->corner_to_face_map_cache.is_cached())
|
|
{
|
|
/* The vertex to face cache can be built from the vertex to face corner
|
|
* and face corner to face maps if they are both already cached. */
|
|
array_utils::gather(this->runtime->corner_to_face_map_cache.data().as_span(),
|
|
this->runtime->vert_to_corner_map_cache.data().as_span(),
|
|
r_data.as_mutable_span());
|
|
}
|
|
else {
|
|
bke::mesh::build_vert_to_face_indices(this->faces(), this->corner_verts(), offsets, r_data);
|
|
}
|
|
});
|
|
return {offsets, this->runtime->vert_to_face_map_cache.data()};
|
|
}
|
|
|
|
blender::GroupedSpan<int> Mesh::vert_to_corner_map() const
|
|
{
|
|
using namespace blender;
|
|
const OffsetIndices offsets = this->vert_to_face_map_offsets();
|
|
this->runtime->vert_to_corner_map_cache.ensure([&](Array<int> &r_data) {
|
|
r_data = bke::mesh::build_vert_to_corner_indices(this->corner_verts(), offsets);
|
|
});
|
|
return {offsets, this->runtime->vert_to_corner_map_cache.data()};
|
|
}
|
|
|
|
const blender::bke::LooseVertCache &Mesh::loose_verts() const
|
|
{
|
|
using namespace blender::bke;
|
|
this->runtime->loose_verts_cache.ensure([&](LooseVertCache &r_data) {
|
|
const Span<int> verts = this->edges().cast<int>();
|
|
bit_vector_with_reset_bits_or_empty(
|
|
verts, this->verts_num, r_data.is_loose_bits, r_data.count);
|
|
});
|
|
return this->runtime->loose_verts_cache.data();
|
|
}
|
|
|
|
const blender::bke::LooseVertCache &Mesh::verts_no_face() const
|
|
{
|
|
using namespace blender::bke;
|
|
this->runtime->verts_no_face_cache.ensure([&](LooseVertCache &r_data) {
|
|
const Span<int> verts = this->corner_verts();
|
|
bit_vector_with_reset_bits_or_empty(
|
|
verts, this->verts_num, r_data.is_loose_bits, r_data.count);
|
|
});
|
|
return this->runtime->verts_no_face_cache.data();
|
|
}
|
|
|
|
bool Mesh::no_overlapping_topology() const
|
|
{
|
|
return this->flag & ME_NO_OVERLAPPING_TOPOLOGY;
|
|
}
|
|
|
|
const blender::bke::LooseEdgeCache &Mesh::loose_edges() const
|
|
{
|
|
using namespace blender::bke;
|
|
this->runtime->loose_edges_cache.ensure([&](LooseEdgeCache &r_data) {
|
|
const Span<int> edges = this->corner_edges();
|
|
bit_vector_with_reset_bits_or_empty(
|
|
edges, this->edges_num, r_data.is_loose_bits, r_data.count);
|
|
});
|
|
return this->runtime->loose_edges_cache.data();
|
|
}
|
|
|
|
void Mesh::tag_loose_verts_none() const
|
|
{
|
|
using namespace blender::bke;
|
|
this->runtime->loose_verts_cache.ensure([&](LooseVertCache &r_data) {
|
|
r_data.is_loose_bits.clear_and_shrink();
|
|
r_data.count = 0;
|
|
});
|
|
try_tag_verts_no_face_none(*this);
|
|
}
|
|
|
|
void Mesh::tag_loose_edges_none() const
|
|
{
|
|
using namespace blender::bke;
|
|
this->runtime->loose_edges_cache.ensure([&](LooseEdgeCache &r_data) {
|
|
r_data.is_loose_bits.clear_and_shrink();
|
|
r_data.count = 0;
|
|
});
|
|
try_tag_verts_no_face_none(*this);
|
|
}
|
|
|
|
void Mesh::tag_overlapping_none()
|
|
{
|
|
using namespace blender::bke;
|
|
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.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();
|
|
|
|
r_data.reinitialize(poly_to_tri_count(faces.size(), corner_verts.size()));
|
|
|
|
if (BKE_mesh_face_normals_are_dirty(this)) {
|
|
blender::bke::mesh::corner_tris_calc(positions, faces, corner_verts, r_data);
|
|
}
|
|
else {
|
|
blender::bke::mesh::corner_tris_calc_with_normals(
|
|
positions, faces, corner_verts, this->face_normals(), r_data);
|
|
}
|
|
});
|
|
|
|
return this->runtime->corner_tris_cache.data.data();
|
|
}
|
|
|
|
blender::Span<int> Mesh::corner_tri_faces() const
|
|
{
|
|
using namespace blender;
|
|
this->runtime->corner_tri_faces_cache.ensure([&](blender::Array<int> &r_data) {
|
|
const OffsetIndices faces = this->faces();
|
|
r_data.reinitialize(poly_to_tri_count(faces.size(), this->corners_num));
|
|
bke::mesh::corner_tris_calc_face_indices(faces, r_data);
|
|
});
|
|
return this->runtime->corner_tri_faces_cache.data();
|
|
}
|
|
|
|
int BKE_mesh_runtime_corner_tris_len(const Mesh *mesh)
|
|
{
|
|
/* Allow returning the size without calculating the cache. */
|
|
return poly_to_tri_count(mesh->faces_num, mesh->corners_num);
|
|
}
|
|
|
|
void BKE_mesh_runtime_ensure_edit_data(Mesh *mesh)
|
|
{
|
|
if (!mesh->runtime->edit_data) {
|
|
mesh->runtime->edit_data = std::make_unique<blender::bke::EditMeshData>();
|
|
}
|
|
}
|
|
|
|
void BKE_mesh_runtime_clear_cache(Mesh *mesh)
|
|
{
|
|
using namespace blender::bke;
|
|
free_mesh_eval(*mesh->runtime);
|
|
free_batch_cache(*mesh->runtime);
|
|
mesh->runtime->edit_data.reset();
|
|
BKE_mesh_runtime_clear_geometry(mesh);
|
|
}
|
|
|
|
void BKE_mesh_runtime_clear_geometry(Mesh *mesh)
|
|
{
|
|
/* Tagging shared caches dirty will free the allocated data if there is only one user. */
|
|
free_bvh_cache(*mesh->runtime);
|
|
mesh->runtime->subdiv_ccg.reset();
|
|
mesh->runtime->bounds_cache.tag_dirty();
|
|
mesh->runtime->vert_to_face_offset_cache.tag_dirty();
|
|
mesh->runtime->vert_to_face_map_cache.tag_dirty();
|
|
mesh->runtime->vert_to_corner_map_cache.tag_dirty();
|
|
mesh->runtime->corner_to_face_map_cache.tag_dirty();
|
|
mesh->runtime->vert_normals_cache.tag_dirty();
|
|
mesh->runtime->face_normals_cache.tag_dirty();
|
|
mesh->runtime->corner_normals_cache.tag_dirty();
|
|
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.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();
|
|
mesh->runtime->subsurf_optimal_display_edges.clear_and_shrink();
|
|
mesh->flag &= ~ME_NO_OVERLAPPING_TOPOLOGY;
|
|
}
|
|
|
|
void Mesh::tag_edges_split()
|
|
{
|
|
/* Triangulation didn't change because vertex positions and loop vertex indices didn't change. */
|
|
free_bvh_cache(*this->runtime);
|
|
this->runtime->vert_normals_cache.tag_dirty();
|
|
this->runtime->subdiv_ccg.reset();
|
|
this->runtime->vert_to_face_offset_cache.tag_dirty();
|
|
this->runtime->vert_to_face_map_cache.tag_dirty();
|
|
this->runtime->vert_to_corner_map_cache.tag_dirty();
|
|
if (this->runtime->loose_edges_cache.is_cached() &&
|
|
this->runtime->loose_edges_cache.data().count != 0)
|
|
{
|
|
this->runtime->loose_edges_cache.tag_dirty();
|
|
}
|
|
if (this->runtime->loose_verts_cache.is_cached() &&
|
|
this->runtime->loose_verts_cache.data().count != 0)
|
|
{
|
|
this->runtime->loose_verts_cache.tag_dirty();
|
|
}
|
|
if (this->runtime->verts_no_face_cache.is_cached() &&
|
|
this->runtime->verts_no_face_cache.data().count != 0)
|
|
{
|
|
this->runtime->verts_no_face_cache.tag_dirty();
|
|
}
|
|
this->runtime->subsurf_face_dot_tags.clear_and_shrink();
|
|
this->runtime->subsurf_optimal_display_edges.clear_and_shrink();
|
|
this->runtime->shrinkwrap_boundary_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_sharpness_changed()
|
|
{
|
|
this->runtime->corner_normals_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_custom_normals_changed()
|
|
{
|
|
this->runtime->corner_normals_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_face_winding_changed()
|
|
{
|
|
this->runtime->vert_normals_cache.tag_dirty();
|
|
this->runtime->face_normals_cache.tag_dirty();
|
|
this->runtime->corner_normals_cache.tag_dirty();
|
|
this->runtime->vert_to_corner_map_cache.tag_dirty();
|
|
this->runtime->shrinkwrap_boundary_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_positions_changed()
|
|
{
|
|
this->runtime->vert_normals_cache.tag_dirty();
|
|
this->runtime->face_normals_cache.tag_dirty();
|
|
this->runtime->corner_normals_cache.tag_dirty();
|
|
this->runtime->shrinkwrap_boundary_cache.tag_dirty();
|
|
this->tag_positions_changed_no_normals();
|
|
}
|
|
|
|
void Mesh::tag_positions_changed_no_normals()
|
|
{
|
|
free_bvh_cache(*this->runtime);
|
|
this->runtime->corner_tris_cache.tag_dirty();
|
|
this->runtime->bounds_cache.tag_dirty();
|
|
this->runtime->shrinkwrap_boundary_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_positions_changed_uniformly()
|
|
{
|
|
/* The normals and triangulation didn't change, since all verts moved by the same amount. */
|
|
free_bvh_cache(*this->runtime);
|
|
this->runtime->bounds_cache.tag_dirty();
|
|
}
|
|
|
|
void Mesh::tag_topology_changed()
|
|
{
|
|
BKE_mesh_runtime_clear_geometry(this);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Mesh Batch Cache Callbacks
|
|
* \{ */
|
|
|
|
/* Draw Engine */
|
|
|
|
void (*BKE_mesh_batch_cache_dirty_tag_cb)(Mesh *mesh, eMeshBatchDirtyMode mode) = nullptr;
|
|
void (*BKE_mesh_batch_cache_free_cb)(void *batch_cache) = nullptr;
|
|
|
|
void BKE_mesh_batch_cache_dirty_tag(Mesh *mesh, eMeshBatchDirtyMode mode)
|
|
{
|
|
if (mesh->runtime->batch_cache) {
|
|
BKE_mesh_batch_cache_dirty_tag_cb(mesh, mode);
|
|
}
|
|
}
|
|
void BKE_mesh_batch_cache_free(void *batch_cache)
|
|
{
|
|
BKE_mesh_batch_cache_free_cb(batch_cache);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Mesh Runtime Validation
|
|
* \{ */
|
|
|
|
#ifndef NDEBUG
|
|
|
|
bool BKE_mesh_runtime_is_valid(Mesh *mesh_eval)
|
|
{
|
|
const bool do_verbose = true;
|
|
const bool do_fixes = false;
|
|
|
|
bool is_valid = true;
|
|
bool changed = true;
|
|
|
|
if (do_verbose) {
|
|
printf("MESH: %s\n", mesh_eval->id.name + 2);
|
|
}
|
|
|
|
MutableSpan<float3> positions = mesh_eval->vert_positions_for_write();
|
|
MutableSpan<blender::int2> edges = mesh_eval->edges_for_write();
|
|
Span<int> face_offsets = mesh_eval->face_offsets();
|
|
Span<int> corner_verts = mesh_eval->corner_verts();
|
|
MutableSpan<int> corner_edges = mesh_eval->corner_edges_for_write();
|
|
|
|
is_valid &= BKE_mesh_validate_all_customdata(
|
|
&mesh_eval->vert_data,
|
|
mesh_eval->verts_num,
|
|
&mesh_eval->edge_data,
|
|
mesh_eval->edges_num,
|
|
&mesh_eval->corner_data,
|
|
mesh_eval->corners_num,
|
|
&mesh_eval->face_data,
|
|
mesh_eval->faces_num,
|
|
false, /* setting mask here isn't useful, gives false positives */
|
|
do_verbose,
|
|
do_fixes,
|
|
&changed);
|
|
|
|
MDeformVert *dverts = static_cast<MDeformVert *>(
|
|
CustomData_get_layer_for_write(&mesh_eval->vert_data, CD_MDEFORMVERT, mesh_eval->verts_num));
|
|
is_valid &= BKE_mesh_validate_arrays(
|
|
mesh_eval,
|
|
reinterpret_cast<float(*)[3]>(positions.data()),
|
|
positions.size(),
|
|
edges.data(),
|
|
edges.size(),
|
|
static_cast<MFace *>(CustomData_get_layer_for_write(
|
|
&mesh_eval->fdata_legacy, CD_MFACE, mesh_eval->totface_legacy)),
|
|
mesh_eval->totface_legacy,
|
|
corner_verts.data(),
|
|
corner_edges.data(),
|
|
corner_verts.size(),
|
|
face_offsets.data(),
|
|
mesh_eval->faces_num,
|
|
dverts,
|
|
do_verbose,
|
|
do_fixes,
|
|
&changed);
|
|
|
|
BLI_assert(changed == false);
|
|
|
|
return is_valid;
|
|
}
|
|
|
|
#endif /* !NDEBUG */
|
|
|
|
/** \} */
|