[Impeller] Specialize RRect geometry (#40337)

[Impeller] Specialize RRect geometry
diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc
index a1dbd35..ec41a4c 100644
--- a/impeller/aiks/canvas.cc
+++ b/impeller/aiks/canvas.cc
@@ -237,7 +237,20 @@
   if (AttemptDrawBlurredRRect(rect, corner_radius, paint)) {
     return;
   }
-  DrawPath(PathBuilder{}.AddRoundedRect(rect, corner_radius).TakePath(), paint);
+  if (paint.style == Paint::Style::kFill) {
+    Entity entity;
+    entity.SetTransformation(GetCurrentTransformation());
+    entity.SetStencilDepth(GetStencilDepth());
+    entity.SetBlendMode(paint.blend_mode);
+    entity.SetContents(paint.WithFilters(paint.CreateContentsForGeometry(
+        Geometry::MakeRRect(rect, corner_radius))));
+
+    GetCurrentPass().AddEntity(entity);
+    return;
+  } else {
+    DrawPath(PathBuilder{}.AddRoundedRect(rect, corner_radius).TakePath(),
+             paint);
+  }
 }
 
 void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
@@ -250,8 +263,23 @@
 }
 
 void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) {
+  ClipGeometry(Geometry::MakeFillPath(path), clip_op);
+}
+
+void Canvas::ClipRect(const Rect& rect, Entity::ClipOperation clip_op) {
+  ClipGeometry(Geometry::MakeRect(rect), clip_op);
+}
+
+void Canvas::ClipRRect(const Rect& rect,
+                       Scalar corner_radius,
+                       Entity::ClipOperation clip_op) {
+  ClipGeometry(Geometry::MakeRRect(rect, corner_radius), clip_op);
+}
+
+void Canvas::ClipGeometry(std::unique_ptr<Geometry> geometry,
+                          Entity::ClipOperation clip_op) {
   auto contents = std::make_shared<ClipContents>();
-  contents->SetGeometry(Geometry::MakeFillPath(path));
+  contents->SetGeometry(std::move(geometry));
   contents->SetClipOperation(clip_op);
 
   Entity entity;
@@ -379,7 +407,7 @@
     // the size of the render target that would have been allocated will be
     // absent. Explicitly add back a clip to reproduce that behavior. Since
     // clips never require a render target switch, this is a cheap operation.
-    ClipPath(PathBuilder{}.AddRect(bounds.value()).TakePath());
+    ClipRect(bounds.value());
   }
 }
 
diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h
index 17f2d1b..80b9c74 100644
--- a/impeller/aiks/canvas.h
+++ b/impeller/aiks/canvas.h
@@ -91,6 +91,15 @@
       const Path& path,
       Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect);
 
