BLI: add IndexMask::from_ranges constructor function

This can be useful when going from a selection of curves to the corresponding
selection of points.

The simple implementation given here should work quite well in the majority of
cases. There is still optimization potential for some cases involving masks with
many gaps. The implementation is also single threaded for now. Using
multi-threading is possible, but should ideally be done in some way to still
lets us exploit the fact that the index ranges are already in sorted order.

Pull Request: https://projects.blender.org/blender/blender/pulls/133323
This commit is contained in:
Jacques Lucke 2025-01-21 18:18:12 +01:00
parent 890455affe
commit fb7eef9271
4 changed files with 100 additions and 8 deletions

View file

@ -13,6 +13,7 @@
#include "BLI_index_mask_fwd.hh"
#include "BLI_index_ranges_builder_fwd.hh"
#include "BLI_linear_allocator.hh"
#include "BLI_offset_indices.hh"
#include "BLI_offset_span.hh"
#include "BLI_task.hh"
#include "BLI_unique_sorted_indices.hh"
@ -208,6 +209,11 @@ class IndexMask : private IndexMaskData {
static IndexMask from_bools(const IndexMask &universe,
const VArray<bool> &bools,
IndexMaskMemory &memory);
/** Construct a mask from the ranges referenced by the offset indices. */
template<typename T>
static IndexMask from_ranges(OffsetIndices<T> offsets,
const IndexMask &mask,
IndexMaskMemory &memory);
/**
* Constructs a mask by repeating the indices in the given mask with a stride.
* For example, with an input mask containing `{3, 5}` and a stride of 10 the resulting mask
@ -581,6 +587,13 @@ template<typename T> void build_reverse_map(const IndexMask &mask, MutableSpan<T
int64_t consolidate_index_mask_segments(MutableSpan<IndexMaskSegment> segments,
IndexMaskMemory &memory);
/**
* Adds index mask segments to the the vector for the given range. Ranges shorter than
* #max_segment_size fit into a single segment. Larger ranges are split into multiple segments.
*/
template<int64_t N>
void index_range_to_mask_segments(const IndexRange range, Vector<IndexMaskSegment, N> &r_segments);
/* -------------------------------------------------------------------- */
/** \name #RawMaskIterator Inline Methods
* \{ */
@ -1081,6 +1094,20 @@ inline bool operator!=(const IndexMask &a, const IndexMask &b)
return !(a == b);
}
template<int64_t N>
inline void index_range_to_mask_segments(const IndexRange range,
Vector<IndexMaskSegment, N> &r_segments)
{
const std::array<int16_t, max_segment_size> &static_indices_array = get_static_indices_array();
const int64_t full_size = range.size();
for (int64_t i = 0; i < full_size; i += max_segment_size) {
const int64_t size = std::min(i + max_segment_size, full_size) - i;
r_segments.append(
IndexMaskSegment(range.first() + i, Span(static_indices_array).take_front(size)));
}
}
} // namespace blender::index_mask
namespace blender {

View file

@ -642,6 +642,19 @@ IndexMask IndexMask::from_bools(const IndexMask &universe,
universe, GrainSize(512), memory, [&](const int64_t index) { return bools[index]; });
}
template<typename T>
IndexMask IndexMask::from_ranges(OffsetIndices<T> offsets,
const IndexMask &mask,
IndexMaskMemory &memory)
{
Vector<IndexMaskSegment, 16> segments;
mask.foreach_range([&](const IndexRange mask_range) {
const IndexRange range = offsets[mask_range];
index_range_to_mask_segments(range, segments);
});
return IndexMask::from_segments(segments, memory);
}
IndexMask IndexMask::from_union(const IndexMask &mask_a,
const IndexMask &mask_b,
IndexMaskMemory &memory)
@ -1188,5 +1201,11 @@ template IndexMask IndexMask::from_indices(Span<int32_t>, IndexMaskMemory &);
template IndexMask IndexMask::from_indices(Span<int64_t>, IndexMaskMemory &);
template void IndexMask::to_indices(MutableSpan<int32_t>) const;
template void IndexMask::to_indices(MutableSpan<int64_t>) const;
template IndexMask IndexMask::from_ranges(OffsetIndices<int32_t>,
const IndexMask &,
IndexMaskMemory &);
template IndexMask IndexMask::from_ranges(OffsetIndices<int64_t>,
const IndexMask &,
IndexMaskMemory &);
} // namespace blender::index_mask

