From 4e66769ec09461384af48b0bfdafc28884f01d19 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Thu, 30 Nov 2023 14:12:50 -0500 Subject: [PATCH] Sculpt: Improve "Hide/Show" operator performance for mesh Parallelize the implementation and move constant checks out of hot loops. Only push undo steps for PBVH nodes that actually have changed hide status. Optimize the "show all" case to remove the hide attributes. For timings, I recorded some information on a 16 million vertex mesh: - Hide masked (1/4 of mesh): 171 ms to 33 ms - Hide small box: 13 ms to 14 ms - Show all with small box hidden: 226 ms to 10 ms - Show with all visible: 173 ms to 0.36 ms There are a few other visbility operators that aren't affected: - SCULPT_OT_face_set_invert_visibility - SCULPT_OT_reveal_all - SCULPT_OT_face_set_change_visibility In separate steps, they should be moved to use the same structure, or maybe even removed in the case of "reveal all". I expect the "gather, change hidden verts, scatter" steps for each node could be made a bit more efficient, they do some redundant work. But it was also a simple way to share a reasonable amount of code. --- .../editors/sculpt_paint/paint_hide.cc | 177 +++++++++++++----- 1 file changed, 129 insertions(+), 48 deletions(-) diff --git a/source/blender/editors/sculpt_paint/paint_hide.cc b/source/blender/editors/sculpt_paint/paint_hide.cc index e5b8700ce71..8bedeeca3ea 100644 --- a/source/blender/editors/sculpt_paint/paint_hide.cc +++ b/source/blender/editors/sculpt_paint/paint_hide.cc @@ -9,7 +9,9 @@ #include "MEM_guardedalloc.h" +#include "BLI_array_utils.hh" #include "BLI_bitmap.h" +#include "BLI_enumerable_thread_specific.hh" #include "BLI_math_geom.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" @@ -49,9 +51,6 @@ /* For undo push. */ #include "sculpt_intern.hh" -using blender::Span; -using blender::Vector; - namespace blender::ed::sculpt_paint::hide { enum class VisAction { @@ -66,6 +65,11 @@ enum VisArea { Masked, }; +static bool action_to_hide(const VisAction action) +{ + return action == VisAction::Hide; +} + /* Return true if the element should be hidden/shown. */ static bool is_effected(const VisArea area, const float planes[4][4], @@ -83,49 +87,131 @@ static bool is_effected(const VisArea area, return ((inside && area == VisArea::Inside) || (!inside && area == VisArea::Outside)); } -static void partialvis_update_mesh(Object *ob, - PBVH *pbvh, +static void vert_show_all(Object &object, const Span nodes) +{ + Mesh &mesh = *static_cast(object.data); + bke::MutableAttributeAccessor attributes = mesh.attributes_for_write(); + if (const VArray attribute = *attributes.lookup(".hide_vert", ATTR_DOMAIN_POINT)) { + const VArraySpan hide_vert(attribute); + for (PBVHNode *node : nodes) { + const Span verts = BKE_pbvh_node_get_vert_indices(node); + if (std::any_of(verts.begin(), verts.end(), [&](const int i) { return hide_vert[i]; })) { + SCULPT_undo_push_node(&object, node, SCULPT_UNDO_HIDDEN); + BKE_pbvh_node_mark_rebuild_draw(node); + } + } + } + for (PBVHNode *node : nodes) { + BKE_pbvh_node_fully_hidden_set(node, false); + } + attributes.remove(".hide_vert"); +} + +static bool vert_hide_is_changed(const Span verts, + const Span orig_hide, + const Span new_hide) +{ + for (const int i : verts.index_range()) { + if (orig_hide[verts[i]] != new_hide[i]) { + return true; + } + } + return false; +} + +static void vert_hide_update(Object &object, + const Span nodes, + FunctionRef, MutableSpan)> calc_hide) +{ + Mesh &mesh = *static_cast(object.data); + bke::MutableAttributeAccessor attributes = mesh.attributes_for_write(); + bke::SpanAttributeWriter hide_vert = attributes.lookup_or_add_for_write_span( + ".hide_vert", ATTR_DOMAIN_POINT); + + threading::EnumerableThreadSpecific> all_new_hide; + threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) { + Vector &new_hide = all_new_hide.local(); + for (PBVHNode *node : nodes.slice(range)) { + const Span verts = BKE_pbvh_node_get_vert_indices(node); + + new_hide.reinitialize(verts.size()); + array_utils::gather(hide_vert.span.as_span(), verts, new_hide.as_mutable_span()); + calc_hide(verts, new_hide); + if (!vert_hide_is_changed(verts, hide_vert.span, new_hide)) { + continue; + } + + SCULPT_undo_push_node(&object, node, SCULPT_UNDO_HIDDEN); + + /* Don't tag a visibility update, we handle updating the fully hidden status here. */ + BKE_pbvh_node_mark_rebuild_draw(node); + BKE_pbvh_node_fully_hidden_set(node, !new_hide.contains(false)); + + array_utils::scatter(new_hide.as_span(), verts, hide_vert.span); + } + }); + hide_vert.finish(); +} + +static void partialvis_update_mesh(Object &object, + PBVH &pbvh, const VisAction action, const VisArea area, const float planes[4][4], const Span nodes) { - for (PBVHNode *node : nodes) { - Mesh *mesh = static_cast(ob->data); - const Span positions = BKE_pbvh_get_vert_positions(pbvh); - bool any_changed = false; - bool any_visible = false; - - const Span verts = BKE_pbvh_node_get_vert_indices(node); - - bke::MutableAttributeAccessor attributes = mesh->attributes_for_write(); - const VArray mask = *attributes.lookup_or_default( - ".sculpt_mask", ATTR_DOMAIN_POINT, 0.0f); - - bke::SpanAttributeWriter hide_vert = attributes.lookup_or_add_for_write_span( - ".hide_vert", ATTR_DOMAIN_POINT); - - SCULPT_undo_push_node(ob, node, SCULPT_UNDO_HIDDEN); - - for (const int vert : verts) { - /* Hide vertex if in the hide volume. */ - if (is_effected(area, planes, positions[vert], mask[vert])) { - hide_vert.span[vert] = (action == VisAction::Hide); - any_changed = true; - } - - if (!hide_vert.span[vert]) { - any_visible = true; - } - } - - hide_vert.finish(); - - if (any_changed) { - BKE_pbvh_node_mark_rebuild_draw(node); - BKE_pbvh_node_fully_hidden_set(node, !any_visible); - } + Mesh &mesh = *static_cast(object.data); + bke::MutableAttributeAccessor attributes = mesh.attributes_for_write(); + if (action == VisAction::Show && !attributes.contains(".hide_vert")) { + /* If everything is already visible, don't do anything.*/ + return; } + + const bool value = action_to_hide(action); + switch (area) { + case VisArea::Inside: + case VisArea::Outside: { + const Span positions = BKE_pbvh_get_vert_positions(&pbvh); + vert_hide_update(object, nodes, [&](const Span verts, MutableSpan hide) { + for (const int i : verts.index_range()) { + if (isect_point_planes_v3(planes, 4, positions[verts[i]])) { + hide[i] = value; + } + } + }); + break; + } + case VisArea::All: + switch (action) { + case VisAction::Hide: + vert_hide_update(object, nodes, [&](const Span /*verts*/, MutableSpan hide) { + hide.fill(true); + }); + break; + case VisAction::Show: + vert_show_all(object, nodes); + break; + } + break; + case VisArea::Masked: + const VArraySpan mask = *attributes.lookup(".sculpt_mask", ATTR_DOMAIN_POINT); + if (action == VisAction::Show && mask.is_empty()) { + vert_show_all(object, nodes); + } + else { + vert_hide_update(object, nodes, [&](const Span verts, MutableSpan hide) { + for (const int i : verts.index_range()) { + if (mask[verts[i]] > 0.5f) { + hide[i] = value; + } + } + }); + } + break; + } + + BKE_mesh_flush_hidden_from_verts(&mesh); + BKE_pbvh_update_hide_attributes_from_mesh(&pbvh); } /* Hide or show elements in multires grids with a special GridFlags @@ -217,6 +303,8 @@ static void partialvis_update_grids(Depsgraph *depsgraph, multires_mark_as_modified(depsgraph, ob, MULTIRES_HIDDEN_MODIFIED); } } + + BKE_pbvh_sync_visibility_from_verts(pbvh, static_cast(ob->data)); } static void partialvis_update_bmesh_verts(BMesh *bm, @@ -351,7 +439,6 @@ static int hide_show_exec(bContext *C, wmOperator *op) ARegion *region = CTX_wm_region(C); Object *ob = CTX_data_active_object(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - Mesh *mesh = static_cast(ob->data); /* Read operator properties. */ const VisAction action = VisAction(RNA_enum_get(op->ptr, "action")); @@ -383,7 +470,7 @@ static int hide_show_exec(bContext *C, wmOperator *op) switch (pbvh_type) { case PBVH_FACES: - partialvis_update_mesh(ob, pbvh, action, area, clip_planes, nodes); + partialvis_update_mesh(*ob, *pbvh, action, area, clip_planes, nodes); break; case PBVH_GRIDS: partialvis_update_grids(depsgraph, ob, pbvh, action, area, clip_planes, nodes); @@ -398,12 +485,6 @@ static int hide_show_exec(bContext *C, wmOperator *op) SCULPT_topology_islands_invalidate(ob->sculpt); - /* Ensure that edges and faces get hidden as well (not used by - * sculpt but it looks wrong when entering editmode otherwise). */ - if (ELEM(pbvh_type, PBVH_FACES, PBVH_GRIDS)) { - BKE_pbvh_sync_visibility_from_verts(pbvh, mesh); - } - RegionView3D *rv3d = CTX_wm_region_view3d(C); if (!BKE_sculptsession_use_pbvh_draw(ob, rv3d)) { DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);