Sculpt: Specialize mask fill/invert operator per PBVH type

Two main user-facing benefits: only nodes with actually changed
values have undo steps pushed, meaning reduced memory usage
and undo runtime, and the mask attribute is removed when cleared,
reducing memory usage and theoretically improving performance
afterwards.

On the code side, the changes consist conceptually of expanding the
PBVH vertex iter macro for each PBVH type and simplifying the resulting
logic. This results in significantly more code; the idea is that in the
future abstractions to reduce duplication  will become clearer. We just
want abstractions that relate closely to the actual data processing,
which is much simpler than the existing macro makes it look.
This commit is contained in:
Hans Goudey
2023-12-03 18:12:45 -05:00
parent 7332a1eb90
commit beda9e4656
2 changed files with 382 additions and 42 deletions

View File

@@ -270,6 +270,11 @@ template<typename BitSpanT, typename Fn> inline void foreach_1_index(const BitSp
foreach_1_index_expr([](const BitInt x) { return x; }, fn, data);
}
template<typename BitSpanT, typename Fn> inline void foreach_0_index(const BitSpanT &data, Fn &&fn)
{
foreach_1_index_expr([](const BitInt x) { return ~x; }, fn, data);
}
template<typename BitSpanT1, typename BitSpanT2>
inline bool spans_equal(const BitSpanT1 &a, const BitSpanT2 &b)
{

View File

@@ -16,8 +16,12 @@
#include "DNA_object_types.h"
#include "DNA_vec_types.h"
#include "BLI_array_utils.hh"
#include "BLI_bit_span_ops.hh"
#include "BLI_bitmap_draw_2d.h"
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_lasso_2d.h"
#include "BLI_math_base.hh"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_polyfill_2d.h"
@@ -26,6 +30,7 @@
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
#include "BKE_attribute.hh"
#include "BKE_brush.hh"
#include "BKE_ccg.h"
#include "BKE_context.hh"
@@ -35,6 +40,7 @@
#include "BKE_paint.hh"
#include "BKE_pbvh_api.hh"
#include "BKE_scene.h"
#include "BKE_subdiv_ccg.hh"
#include "BKE_subsurf.hh"
#include "DEG_depsgraph.hh"
@@ -101,60 +107,389 @@ static float mask_flood_fill_get_new_value_for_elem(const float elem,
return 0.0f;
}
static int mask_flood_fill_exec(bContext *C, wmOperator *op)
static Span<int> get_visible_verts(const PBVHNode &node,
const Span<bool> hide_vert,
Vector<int> &indices)
{
const Scene *scene = CTX_data_scene(C);
Object *ob = CTX_data_active_object(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
if (BKE_pbvh_node_fully_hidden_get(&node)) {
return {};
}
const Span<int> verts = BKE_pbvh_node_get_unique_vert_indices(&node);
if (hide_vert.is_empty()) {
return verts;
}
indices.resize(verts.size());
const int *end = std::copy_if(verts.begin(), verts.end(), indices.begin(), [&](const int vert) {
return !hide_vert[vert];
});
indices.resize(end - indices.begin());
return indices;
}
PaintMaskFloodMode mode = PaintMaskFloodMode(RNA_enum_get(op->ptr, "mode"));
const float value = RNA_float_get(op->ptr, "value");
static Span<int> get_hidden_verts(const PBVHNode &node,
const Span<bool> hide_vert,
Vector<int> &indices)
{
if (hide_vert.is_empty()) {
return {};
}
const Span<int> verts = BKE_pbvh_node_get_unique_vert_indices(&node);
if (BKE_pbvh_node_fully_hidden_get(&node)) {
return verts;
}
indices.resize(verts.size());
const int *end = std::copy_if(verts.begin(), verts.end(), indices.begin(), [&](const int vert) {
return hide_vert[vert];
});
indices.resize(end - indices.begin());
return indices;
}
MultiresModifierData *mmd = BKE_sculpt_multires_active(scene, ob);
BKE_sculpt_mask_layers_ensure(depsgraph, CTX_data_main(C), ob, mmd);
static bool try_remove_mask_mesh(Object &object, const Span<PBVHNode *> nodes)
{
Mesh &mesh = *static_cast<Mesh *>(object.data);
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
const VArraySpan mask = *attributes.lookup<float>(".sculpt_mask", ATTR_DOMAIN_POINT);
if (mask.is_empty()) {
return true;
}
BKE_sculpt_update_object_for_edit(depsgraph, ob, false);
PBVH *pbvh = ob->sculpt->pbvh;
const bool multires = (BKE_pbvh_type(pbvh) == PBVH_GRIDS);
Vector<PBVHNode *> nodes = bke::pbvh::search_gather(pbvh, {});
const SculptMaskWriteInfo mask_write = SCULPT_mask_get_for_write(ob->sculpt);
SCULPT_undo_push_begin(ob, op);
/* If there are any hidden vertices that shouldn't be affected with a mask value set, the
* attribute cannot be removed. This could also be done by building an IndexMask in the full
* vertex domain. */
const VArraySpan hide_vert = *attributes.lookup<bool>(".hide_vert", ATTR_DOMAIN_POINT);
threading::EnumerableThreadSpecific<Vector<int>> all_index_data;
const bool hidden_masked_verts = threading::parallel_reduce(
nodes.index_range(),
1,
false,
[&](const IndexRange range, bool init) {
if (init) {
return init;
}
Vector<int> &index_data = all_index_data.local();
for (const PBVHNode *node : nodes.slice(range)) {
const Span<int> verts = get_hidden_verts(*node, hide_vert, index_data);
if (std::any_of(verts.begin(), verts.end(), [&](int i) { return mask[i] > 0.0f; })) {
return true;
}
}
return false;
},
std::logical_or());
if (hidden_masked_verts) {
return false;
}
/* Store undo data for nodes with changed mask. */
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (const int i : range) {
PBVHNode *node = nodes[i];
SCULPT_undo_push_node(ob, node, SCULPT_UNDO_MASK);
bool redraw = false;
PBVHVertexIter vi;
BKE_pbvh_vertex_iter_begin (pbvh, node, vi, PBVH_ITER_UNIQUE) {
float prevmask = vi.mask;
const float new_mask = mask_flood_fill_get_new_value_for_elem(prevmask, mode, value);
if (prevmask != new_mask) {
SCULPT_mask_vert_set(BKE_pbvh_type(ob->sculpt->pbvh), mask_write, new_mask, vi);
redraw = true;
}
}
BKE_pbvh_vertex_iter_end;
if (redraw) {
BKE_pbvh_node_mark_update_mask(node);
if (multires) {
BKE_pbvh_node_mark_normals_update(node);
}
for (PBVHNode *node : nodes.slice(range)) {
const Span<int> verts = BKE_pbvh_node_get_unique_vert_indices(node);
if (std::all_of(verts.begin(), verts.end(), [&](const int i) { return mask[i] == 0.0f; })) {
continue;
}
SCULPT_undo_push_node(&object, node, SCULPT_UNDO_MASK);
BKE_pbvh_node_mark_redraw(node);
}
});
if (multires) {
multires_mark_as_modified(depsgraph, ob, MULTIRES_COORDS_MODIFIED);
attributes.remove(".sculpt_mask");
return true;
}
static void fill_mask_mesh(Object &object, const float value, const Span<PBVHNode *> nodes)
{
Mesh &mesh = *static_cast<Mesh *>(object.data);
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
const VArraySpan hide_vert = *attributes.lookup<bool>(".hide_vert", ATTR_DOMAIN_POINT);
if (value == 0.0f) {
if (try_remove_mask_mesh(object, nodes)) {
return;
}
}
BKE_pbvh_update_mask(pbvh);
bke::SpanAttributeWriter<float> mask = attributes.lookup_or_add_for_write_only_span<float>(
".sculpt_mask", ATTR_DOMAIN_POINT);
SCULPT_undo_push_end(ob);
threading::EnumerableThreadSpecific<Vector<int>> all_index_data;
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
Vector<int> &index_data = all_index_data.local();
for (PBVHNode *node : nodes.slice(range)) {
const Span<int> verts = get_visible_verts(*node, hide_vert, index_data);
if (std::all_of(verts.begin(), verts.end(), [&](int i) { return mask.span[i] == value; })) {
continue;
}
SCULPT_undo_push_node(&object, node, SCULPT_UNDO_MASK);
mask.span.fill_indices(verts, value);
BKE_pbvh_node_mark_redraw(node);
}
});
mask.finish();
}
static void fill_mask_grids(Main &bmain,
const Scene &scene,
Depsgraph &depsgraph,
Object &object,
const float value,
const Span<PBVHNode *> nodes)
{
SubdivCCG &subdiv_ccg = *object.sculpt->subdiv_ccg;
CCGKey key;
BKE_subdiv_ccg_key_top_level(key, subdiv_ccg);
if (value == 0.0f && !key.has_mask) {
/* Unlike meshes, don't dynamically remove masks since it is interleaved with other data. */
return;
}
MultiresModifierData &mmd = *BKE_sculpt_multires_active(&scene, &object);
BKE_sculpt_mask_layers_ensure(&depsgraph, &bmain, &object, &mmd);
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
const Span<CCGElem *> grids = subdiv_ccg.grids;
bool any_changed = false;
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
const Span<int> grid_indices = BKE_pbvh_node_get_grid_indices(*node);
if (std::all_of(grid_indices.begin(), grid_indices.end(), [&](const int grid) {
CCGElem *elem = grids[grid];
for (const int i : IndexRange(key.grid_area)) {
if (*CCG_elem_offset_mask(&key, elem, i) != value) {
return false;
}
}
return true;
}))
{
continue;
}
SCULPT_undo_push_node(&object, node, SCULPT_UNDO_MASK);
if (grid_hidden.is_empty()) {
for (const int grid : grid_indices) {
CCGElem *elem = grids[grid];
for (const int i : IndexRange(key.grid_area)) {
*CCG_elem_offset_mask(&key, elem, i) = value;
}
}
}
else {
for (const int grid : grid_indices) {
CCGElem *elem = grids[grid];
bits::foreach_0_index(grid_hidden[grid], [&](const int i) {
*CCG_elem_offset_mask(&key, elem, i) = value;
});
}
}
BKE_pbvh_node_mark_redraw(node);
any_changed = true;
}
});
if (any_changed) {
multires_mark_as_modified(&depsgraph, &object, MULTIRES_COORDS_MODIFIED);
}
}
static void fill_mask_bmesh(Object &object, const float value, const Span<PBVHNode *> nodes)
{
BMesh &bm = *object.sculpt->bm;
const int offset = CustomData_get_offset_named(&bm.vdata, CD_PROP_FLOAT, ".sculpt_mask");
if (value == 0.0f && offset == -1) {
return;
}
if (offset == -1) {
/* Mask is not dynamically added or removed for dynamic topology sculpting. */
BLI_assert_unreachable();
return;
}
SCULPT_undo_push_node(&object, nodes.first(), SCULPT_UNDO_MASK);
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
bool redraw = false;
for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(node)) {
if (!BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
if (BM_ELEM_CD_GET_FLOAT(vert, offset) != value) {
BM_ELEM_CD_SET_FLOAT(vert, offset, value);
redraw = true;
}
}
}
if (redraw) {
BKE_pbvh_node_mark_redraw(node);
}
}
});
}
static void fill_mask(Main &bmain,
const Scene &scene,
Depsgraph &depsgraph,
Object &object,
const float value,
const Span<PBVHNode *> nodes)
{
PBVH &pbvh = *object.sculpt->pbvh;
switch (BKE_pbvh_type(&pbvh)) {
case PBVH_FACES:
fill_mask_mesh(object, value, nodes);
break;
case PBVH_GRIDS:
fill_mask_grids(bmain, scene, depsgraph, object, value, nodes);
break;
case PBVH_BMESH:
fill_mask_bmesh(object, value, nodes);
break;
}
/* Avoid calling #BKE_pbvh_node_mark_update_mask by doing that update here. */
for (PBVHNode *node : nodes) {
BKE_pbvh_node_fully_masked_set(node, value == 1.0f);
BKE_pbvh_node_fully_unmasked_set(node, value == 0.0f);
}
}
static void invert_mask_mesh(Object &object, const Span<PBVHNode *> nodes)
{
Mesh &mesh = *static_cast<Mesh *>(object.data);
bke::MutableAttributeAccessor attributes = mesh.attributes_for_write();
const VArraySpan hide_vert = *attributes.lookup<bool>(".hide_vert", ATTR_DOMAIN_POINT);
bke::SpanAttributeWriter<float> mask = attributes.lookup_or_add_for_write_span<float>(
".sculpt_mask", ATTR_DOMAIN_POINT);
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
SCULPT_undo_push_node(&object, node, SCULPT_UNDO_MASK);
for (const int vert : BKE_pbvh_node_get_unique_vert_indices(node)) {
if (!hide_vert.is_empty() && hide_vert[vert]) {
continue;
}
mask.span[vert] = 1.0f - mask.span[vert];
}
BKE_pbvh_node_mark_redraw(node);
bke::pbvh::node_update_mask_mesh(mask.span, *node);
}
});
mask.finish();
}
static void invert_mask_grids(Main &bmain,
const Scene &scene,
Depsgraph &depsgraph,
Object &object,
const Span<PBVHNode *> nodes)
{
SubdivCCG &subdiv_ccg = *object.sculpt->subdiv_ccg;
MultiresModifierData &mmd = *BKE_sculpt_multires_active(&scene, &object);
BKE_sculpt_mask_layers_ensure(&depsgraph, &bmain, &object, &mmd);
const BitGroupVector<> &grid_hidden = subdiv_ccg.grid_hidden;
CCGKey key;
BKE_subdiv_ccg_key_top_level(key, subdiv_ccg);
const Span<CCGElem *> grids = subdiv_ccg.grids;
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
SCULPT_undo_push_node(&object, node, SCULPT_UNDO_MASK);
const Span<int> grid_indices = BKE_pbvh_node_get_grid_indices(*node);
if (grid_hidden.is_empty()) {
for (const int grid : grid_indices) {
CCGElem *elem = grids[grid];
for (const int i : IndexRange(key.grid_area)) {
*CCG_elem_offset_mask(&key, elem, i) = 1.0f - *CCG_elem_offset_mask(&key, elem, i);
}
}
}
else {
for (const int grid : grid_indices) {
CCGElem *elem = grids[grid];
bits::foreach_0_index(grid_hidden[grid], [&](const int i) {
*CCG_elem_offset_mask(&key, elem, i) = 1.0f - *CCG_elem_offset_mask(&key, elem, i);
});
}
}
BKE_pbvh_node_mark_update_mask(node);
bke::pbvh::node_update_mask_grids(key, grids, *node);
}
});
multires_mark_as_modified(&depsgraph, &object, MULTIRES_COORDS_MODIFIED);
}
static void invert_mask_bmesh(Object &object, const Span<PBVHNode *> nodes)
{
BMesh &bm = *object.sculpt->bm;
const int offset = CustomData_get_offset_named(&bm.vdata, CD_PROP_FLOAT, ".sculpt_mask");
if (offset == -1) {
BLI_assert_unreachable();
return;
}
SCULPT_undo_push_node(&object, nodes.first(), SCULPT_UNDO_MASK);
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
for (PBVHNode *node : nodes.slice(range)) {
for (BMVert *vert : BKE_pbvh_bmesh_node_unique_verts(node)) {
if (!BM_elem_flag_test(vert, BM_ELEM_HIDDEN)) {
BM_ELEM_CD_SET_FLOAT(vert, offset, 1.0f - BM_ELEM_CD_GET_FLOAT(vert, offset));
}
}
BKE_pbvh_node_mark_update_mask(node);
bke::pbvh::node_update_mask_bmesh(offset, *node);
}
});
}
static void invert_mask(Main &bmain,
const Scene &scene,
Depsgraph &depsgraph,
Object &object,
const Span<PBVHNode *> nodes)
{
switch (BKE_pbvh_type(object.sculpt->pbvh)) {
case PBVH_FACES:
invert_mask_mesh(object, nodes);
break;
case PBVH_GRIDS:
invert_mask_grids(bmain, scene, depsgraph, object, nodes);
break;
case PBVH_BMESH:
invert_mask_bmesh(object, nodes);
break;
}
}
static int mask_flood_fill_exec(bContext *C, wmOperator *op)
{
Main &bmain = *CTX_data_main(C);
const Scene &scene = *CTX_data_scene(C);
Object &object = *CTX_data_active_object(C);
Depsgraph &depsgraph = *CTX_data_ensure_evaluated_depsgraph(C);
const PaintMaskFloodMode mode = PaintMaskFloodMode(RNA_enum_get(op->ptr, "mode"));
const float value = RNA_float_get(op->ptr, "value");
BKE_sculpt_update_object_for_edit(&depsgraph, &object, false);
SCULPT_undo_push_begin(&object, op);
Vector<PBVHNode *> nodes = bke::pbvh::search_gather(object.sculpt->pbvh, {});
switch (mode) {
case PAINT_MASK_FLOOD_VALUE:
fill_mask(bmain, scene, depsgraph, object, value, nodes);
break;
case PAINT_MASK_FLOOD_VALUE_INVERSE:
fill_mask(bmain, scene, depsgraph, object, 1.0f - value, nodes);
break;
case PAINT_MASK_INVERT:
invert_mask(bmain, scene, depsgraph, object, nodes);
break;
}
SCULPT_undo_push_end(&object);
SCULPT_tag_update_overlays(C);
@@ -1715,7 +2050,7 @@ static int sculpt_trim_gesture_lasso_exec(bContext *C, wmOperator *op)
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Object *object = CTX_data_active_object(C);
BKE_sculpt_update_object_for_edit(depsgraph, object, false);
BKE_sculpt_update_object_for_edit(depsgraph, object, false);
SculptSession *ss = object->sculpt;
if (BKE_pbvh_type(ss->pbvh) != PBVH_FACES) {