View file

@ -974,18 +974,11 @@ static IndexMaskSegment evaluate_exact_with_indices(const Expr &root_expression,
static Vector<IndexMaskSegment> build_result_mask_segments(
const Span<EvaluatedSegment> evaluated_segments)
{
const std::array<int16_t, max_segment_size> &static_indices_array = get_static_indices_array();
Vector<IndexMaskSegment> result_mask_segments;
for (const EvaluatedSegment &evaluated_segment : evaluated_segments) {
switch (evaluated_segment.type) {
case EvaluatedSegment::Type::Full: {
const int64_t full_size = evaluated_segment.bounds.size();
for (int64_t i = 0; i < full_size; i += max_segment_size) {
const int64_t size = std::min(i + max_segment_size, full_size) - i;
result_mask_segments.append(IndexMaskSegment(
evaluated_segment.bounds.first() + i, Span(static_indices_array).take_front(size)));
}
index_range_to_mask_segments(evaluated_segment.bounds, result_mask_segments);
break;
}
case EvaluatedSegment::Type::Copy: {

View file

@ -1222,4 +1222,57 @@ TEST(index_mask, SliceAndShift)
}
}
TEST(index_mask, IndexRangeToMaskSegments)
{
auto test_range = [](const IndexRange range) {
Vector<IndexMaskSegment> segments;
index_range_to_mask_segments(range, segments);
IndexMaskMemory memory;
const IndexMask mask = IndexMask::from_segments(segments, memory);
const std::optional<IndexRange> new_range = mask.to_range();
EXPECT_TRUE(new_range.has_value());
EXPECT_EQ(range, *new_range);
};
test_range(IndexRange::from_begin_size(1'000, 0));
test_range(IndexRange::from_begin_end_inclusive(0, 10));
test_range(IndexRange::from_begin_end_inclusive(0, 10'000));
test_range(IndexRange::from_begin_end_inclusive(0, 100'000));
test_range(IndexRange::from_begin_end_inclusive(0, 1'000'000));
test_range(IndexRange::from_begin_end_inclusive(50'000, 1'000'000));
test_range(IndexRange::from_begin_end_inclusive(999'999, 1'000'000));
test_range(IndexRange::from_begin_end_inclusive(1'000'000, 1'000'000));
}
TEST(index_mask, FromRanges)
{
IndexMaskMemory memory;
Array<int> data = {5, 100, 400, 500, 100'000, 200'000};
OffsetIndices<int> offsets(data);
{
const IndexMask mask = IndexMask::from_ranges(offsets, offsets.index_range(), memory);
EXPECT_EQ(mask.size(), 199'995);
EXPECT_EQ(*mask.to_range(), IndexRange::from_begin_end(5, 200'000));
}
{
const IndexMask mask = IndexMask::from_ranges(offsets, IndexRange(0), memory);
EXPECT_TRUE(mask.is_empty());
}
{
const IndexMask mask = IndexMask::from_ranges(offsets, IndexRange(1), memory);
EXPECT_EQ(*mask.to_range(), IndexRange::from_begin_end(5, 100));
}
{
const IndexMask offsets_mask = IndexMask::from_indices(Span<int>({1, 4}), memory);
const IndexMask mask = IndexMask::from_ranges(offsets, offsets_mask, memory);
EXPECT_EQ(mask,
IndexMask::from_initializers({IndexRange::from_begin_end(100, 400),
IndexRange::from_begin_end(100'000, 200'000)},
memory));
}
}
} // namespace blender::index_mask::tests