+  void ClipRect(
+      const Rect& rect,
+      Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect);
+
+  void ClipRRect(
+      const Rect& rect,
+      Scalar corner_radius,
+      Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect);
+
   void DrawPicture(Picture picture);
 
   void DrawTextFrame(const TextFrame& text_frame,
@@ -126,6 +135,9 @@
 
   size_t GetStencilDepth() const;
 
+  void ClipGeometry(std::unique_ptr<Geometry> geometry,
+                    Entity::ClipOperation clip_op);
+
   void Save(bool create_subpass,
             BlendMode = BlendMode::kSourceOver,
             std::optional<EntityPass::BackdropFilterProc> backdrop_filter =
diff --git a/impeller/display_list/display_list_dispatcher.cc b/impeller/display_list/display_list_dispatcher.cc
index e7ee036..06ee1ee 100644
--- a/impeller/display_list/display_list_dispatcher.cc
+++ b/impeller/display_list/display_list_dispatcher.cc
@@ -938,8 +938,7 @@
 void DisplayListDispatcher::clipRect(const SkRect& rect,
                                      ClipOp clip_op,
                                      bool is_aa) {
-  auto path = PathBuilder{}.AddRect(ToRect(rect)).TakePath();
-  canvas_.ClipPath(path, ToClipOperation(clip_op));
+  canvas_.ClipRect(ToRect(rect), ToClipOperation(clip_op));
 }
 
 static PathBuilder::RoundingRadii ToRoundingRadii(const SkRRect& rrect) {
@@ -1038,7 +1037,12 @@
 void DisplayListDispatcher::clipRRect(const SkRRect& rrect,
                                       ClipOp clip_op,
                                       bool is_aa) {
-  canvas_.ClipPath(ToPath(rrect), ToClipOperation(clip_op));
+  if (rrect.isSimple()) {
+    canvas_.ClipRRect(ToRect(rrect.rect()), rrect.getSimpleRadii().fX,
+                      ToClipOperation(clip_op));
+  } else {
+    canvas_.ClipPath(ToPath(rrect), ToClipOperation(clip_op));
+  }
 }
 
 // |flutter::DlOpReceiver|
diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc
index 9afede2..8f0ea29 100644
--- a/impeller/entity/entity_unittests.cc
+++ b/impeller/entity/entity_unittests.cc
@@ -219,6 +219,18 @@
   ASSERT_TRUE(OpenPlaygroundHere(entity));
 }
 
+TEST_P(EntityTest, CanDrawRRect) {
+  auto contents = std::make_shared<SolidColorContents>();
+  contents->SetGeometry(Geometry::MakeRRect({100, 100, 100, 100}, 10.0));
+  contents->SetColor(Color::Red());
+
+  Entity entity;
+  entity.SetTransformation(Matrix::MakeScale(GetContentScale()));
+  entity.SetContents(contents);
+
+  ASSERT_TRUE(OpenPlaygroundHere(entity));
+}
+
 TEST_P(EntityTest, GeometryBoundsAreTransformed) {
   auto geometry = Geometry::MakeRect({100, 100, 100, 100});
   auto transform = Matrix::MakeScale({2.0, 2.0, 2.0});
diff --git a/impeller/entity/geometry.cc b/impeller/entity/geometry.cc
index 0c2c0d2..80bf058 100644
--- a/impeller/entity/geometry.cc
+++ b/impeller/entity/geometry.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "impeller/entity/geometry.h"
+
 #include "impeller/entity/contents/content_context.h"
 #include "impeller/entity/entity.h"
 #include "impeller/entity/position_color.vert.h"
@@ -32,6 +33,11 @@
   return std::make_unique<FillPathGeometry>(path);
 }
 
+// static
+std::unique_ptr<Geometry> Geometry::MakeRRect(Rect rect, Scalar corner_radius) {
+  return std::make_unique<RRectGeometry>(rect, corner_radius);
+}
+
 std::unique_ptr<Geometry> Geometry::MakeStrokePath(const Path& path,
                                                    Scalar stroke_width,
                                                    Scalar miter_limit,
@@ -706,4 +712,163 @@
   return rect_.TransformBounds(transform);
 }
 
+/////// RRect Geometry ///////
+
+RRectGeometry::RRectGeometry(Rect rect, Scalar corner_radius)
+    : rect_(rect), corner_radius_(corner_radius) {}
+
+RRectGeometry::~RRectGeometry() = default;
+
+static void AppendRRectCorner(Path::Polyline polyline,
+                              Point corner,
+                              VertexBufferBuilder<Point>& vtx_builder) {
+  for (auto i = 1u; i < polyline.points.size(); i++) {
+    vtx_builder.AddVertices({
+        polyline.points[i - 1],
+        polyline.points[i],
+        corner,
+    });
+  }
+}
+
+VertexBufferBuilder<Point> RRectGeometry::CreatePositionBuffer(
+    const Entity& entity) const {
+  VertexBufferBuilder<Point> vtx_builder;
+
+  // The rounded rectangle is split into parts:
+  //  * four corner sections defined by an arc
+  //  * An interior shape composed of three rectangles.
+
+  auto left = rect_.GetLeft();
+  auto right = rect_.GetRight();
+  auto bottom = rect_.GetBottom();
+  auto top = rect_.GetTop();
+  auto radii = PathBuilder::RoundingRadii(corner_radius_, corner_radius_,
+                                          corner_radius_, corner_radius_);
+
+  auto topLeft =
+      PathBuilder{}
+          .MoveTo({rect_.origin.x, rect_.origin.y + corner_radius_})
+          .AddRoundedRectTopLeft(rect_, radii)
+          .TakePath()
+          .CreatePolyline(entity.GetTransformation().GetMaxBasisLength());
+  auto topRight =
+      PathBuilder{}
+          .MoveTo({right - radii.top_right.x, rect_.origin.y})
+          .AddRoundedRectTopRight(rect_, radii)
+          .TakePath()
+          .CreatePolyline(entity.GetTransformation().GetMaxBasisLength());
+  auto bottomLeft =
+      PathBuilder{}
+          .MoveTo({left + corner_radius_, bottom})
+          .AddRoundedRectBottomLeft(rect_, radii)
+          .TakePath()
+          .CreatePolyline(entity.GetTransformation().GetMaxBasisLength());
+  auto bottomRight =
+      PathBuilder{}
+          .MoveTo({right, bottom - corner_radius_})
+          .AddRoundedRectBottomRight(rect_, radii)
+          .TakePath()
+          .CreatePolyline(entity.GetTransformation().GetMaxBasisLength());
+
+  vtx_builder.Reserve(12 * (topLeft.points.size() - 1) + 18);
+
+  AppendRRectCorner(topLeft, Point(left + corner_radius_, top + corner_radius_),
+                    vtx_builder);
+
+  AppendRRectCorner(topRight,
+                    Point(right - corner_radius_, top + corner_radius_),
+                    vtx_builder);
+
+  AppendRRectCorner(bottomLeft,
+                    Point(left + corner_radius_, bottom - corner_radius_),
+                    vtx_builder);
+
+  AppendRRectCorner(bottomRight,
+                    Point(right - corner_radius_, bottom - corner_radius_),
+                    vtx_builder);
+  vtx_builder.AddVertices({
+      // Top Component.
+      Point(left + corner_radius_, top + corner_radius_),
+      Point(left + corner_radius_, top),
+      Point(right - corner_radius_, top + corner_radius_),
+
+      Point(left + corner_radius_, top),
+      Point(right - corner_radius_, top + corner_radius_),
+      Point(right - corner_radius_, top),
+
+      // Bottom Component.
+      Point(left + corner_radius_, bottom - corner_radius_),
+      Point(left + corner_radius_, bottom),
+      Point(right - corner_radius_, bottom - corner_radius_),
+
+      Point(left + corner_radius_, bottom),
+      Point(right - corner_radius_, bottom - corner_radius_),
+      Point(right - corner_radius_, bottom),
+
+      // // Center Component.
+      Point(left, top + corner_radius_),
+      Point(right, top + corner_radius_),
+      Point(right, bottom - corner_radius_),
+
+      Point(left, top + corner_radius_),
+      Point(left, bottom - corner_radius_),
+      Point(right, bottom - corner_radius_),
+  });
+
+  return vtx_builder;
+}
+
+GeometryResult RRectGeometry::GetPositionBuffer(const ContentContext& renderer,
+                                                const Entity& entity,
+                                                RenderPass& pass) {
+  auto vtx_builder = CreatePositionBuffer(entity);
+
+  return GeometryResult{
+      .type = PrimitiveType::kTriangle,
+      .vertex_buffer =
+          vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer()),
+      .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
+                   entity.GetTransformation(),
+      .prevent_overdraw = false,
+  };
+}
+
+GeometryResult RRectGeometry::GetPositionUVBuffer(
+    Rect texture_coverage,
+    Matrix effect_transform,
+    const ContentContext& renderer,
+    const Entity& entity,
+    RenderPass& pass) {
+  auto vtx_builder = CreatePositionBuffer(entity);
+
+  VertexBufferBuilder<TextureFillVertexShader::PerVertexData> vertex_builder;
+  vtx_builder.IterateVertices(
+      [&vertex_builder, &texture_coverage, &effect_transform](Point position) {
+        TextureFillVertexShader::PerVertexData data;
+        data.position = position;
+        auto coverage_coords =
+            (position - texture_coverage.origin) / texture_coverage.size;
+        data.texture_coords = effect_transform * coverage_coords;
+        vertex_builder.AppendVertex(data);
+      });
+
+  return GeometryResult{
+      .type = PrimitiveType::kTriangle,
+      .vertex_buffer =
+          vertex_builder.CreateVertexBuffer(pass.GetTransientsBuffer()),
+      .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
+                   entity.GetTransformation(),
+      .prevent_overdraw = true,
+  };
+}
+
+GeometryVertexType RRectGeometry::GetVertexType() const {
+  return GeometryVertexType::kPosition;
+}
+
+std::optional<Rect> RRectGeometry::GetCoverage(const Matrix& transform) const {
+  return rect_.TransformBounds(transform);
+}
+
 }  // namespace impeller
