Curves: Optmize and simplify point and curve deletion
Using the new index mask implementation, things can be a bit simpler. It's also simpler to use `complement` instead of `to_ranges_invert`, which just made everything less standard. Also create the new curve offsets in place instead of copying, and use implicit sharing to share attributes when no curves were deleted. With a version of `IndexMask::complement()` optmized locally, I observed the following speedups with a 1.2 million point curve system: - Delete points: 29 FPS -> 45 FPS - Delete curves: 48 FPS -> 49 FPS - Delete tip points: 25 FPS -> 32 FPS Also add a method to apply the "gather" function to all attributes, mostly as a continued experiment of consolidating attribute propagation. This can be used more elsewhere in the future.
This commit is contained in:
@@ -919,4 +919,10 @@ inline const AnonymousAttributeID &AttributeIDRef::anonymous_id() const
|
||||
return *anonymous_id_;
|
||||
}
|
||||
|
||||
void gather_attributes(AttributeAccessor src_attributes,
|
||||
eAttrDomain domain,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
const IndexMask &selection,
|
||||
MutableAttributeAccessor dst_attributes);
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -994,4 +994,34 @@ Vector<AttributeTransferData> retrieve_attributes_for_transfer(
|
||||
|
||||
/** \} */
|
||||
|
||||
void gather_attributes(const AttributeAccessor src_attributes,
|
||||
const eAttrDomain domain,
|
||||
const AnonymousAttributePropagationInfo &propagation_info,
|
||||
const IndexMask &selection,
|
||||
MutableAttributeAccessor dst_attributes)
|
||||
{
|
||||
const int src_size = src_attributes.domain_size(domain);
|
||||
src_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
|
||||
if (meta_data.domain != domain) {
|
||||
return true;
|
||||
}
|
||||
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
|
||||
return true;
|
||||
}
|
||||
const bke::GAttributeReader src = src_attributes.lookup(id, domain);
|
||||
if (selection.size() == src_size && src.sharing_info && src.varray.is_span()) {
|
||||
const bke::AttributeInitShared init(src.varray.get_internal_span().data(),
|
||||
*src.sharing_info);
|
||||
dst_attributes.add(id, domain, meta_data.data_type, init);
|
||||
}
|
||||
else {
|
||||
bke::GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
id, domain, meta_data.data_type);
|
||||
array_utils::gather(src.varray, selection, dst.span);
|
||||
dst.finish();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "BLI_length_parameterize.hh"
|
||||
#include "BLI_math_matrix.hh"
|
||||
#include "BLI_math_rotation_legacy.hh"
|
||||
#include "BLI_multi_value_map.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "BLO_read_write.h"
|
||||
@@ -1108,113 +1109,55 @@ bool CurvesGeometry::bounds_min_max(float3 &min, float3 &max) const
|
||||
return true;
|
||||
}
|
||||
|
||||
static void copy_construct_data(const GSpan src, GMutableSpan dst)
|
||||
{
|
||||
BLI_assert(src.size() == dst.size());
|
||||
src.type().copy_construct_n(src.data(), dst.data(), src.size());
|
||||
}
|
||||
|
||||
static CurvesGeometry copy_with_removed_points(
|
||||
const CurvesGeometry &curves,
|
||||
const IndexMask &points_to_delete,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
/* Use a map from points to curves to facilitate using an #IndexMask input. */
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask points_to_copy = points_to_delete.complement(curves.points_range(), memory);
|
||||
|
||||
const Array<int> point_to_curve_map = curves.point_to_curve_map();
|
||||
|
||||
const Vector<IndexRange> copy_point_ranges = points_to_delete.to_ranges_invert(
|
||||
curves.points_range());
|
||||
|
||||
/* For every range of points to copy, find the offset in the result curves point layers. */
|
||||
int new_point_count = 0;
|
||||
Array<int> copy_point_range_dst_offsets(copy_point_ranges.size());
|
||||
for (const int i : copy_point_ranges.index_range()) {
|
||||
copy_point_range_dst_offsets[i] = new_point_count;
|
||||
new_point_count += copy_point_ranges[i].size();
|
||||
}
|
||||
BLI_assert(new_point_count == (curves.points_num() - points_to_delete.size()));
|
||||
|
||||
/* Find out how many non-deleted points there are in every curve. */
|
||||
Array<int> curve_point_counts(curves.curves_num(), 0);
|
||||
for (const IndexRange range : copy_point_ranges) {
|
||||
for (const int point_i : range) {
|
||||
curve_point_counts[point_to_curve_map[point_i]]++;
|
||||
}
|
||||
}
|
||||
points_to_copy.foreach_index(
|
||||
[&](const int64_t point_i) { curve_point_counts[point_to_curve_map[point_i]]++; });
|
||||
|
||||
/* Build the offsets for the new curve points, skipping curves that had all points deleted.
|
||||
* Also store the original indices of the corresponding input curves, to facilitate parallel
|
||||
* copying of curve domain data. */
|
||||
int new_curve_count = 0;
|
||||
int curve_point_offset = 0;
|
||||
Vector<int> new_curve_offsets;
|
||||
Vector<int> new_curve_orig_indices;
|
||||
new_curve_offsets.append(0);
|
||||
for (const int i : curve_point_counts.index_range()) {
|
||||
if (curve_point_counts[i] > 0) {
|
||||
curve_point_offset += curve_point_counts[i];
|
||||
new_curve_offsets.append(curve_point_offset);
|
||||
|
||||
new_curve_count++;
|
||||
new_curve_orig_indices.append(i);
|
||||
}
|
||||
}
|
||||
|
||||
CurvesGeometry new_curves{new_point_count, new_curve_count};
|
||||
Vector<bke::AttributeTransferData> point_attributes = bke::retrieve_attributes_for_transfer(
|
||||
curves.attributes(),
|
||||
new_curves.attributes_for_write(),
|
||||
ATTR_DOMAIN_MASK_POINT,
|
||||
propagation_info);
|
||||
Vector<bke::AttributeTransferData> curve_attributes = bke::retrieve_attributes_for_transfer(
|
||||
curves.attributes(),
|
||||
new_curves.attributes_for_write(),
|
||||
ATTR_DOMAIN_MASK_CURVE,
|
||||
propagation_info);
|
||||
|
||||
threading::parallel_invoke(
|
||||
256 < new_point_count * new_curve_count,
|
||||
/* Initialize curve offsets. */
|
||||
[&]() { new_curves.offsets_for_write().copy_from(new_curve_offsets); },
|
||||
[&]() {
|
||||
/* Copy over point attributes. */
|
||||
for (bke::AttributeTransferData &attribute : point_attributes) {
|
||||
threading::parallel_for(copy_point_ranges.index_range(), 128, [&](IndexRange range) {
|
||||
for (const int range_i : range) {
|
||||
const IndexRange src_range = copy_point_ranges[range_i];
|
||||
const IndexRange dst_range(copy_point_range_dst_offsets[range_i], src_range.size());
|
||||
copy_construct_data(attribute.src.slice(src_range),
|
||||
attribute.dst.span.slice(dst_range));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
[&]() {
|
||||
/* Copy over curve attributes.
|
||||
* In some cases points are just dissolved, so the number of
|
||||
* curves will be the same. That could be optimized in the future. */
|
||||
for (bke::AttributeTransferData &attribute : curve_attributes) {
|
||||
if (new_curves.curves_num() == curves.curves_num()) {
|
||||
attribute.dst.span.copy_from(attribute.src);
|
||||
}
|
||||
else {
|
||||
bke::attribute_math::gather(attribute.src, new_curve_orig_indices, attribute.dst.span);
|
||||
}
|
||||
}
|
||||
const IndexMask curves_to_copy = IndexMask::from_predicate(
|
||||
curves.curves_range(), GrainSize(4096), memory, [&](const int64_t i) {
|
||||
return curve_point_counts[i] > 0;
|
||||
});
|
||||
|
||||
for (bke::AttributeTransferData &attribute : point_attributes) {
|
||||
attribute.dst.finish();
|
||||
CurvesGeometry dst_curves(points_to_copy.size(), curves_to_copy.size());
|
||||
|
||||
threading::parallel_invoke(
|
||||
dst_curves.curves_num() > 1024,
|
||||
[&]() {
|
||||
MutableSpan<int> new_curve_offsets = dst_curves.offsets_for_write();
|
||||
array_utils::gather(
|
||||
curve_point_counts.as_span(), curves_to_copy, new_curve_offsets.drop_back(1));
|
||||
offset_indices::accumulate_counts_to_offsets(new_curve_offsets);
|
||||
},
|
||||
[&]() {
|
||||
gather_attributes(curves.attributes(),
|
||||
ATTR_DOMAIN_POINT,
|
||||
propagation_info,
|
||||
points_to_copy,
|
||||
dst_curves.attributes_for_write());
|
||||
gather_attributes(curves.attributes(),
|
||||
ATTR_DOMAIN_CURVE,
|
||||
propagation_info,
|
||||
curves_to_copy,
|
||||
dst_curves.attributes_for_write());
|
||||
});
|
||||
|
||||
if (dst_curves.curves_num() == curves.curves_num()) {
|
||||
dst_curves.runtime->type_counts = curves.runtime->type_counts;
|
||||
}
|
||||
for (bke::AttributeTransferData &attribute : curve_attributes) {
|
||||
attribute.dst.finish();
|
||||
else {
|
||||
dst_curves.remove_attributes_based_on_types();
|
||||
}
|
||||
|
||||
if (new_curves.curves_num() != curves.curves_num()) {
|
||||
new_curves.remove_attributes_based_on_types();
|
||||
}
|
||||
|
||||
return new_curves;
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
void CurvesGeometry::remove_points(const IndexMask &points_to_delete,
|
||||
@@ -1229,103 +1172,73 @@ void CurvesGeometry::remove_points(const IndexMask &points_to_delete,
|
||||
*this = copy_with_removed_points(*this, points_to_delete, propagation_info);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void gather_group_to_group(const OffsetIndices<int> src_offsets,
|
||||
const OffsetIndices<int> dst_offsets,
|
||||
const IndexMask &selection,
|
||||
const Span<T> src,
|
||||
MutableSpan<T> dst)
|
||||
{
|
||||
selection.foreach_index(GrainSize(256), [&](const int64_t src_i, const int64_t dst_i) {
|
||||
dst.slice(dst_offsets[dst_i]).copy_from(src.slice(src_offsets[src_i]));
|
||||
});
|
||||
}
|
||||
|
||||
static void gather_group_to_group(const OffsetIndices<int> src_offsets,
|
||||
const OffsetIndices<int> dst_offsets,
|
||||
const IndexMask &selection,
|
||||
const GSpan src,
|
||||
GMutableSpan dst)
|
||||
{
|
||||
attribute_math::convert_to_static_type(src.type(), [&](auto dummy) {
|
||||
using T = decltype(dummy);
|
||||
gather_group_to_group(src_offsets, dst_offsets, selection, src.typed<T>(), dst.typed<T>());
|
||||
});
|
||||
}
|
||||
|
||||
static CurvesGeometry copy_with_removed_curves(
|
||||
const CurvesGeometry &curves,
|
||||
const IndexMask &curves_to_delete,
|
||||
const AnonymousAttributePropagationInfo &propagation_info)
|
||||
{
|
||||
const OffsetIndices old_points_by_curve = curves.points_by_curve();
|
||||
const Span<int> old_offsets = curves.offsets();
|
||||
const Vector<IndexRange> old_curve_ranges = curves_to_delete.to_ranges_invert(
|
||||
curves.curves_range());
|
||||
Vector<IndexRange> new_curve_ranges;
|
||||
Vector<IndexRange> old_point_ranges;
|
||||
Vector<IndexRange> new_point_ranges;
|
||||
int new_tot_points = 0;
|
||||
int new_tot_curves = 0;
|
||||
for (const IndexRange &curve_range : old_curve_ranges) {
|
||||
new_curve_ranges.append(IndexRange(new_tot_curves, curve_range.size()));
|
||||
new_tot_curves += curve_range.size();
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask curves_to_copy = curves_to_delete.complement(curves.curves_range(), memory);
|
||||
|
||||
const IndexRange old_point_range = old_points_by_curve[curve_range];
|
||||
old_point_ranges.append(old_point_range);
|
||||
new_point_ranges.append(IndexRange(new_tot_points, old_point_range.size()));
|
||||
new_tot_points += old_point_range.size();
|
||||
}
|
||||
CurvesGeometry dst_curves(0, curves_to_copy.size());
|
||||
MutableSpan<int> new_curve_offsets = dst_curves.offsets_for_write();
|
||||
offset_indices::gather_group_sizes(
|
||||
curves.points_by_curve(), curves_to_copy, new_curve_offsets.drop_back(1));
|
||||
offset_indices::accumulate_counts_to_offsets(new_curve_offsets);
|
||||
dst_curves.resize(new_curve_offsets.last(), dst_curves.curves_num());
|
||||
|
||||
CurvesGeometry new_curves{new_tot_points, new_tot_curves};
|
||||
Vector<bke::AttributeTransferData> point_attributes = bke::retrieve_attributes_for_transfer(
|
||||
curves.attributes(),
|
||||
new_curves.attributes_for_write(),
|
||||
ATTR_DOMAIN_MASK_POINT,
|
||||
propagation_info);
|
||||
Vector<bke::AttributeTransferData> curve_attributes = bke::retrieve_attributes_for_transfer(
|
||||
curves.attributes(),
|
||||
new_curves.attributes_for_write(),
|
||||
ATTR_DOMAIN_MASK_CURVE,
|
||||
propagation_info);
|
||||
const OffsetIndices src_points_by_curve = curves.points_by_curve();
|
||||
const OffsetIndices dst_points_by_curve = dst_curves.points_by_curve();
|
||||
|
||||
threading::parallel_invoke(
|
||||
256 < new_tot_points * new_tot_curves,
|
||||
/* Initialize curve offsets. */
|
||||
[&]() {
|
||||
MutableSpan<int> new_offsets = new_curves.offsets_for_write();
|
||||
new_offsets.last() = new_tot_points;
|
||||
threading::parallel_for(
|
||||
old_curve_ranges.index_range(), 128, [&](const IndexRange ranges_range) {
|
||||
for (const int range_i : ranges_range) {
|
||||
const IndexRange old_curve_range = old_curve_ranges[range_i];
|
||||
const IndexRange new_curve_range = new_curve_ranges[range_i];
|
||||
const IndexRange old_point_range = old_point_ranges[range_i];
|
||||
const IndexRange new_point_range = new_point_ranges[range_i];
|
||||
const int offset_shift = new_point_range.start() - old_point_range.start();
|
||||
const int curves_in_range = old_curve_range.size();
|
||||
threading::parallel_for(
|
||||
IndexRange(curves_in_range), 512, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
const int old_curve_i = old_curve_range[i];
|
||||
const int new_curve_i = new_curve_range[i];
|
||||
const int old_offset = old_offsets[old_curve_i];
|
||||
const int new_offset = old_offset + offset_shift;
|
||||
new_offsets[new_curve_i] = new_offset;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
[&]() {
|
||||
/* Copy over point attributes. */
|
||||
for (bke::AttributeTransferData &attribute : point_attributes) {
|
||||
threading::parallel_for(old_curve_ranges.index_range(), 128, [&](IndexRange range) {
|
||||
for (const int range_i : range) {
|
||||
copy_construct_data(attribute.src.slice(old_point_ranges[range_i]),
|
||||
attribute.dst.span.slice(new_point_ranges[range_i]));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
[&]() {
|
||||
/* Copy over curve attributes. */
|
||||
for (bke::AttributeTransferData &attribute : curve_attributes) {
|
||||
threading::parallel_for(old_curve_ranges.index_range(), 128, [&](IndexRange range) {
|
||||
for (const int range_i : range) {
|
||||
copy_construct_data(attribute.src.slice(old_curve_ranges[range_i]),
|
||||
attribute.dst.span.slice(new_curve_ranges[range_i]));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
const AttributeAccessor src_attributes = curves.attributes();
|
||||
MutableAttributeAccessor dst_attributes = dst_curves.attributes_for_write();
|
||||
|
||||
for (bke::AttributeTransferData &attribute : point_attributes) {
|
||||
attribute.dst.finish();
|
||||
}
|
||||
for (bke::AttributeTransferData &attribute : curve_attributes) {
|
||||
attribute.dst.finish();
|
||||
}
|
||||
src_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData meta_data) {
|
||||
if (meta_data.domain != ATTR_DOMAIN_POINT) {
|
||||
return true;
|
||||
}
|
||||
if (id.is_anonymous() && !propagation_info.propagate(id.anonymous_id())) {
|
||||
return true;
|
||||
}
|
||||
const GVArraySpan src = *src_attributes.lookup(id);
|
||||
GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(
|
||||
id, meta_data.domain, meta_data.data_type);
|
||||
gather_group_to_group(src_points_by_curve, dst_points_by_curve, curves_to_copy, src, dst.span);
|
||||
dst.finish();
|
||||
return true;
|
||||
});
|
||||
|
||||
new_curves.remove_attributes_based_on_types();
|
||||
gather_attributes(
|
||||
src_attributes, ATTR_DOMAIN_CURVE, propagation_info, curves_to_copy, dst_attributes);
|
||||
|
||||
return new_curves;
|
||||
dst_curves.remove_attributes_based_on_types();
|
||||
dst_curves.update_curve_types();
|
||||
|
||||
return dst_curves;
|
||||
}
|
||||
|
||||
void CurvesGeometry::remove_curves(const IndexMask &curves_to_delete,
|
||||
|
||||
Reference in New Issue
Block a user