Curves: Add cyclic curve offsets cache

This will be used to simplify the rendering of cyclic curves and improve
the performance. The cache is just a prefix sum of the cyclic attribute.

Pull Request: https://projects.blender.org/blender/blender/pulls/144444
This commit is contained in:
Hans Goudey
2025-08-12 18:21:52 +02:00
committed by Hans Goudey
parent 809f7abfb6
commit cfb8696a73
3 changed files with 90 additions and 0 deletions

View File

@@ -9,6 +9,7 @@
* \brief Low-level operations for curves.
*/
#include "BLI_array_utils.hh"
#include "BLI_bounds_types.hh"
#include "BLI_implicit_sharing_ptr.hh"
#include "BLI_index_mask_fwd.hh"
@@ -94,6 +95,8 @@ class CurvesGeometryRuntime {
};
mutable SharedCache<EvaluatedOffsets> evaluated_offsets_cache;
mutable SharedCache<std::optional<Vector<int>>> cyclic_offsets_cache;
mutable SharedCache<Vector<curves::nurbs::BasisCache>> nurbs_basis_cache;
/**
@@ -382,6 +385,12 @@ class CurvesGeometry : public ::CurvesGeometry {
*/
Span<int> bezier_evaluated_offsets_for_curve(int curve_index) const;
/**
* A prefix sum of the cyclic attribute, in other words the number of cyclic curves that precede
* each curve. Used for rendering. If there are no cyclic curves, `std::nullopt` is returned.
*/
std::optional<Span<int>> cyclic_offsets() const;
Span<float3> evaluated_positions() const;
Span<float3> evaluated_tangents() const;
Span<float3> evaluated_normals() const;

View File

@@ -131,6 +131,7 @@ CurvesGeometry::CurvesGeometry(const CurvesGeometry &other)
other.runtime->custom_knots_sharing_info,
other.runtime->type_counts,
other.runtime->evaluated_offsets_cache,
other.runtime->cyclic_offsets_cache,
other.runtime->nurbs_basis_cache,
other.runtime->evaluated_position_cache,
other.runtime->bounds_cache,
@@ -715,6 +716,51 @@ OffsetIndices<int> CurvesGeometry::evaluated_points_by_curve() const
return OffsetIndices<int>(runtime.evaluated_offsets_cache.data().evaluated_offsets);
}
std::optional<Span<int>> CurvesGeometry::cyclic_offsets() const
{
this->runtime->cyclic_offsets_cache.ensure([&](std::optional<Vector<int>> &r_data) {
const VArray<bool> cyclic = this->cyclic();
const auto ensure_vector = [&]() {
if (r_data) {
r_data->resize(cyclic.size() + 1);
}
else {
r_data.emplace(cyclic.size() + 1);
}
return r_data->as_mutable_span();
};
if (const std::optional<bool> single = cyclic.get_if_single()) {
if (*single) {
array_utils::fill_index_range(ensure_vector());
}
else {
r_data = std::nullopt;
}
return;
}
MutableSpan span = ensure_vector();
int sum = 0;
for (const int i : cyclic.index_range()) {
span[i] = sum;
if (cyclic[i]) {
sum++;
}
}
span.last() = sum;
if (sum == 0) {
r_data = std::nullopt;
}
});
if (!this->runtime->cyclic_offsets_cache.data()) {
return std::nullopt;
}
return this->runtime->cyclic_offsets_cache.data()->as_span();
}
IndexMask CurvesGeometry::indices_for_curve_type(const CurveType type,
IndexMaskMemory &memory) const
{
@@ -1211,6 +1257,7 @@ void CurvesGeometry::tag_topology_changed()
this->runtime->custom_knot_offsets_cache.tag_dirty();
this->tag_positions_changed();
this->runtime->evaluated_offsets_cache.tag_dirty();
this->runtime->cyclic_offsets_cache.tag_dirty();
this->runtime->nurbs_basis_cache.tag_dirty();
this->runtime->max_material_index_cache.tag_dirty();
this->runtime->check_type_counts = true;

View File

@@ -85,6 +85,40 @@ TEST(curves_geometry, TypeCount)
EXPECT_EQ(counts[CURVE_TYPE_NURBS], 3);
}
TEST(curves_geometry, CyclicOffsets)
{
CurvesGeometry curves = create_basic_curves(100, 10);
{
const std::optional<Span<int>> cyclic_offsets = curves.cyclic_offsets();
EXPECT_FALSE(cyclic_offsets.has_value());
}
{
curves.cyclic_for_write().fill(true);
curves.tag_topology_changed();
const std::optional<Span<int>> cyclic_offsets = curves.cyclic_offsets();
EXPECT_TRUE(cyclic_offsets.has_value());
EXPECT_EQ_SPAN<int>(*cyclic_offsets, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
}
{
curves.cyclic_for_write().fill(false);
curves.tag_topology_changed();
const std::optional<Span<int>> cyclic_offsets = curves.cyclic_offsets();
EXPECT_FALSE(cyclic_offsets.has_value());
}
{
curves.attributes_for_write().remove("cyclic");
const std::optional<Span<int>> cyclic_offsets = curves.cyclic_offsets();
EXPECT_FALSE(cyclic_offsets.has_value());
}
{
curves.cyclic_for_write().copy_from(
{false, true, false, true, false, false, false, false, true, false});
curves.tag_topology_changed();
const std::optional<Span<int>> cyclic_offsets = curves.cyclic_offsets();
EXPECT_EQ_SPAN<int>(*cyclic_offsets, {0, 0, 1, 1, 2, 2, 2, 2, 2, 3, 3});
}
}
TEST(curves_geometry, CatmullRomEvaluation)
{
CurvesGeometry curves(4, 1);