diff --git a/impeller/entity/geometry.h b/impeller/entity/geometry.h
index bdf34d6..4bb5180 100644
--- a/impeller/entity/geometry.h
+++ b/impeller/entity/geometry.h
@@ -50,6 +50,8 @@
 
   static std::unique_ptr<Geometry> MakeFillPath(const Path& path);
 
+  static std::unique_ptr<Geometry> MakeRRect(Rect rect, Scalar corner_radius);
+
   static std::unique_ptr<Geometry> MakeStrokePath(
       const Path& path,
       Scalar stroke_width = 0.0,
@@ -264,4 +266,37 @@
   FML_DISALLOW_COPY_AND_ASSIGN(RectGeometry);
 };
 
+class RRectGeometry : public Geometry {
+ public:
+  explicit RRectGeometry(Rect rect, Scalar corner_radius);
+
+  ~RRectGeometry();
+
+ private:
+  // |Geometry|
+  GeometryResult GetPositionBuffer(const ContentContext& renderer,
+                                   const Entity& entity,
+                                   RenderPass& pass) override;
+
+  // |Geometry|
+  GeometryResult GetPositionUVBuffer(Rect texture_coverage,
+                                     Matrix effect_transform,
+                                     const ContentContext& renderer,
+                                     const Entity& entity,
+                                     RenderPass& pass) override;
+
+  // |Geometry|
+  GeometryVertexType GetVertexType() const override;
+
+  // |Geometry|
+  std::optional<Rect> GetCoverage(const Matrix& transform) const override;
+
+  VertexBufferBuilder<Point> CreatePositionBuffer(const Entity& entity) const;
+
+  Rect rect_;
+  Scalar corner_radius_;
+
+  FML_DISALLOW_COPY_AND_ASSIGN(RRectGeometry);
+};
+
 }  // namespace impeller
