From cfb8696a73b74d7907ff367ffb1f9f584b408a60 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Tue, 12 Aug 2025 18:21:52 +0200 Subject: [PATCH] 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 --- source/blender/blenkernel/BKE_curves.hh | 9 ++++ .../blenkernel/intern/curves_geometry.cc | 47 +++++++++++++++++++ .../blenkernel/intern/curves_geometry_test.cc | 34 ++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/source/blender/blenkernel/BKE_curves.hh b/source/blender/blenkernel/BKE_curves.hh index eac271ad7d8..d67df015bfd 100644 --- a/source/blender/blenkernel/BKE_curves.hh +++ b/source/blender/blenkernel/BKE_curves.hh @@ -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 evaluated_offsets_cache; + mutable SharedCache>> cyclic_offsets_cache; + mutable SharedCache> nurbs_basis_cache; /** @@ -382,6 +385,12 @@ class CurvesGeometry : public ::CurvesGeometry { */ Span 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> cyclic_offsets() const; + Span evaluated_positions() const; Span evaluated_tangents() const; Span evaluated_normals() const; diff --git a/source/blender/blenkernel/intern/curves_geometry.cc b/source/blender/blenkernel/intern/curves_geometry.cc index c4442037304..b99d6742302 100644 --- a/source/blender/blenkernel/intern/curves_geometry.cc +++ b/source/blender/blenkernel/intern/curves_geometry.cc @@ -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 CurvesGeometry::evaluated_points_by_curve() const return OffsetIndices(runtime.evaluated_offsets_cache.data().evaluated_offsets); } +std::optional> CurvesGeometry::cyclic_offsets() const +{ + this->runtime->cyclic_offsets_cache.ensure([&](std::optional> &r_data) { + const VArray 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 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; diff --git a/source/blender/blenkernel/intern/curves_geometry_test.cc b/source/blender/blenkernel/intern/curves_geometry_test.cc index fa180e0b0d9..8913c32c061 100644 --- a/source/blender/blenkernel/intern/curves_geometry_test.cc +++ b/source/blender/blenkernel/intern/curves_geometry_test.cc @@ -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> cyclic_offsets = curves.cyclic_offsets(); + EXPECT_FALSE(cyclic_offsets.has_value()); + } + { + curves.cyclic_for_write().fill(true); + curves.tag_topology_changed(); + const std::optional> cyclic_offsets = curves.cyclic_offsets(); + EXPECT_TRUE(cyclic_offsets.has_value()); + EXPECT_EQ_SPAN(*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> cyclic_offsets = curves.cyclic_offsets(); + EXPECT_FALSE(cyclic_offsets.has_value()); + } + { + curves.attributes_for_write().remove("cyclic"); + const std::optional> 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> cyclic_offsets = curves.cyclic_offsets(); + EXPECT_EQ_SPAN(*cyclic_offsets, {0, 0, 1, 1, 2, 2, 2, 2, 2, 3, 3}); + } +} + TEST(curves_geometry, CatmullRomEvaluation) { CurvesGeometry curves(4, 1);