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 662ea009cd6..8ccf32f4806 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 88eeff25409..210f64b7f9e 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 7b54fef8a55..16c8978bbc6 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 77acc57dd91..1f0376e8461 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 612d9cb3a1a..8e568c3f2a9 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 bcf9190766a..53fd80ba969 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 5121e8e045c..1f677294f89 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 21ad2fdbb31..78a6e5f6647 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 ef3424885db..93855dadd48 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 de25ca311e7..56ac9f4dc3f 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;