diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 1b1a9a903913c..331f9b51ac973 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -14,6 +14,7 @@ #include "gtest/gtest.h" #include "impeller/core/device_buffer.h" #include "impeller/core/formats.h" +#include "impeller/core/host_buffer.h" #include "impeller/core/texture_descriptor.h" #include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/contents/conical_gradient_contents.h" @@ -2424,6 +2425,59 @@ TEST_P(EntityTest, GiantStrokePathAllocation) { EXPECT_NEAR(point.y, expected[4].y, 0.1); } +TEST_P(EntityTest, GiantLineStripPathAllocation) { + PathBuilder builder{}; + for (int i = 0; i < 10000; i++) { + builder.LineTo(Point(i, i)); + } + Path path = builder.TakePath(); + + ContentContext content_context(GetContext(), /*typographer_context=*/nullptr); + Entity entity; + + auto host_buffer = HostBuffer::Create(GetContext()->GetResourceAllocator(), + GetContext()->GetIdleWaiter()); + auto tessellator = Tessellator(); + + auto vertex_buffer = tessellator.GenerateLineStrip(path, *host_buffer, 1.0); + + // Validate the buffer data overflowed the small buffer + EXPECT_GT(vertex_buffer.vertex_count, kPointArenaSize); + + // Validate that there are no uninitialized points near the gap. + Point* written_data = reinterpret_cast( + (vertex_buffer.vertex_buffer.GetBuffer()->OnGetContents() + + vertex_buffer.vertex_buffer.GetRange().offset)); + + std::vector expected = { + Point(4093, 4093), // + Point(4094, 4094), // + Point(4095, 4095), // + Point(4096, 4096), // + Point(4097, 4097) // + }; + + Point point = written_data[kPointArenaSize - 2]; + EXPECT_NEAR(point.x, expected[0].x, 0.1); + EXPECT_NEAR(point.y, expected[0].y, 0.1); + + point = written_data[kPointArenaSize - 1]; + EXPECT_NEAR(point.x, expected[1].x, 0.1); + EXPECT_NEAR(point.y, expected[1].y, 0.1); + + point = written_data[kPointArenaSize]; + EXPECT_NEAR(point.x, expected[2].x, 0.1); + EXPECT_NEAR(point.y, expected[2].y, 0.1); + + point = written_data[kPointArenaSize + 1]; + EXPECT_NEAR(point.x, expected[3].x, 0.1); + EXPECT_NEAR(point.y, expected[3].y, 0.1); + + point = written_data[kPointArenaSize + 2]; + EXPECT_NEAR(point.x, expected[4].x, 0.1); + EXPECT_NEAR(point.y, expected[4].y, 0.1); +} + } // namespace testing } // namespace impeller diff --git a/impeller/entity/geometry/stroke_path_geometry.cc b/impeller/entity/geometry/stroke_path_geometry.cc index ec6178b6036ff..a4fc27edbaf73 100644 --- a/impeller/entity/geometry/stroke_path_geometry.cc +++ b/impeller/entity/geometry/stroke_path_geometry.cc @@ -41,7 +41,7 @@ class PositionWriter { bool HasOversizedBuffer() const { return !oversized_.empty(); } - const std::vector& GetOveriszedBuffer() const { return oversized_; } + const std::vector& GetOversizedBuffer() const { return oversized_; } private: std::vector& points_; @@ -618,7 +618,7 @@ GeometryResult StrokePathGeometry::GetPositionBuffer( .mode = GeometryResult::Mode::kPreventOverdraw}; } const std::vector& oversized_data = - position_writer.GetOveriszedBuffer(); + position_writer.GetOversizedBuffer(); BufferView buffer_view = host_buffer.Emplace( /*buffer=*/nullptr, // (arena_length + oversized_length) * sizeof(Point), // diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc index 7762ada248305..f5ea8fb3b94b0 100644 --- a/impeller/geometry/path.cc +++ b/impeller/geometry/path.cc @@ -59,6 +59,10 @@ bool Path::IsEmpty() const { data_->components[0] == ComponentType::kContour); } +bool Path::IsSingleContour() const { + return data_->single_countour; +} + /// Determine required storage for points and indices. std::pair Path::CountStorage(Scalar scale) const { size_t points = 0; diff --git a/impeller/geometry/path.h b/impeller/geometry/path.h index edad3c9a44175..d81b3cb0ec6f8 100644 --- a/impeller/geometry/path.h +++ b/impeller/geometry/path.h @@ -153,6 +153,9 @@ class Path { bool IsEmpty() const; + /// @brief Whether the line contains a single contour. + bool IsSingleContour() const; + bool GetLinearComponentAtIndex(size_t index, LinearPathComponent& linear) const; @@ -219,6 +222,7 @@ class Path { FillType fill = FillType::kNonZero; Convexity convexity = Convexity::kUnknown; + bool single_countour = true; std::optional bounds; std::vector points; std::vector components; diff --git a/impeller/geometry/path_builder.cc b/impeller/geometry/path_builder.cc index 6d49fca61d4b0..fb787f145dac8 100644 --- a/impeller/geometry/path_builder.cc +++ b/impeller/geometry/path_builder.cc @@ -18,13 +18,22 @@ PathBuilder::~PathBuilder() = default; Path PathBuilder::CopyPath(FillType fill) { prototype_.fill = fill; + prototype_.single_countour = + current_contour_location_ == 0u || + (contour_count_ == 2 && + prototype_.components.back() == Path::ComponentType::kContour); return Path(prototype_); } Path PathBuilder::TakePath(FillType fill) { prototype_.fill = fill; UpdateBounds(); + prototype_.single_countour = + current_contour_location_ == 0u || + (contour_count_ == 2 && + prototype_.components.back() == Path::ComponentType::kContour); current_contour_location_ = 0u; + contour_count_ = 1; return Path(std::move(prototype_)); } @@ -276,6 +285,7 @@ void PathBuilder::AddContourComponent(const Point& destination, points.push_back(destination); points.push_back(closed); components.push_back(Path::ComponentType::kContour); + contour_count_ += 1; } prototype_.bounds.reset(); } @@ -450,6 +460,7 @@ PathBuilder& PathBuilder::AddPath(const Path& path) { for (auto component : path.data_->components) { if (component == Path::ComponentType::kContour) { current_contour_location_ = source_offset; + contour_count_ += 1; } source_offset += Path::VerbToOffset(component); } diff --git a/impeller/geometry/path_builder.h b/impeller/geometry/path_builder.h index abd899d2368c4..0184687cb611e 100644 --- a/impeller/geometry/path_builder.h +++ b/impeller/geometry/path_builder.h @@ -111,6 +111,7 @@ class PathBuilder { Point subpath_start_; Point current_; size_t current_contour_location_ = 0u; + size_t contour_count_ = 0u; Path::Data prototype_; PathBuilder& AddRoundedRectTopLeft(Rect rect, RoundingRadii radii); diff --git a/impeller/geometry/path_component.cc b/impeller/geometry/path_component.cc index c6ba536b2219a..1637bc9eb4a06 100644 --- a/impeller/geometry/path_component.cc +++ b/impeller/geometry/path_component.cc @@ -5,6 +5,7 @@ #include "path_component.h" #include +#include #include "impeller/geometry/scalar.h" #include "impeller/geometry/wangs_formula.h" @@ -77,6 +78,29 @@ void StripVertexWriter::Write(Point point) { point_buffer_[count_++] = point; } +/////////// LineStripVertexWriter //////// + +LineStripVertexWriter::LineStripVertexWriter(std::vector& points) + : points_(points) {} + +void LineStripVertexWriter::EndContour() {} + +void LineStripVertexWriter::Write(Point point) { + if (offset_ >= points_.size()) { + overflow_.push_back(point); + } else { + points_[offset_++] = point; + } +} + +const std::vector& LineStripVertexWriter::GetOversizedBuffer() const { + return overflow_; +} + +std::pair LineStripVertexWriter::GetVertexCount() const { + return std::make_pair(offset_, overflow_.size()); +} + /////////// GLESVertexWriter /////////// GLESVertexWriter::GLESVertexWriter(std::vector& points, diff --git a/impeller/geometry/path_component.h b/impeller/geometry/path_component.h index 156b3de04d533..b558c006a36e6 100644 --- a/impeller/geometry/path_component.h +++ b/impeller/geometry/path_component.h @@ -67,6 +67,27 @@ class StripVertexWriter : public VertexWriter { uint16_t* index_buffer_ = nullptr; }; +/// @brief A vertex writer that generates a line strip topology. +class LineStripVertexWriter : public VertexWriter { + public: + explicit LineStripVertexWriter(std::vector& points); + + ~LineStripVertexWriter() = default; + + void EndContour() override; + + void Write(Point point) override; + + std::pair GetVertexCount() const; + + const std::vector& GetOversizedBuffer() const; + + private: + size_t offset_ = 0u; + std::vector& points_; + std::vector overflow_; +}; + /// @brief A vertex writer that has no hardware requirements. class GLESVertexWriter : public VertexWriter { public: diff --git a/impeller/geometry/path_unittests.cc b/impeller/geometry/path_unittests.cc index 51c29756f5bd6..ef802cbe7c0eb 100644 --- a/impeller/geometry/path_unittests.cc +++ b/impeller/geometry/path_unittests.cc @@ -50,6 +50,142 @@ TEST(PathTest, PathCreatePolyLineDoesNotDuplicatePoints) { ASSERT_EQ(polyline.GetPoint(4).x, 50); } +TEST(PathTest, PathSingleContour) { + // Closed shapes. + { + Path path = PathBuilder{}.AddCircle({100, 100}, 50).TakePath(); + EXPECT_TRUE(path.IsSingleContour()); + } + + { + Path path = + PathBuilder{}.AddOval(Rect::MakeXYWH(100, 100, 100, 100)).TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } + + { + Path path = + PathBuilder{}.AddRect(Rect::MakeXYWH(100, 100, 100, 100)).TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddRoundRect(RoundRect::MakeRectRadius( + Rect::MakeXYWH(100, 100, 100, 100), 10)) + .TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } + + // Open shapes. + { + Point p(100, 100); + Path path = PathBuilder{}.AddLine(p, {200, 100}).TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } + + { + Path path = + PathBuilder{} + .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100}) + .TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100}) + .TakePath(); + + EXPECT_TRUE(path.IsSingleContour()); + } +} + +TEST(PathTest, PathSingleContourDoubleShapes) { + // Closed shapes. + { + Path path = PathBuilder{} + .AddCircle({100, 100}, 50) + .AddCircle({100, 100}, 50) + .TakePath(); + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddOval(Rect::MakeXYWH(100, 100, 100, 100)) + .AddOval(Rect::MakeXYWH(100, 100, 100, 100)) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddRect(Rect::MakeXYWH(100, 100, 100, 100)) + .AddRect(Rect::MakeXYWH(100, 100, 100, 100)) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddRoundRect(RoundRect::MakeRectRadius( + Rect::MakeXYWH(100, 100, 100, 100), 10)) + .AddRoundRect(RoundRect::MakeRectRadius( + Rect::MakeXYWH(100, 100, 100, 100), 10)) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddRoundRect(RoundRect::MakeRectXY( + Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20))) + .AddRoundRect(RoundRect::MakeRectXY( + Rect::MakeXYWH(100, 100, 100, 100), Size(10, 20))) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + // Open shapes. + { + Point p(100, 100); + Path path = + PathBuilder{}.AddLine(p, {200, 100}).AddLine(p, {200, 100}).TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = + PathBuilder{} + .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100}) + .AddCubicCurve({100, 100}, {100, 50}, {100, 150}, {200, 100}) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } + + { + Path path = PathBuilder{} + .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100}) + .Close() + .AddQuadraticCurve({100, 100}, {100, 50}, {200, 100}) + .TakePath(); + + EXPECT_FALSE(path.IsSingleContour()); + } +} + TEST(PathTest, PathBuilderSetsCorrectContourPropertiesForAddCommands) { // Closed shapes. { diff --git a/impeller/tessellator/tessellator.cc b/impeller/tessellator/tessellator.cc index 85d3afb3587bf..383440658cc7f 100644 --- a/impeller/tessellator/tessellator.cc +++ b/impeller/tessellator/tessellator.cc @@ -4,6 +4,7 @@ #include "impeller/tessellator/tessellator.h" #include +#include #include "impeller/core/device_buffer.h" #include "impeller/geometry/path_component.h" @@ -58,6 +59,8 @@ VertexBuffer Tessellator::TessellateConvex(const Path& path, index_buffer.GetBuffer()->OnGetContents() + index_buffer.GetRange().offset)); path.WritePolyline(tolerance, writer); + point_buffer.GetBuffer()->Flush(point_buffer.GetRange()); + index_buffer.GetBuffer()->Flush(index_buffer.GetRange()); return VertexBuffer{ .vertex_buffer = std::move(point_buffer), @@ -73,6 +76,8 @@ VertexBuffer Tessellator::TessellateConvex(const Path& path, index_buffer.GetBuffer()->OnGetContents() + index_buffer.GetRange().offset)); path.WritePolyline(tolerance, writer); + point_buffer.GetBuffer()->Flush(point_buffer.GetRange()); + index_buffer.GetBuffer()->Flush(index_buffer.GetRange()); return VertexBuffer{ .vertex_buffer = std::move(point_buffer), @@ -112,6 +117,50 @@ VertexBuffer Tessellator::TessellateConvex(const Path& path, }; } +VertexBuffer Tessellator::GenerateLineStrip(const Path& path, + HostBuffer& host_buffer, + Scalar tolerance) { + LineStripVertexWriter writer(stroke_points_); + path.WritePolyline(tolerance, writer); + + const auto [arena_length, oversized_length] = writer.GetVertexCount(); + + if (oversized_length == 0) { + return VertexBuffer{ + .vertex_buffer = + host_buffer.Emplace(stroke_points_.data(), + arena_length * sizeof(Point), alignof(Point)), + .index_buffer = {}, + .vertex_count = arena_length, + .index_type = IndexType::kNone, + }; + } + const std::vector& oversized_data = writer.GetOversizedBuffer(); + BufferView buffer_view = host_buffer.Emplace( + /*buffer=*/nullptr, // + (arena_length + oversized_length) * sizeof(Point), // + alignof(Point) // + ); + memcpy(buffer_view.GetBuffer()->OnGetContents() + + buffer_view.GetRange().offset, // + stroke_points_.data(), // + arena_length * sizeof(Point) // + ); + memcpy(buffer_view.GetBuffer()->OnGetContents() + + buffer_view.GetRange().offset + arena_length * sizeof(Point), // + oversized_data.data(), // + oversized_data.size() * sizeof(Point) // + ); + buffer_view.GetBuffer()->Flush(buffer_view.GetRange()); + + return VertexBuffer{ + .vertex_buffer = buffer_view, + .index_buffer = {}, + .vertex_count = arena_length + oversized_length, + .index_type = IndexType::kNone, + }; +} + void Tessellator::TessellateConvexInternal(const Path& path, std::vector& point_buffer, std::vector& index_buffer, diff --git a/impeller/tessellator/tessellator.h b/impeller/tessellator/tessellator.h index 053bcc3618bac..604b7ce4a77dd 100644 --- a/impeller/tessellator/tessellator.h +++ b/impeller/tessellator/tessellator.h @@ -178,15 +178,13 @@ class Tessellator { /// @brief Given a convex path, create a triangle fan structure. /// /// @param[in] path The path to tessellate. + /// @param[in] host_buffer The host buffer for allocation of vertices/index + /// data. /// @param[in] tolerance The tolerance value for conversion of the path to /// a polyline. This value is often derived from the - /// Matrix::GetMaxBasisLength of the CTM applied to the - /// path for rendering. + /// Matrix::GetMaxBasisLengthXY of the CTM applied to + /// the path for rendering. /// - /// @return A point vector containing the vertices in triangle strip format. - /// - /// @param[in] host_buffer The host buffer for allocation of vertices/index - /// data. /// @return A vertex buffer containing all data from the provided curve. VertexBuffer TessellateConvex(const Path& path, HostBuffer& host_buffer, @@ -194,6 +192,27 @@ class Tessellator { bool supports_primitive_restart = false, bool supports_triangle_fan = false); + //---------------------------------------------------------------------------- + /// @brief Given a path, create a line strip primitive structure. + /// + /// A line strip is a series of vertices that draws a line + /// rendered at a specified width (in our case, always 1.0 + /// physical pixel) that is tessellated by the rasterizer. See + /// also PrimitiveType::kLineStrip. + /// + /// @param[in] path The path to tessellate. + /// @param[in] host_buffer The host buffer for allocation of vertices/index + /// data. + /// @param[in] tolerance The tolerance value for conversion of the path to + /// a polyline. This value is often derived from the + /// Matrix::GetMaxBasisLengthXY of the CTM applied to + /// the path for rendering. + /// + /// @return A vertex buffer containing all data from the provided curve. + VertexBuffer GenerateLineStrip(const Path& path, + HostBuffer& host_buffer, + Scalar tolerance); + /// Visible for testing. /// /// This method only exists for the ease of benchmarking without using the