diff --git a/impeller/geometry/path_builder.cc b/impeller/geometry/path_builder.cc
index f7f68cf..ef34248 100644
--- a/impeller/geometry/path_builder.cc
+++ b/impeller/geometry/path_builder.cc
@@ -201,11 +201,6 @@
 
   current_ = rect.origin + Point{radii.top_left.x, 0.0};
 
-  const auto magic_top_right = radii.top_right * kArcApproximationMagic;
-  const auto magic_bottom_right = radii.bottom_right * kArcApproximationMagic;
-  const auto magic_bottom_left = radii.bottom_left * kArcApproximationMagic;
-  const auto magic_top_left = radii.top_left * kArcApproximationMagic;
-
   MoveTo({rect.origin.x + radii.top_left.x, rect.origin.y});
 
   //----------------------------------------------------------------------------
@@ -218,13 +213,7 @@
   //----------------------------------------------------------------------------
   // Top right arc.
   //
-  prototype_.AddCubicComponent(
-      {rect.origin.x + rect.size.width - radii.top_right.x, rect.origin.y},
-      {rect.origin.x + rect.size.width - radii.top_right.x + magic_top_right.x,
-       rect.origin.y},
-      {rect.origin.x + rect.size.width,
-       rect.origin.y + radii.top_right.y - magic_top_right.y},
-      {rect.origin.x + rect.size.width, rect.origin.y + radii.top_right.y});
+  AddRoundedRectTopRight(rect, radii);
 
   //----------------------------------------------------------------------------
   // Right line.
@@ -237,17 +226,7 @@
   //----------------------------------------------------------------------------
   // Bottom right arc.
   //
-  prototype_.AddCubicComponent(
-      {rect.origin.x + rect.size.width,
-       rect.origin.y + rect.size.height - radii.bottom_right.y},
-      {rect.origin.x + rect.size.width, rect.origin.y + rect.size.height -
-                                            radii.bottom_right.y +
-                                            magic_bottom_right.y},
-      {rect.origin.x + rect.size.width - radii.bottom_right.x +
-           magic_bottom_right.x,
-       rect.origin.y + rect.size.height},
-      {rect.origin.x + rect.size.width - radii.bottom_right.x,
-       rect.origin.y + rect.size.height});
+  AddRoundedRectBottomRight(rect, radii);
 
   //----------------------------------------------------------------------------
   // Bottom line.
@@ -260,13 +239,7 @@
   //----------------------------------------------------------------------------
   // Bottom left arc.
   //
