diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc index 2eb682f5ff722..3214a7e6a6b20 100644 --- a/impeller/geometry/path.cc +++ b/impeller/geometry/path.cc @@ -349,4 +349,29 @@ std::optional Path::GetTransformedBoundingBox( return bounds->TransformBounds(transform); } +bool Path::HasTessellatedData(Scalar scale) const { + return ScalarNearlyEqual(scale, data_->tessellated_data.scale); +} + +const Path::TessellatedData& Path::GetTessellatedData() const { + return data_->tessellated_data; +} + +void Path::StoreTessellatedData(Scalar scale, + const float* vertices, + size_t vertices_count, + const uint16_t* indices, + size_t indices_count) const { + auto& data = data_->tessellated_data; + data.scale = scale; + data.points.resize(vertices_count * 2); + data.indices.resize(indices_count); + for (auto i = 0u; i < vertices_count * 2; i++) { + data.points[i] = vertices[i]; + } + for (auto i = 0u; i < indices_count; i++) { + data.indices[i] = indices[i]; + } +} + } // namespace impeller diff --git a/impeller/geometry/path.h b/impeller/geometry/path.h index be1bf4730b117..e2eecdc0422d7 100644 --- a/impeller/geometry/path.h +++ b/impeller/geometry/path.h @@ -5,15 +5,21 @@ #ifndef FLUTTER_IMPELLER_GEOMETRY_PATH_H_ #define FLUTTER_IMPELLER_GEOMETRY_PATH_H_ +#include #include #include #include #include #include "impeller/geometry/path_component.h" +#include "impeller/geometry/scalar.h" namespace impeller { +namespace testing { +class TessellatorAccess; +} + enum class Cap { kButt, kRound, @@ -177,6 +183,35 @@ class Path { private: friend class PathBuilder; + friend class Tessellator; + friend class testing::TessellatorAccess; + + struct TessellatedData { + Scalar scale = 0.0; + std::vector points; + std::vector indices; + }; + + // The following methods are not thread safe and should only be called + // by the tessellator component. This functionality should be removed + // after https://github.com/flutter/flutter/issues/123671 is fixed. + + /// @brief Whether or not there is tessellated data at or close to the + /// provided [scale]. + bool HasTessellatedData(Scalar scale) const; + + /// @brief Retrieve the tessellated data for the current path. + /// + /// You must check that this data matches the current scale with + /// [HasTessellatedData] first. + const TessellatedData& GetTessellatedData() const; + + /// @brief Update the tessellated data for the given [scale]. + void StoreTessellatedData(Scalar scale, + const float* vertices, + size_t vertices_count, + const uint16_t* indices, + size_t indices_count) const; struct ComponentIndexPair { ComponentType type = ComponentType::kLinear; @@ -211,8 +246,8 @@ class Path { std::vector components; std::vector points; std::vector contours; - std::optional bounds; + mutable TessellatedData tessellated_data; }; explicit Path(Data data); diff --git a/impeller/tessellator/tessellator.cc b/impeller/tessellator/tessellator.cc index dcc516758e6cd..c48b991f3d0e6 100644 --- a/impeller/tessellator/tessellator.cc +++ b/impeller/tessellator/tessellator.cc @@ -67,6 +67,12 @@ Tessellator::Result Tessellator::Tessellate(const Path& path, if (!callback) { return Result::kInputError; } + if (path.HasTessellatedData(tolerance)) { + auto& data = path.GetTessellatedData(); + callback(data.points.data(), data.points.size() / 2, data.indices.data(), + data.indices.size()); + return Result::kSuccess; + } point_buffer_->clear(); auto polyline = @@ -144,6 +150,8 @@ Tessellator::Result Tessellator::Tessellate(const Path& path, element_item_count)) { return Result::kInputError; } + path.StoreTessellatedData(tolerance, vertices, vertex_item_count, + indices.data(), element_item_count); } else { std::vector points; std::vector data; diff --git a/impeller/tessellator/tessellator_unittests.cc b/impeller/tessellator/tessellator_unittests.cc index 0a2ceae2cb450..ce99fdcb73528 100644 --- a/impeller/tessellator/tessellator_unittests.cc +++ b/impeller/tessellator/tessellator_unittests.cc @@ -2,17 +2,32 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include #include "flutter/testing/testing.h" #include "gtest/gtest.h" #include "impeller/geometry/geometry_asserts.h" #include "impeller/geometry/path.h" #include "impeller/geometry/path_builder.h" +#include "impeller/geometry/scalar.h" #include "impeller/tessellator/tessellator.h" namespace impeller { namespace testing { +class TessellatorAccess { + public: + static bool HasData(const Path& path, Scalar scale) { + return path.HasTessellatedData(scale); + } + + static std::pair, std::vector> GetData( + const Path& path) { + auto& result = path.GetTessellatedData(); + return std::make_pair(result.points, result.indices); + } +}; + TEST(TessellatorTest, TessellatorBuilderReturnsCorrectResultStatus) { // Zero points. { @@ -484,6 +499,90 @@ TEST(TessellatorTest, FilledRoundRectTessellationVertices) { Rect::MakeXYWH(5000, 10000, 2000, 3000), {50, 70}); } +TEST(TessellatorTest, CachesPathData) { + Tessellator t; + auto path = PathBuilder{} + .AddCircle(Point{100, 100}, 10) + .TakePath(FillType::kPositive); + + EXPECT_FALSE(TessellatorAccess::HasData(path, 1.0)); + + std::vector test_vertices; + std::vector test_indicies; + Tessellator::Result result = + t.Tessellate(path, 1.0f, + [&](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { + for (auto i = 0u; i < vertices_count * 2; i++) { + test_vertices.push_back(vertices[i]); + } + for (auto i = 0u; i < indices_count; i++) { + test_indicies.push_back(indices[i]); + } + return true; + }); + + EXPECT_EQ(result, Tessellator::Result::kSuccess); + EXPECT_TRUE(TessellatorAccess::HasData(path, 1.0)); + EXPECT_TRUE(TessellatorAccess::HasData(path, 1.0000001)); + EXPECT_FALSE(TessellatorAccess::HasData(path, 2.0)); + + auto [points, indices] = TessellatorAccess::GetData(path); + + ASSERT_EQ(points.size(), test_vertices.size()); + ASSERT_EQ(indices.size(), test_indicies.size()); + + for (auto i = 0u; i < test_vertices.size(); i++) { + EXPECT_EQ(test_vertices[i], points[i]); + } + for (auto i = 0u; i < test_indicies.size(); i++) { + EXPECT_EQ(test_indicies[i], indices[i]); + } +} + +TEST(TessellatorTest, CachesSingleScalarData) { + Tessellator t; + auto path = PathBuilder{} + .AddCircle(Point{100, 100}, 10) + .TakePath(FillType::kPositive); + + EXPECT_FALSE(TessellatorAccess::HasData(path, 1.0)); + + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, const uint16_t* indices, + size_t indices_count) { return true; }); + + EXPECT_EQ(result, Tessellator::Result::kSuccess); + EXPECT_TRUE(TessellatorAccess::HasData(path, 1.0)); + + result = t.Tessellate( + path, 2.0f, + [](const float* vertices, size_t vertices_count, const uint16_t* indices, + size_t indices_count) { return true; }); + + EXPECT_EQ(result, Tessellator::Result::kSuccess); + EXPECT_FALSE(TessellatorAccess::HasData(path, 1.0)); + EXPECT_TRUE(TessellatorAccess::HasData(path, 2.0)); +} + +TEST(TessellatorTest, DoesNotCacheFailedTessellation) { + Tessellator t; + auto path = PathBuilder{} + .AddCircle(Point{100, 100}, 10) + .TakePath(FillType::kPositive); + + EXPECT_FALSE(TessellatorAccess::HasData(path, 1.0)); + + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, const uint16_t* indices, + size_t indices_count) { return false; }); + + EXPECT_EQ(result, Tessellator::Result::kInputError); + EXPECT_FALSE(TessellatorAccess::HasData(path, 1.0)); +} + #if !NDEBUG TEST(TessellatorTest, ChecksConcurrentPolylineUsage) { auto tessellator = std::make_shared();