From d64bc16426debde08ccd59fc39cbc24a7fd17ae7 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 9 May 2023 11:45:00 -0700 Subject: [PATCH] [Impeller] track path convexity and use it to simplify fill tessellation. (flutter/engine#41834) We can do this on the GPU, but we'll need a fallback when we don't have polyline compute. From experimentation, Skia has already computed this value when Impeller gets that path. Additionally, this will allow us to speed up tessellation of known convex shapes like ovals that we don't want to add special cases for in the API. --- engine/src/flutter/impeller/aiks/canvas.cc | 10 ++-- .../impeller/display_list/dl_dispatcher.cc | 7 ++- .../impeller/display_list/skia_conversions.cc | 3 + .../impeller/entity/entity_unittests.cc | 36 ++++++++++++ .../src/flutter/impeller/entity/geometry.cc | 55 ++++++++++++++++++- engine/src/flutter/impeller/entity/geometry.h | 5 ++ engine/src/flutter/impeller/geometry/path.cc | 8 +++ engine/src/flutter/impeller/geometry/path.h | 12 ++++ .../flutter/impeller/geometry/path_builder.cc | 6 ++ .../flutter/impeller/geometry/path_builder.h | 3 + 10 files changed, 138 insertions(+), 7 deletions(-) diff --git a/engine/src/flutter/impeller/aiks/canvas.cc b/engine/src/flutter/impeller/aiks/canvas.cc index 662ea009cd..8ccf32f480 100644 --- a/engine/src/flutter/impeller/aiks/canvas.cc +++ b/engine/src/flutter/impeller/aiks/canvas.cc @@ -251,10 +251,8 @@ void Canvas::DrawRRect(Rect rect, Scalar corner_radius, const Paint& paint) { GetCurrentPass().AddEntity(entity); return; - } else { - DrawPath(PathBuilder{}.AddRoundedRect(rect, corner_radius).TakePath(), - paint); } + DrawPath(PathBuilder{}.AddRoundedRect(rect, corner_radius).TakePath(), paint); } void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) { @@ -263,7 +261,11 @@ void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) { paint)) { return; } - DrawPath(PathBuilder{}.AddCircle(center, radius).TakePath(), paint); + auto circle_path = PathBuilder{} + .AddCircle(center, radius) + .SetConvexity(Convexity::kConvex) + .TakePath(); + DrawPath(circle_path, paint); } void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) { diff --git a/engine/src/flutter/impeller/display_list/dl_dispatcher.cc b/engine/src/flutter/impeller/display_list/dl_dispatcher.cc index 88eeff2540..210f64b7f9 100644 --- a/engine/src/flutter/impeller/display_list/dl_dispatcher.cc +++ b/engine/src/flutter/impeller/display_list/dl_dispatcher.cc @@ -845,6 +845,7 @@ void DlDispatcher::drawLine(const SkPoint& p0, const SkPoint& p1) { auto path = PathBuilder{} .AddLine(skia_conversions::ToPoint(p0), skia_conversions::ToPoint(p1)) + .SetConvexity(Convexity::kConvex) .TakePath(); Paint paint = paint_; paint.style = Paint::Style::kStroke; @@ -862,8 +863,10 @@ void DlDispatcher::drawOval(const SkRect& bounds) { canvas_.DrawCircle(skia_conversions::ToPoint(bounds.center()), bounds.width() * 0.5, paint_); } else { - auto path = - PathBuilder{}.AddOval(skia_conversions::ToRect(bounds)).TakePath(); + auto path = PathBuilder{} + .AddOval(skia_conversions::ToRect(bounds)) + .SetConvexity(Convexity::kConvex) + .TakePath(); canvas_.DrawPath(path, paint_); } } diff --git a/engine/src/flutter/impeller/display_list/skia_conversions.cc b/engine/src/flutter/impeller/display_list/skia_conversions.cc index 7b54fef8a5..16c8978bbc 100644 --- a/engine/src/flutter/impeller/display_list/skia_conversions.cc +++ b/engine/src/flutter/impeller/display_list/skia_conversions.cc @@ -109,12 +109,15 @@ Path ToPath(const SkPath& path) { fill_type = FillType::kNonZero; break; } + builder.SetConvexity(path.isConvex() ? Convexity::kConvex + : Convexity::kUnknown); return builder.TakePath(fill_type); } Path ToPath(const SkRRect& rrect) { return PathBuilder{} .AddRoundedRect(ToRect(rrect.getBounds()), ToRoundingRadii(rrect)) + .SetConvexity(Convexity::kConvex) .TakePath(); } diff --git a/engine/src/flutter/impeller/entity/entity_unittests.cc b/engine/src/flutter/impeller/entity/entity_unittests.cc index 77acc57dd9..1f0376e846 100644 --- a/engine/src/flutter/impeller/entity/entity_unittests.cc +++ b/engine/src/flutter/impeller/entity/entity_unittests.cc @@ -2572,5 +2572,41 @@ TEST_P(EntityTest, TiledTextureContentsIsOpaque) { ASSERT_FALSE(contents.IsOpaque()); } +TEST_P(EntityTest, TessellateConvex) { + { + // Sanity check simple rectangle. + auto [pts, indices] = + TessellateConvex(PathBuilder{} + .AddRect(Rect::MakeLTRB(0, 0, 10, 10)) + .TakePath() + .CreatePolyline(1.0)); + + std::vector expected = { + {0, 0}, {10, 0}, {10, 10}, {0, 10}, // + }; + std::vector expected_indices = {0, 1, 2, 0, 2, 3}; + ASSERT_EQ(pts, expected); + ASSERT_EQ(indices, expected_indices); + } + + { + auto [pts, indices] = + TessellateConvex(PathBuilder{} + .AddRect(Rect::MakeLTRB(0, 0, 10, 10)) + .AddRect(Rect::MakeLTRB(20, 20, 30, 30)) + .TakePath() + .CreatePolyline(1.0)); + + std::vector expected = { + {0, 0}, {10, 0}, {10, 10}, {0, 10}, // + {20, 20}, {30, 20}, {30, 30}, {20, 30} // + }; + std::vector expected_indices = {0, 1, 2, 0, 2, 3, + 0, 6, 7, 0, 7, 8}; + ASSERT_EQ(pts, expected); + ASSERT_EQ(indices, expected_indices); + } +} + } // namespace testing } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/geometry.cc b/engine/src/flutter/impeller/entity/geometry.cc index 612d9cb3a1..8e568c3f2a 100644 --- a/engine/src/flutter/impeller/entity/geometry.cc +++ b/engine/src/flutter/impeller/entity/geometry.cc @@ -16,6 +16,37 @@ namespace impeller { +/// Given a convex polyline, create a triangle fan structure. +std::pair, std::vector> TessellateConvex( + Path::Polyline polyline) { + std::vector output; + std::vector index; + + for (auto j = 0u; j < polyline.contours.size(); j++) { + auto [start, end] = polyline.GetContourPointBounds(j); + auto center = polyline.points[start]; + + // Some polygons will not self close and an additional triangle + // must be inserted, others will self close and we need to avoid + // inserting an extra triangle. + if (polyline.points[end - 1] == polyline.points[start]) { + end--; + } + output.emplace_back(center); + output.emplace_back(polyline.points[start + 1]); + + for (auto i = start + 2; i < end; i++) { + const auto& point_b = polyline.points[i]; + output.emplace_back(point_b); + + index.emplace_back(0); + index.emplace_back(i - 1); + index.emplace_back(i); + } + } + return std::make_pair(output, index); +} + Geometry::Geometry() = default; Geometry::~Geometry() = default; @@ -103,8 +134,30 @@ GeometryResult FillPathGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) { - VertexBuffer vertex_buffer; auto& host_buffer = pass.GetTransientsBuffer(); + VertexBuffer vertex_buffer; + + if (path_.GetFillType() == FillType::kNonZero && // + path_.IsConvex()) { + auto [points, indicies] = TessellateConvex( + path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength())); + + vertex_buffer.vertex_buffer = host_buffer.Emplace( + points.data(), points.size() * sizeof(Point), alignof(Point)); + vertex_buffer.index_buffer = host_buffer.Emplace( + indicies.data(), indicies.size() * sizeof(uint16_t), alignof(uint16_t)); + vertex_buffer.index_count = indicies.size(); + vertex_buffer.index_type = IndexType::k16bit; + + return GeometryResult{ + .type = PrimitiveType::kTriangle, + .vertex_buffer = vertex_buffer, + .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(), + .prevent_overdraw = false, + }; + } + auto tesselation_result = renderer.GetTessellator()->Tessellate( path_.GetFillType(), path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()), diff --git a/engine/src/flutter/impeller/entity/geometry.h b/engine/src/flutter/impeller/entity/geometry.h index bcf9190766..53fd80ba96 100644 --- a/engine/src/flutter/impeller/entity/geometry.h +++ b/engine/src/flutter/impeller/entity/geometry.h @@ -33,6 +33,11 @@ enum GeometryVertexType { kUV, }; +/// @brief Given a polyline created from a convex filled path, perform a +/// tessellation. +std::pair, std::vector> TessellateConvex( + Path::Polyline polyline); + class Geometry { public: Geometry(); diff --git a/engine/src/flutter/impeller/geometry/path.cc b/engine/src/flutter/impeller/geometry/path.cc index 5121e8e045..1f677294f8 100644 --- a/engine/src/flutter/impeller/geometry/path.cc +++ b/engine/src/flutter/impeller/geometry/path.cc @@ -53,6 +53,14 @@ FillType Path::GetFillType() const { return fill_; } +bool Path::IsConvex() const { + return convexity_ == Convexity::kConvex; +} + +void Path::SetConvexity(Convexity value) { + convexity_ = value; +} + Path& Path::AddLinearComponent(Point p1, Point p2) { linears_.emplace_back(p1, p2); components_.emplace_back(ComponentType::kLinear, linears_.size() - 1); diff --git a/engine/src/flutter/impeller/geometry/path.h b/engine/src/flutter/impeller/geometry/path.h index 21ad2fdbb3..78a6e5f664 100644 --- a/engine/src/flutter/impeller/geometry/path.h +++ b/engine/src/flutter/impeller/geometry/path.h @@ -34,6 +34,11 @@ enum class FillType { kAbsGeqTwo, }; +enum class Convexity { + kUnknown, + kConvex, +}; + //------------------------------------------------------------------------------ /// @brief Paths are lightweight objects that describe a collection of /// linear, quadratic, or cubic segments. These segments may be @@ -94,6 +99,8 @@ class Path { FillType GetFillType() const; + bool IsConvex() const; + Path& AddLinearComponent(Point p1, Point p2); Path& AddQuadraticComponent(Point p1, Point cp, Point p2); @@ -148,6 +155,10 @@ class Path { std::optional> GetMinMaxCoveragePoints() const; private: + friend class PathBuilder; + + void SetConvexity(Convexity value); + struct ComponentIndexPair { ComponentType type = ComponentType::kLinear; size_t index = 0; @@ -159,6 +170,7 @@ class Path { }; FillType fill_ = FillType::kNonZero; + Convexity convexity_ = Convexity::kUnknown; std::vector components_; std::vector linears_; std::vector quads_; diff --git a/engine/src/flutter/impeller/geometry/path_builder.cc b/engine/src/flutter/impeller/geometry/path_builder.cc index ef3424885d..93855dadd4 100644 --- a/engine/src/flutter/impeller/geometry/path_builder.cc +++ b/engine/src/flutter/impeller/geometry/path_builder.cc @@ -21,6 +21,7 @@ Path PathBuilder::CopyPath(FillType fill) const { Path PathBuilder::TakePath(FillType fill) { auto path = prototype_; path.SetFillType(fill); + path.SetConvexity(convexity_); return path; } @@ -103,6 +104,11 @@ PathBuilder& PathBuilder::SmoothQuadraticCurveTo(Point point, bool relative) { return *this; } +PathBuilder& PathBuilder::SetConvexity(Convexity value) { + convexity_ = value; + return *this; +} + PathBuilder& PathBuilder::CubicCurveTo(Point controlPoint1, Point controlPoint2, Point point, diff --git a/engine/src/flutter/impeller/geometry/path_builder.h b/engine/src/flutter/impeller/geometry/path_builder.h index de25ca311e..56ac9f4dc3 100644 --- a/engine/src/flutter/impeller/geometry/path_builder.h +++ b/engine/src/flutter/impeller/geometry/path_builder.h @@ -31,6 +31,8 @@ class PathBuilder { const Path& GetCurrentPath() const; + PathBuilder& SetConvexity(Convexity value); + PathBuilder& MoveTo(Point point, bool relative = false); PathBuilder& Close(); @@ -116,6 +118,7 @@ class PathBuilder { Point subpath_start_; Point current_; Path prototype_; + Convexity convexity_; Point ReflectedQuadraticControlPoint1() const;