-  prototype_.AddCubicComponent(
-      {rect.origin.x + radii.bottom_left.x, rect.origin.y + rect.size.height},
-      {rect.origin.x + radii.bottom_left.x - magic_bottom_left.x,
-       rect.origin.y + rect.size.height},
-      {rect.origin.x, rect.origin.y + rect.size.height - radii.bottom_left.y +
-                          magic_bottom_left.y},
-      {rect.origin.x, rect.origin.y + rect.size.height - radii.bottom_left.y});
+  AddRoundedRectBottomLeft(rect, radii);
 
   //----------------------------------------------------------------------------
   // Left line.
@@ -278,14 +251,64 @@
   //----------------------------------------------------------------------------
   // Top left arc.
   //
+  AddRoundedRectTopLeft(rect, radii);
+
+  Close();
+
+  return *this;
+}
+
+PathBuilder& PathBuilder::AddRoundedRectTopLeft(Rect rect,
+                                                RoundingRadii radii) {
+  const auto magic_top_left = radii.top_left * kArcApproximationMagic;
   prototype_.AddCubicComponent(
       {rect.origin.x, rect.origin.y + radii.top_left.y},
       {rect.origin.x, rect.origin.y + radii.top_left.y - magic_top_left.y},
       {rect.origin.x + radii.top_left.x - magic_top_left.x, rect.origin.y},
       {rect.origin.x + radii.top_left.x, rect.origin.y});
+  return *this;
+}
 
-  Close();
+PathBuilder& PathBuilder::AddRoundedRectTopRight(Rect rect,
+                                                 RoundingRadii radii) {
+  const auto magic_top_right = radii.top_right * kArcApproximationMagic;
+  prototype_.AddCubicComponent(
+      {rect.origin.x + rect.size.width - radii.top_right.x, rect.origin.y},
+      {rect.origin.x + rect.size.width - radii.top_right.x + magic_top_right.x,
+       rect.origin.y},
+      {rect.origin.x + rect.size.width,
+       rect.origin.y + radii.top_right.y - magic_top_right.y},
+      {rect.origin.x + rect.size.width, rect.origin.y + radii.top_right.y});
+  return *this;
+}
 
+PathBuilder& PathBuilder::AddRoundedRectBottomRight(Rect rect,
+                                                    RoundingRadii radii) {
+  const auto magic_bottom_right = radii.bottom_right * kArcApproximationMagic;
+  prototype_.AddCubicComponent(
+      {rect.origin.x + rect.size.width,
+       rect.origin.y + rect.size.height - radii.bottom_right.y},
+      {rect.origin.x + rect.size.width, rect.origin.y + rect.size.height -
+                                            radii.bottom_right.y +
+                                            magic_bottom_right.y},
+      {rect.origin.x + rect.size.width - radii.bottom_right.x +
+           magic_bottom_right.x,
+       rect.origin.y + rect.size.height},
+      {rect.origin.x + rect.size.width - radii.bottom_right.x,
+       rect.origin.y + rect.size.height});
+  return *this;
+}
+
+PathBuilder& PathBuilder::AddRoundedRectBottomLeft(Rect rect,
+                                                   RoundingRadii radii) {
+  const auto magic_bottom_left = radii.bottom_left * kArcApproximationMagic;
+  prototype_.AddCubicComponent(
+      {rect.origin.x + radii.bottom_left.x, rect.origin.y + rect.size.height},
+      {rect.origin.x + radii.bottom_left.x - magic_bottom_left.x,
+       rect.origin.y + rect.size.height},
+      {rect.origin.x, rect.origin.y + rect.size.height - radii.bottom_left.y +
+                          magic_bottom_left.y},
+      {rect.origin.x, rect.origin.y + rect.size.height - radii.bottom_left.y});
   return *this;
 }
 
diff --git a/impeller/geometry/path_builder.h b/impeller/geometry/path_builder.h
index 13e9240..3c4dede 100644
--- a/impeller/geometry/path_builder.h
+++ b/impeller/geometry/path_builder.h
@@ -102,6 +102,14 @@
 
   PathBuilder& AddRoundedRect(Rect rect, Scalar radius);
 
+  PathBuilder& AddRoundedRectTopLeft(Rect rect, RoundingRadii radii);
+
+  PathBuilder& AddRoundedRectTopRight(Rect rect, RoundingRadii radii);
+
+  PathBuilder& AddRoundedRectBottomRight(Rect rect, RoundingRadii radii);
+
+  PathBuilder& AddRoundedRectBottomLeft(Rect rect, RoundingRadii radii);
+
   PathBuilder& AddPath(const Path& path);
 
  private: