From 8e967cfeaf478ca8adca3f4500c21a5be7b3a25e Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sat, 22 Apr 2023 13:46:11 +0200 Subject: [PATCH] Mesh: Cache loose vertices Similar to the cache of loose edges added in 1ea169d90e39647eac72, cache the number of loose vertices and which are loose in a bit map. This can save significant time when drawing large meshes in the viewport, because recalculations can be avoided when the data doesn't change, and because many geometry nodes set the loose geometry caches eagerly when the meshes contain no loose elements. There are two types of loose vertices: 1. Vertices not used by any edges or faces `Mesh.loose_verts()` 2. Vertices not used by any faces (may be used by loose edges) `Mesh.verts_no_face()` Because both are used by Blender in various places, because the cost is only a bit per vertex (or constant at best) and for design consistency, we cache both types of loose elements. The bit maps will only be allocated when they're actually used, but they are already accessed in a few important places: - Attribute domain interpolation - Subdivision surface modifier - Viewport drawing Just skipping viewport drawing calculation after certain geometry nodes setups can have a large impact. Here is the time taken by viewport loose geometry extraction before and after the change: - 4 million vertex grid node: 28 ms to 0 ms - Large molecular nodes setup (curve to mesh node): 104 ms to 0 ms - Realize instances with 1 million cubes: 131 ms to 0 ms Pull Request: https://projects.blender.org/blender/blender/pulls/105567 --- source/blender/blenkernel/BKE_mesh_types.h | 22 +++-- source/blender/blenkernel/intern/bvhutils.cc | 36 ++----- .../intern/curve_to_mesh_convert.cc | 19 ++-- .../intern/geometry_component_mesh.cc | 40 ++++---- source/blender/blenkernel/intern/mesh.cc | 2 + .../blender/blenkernel/intern/mesh_runtime.cc | 95 +++++++++++++++---- .../intern/subdiv_converter_mesh.cc | 50 +++++----- .../draw_cache_extract_mesh_render_data.cc | 52 ++++------ .../geometry/intern/mesh_primitive_cuboid.cc | 1 + .../geometry/intern/realize_instances.cc | 8 ++ source/blender/makesdna/DNA_mesh_types.h | 18 ++++ .../nodes/node_geo_duplicate_elements.cc | 1 + .../nodes/node_geo_mesh_primitive_circle.cc | 3 + .../nodes/node_geo_mesh_primitive_cone.cc | 1 + .../nodes/node_geo_mesh_primitive_grid.cc | 1 + .../node_geo_mesh_primitive_uv_sphere.cc | 1 + 16 files changed, 212 insertions(+), 138 deletions(-) diff --git a/source/blender/blenkernel/BKE_mesh_types.h b/source/blender/blenkernel/BKE_mesh_types.h index c0eee96e7d9..b3b3ce72cef 100644 --- a/source/blender/blenkernel/BKE_mesh_types.h +++ b/source/blender/blenkernel/BKE_mesh_types.h @@ -69,20 +69,25 @@ namespace blender::bke { /** * Cache of a mesh's loose edges, accessed with #Mesh::loose_edges(). * */ -struct LooseEdgeCache { +struct LooseGeomCache { /** - * A bitmap set to true for each loose edge, false if the edge is used by any face. - * Allocated only if there is at least one loose edge. + * A bitmap set to true for each loose element, false if the element is used by any face. + * Allocated only if there is at least one loose element. */ blender::BitVector<> is_loose_bits; /** - * The number of loose edges. If zero, the #is_loose_bits shouldn't be accessed. + * The number of loose elements. If zero, the #is_loose_bits shouldn't be accessed. * If less than zero, the cache has been accessed in an invalid way * (i.e.directly instead of through #Mesh::loose_edges()). */ int count = -1; }; +struct LooseEdgeCache : public LooseGeomCache { +}; +struct LooseVertCache : public LooseGeomCache { +}; + struct MeshRuntime { /* Evaluated mesh for objects which do not have effective modifiers. * This mesh is used as a result of modifier stack evaluation. @@ -166,11 +171,12 @@ struct MeshRuntime { mutable Vector vert_normals; mutable Vector poly_normals; - /** - * A cache of data about the loose edges. Can be shared with other data-blocks with unchanged - * topology. Accessed with #Mesh::loose_edges(). - */ + /** Cache of data about edges not used by faces. See #Mesh::loose_edges(). */ SharedCache loose_edges_cache; + /** Cache of data about vertices not used by edges. See #Mesh::loose_verts(). */ + SharedCache loose_verts_cache; + /** Cache of data about vertices not used by faces. See #Mesh::loose_verts(). */ + SharedCache verts_no_face_cache; /** * A bit vector the size of the number of vertices, set to true for the center vertices of diff --git a/source/blender/blenkernel/intern/bvhutils.cc b/source/blender/blenkernel/intern/bvhutils.cc index c03625de339..b0660966e77 100644 --- a/source/blender/blenkernel/intern/bvhutils.cc +++ b/source/blender/blenkernel/intern/bvhutils.cc @@ -1141,30 +1141,6 @@ BVHTree *bvhtree_from_mesh_looptri_ex(BVHTreeFromMesh *data, return tree; } -static BitVector<> loose_verts_map_get(const Span edges, - int verts_num, - int *r_loose_vert_num) -{ - BitVector<> loose_verts_mask(verts_num, true); - - int num_linked_verts = 0; - for (const int64_t i : edges.index_range()) { - const blender::int2 &edge = edges[i]; - if (loose_verts_mask[edge[0]]) { - loose_verts_mask[edge[0]].reset(); - num_linked_verts++; - } - if (loose_verts_mask[edge[1]]) { - loose_verts_mask[edge[1]].reset(); - num_linked_verts++; - } - } - - *r_loose_vert_num = verts_num - num_linked_verts; - - return loose_verts_mask; -} - static BitVector<> looptri_no_hidden_map_get(const blender::OffsetIndices polys, const VArray &hide_poly, const int looptri_len, @@ -1237,10 +1213,14 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data, switch (bvh_cache_type) { case BVHTREE_FROM_LOOSEVERTS: { - int mask_bits_act_len = -1; - const BitVector<> mask = loose_verts_map_get(edges, mesh->totvert, &mask_bits_act_len); - data->tree = bvhtree_from_mesh_verts_create_tree( - 0.0f, tree_type, 6, positions, mesh->totvert, mask, mask_bits_act_len); + const blender::bke::LooseVertCache &loose_verts = mesh->loose_verts(); + data->tree = bvhtree_from_mesh_verts_create_tree(0.0f, + tree_type, + 6, + positions, + mesh->totvert, + loose_verts.is_loose_bits, + loose_verts.count); break; } case BVHTREE_FROM_VERTS: { diff --git a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc index 484ed7e71a0..2e50a906c17 100644 --- a/source/blender/blenkernel/intern/curve_to_mesh_convert.cc +++ b/source/blender/blenkernel/intern/curve_to_mesh_convert.cc @@ -237,7 +237,8 @@ struct ResultOffsets { Array profile_indices; /** Whether any curve in the profile or curve input has only a single evaluated point. */ - bool any_single_point_curve; + bool any_single_point_main; + bool any_single_point_profile; }; static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool fill_caps) { @@ -315,10 +316,8 @@ static ResultOffsets calculate_result_offsets(const CurvesInfo &info, const bool } } }, - [&]() { - result.any_single_point_curve = offsets_contain_single_point(main_offsets) || - offsets_contain_single_point(profile_offsets); - }); + [&]() { result.any_single_point_main = offsets_contain_single_point(main_offsets); }, + [&]() { result.any_single_point_profile = offsets_contain_single_point(profile_offsets); }); return result; } @@ -765,9 +764,13 @@ Mesh *curve_to_mesh_sweep(const CurvesGeometry &main, positions.slice(info.vert_range)); }); - if (!offsets.any_single_point_curve) { - /* If there are no single point curves, every curve combination will always have faces. */ - mesh->loose_edges_tag_none(); + if (!offsets.any_single_point_main) { + /* If there are no single point curves, every combination will have at least loose edges. */ + mesh->tag_loose_verts_none(); + if (!offsets.any_single_point_profile) { + /* If there are no single point profiles, every combination will have faces. */ + mesh->loose_edges_tag_none(); + } } SpanAttributeWriter sharp_edges; diff --git a/source/blender/blenkernel/intern/geometry_component_mesh.cc b/source/blender/blenkernel/intern/geometry_component_mesh.cc index 62bc979eb6e..b723060eada 100644 --- a/source/blender/blenkernel/intern/geometry_component_mesh.cc +++ b/source/blender/blenkernel/intern/geometry_component_mesh.cc @@ -190,29 +190,27 @@ void adapt_mesh_domain_corner_to_point_impl(const Mesh &mesh, BLI_assert(r_values.size() == mesh.totvert); const Span corner_verts = mesh.corner_verts(); - Array loose_verts(mesh.totvert, true); - r_values.fill(true); for (const int corner : IndexRange(mesh.totloop)) { const int point_index = corner_verts[corner]; - loose_verts[point_index] = false; if (!old_values[corner]) { r_values[point_index] = false; } } /* Deselect loose vertices without corners that are still selected from the 'true' default. */ - /* The record fact says that the value is true. - * Writing to the array from different threads is okay because each thread sets the same value. - */ - threading::parallel_for(loose_verts.index_range(), 2048, [&](const IndexRange range) { - for (const int vert_index : range) { - if (loose_verts[vert_index]) { - r_values[vert_index] = false; + const bke::LooseVertCache &loose_verts = mesh.verts_no_face(); + if (loose_verts.count > 0) { + const BitSpan bits = loose_verts.is_loose_bits; + 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; + } } - } - }); + }); + } } static GVArray adapt_mesh_domain_corner_to_point(const Mesh &mesh, const GVArray &varray) @@ -754,20 +752,26 @@ static bool can_simple_adapt_for_single(const Mesh &mesh, /* All other domains are always connected to points. */ return true; case ATTR_DOMAIN_EDGE: - /* There may be loose vertices not connected to edges. */ - return ELEM(to_domain, ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER); + if (to_domain == ATTR_DOMAIN_POINT) { + return mesh.loose_verts().count == 0; + } + return true; case ATTR_DOMAIN_FACE: - /* There may be loose vertices or edges not connected to faces. */ + if (to_domain == ATTR_DOMAIN_POINT) { + return mesh.verts_no_face().count == 0; + } if (to_domain == ATTR_DOMAIN_EDGE) { return mesh.loose_edges().count == 0; } - return to_domain == ATTR_DOMAIN_CORNER; + return true; case ATTR_DOMAIN_CORNER: - /* Only faces are always connected to corners. */ + if (to_domain == ATTR_DOMAIN_POINT) { + return mesh.verts_no_face().count == 0; + } if (to_domain == ATTR_DOMAIN_EDGE) { return mesh.loose_edges().count == 0; } - return to_domain == ATTR_DOMAIN_FACE; + return true; default: BLI_assert_unreachable(); return false; diff --git a/source/blender/blenkernel/intern/mesh.cc b/source/blender/blenkernel/intern/mesh.cc index f7c032e2867..281d1cad396 100644 --- a/source/blender/blenkernel/intern/mesh.cc +++ b/source/blender/blenkernel/intern/mesh.cc @@ -130,6 +130,8 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int * when the source is persistent and edits to the destination mesh don't affect the caches. * Caches will be "un-shared" as necessary later on. */ mesh_dst->runtime->bounds_cache = mesh_src->runtime->bounds_cache; + mesh_dst->runtime->loose_verts_cache = mesh_src->runtime->loose_verts_cache; + mesh_dst->runtime->verts_no_face_cache = mesh_src->runtime->verts_no_face_cache; mesh_dst->runtime->loose_edges_cache = mesh_src->runtime->loose_edges_cache; mesh_dst->runtime->looptris_cache = mesh_src->runtime->looptris_cache; diff --git a/source/blender/blenkernel/intern/mesh_runtime.cc b/source/blender/blenkernel/intern/mesh_runtime.cc index d4d15923797..36db52b3180 100644 --- a/source/blender/blenkernel/intern/mesh_runtime.cc +++ b/source/blender/blenkernel/intern/mesh_runtime.cc @@ -106,32 +106,90 @@ MeshRuntime::~MeshRuntime() } } +static int reset_bits_and_count(MutableBitSpan bits, const Span indices_to_reset) +{ + int count = bits.size(); + for (const int vert : indices_to_reset) { + if (bits[vert]) { + bits[vert].reset(); + count--; + } + } + return count; +} + +static void bit_vector_with_reset_bits_or_empty(const Span 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 +const blender::bke::LooseVertCache &Mesh::loose_verts() const +{ + using namespace blender::bke; + this->runtime->loose_verts_cache.ensure([&](LooseVertCache &r_data) { + const Span verts = this->edges().cast(); + bit_vector_with_reset_bits_or_empty(verts, this->totvert, 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 verts = this->corner_verts(); + bit_vector_with_reset_bits_or_empty(verts, this->totvert, r_data.is_loose_bits, r_data.count); + }); + return this->runtime->verts_no_face_cache.data(); +} + const blender::bke::LooseEdgeCache &Mesh::loose_edges() const { using namespace blender::bke; this->runtime->loose_edges_cache.ensure([&](LooseEdgeCache &r_data) { - blender::BitVector<> &loose_edges = r_data.is_loose_bits; - loose_edges.resize(0); - loose_edges.resize(this->totedge, true); - - int count = this->totedge; - for (const int edge : this->corner_edges()) { - if (loose_edges[edge]) { - loose_edges[edge].reset(); - count--; - } - } - if (count == 0) { - loose_edges.clear_and_shrink(); - } - r_data.count = count; + const Span edges = this->corner_edges(); + bit_vector_with_reset_bits_or_empty(edges, this->totedge, 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::loose_edges_tag_none() const { using namespace blender::bke; @@ -139,6 +197,7 @@ void Mesh::loose_edges_tag_none() const r_data.is_loose_bits.clear_and_shrink(); r_data.count = 0; }); + try_tag_verts_no_face_none(*this); } blender::Span Mesh::looptris() const @@ -219,6 +278,8 @@ 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->loose_verts_cache.tag_dirty(); + mesh->runtime->verts_no_face_cache.tag_dirty(); mesh->runtime->looptris_cache.tag_dirty(); mesh->runtime->subsurf_face_dot_tags.clear_and_shrink(); mesh->runtime->subsurf_optimal_display_edges.clear_and_shrink(); @@ -237,6 +298,8 @@ void BKE_mesh_tag_edges_split(struct Mesh *mesh) reset_normals(*mesh->runtime); free_subdiv_ccg(*mesh->runtime); mesh->runtime->loose_edges_cache.tag_dirty(); + mesh->runtime->loose_verts_cache.tag_dirty(); + mesh->runtime->verts_no_face_cache.tag_dirty(); mesh->runtime->subsurf_face_dot_tags.clear_and_shrink(); mesh->runtime->subsurf_optimal_display_edges.clear_and_shrink(); if (mesh->runtime->shrinkwrap_data) { diff --git a/source/blender/blenkernel/intern/subdiv_converter_mesh.cc b/source/blender/blenkernel/intern/subdiv_converter_mesh.cc index 04b2937f2ad..6ba7ad28324 100644 --- a/source/blender/blenkernel/intern/subdiv_converter_mesh.cc +++ b/source/blender/blenkernel/intern/subdiv_converter_mesh.cc @@ -177,6 +177,9 @@ static bool is_infinite_sharp_vertex(const OpenSubdiv_Converter *converter, return true; } #endif + if (storage->infinite_sharp_vertices_map == nullptr) { + return false; + } const int vertex_index = storage->manifold_vertex_index_reverse[manifold_vertex_index]; return BLI_BITMAP_TEST_BOOL(storage->infinite_sharp_vertices_map, vertex_index); } @@ -264,7 +267,7 @@ static void free_user_data(const OpenSubdiv_Converter *converter) ConverterStorage *user_data = static_cast(converter->user_data); MEM_SAFE_FREE(user_data->loop_uv_indices); MEM_freeN(user_data->manifold_vertex_index); - MEM_freeN(user_data->infinite_sharp_vertices_map); + MEM_SAFE_FREE(user_data->infinite_sharp_vertices_map); MEM_freeN(user_data->manifold_vertex_index_reverse); MEM_freeN(user_data->manifold_edge_index_reverse); MEM_freeN(user_data); @@ -306,7 +309,7 @@ static void init_functions(OpenSubdiv_Converter *converter) converter->freeUserData = free_user_data; } -static void initialize_manifold_index_array(const BLI_bitmap *used_map, +static void initialize_manifold_index_array(const blender::BitSpan not_used_map, const int num_elements, int **r_indices, int **r_indices_reverse, @@ -323,7 +326,7 @@ static void initialize_manifold_index_array(const BLI_bitmap *used_map, } int offset = 0; for (int i = 0; i < num_elements; i++) { - if (BLI_BITMAP_TEST_BOOL(used_map, i)) { + if (not_used_map.is_empty() || !not_used_map[i]) { if (indices != nullptr) { indices[i] = i - offset; } @@ -349,42 +352,35 @@ static void initialize_manifold_index_array(const BLI_bitmap *used_map, static void initialize_manifold_indices(ConverterStorage *storage) { + using namespace blender; const Mesh *mesh = storage->mesh; - const blender::Span edges = storage->edges; - const blender::OffsetIndices polys = storage->polys; - const blender::Span corner_verts = storage->corner_verts; - const blender::Span corner_edges = storage->corner_edges; - /* Set bits of elements which are not loose. */ - BLI_bitmap *vert_used_map = BLI_BITMAP_NEW(mesh->totvert, "vert used map"); - BLI_bitmap *edge_used_map = BLI_BITMAP_NEW(mesh->totedge, "edge used map"); - for (int poly_index = 0; poly_index < mesh->totpoly; poly_index++) { - for (const int corner : polys[poly_index]) { - BLI_BITMAP_ENABLE(vert_used_map, corner_verts[corner]); - BLI_BITMAP_ENABLE(edge_used_map, corner_edges[corner]); - } - } - initialize_manifold_index_array(vert_used_map, + const bke::LooseVertCache &loose_verts = mesh->verts_no_face(); + const bke::LooseEdgeCache &loose_edges = mesh->loose_edges(); + initialize_manifold_index_array(loose_verts.is_loose_bits, mesh->totvert, &storage->manifold_vertex_index, &storage->manifold_vertex_index_reverse, &storage->num_manifold_vertices); - initialize_manifold_index_array(edge_used_map, + initialize_manifold_index_array(loose_edges.is_loose_bits, mesh->totedge, nullptr, &storage->manifold_edge_index_reverse, &storage->num_manifold_edges); /* Initialize infinite sharp mapping. */ - storage->infinite_sharp_vertices_map = BLI_BITMAP_NEW(mesh->totvert, "vert used map"); - for (int edge_index = 0; edge_index < mesh->totedge; edge_index++) { - if (!BLI_BITMAP_TEST_BOOL(edge_used_map, edge_index)) { - const blender::int2 &edge = edges[edge_index]; - BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[0]); - BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[1]); + if (loose_edges.count > 0) { + const Span edges = storage->edges; + storage->infinite_sharp_vertices_map = BLI_BITMAP_NEW(mesh->totvert, "vert used map"); + for (int edge_index = 0; edge_index < mesh->totedge; edge_index++) { + if (loose_edges.is_loose_bits[edge_index]) { + const int2 edge = edges[edge_index]; + BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[0]); + BLI_BITMAP_ENABLE(storage->infinite_sharp_vertices_map, edge[1]); + } } } - /* Free working variables. */ - MEM_freeN(vert_used_map); - MEM_freeN(edge_used_map); + else { + storage->infinite_sharp_vertices_map = nullptr; + } } static void init_user_data(OpenSubdiv_Converter *converter, diff --git a/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc b/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc index 818307a7780..5952d3f9d57 100644 --- a/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc +++ b/source/blender/draw/intern/draw_cache_extract_mesh_render_data.cc @@ -30,46 +30,32 @@ /** \name Update Loose Geometry * \{ */ -static void mesh_render_data_loose_geom_mesh(const MeshRenderData *mr, MeshBufferCache *cache) +static void extract_set_bits(const blender::BitSpan bits, blender::MutableSpan indices) { - using namespace blender; - BLI_bitmap *lvert_map = BLI_BITMAP_NEW(mr->vert_len, __func__); - - const bke::LooseEdgeCache &loose_edges = mr->me->loose_edges(); - if (loose_edges.count > 0) { - cache->loose_geom.edges.reinitialize(loose_edges.count); - - int count = 0; - for (const int64_t i : loose_edges.is_loose_bits.index_range()) { - if (loose_edges.is_loose_bits[i]) { - cache->loose_geom.edges[count] = int(i); - count++; - } - } - } - - /* Tag verts as not loose. */ - for (const int2 &edge : mr->edges) { - BLI_BITMAP_ENABLE(lvert_map, edge[0]); - BLI_BITMAP_ENABLE(lvert_map, edge[1]); - } - int count = 0; - Array loose_verts(mr->vert_len); - for (int v = 0; v < mr->vert_len; v++) { - if (!BLI_BITMAP_TEST(lvert_map, v)) { - loose_verts[count] = v; + for (const int64_t i : bits.index_range()) { + if (bits[i]) { + indices[count] = int(i); count++; } } - if (count < mr->vert_len) { - cache->loose_geom.verts = loose_verts.as_span().take_front(count); - } - else { - cache->loose_geom.verts = std::move(loose_verts); + BLI_assert(count == indices.size()); +} + +static void mesh_render_data_loose_geom_mesh(const MeshRenderData *mr, MeshBufferCache *cache) +{ + using namespace blender; + const bke::LooseEdgeCache &loose_edges = mr->me->loose_edges(); + if (loose_edges.count > 0) { + cache->loose_geom.edges.reinitialize(loose_edges.count); + extract_set_bits(loose_edges.is_loose_bits, cache->loose_geom.edges); } - MEM_freeN(lvert_map); + const bke::LooseVertCache &loose_verts = mr->me->loose_verts(); + if (loose_verts.count > 0) { + cache->loose_geom.verts.reinitialize(loose_verts.count); + extract_set_bits(loose_verts.is_loose_bits, cache->loose_geom.verts); + } } static void mesh_render_data_loose_verts_bm(const MeshRenderData *mr, diff --git a/source/blender/geometry/intern/mesh_primitive_cuboid.cc b/source/blender/geometry/intern/mesh_primitive_cuboid.cc index a07c8a7268f..78a8c59f294 100644 --- a/source/blender/geometry/intern/mesh_primitive_cuboid.cc +++ b/source/blender/geometry/intern/mesh_primitive_cuboid.cc @@ -417,6 +417,7 @@ Mesh *create_cuboid_mesh(const float3 &size, const float3 bounds = size * 0.5f; mesh->bounds_set_eager({-bounds, bounds}); + mesh->tag_loose_verts_none(); return mesh; } diff --git a/source/blender/geometry/intern/realize_instances.cc b/source/blender/geometry/intern/realize_instances.cc index 9bf0823aa35..334c3df094f 100644 --- a/source/blender/geometry/intern/realize_instances.cc +++ b/source/blender/geometry/intern/realize_instances.cc @@ -203,6 +203,7 @@ struct AllMeshesInfo { /** True if we know that there are no loose edges in any of the input meshes. */ bool no_loose_edges_hint = false; + bool no_loose_verts_hint = false; }; struct AllCurvesInfo { @@ -947,6 +948,10 @@ static AllMeshesInfo preprocess_meshes(const GeometrySet &geometry_set, info.order.begin(), info.order.end(), [](const Mesh *mesh) { return mesh->runtime->loose_edges_cache.is_cached() && mesh->loose_edges().count == 0; }); + info.no_loose_verts_hint = std::all_of( + info.order.begin(), info.order.end(), [](const Mesh *mesh) { + return mesh->runtime->loose_verts_cache.is_cached() && mesh->loose_verts().count == 0; + }); return info; } @@ -1155,6 +1160,9 @@ static void execute_realize_mesh_tasks(const RealizeInstancesOptions &options, if (all_meshes_info.no_loose_edges_hint) { dst_mesh->loose_edges_tag_none(); } + if (all_meshes_info.no_loose_verts_hint) { + dst_mesh->tag_loose_verts_none(); + } } /** \} */ diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h index 7260322d649..8879647c1e6 100644 --- a/source/blender/makesdna/DNA_mesh_types.h +++ b/source/blender/makesdna/DNA_mesh_types.h @@ -27,6 +27,7 @@ namespace bke { struct MeshRuntime; class AttributeAccessor; class MutableAttributeAccessor; +struct LooseVertCache; struct LooseEdgeCache; } // namespace bke } // namespace blender @@ -302,6 +303,15 @@ typedef struct Mesh { * Cached information about loose edges, calculated lazily when necessary. */ const blender::bke::LooseEdgeCache &loose_edges() const; + /** + * Cached information about vertices that aren't used by any edges. + */ + const blender::bke::LooseVertCache &loose_verts() const; + /** + * Cached information about vertices that aren't used by faces (but may be used by loose edges). + */ + const blender::bke::LooseVertCache &verts_no_face() const; + /** * Explicitly set the cached number of loose edges to zero. This can improve performance * later on, because finding loose edges lazily can be skipped entirely. @@ -310,6 +320,14 @@ typedef struct Mesh { * cache dirty. If the mesh was changed first, the relevant dirty tags should be called first. */ void loose_edges_tag_none() const; + /** + * Set the number of verices not connected to edges to zero. Similar to #loose_edges_tag_none(). + * There may still be vertices only used by loose edges though. + * + * \note If both #loose_edges_tag_none() and #tag_loose_verts_none() are called, + * all vertices are used by faces, so #verts_no_faces() will be tagged empty as well. + */ + void tag_loose_verts_none() const; /** * Normal direction of polygons, defined by positions and the winding direction of face corners. diff --git a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc index 963ac71297b..68461dfe7b1 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_duplicate_elements.cc @@ -551,6 +551,7 @@ static void duplicate_faces(GeometrySet &geometry_set, } } + new_mesh->tag_loose_verts_none(); new_mesh->loose_edges_tag_none(); copy_face_attributes_without_id(edge_mapping, diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc index 239077e0f44..f301cca32df 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_circle.cc @@ -153,6 +153,8 @@ static Mesh *create_circle_mesh(const float radius, std::iota(corner_verts.begin(), corner_verts.end(), 0); std::iota(corner_edges.begin(), corner_edges.end(), 0); + + mesh->loose_edges_tag_none(); } else if (fill_type == GEO_NODE_MESH_CIRCLE_FILL_TRIANGLE_FAN) { for (const int i : poly_offsets.index_range()) { @@ -170,6 +172,7 @@ static Mesh *create_circle_mesh(const float radius, } } + mesh->tag_loose_verts_none(); mesh->bounds_set_eager(calculate_bounds_circle(radius, verts_num)); return mesh; diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc index 6ba771768f5..1089f008a59 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_cone.cc @@ -723,6 +723,7 @@ Mesh *create_cylinder_or_cone_mesh(const float radius_top, } calculate_selection_outputs(config, attribute_outputs, mesh->attributes_for_write()); + mesh->tag_loose_verts_none(); mesh->loose_edges_tag_none(); mesh->bounds_set_eager(calculate_bounds_cylinder(config)); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc index ed2fc92ebb8..5d9a8c8f2e8 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_grid.cc @@ -149,6 +149,7 @@ Mesh *create_grid_mesh(const int verts_x, calculate_uvs(mesh, positions, corner_verts, size_x, size_y, uv_map_id); } + mesh->tag_loose_verts_none(); mesh->loose_edges_tag_none(); const float3 bounds = float3(size_x * 0.5f, size_y * 0.5f, 0.0f); diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc index f7fedcb4b74..f527a413866 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_primitive_uv_sphere.cc @@ -333,6 +333,7 @@ static Mesh *create_uv_sphere_mesh(const float radius, } }); + mesh->tag_loose_verts_none(); mesh->loose_edges_tag_none(); mesh->bounds_set_eager(calculate_bounds_uv_sphere(radius, segments, rings));