Use DisplayListMatrixClipTracker in DisplayListBuilder (#38349)

* Use DisplayListMatrixClipTracker in DisplayListBuilder

* Ignore is_aa

* Revert "Ignore is_aa"

This reverts commit b201dadc773f8e726ec68ed88114df9be7b5a9b0.

* Tweak code

* Use content_culled

* getLocalClipBounds without device clip bounds roundsOut

* Tweak code and add more tests

* remove virtual
diff --git a/display_list/display_list_builder.cc b/display_list/display_list_builder.cc
index 14289a3..6cb5d6c 100644
--- a/display_list/display_list_builder.cc
+++ b/display_list/display_list_builder.cc
@@ -67,16 +67,15 @@
 }
 
 DisplayListBuilder::DisplayListBuilder(const SkRect& cull_rect,
-                                       bool prepare_rtree) {
+                                       bool prepare_rtree)
+    : tracker_(cull_rect, SkMatrix::I()) {
   if (prepare_rtree) {
     accumulator_ = std::make_unique<RTreeBoundsAccumulator>();
   } else {
     accumulator_ = std::make_unique<RectBoundsAccumulator>();
   }
 
-  // isEmpty protects us against NaN as we normalize any empty cull rects
-  SkRect cull = cull_rect.isEmpty() ? SkRect::MakeEmpty() : cull_rect;
-  layer_stack_.emplace_back(SkM44(), SkMatrix::I(), cull);
+  layer_stack_.emplace_back();
   current_layer_ = &layer_stack_.back();
 }
 
@@ -440,9 +439,10 @@
 }
 
 void DisplayListBuilder::save() {
-  layer_stack_.emplace_back(current_layer_);
+  layer_stack_.emplace_back();
   current_layer_ = &layer_stack_.back();
   current_layer_->has_deferred_save_op_ = true;
+  tracker_.save();
   accumulator()->save();
 }
 
@@ -455,6 +455,7 @@
     // on the stack.
     LayerInfo layer_info = layer_stack_.back();
 
+    tracker_.restore();
     layer_stack_.pop_back();
     current_layer_ = &layer_stack_.back();
     bool is_unbounded = layer_info.is_unbounded();
@@ -463,7 +464,7 @@
     // current accumulator and adjust it as required based on the filter.
     std::shared_ptr<const DlImageFilter> filter = layer_info.filter();
     if (filter) {
-      const SkRect* clip = &current_layer_->clip_bounds();
+      const SkRect clip = tracker_.device_cull_rect();
       if (!accumulator()->restore(
               [filter = filter, matrix = getTransform()](const SkRect& input,
                                                          SkRect& output) {
@@ -473,7 +474,7 @@
                 output.set(output_bounds);
                 return ret;
               },
-              clip)) {
+              &clip)) {
         is_unbounded = true;
       }
     } else {
@@ -544,11 +545,12 @@
       // We will fill the clip of the outer layer when we restore
       AccumulateUnbounded();
     }
-    layer_stack_.emplace_back(current_layer_, save_layer_offset, true,
+    layer_stack_.emplace_back(save_layer_offset, true,
                               current_.getImageFilter());
   } else {
-    layer_stack_.emplace_back(current_layer_, save_layer_offset, true, nullptr);
+    layer_stack_.emplace_back(save_layer_offset, true, nullptr);
   }
+  tracker_.save();
   accumulator()->save();
   current_layer_ = &layer_stack_.back();
   if (options.renders_with_attributes()) {
@@ -566,7 +568,7 @@
   // use them as the temporary layer bounds during rendering the layer, so
   // we set them as if a clip operation were performed.
   if (bounds) {
-    intersect(*bounds);
+    tracker_.clipRect(*bounds, SkClipOp::kIntersect, false);
   }
   if (backdrop) {
     // A backdrop will affect up to the entire surface, bounded by the clip
@@ -590,8 +592,7 @@
       (tx != 0.0 || ty != 0.0)) {
     checkForDeferredSave();
     Push<TranslateOp>(0, 1, tx, ty);
-    current_layer_->matrix().preTranslate(tx, ty);
-    current_layer_->update_matrix33();
+    tracker_.translate(tx, ty);
   }
 }
 void DisplayListBuilder::scale(SkScalar sx, SkScalar sy) {
@@ -599,16 +600,14 @@
       (sx != 1.0 || sy != 1.0)) {
     checkForDeferredSave();
     Push<ScaleOp>(0, 1, sx, sy);
-    current_layer_->matrix().preScale(sx, sy);
-    current_layer_->update_matrix33();
+    tracker_.scale(sx, sy);
   }
 }
 void DisplayListBuilder::rotate(SkScalar degrees) {
   if (SkScalarMod(degrees, 360.0) != 0.0) {
     checkForDeferredSave();
     Push<RotateOp>(0, 1, degrees);
-    current_layer_->matrix().preConcat(SkMatrix::RotateDeg(degrees));
-    current_layer_->update_matrix33();
+    tracker_.rotate(degrees);
   }
 }
 void DisplayListBuilder::skew(SkScalar sx, SkScalar sy) {
@@ -616,8 +615,7 @@
       (sx != 0.0 || sy != 0.0)) {
     checkForDeferredSave();
     Push<SkewOp>(0, 1, sx, sy);
-    current_layer_->matrix().preConcat(SkMatrix::Skew(sx, sy));
-    current_layer_->update_matrix33();
+    tracker_.skew(sx, sy);
   }
 }
 
@@ -636,11 +634,8 @@
     Push<Transform2DAffineOp>(0, 1,
                               mxx, mxy, mxt,
                               myx, myy, myt);
-    current_layer_->matrix().preConcat(SkM44(mxx, mxy,  0,  mxt,
-                                             myx, myy,  0,  myt,
-                                             0,   0,   1,   0,
-                                             0,   0,   0,   1));
-    current_layer_->update_matrix33();
+    tracker_.transform2DAffine(mxx, mxy, mxt,
+                               myx, myy, myt);
   }
 }
 // full 4x4 transform in row major order
@@ -665,19 +660,17 @@
                                      myx, myy, myz, myt,
                                      mzx, mzy, mzz, mzt,
                                      mwx, mwy, mwz, mwt);
-    current_layer_->matrix().preConcat(SkM44(mxx, mxy, mxz, mxt,
-                                             myx, myy, myz, myt,
-                                             mzx, mzy, mzz, mzt,
-                                             mwx, mwy, mwz, mwt));
-    current_layer_->update_matrix33();
+    tracker_.transformFullPerspective(mxx, mxy, mxz, mxt,
+                                      myx, myy, myz, myt,
+                                      mzx, mzy, mzz, mzt,
+                                      mwx, mwy, mwz, mwt);
   }
 }
 // clang-format on
 void DisplayListBuilder::transformReset() {
   checkForDeferredSave();
   Push<TransformResetOp>(0, 0);
-  current_layer_->matrix().setIdentity();
-  current_layer_->update_matrix33();
+  tracker_.setIdentity();
 }
 void DisplayListBuilder::transform(const SkMatrix* matrix) {
   if (matrix != nullptr) {
@@ -704,12 +697,12 @@
   switch (clip_op) {
     case SkClipOp::kIntersect:
       Push<ClipIntersectRectOp>(0, 1, rect, is_aa);
-      intersect(rect);
       break;
     case SkClipOp::kDifference:
       Push<ClipDifferenceRectOp>(0, 1, rect, is_aa);
       break;
   }
+  tracker_.clipRect(rect, clip_op, is_aa);
 }
 void DisplayListBuilder::clipRRect(const SkRRect& rrect,
                                    SkClipOp clip_op,
@@ -721,12 +714,12 @@
     switch (clip_op) {
       case SkClipOp::kIntersect:
         Push<ClipIntersectRRectOp>(0, 1, rrect, is_aa);
-        intersect(rrect.getBounds());
         break;
       case SkClipOp::kDifference:
         Push<ClipDifferenceRRectOp>(0, 1, rrect, is_aa);
         break;
     }
+    tracker_.clipRRect(rrect, clip_op, is_aa);
   }
 }
 void DisplayListBuilder::clipPath(const SkPath& path,
@@ -753,48 +746,16 @@
   switch (clip_op) {
     case SkClipOp::kIntersect:
       Push<ClipIntersectPathOp>(0, 1, path, is_aa);
-      if (!path.isInverseFillType()) {
-        intersect(path.getBounds());
-      }
       break;
     case SkClipOp::kDifference:
       Push<ClipDifferencePathOp>(0, 1, path, is_aa);
-      // Map "kDifference of inverse path" to "kIntersect of the original path".
-      if (path.isInverseFillType()) {
-        intersect(path.getBounds());
-      }
       break;
   }
-}
-void DisplayListBuilder::intersect(const SkRect& rect) {
-  SkRect dev_clip_bounds = getTransform().mapRect(rect);
-  if (!current_layer_->clip_bounds().intersect(dev_clip_bounds)) {
-    current_layer_->clip_bounds().setEmpty();
-  }
-}
-SkRect DisplayListBuilder::getLocalClipBounds() {
-  SkM44 inverse;
-  if (current_layer_->matrix().invert(&inverse)) {
-    SkRect dev_bounds;
-    current_layer_->clip_bounds().roundOut(&dev_bounds);
-    return inverse.asM33().mapRect(dev_bounds);
-  }
-  return kMaxCullRect;
+  tracker_.clipPath(path, clip_op, is_aa);
 }
 
 bool DisplayListBuilder::quickReject(const SkRect& bounds) const {
-  if (bounds.isEmpty()) {
-    return true;
-  }
-  SkMatrix matrix = getTransform();
-  // We don't need the inverse, but this method tells us if the matrix
-  // is singular in which case we can reject all rendering.
-  if (!matrix.invert(nullptr)) {
-    return true;
-  }
-  SkRect dev_bounds;
-  matrix.mapRect(bounds).roundOut(&dev_bounds);
-  return !current_layer_->clip_bounds().intersects(dev_bounds);
+  return tracker_.content_culled(bounds);
 }
 
 void DisplayListBuilder::drawPaint() {
@@ -1357,7 +1318,7 @@
 }
 
 void DisplayListBuilder::AccumulateUnbounded() {
-  accumulator()->accumulate(current_layer_->clip_bounds());
+  accumulator()->accumulate(tracker_.device_cull_rect());
 }
 
 void DisplayListBuilder::AccumulateOpBounds(SkRect& bounds,
@@ -1369,8 +1330,8 @@
   }
 }
 void DisplayListBuilder::AccumulateBounds(SkRect& bounds) {
-  getTransform().mapRect(&bounds);
-  if (bounds.intersect(current_layer_->clip_bounds())) {
+  tracker_.mapRect(&bounds);
+  if (bounds.intersect(tracker_.device_cull_rect())) {
     accumulator()->accumulate(bounds);
   }
 }
diff --git a/display_list/display_list_builder.h b/display_list/display_list_builder.h
index 94a784f..b1caf37 100644
--- a/display_list/display_list_builder.h
+++ b/display_list/display_list_builder.h
@@ -11,6 +11,7 @@
 #include "flutter/display_list/display_list_dispatcher.h"
 #include "flutter/display_list/display_list_flags.h"
 #include "flutter/display_list/display_list_image.h"
+#include "flutter/display_list/display_list_matrix_clip_tracker.h"
 #include "flutter/display_list/display_list_paint.h"
 #include "flutter/display_list/display_list_path_effect.h"
 #include "flutter/display_list/display_list_sampling_options.h"
@@ -210,11 +211,11 @@
   /// Returns the 4x4 full perspective transform representing all transform
   /// operations executed so far in this DisplayList within the enclosing
   /// save stack.
-  SkM44 getTransformFullPerspective() const { return current_layer_->matrix(); }
+  SkM44 getTransformFullPerspective() const { return tracker_.matrix_4x4(); }
   /// Returns the 3x3 partial perspective transform representing all transform
   /// operations executed so far in this DisplayList within the enclosing
   /// save stack.
-  SkMatrix getTransform() const { return current_layer_->matrix33(); }
+  SkMatrix getTransform() const { return tracker_.matrix_3x3(); }
 
   void clipRect(const SkRect& rect, SkClipOp clip_op, bool is_aa) override;
   void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool is_aa) override;
@@ -223,11 +224,11 @@
   /// Conservative estimate of the bounds of all outstanding clip operations
   /// measured in the coordinate space within which this DisplayList will
   /// be rendered.
-  SkRect getDestinationClipBounds() { return current_layer_->clip_bounds(); }
+  SkRect getDestinationClipBounds() { return tracker_.device_cull_rect(); }
   /// Conservative estimate of the bounds of all outstanding clip operations
   /// transformed into the local coordinate space in which currently
   /// recorded rendering operations are interpreted.
-  SkRect getLocalClipBounds();
+  SkRect getLocalClipBounds() { return tracker_.local_cull_rect(); }
 
   /// Return true iff the supplied bounds are easily shown to be outside
   /// of the current clip bounds. This method may conservatively return
@@ -386,33 +387,16 @@
 
   class LayerInfo {
    public:
-    explicit LayerInfo(const SkM44& matrix,
-                       const SkMatrix& matrix33,
-                       const SkRect& clip_bounds,
-                       size_t save_layer_offset = 0,
+    explicit LayerInfo(size_t save_layer_offset = 0,
                        bool has_layer = false,
                        std::shared_ptr<const DlImageFilter> filter = nullptr)
         : save_layer_offset_(save_layer_offset),
           has_layer_(has_layer),
           cannot_inherit_opacity_(false),
           has_compatible_op_(false),
-          matrix_(matrix),
-          matrix33_(matrix33),
-          clip_bounds_(clip_bounds),
           filter_(filter),
           is_unbounded_(false) {}
 
-    explicit LayerInfo(const LayerInfo* current_layer,
-                       size_t save_layer_offset = 0,
-                       bool has_layer = false,
-                       std::shared_ptr<const DlImageFilter> filter = nullptr)
-        : LayerInfo(current_layer->matrix_,
-                    current_layer->matrix33_,
-                    current_layer->clip_bounds_,
-                    save_layer_offset,
-                    has_layer,
-                    filter) {}
-
     // The offset into the memory buffer where the saveLayer DLOp record
     // for this saveLayer() call is placed. This may be needed if the
     // eventual restore() call has discovered important information about
@@ -424,11 +408,6 @@
     bool has_layer() const { return has_layer_; }
     bool cannot_inherit_opacity() const { return cannot_inherit_opacity_; }
     bool has_compatible_op() const { return cannot_inherit_opacity_; }
-    SkM44& matrix() { return matrix_; }
-    SkMatrix& matrix33() { return matrix33_; }
-    SkRect& clip_bounds() { return clip_bounds_; }
-
-    void update_matrix33() { matrix33_ = matrix_.asM33(); }
 
     bool is_group_opacity_compatible() const {
       return !cannot_inherit_opacity_;
@@ -486,9 +465,6 @@
     bool has_layer_;
     bool cannot_inherit_opacity_;
     bool has_compatible_op_;
-    SkM44 matrix_;
-    SkMatrix matrix33_;
-    SkRect clip_bounds_;
     std::shared_ptr<const DlImageFilter> filter_;
     bool is_unbounded_;
     bool has_deferred_save_op_ = false;
@@ -498,6 +474,7 @@
 
   std::vector<LayerInfo> layer_stack_;
   LayerInfo* current_layer_;
+  DisplayListMatrixClipTracker tracker_;
   std::unique_ptr<BoundsAccumulator> accumulator_;
   BoundsAccumulator* accumulator() { return accumulator_.get(); }
 
diff --git a/display_list/display_list_matrix_clip_tracker.cc b/display_list/display_list_matrix_clip_tracker.cc
index 49f653f..d79f693 100644
--- a/display_list/display_list_matrix_clip_tracker.cc
+++ b/display_list/display_list_matrix_clip_tracker.cc
@@ -39,12 +39,13 @@
   void setTransform(const SkMatrix& matrix) override { m44_ = SkM44(matrix); }
   void setTransform(const SkM44& m44) override { m44_ = m44; }
   void setIdentity() override { m44_.setIdentity(); }
+  bool mapRect(const SkRect& rect, SkRect* mapped) const override {
+    return m44_.asM33().mapRect(mapped, rect);
+  }
+  bool canBeInverted() const override { return m44_.asM33().invert(nullptr); }
 
  protected:
   bool has_perspective() const override;
-  bool map_rect(const SkRect& rect, SkRect* mapped) const override {
-    return m44_.asM33().mapRect(mapped, rect);
-  }
 
  private:
   SkM44 m44_;
@@ -80,12 +81,13 @@
     FML_CHECK(false) << "SkM44 was set without upgrading Data";
   }
   void setIdentity() override { matrix_.setIdentity(); }
+  bool mapRect(const SkRect& rect, SkRect* mapped) const override {
+    return matrix_.mapRect(mapped, rect);
+  }
+  bool canBeInverted() const override { return matrix_.invert(nullptr); }
 
  protected:
   bool has_perspective() const override { return matrix_.hasPerspective(); }
-  bool map_rect(const SkRect& rect, SkRect* mapped) const override {
-    return matrix_.mapRect(mapped, rect);
-  }
 
  private:
   SkMatrix matrix_;
@@ -103,21 +105,64 @@
 DisplayListMatrixClipTracker::DisplayListMatrixClipTracker(
     const SkRect& cull_rect,
     const SkMatrix& matrix) {
-  saved_.emplace_back(std::make_unique<Data3x3>(matrix, cull_rect));
+  // isEmpty protects us against NaN as we normalize any empty cull rects
+  SkRect cull = cull_rect.isEmpty() ? SkRect::MakeEmpty() : cull_rect;
+  saved_.emplace_back(std::make_unique<Data3x3>(matrix, cull));
   current_ = saved_.back().get();
 }
 
 DisplayListMatrixClipTracker::DisplayListMatrixClipTracker(
     const SkRect& cull_rect,
     const SkM44& m44) {
+  // isEmpty protects us against NaN as we normalize any empty cull rects
+  SkRect cull = cull_rect.isEmpty() ? SkRect::MakeEmpty() : cull_rect;
   if (is_3x3(m44)) {
-    saved_.emplace_back(std::make_unique<Data3x3>(m44.asM33(), cull_rect));
+    saved_.emplace_back(std::make_unique<Data3x3>(m44.asM33(), cull));
   } else {
-    saved_.emplace_back(std::make_unique<Data4x4>(m44, cull_rect));
+    saved_.emplace_back(std::make_unique<Data4x4>(m44, cull));
   }
   current_ = saved_.back().get();
 }
 
+// clang-format off
+void DisplayListMatrixClipTracker::transform2DAffine(
+    SkScalar mxx, SkScalar mxy, SkScalar mxt,
+    SkScalar myx, SkScalar myy, SkScalar myt) {
+  if (!current_->is_4x4()) {
+    transform(SkMatrix::MakeAll(mxx, mxy, mxt,
+                                myx, myy, myt,
+                                0,   0,   1));
+  } else {
+    transform(SkM44(mxx, mxy, 0, mxt,
+                    myx, myy, 0, myt,
+                    0,   0,   1, 0,
+                    0,   0,   0, 1));
+  }
+}
+void DisplayListMatrixClipTracker::transformFullPerspective(
+    SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
+    SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
+    SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
+    SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) {
+  if (!current_->is_4x4()) {
+    if (                        mxz == 0 &&
+                                myz == 0 &&
+        mzx == 0 && mzy == 0 && mzz == 1 && mzt == 0 &&
+                                mwz == 0) {
+        transform(SkMatrix::MakeAll(mxx, mxy, mxt,
+                                    myx, myy, myt,
+                                    mwx, mwy, mwt));
+        return;
+    }
+  }
+
+  transform(SkM44(mxx, mxy, mxz, mxt,
+                  myx, myy, myz, myt,
+                  mzx, mzy, mzz, mzt,
+                  mwx, mwy, mwz, mwt));
+}
+// clang-format on
+
 void DisplayListMatrixClipTracker::save() {
   if (current_->is_4x4()) {
     saved_.emplace_back(std::make_unique<Data4x4>(current_));
@@ -183,6 +228,19 @@
 void DisplayListMatrixClipTracker::clipPath(const SkPath& path,
                                             SkClipOp op,
                                             bool is_aa) {
+  // Map "kDifference of inverse path" to "kIntersect of the original path" and
+  // map "kIntersect of inverse path" to "kDifference of the original path"
+  if (path.isInverseFillType()) {
+    switch (op) {
+      case SkClipOp::kIntersect:
+        op = SkClipOp::kDifference;
+        break;
+      case SkClipOp::kDifference:
+        op = SkClipOp::kIntersect;
+        break;
+    }
+  }
+
   SkRect bounds;
   switch (op) {
     case SkClipOp::kIntersect:
@@ -202,11 +260,14 @@
   if (cull_rect_.isEmpty() || content_bounds.isEmpty()) {
     return true;
   }
+  if (!canBeInverted()) {
+    return true;
+  }
   if (has_perspective()) {
     return false;
   }
   SkRect mapped;
-  map_rect(content_bounds, &mapped);
+  mapRect(content_bounds, &mapped);
   return !mapped.intersects(cull_rect_);
 }
 
@@ -228,7 +289,7 @@
         break;
       }
       SkRect rect;
-      map_rect(clip, &rect);
+      mapRect(clip, &rect);
       if (is_aa) {
         rect.roundOut(&rect);
       }
@@ -242,7 +303,7 @@
         break;
       }
       SkRect rect;
-      if (map_rect(clip, &rect)) {
+      if (mapRect(clip, &rect)) {
         // This technique only works if it is rect -> rect
         if (is_aa) {
           SkIRect rounded;
diff --git a/display_list/display_list_matrix_clip_tracker.h b/display_list/display_list_matrix_clip_tracker.h
index 52c9d99..1bde44a 100644
--- a/display_list/display_list_matrix_clip_tracker.h
+++ b/display_list/display_list_matrix_clip_tracker.h
@@ -44,9 +44,20 @@
   void rotate(SkScalar degrees) { current_->rotate(degrees); }
   void transform(const SkM44& m44);
   void transform(const SkMatrix& matrix) { current_->transform(matrix); }
+  // clang-format off
+  void transform2DAffine(
+      SkScalar mxx, SkScalar mxy, SkScalar mxt,
+      SkScalar myx, SkScalar myy, SkScalar myt);
+  void transformFullPerspective(
+      SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
+      SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
+      SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
+      SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt);
+  // clang-format on
   void setTransform(const SkMatrix& matrix) { current_->setTransform(matrix); }
   void setTransform(const SkM44& m44);
   void setIdentity() { current_->setIdentity(); }
+  bool mapRect(SkRect* rect) const { return current_->mapRect(*rect, rect); }
 
   void clipRect(const SkRect& rect, SkClipOp op, bool is_aa) {
     current_->clipBounds(rect, op, is_aa);
@@ -77,6 +88,8 @@
     virtual void setTransform(const SkMatrix& matrix) = 0;
     virtual void setTransform(const SkM44& m44) = 0;
     virtual void setIdentity() = 0;
+    virtual bool mapRect(const SkRect& rect, SkRect* mapped) const = 0;
+    virtual bool canBeInverted() const = 0;
 
     virtual void clipBounds(const SkRect& clip, SkClipOp op, bool is_aa);
 
@@ -84,7 +97,6 @@
     Data(const SkRect& rect) : cull_rect_(rect) {}
 
     virtual bool has_perspective() const = 0;
-    virtual bool map_rect(const SkRect& rect, SkRect* mapped) const = 0;
 
     SkRect cull_rect_;
   };
diff --git a/display_list/display_list_matrix_clip_tracker_unittests.cc b/display_list/display_list_matrix_clip_tracker_unittests.cc
index c4ffb18..eff7e90 100644
--- a/display_list/display_list_matrix_clip_tracker_unittests.cc
+++ b/display_list/display_list_matrix_clip_tracker_unittests.cc
@@ -4,6 +4,7 @@
 
 #include "flutter/display_list/display_list_matrix_clip_tracker.h"
 #include "gtest/gtest.h"
+#include "third_party/skia/include/core/SkPath.h"
 
 namespace flutter {
 namespace testing {
@@ -225,5 +226,127 @@
   ASSERT_EQ(tracker2.matrix_4x4(), rotated_m44);
 }
 
+TEST(DisplayListMatrixClipTracker, Transform2DAffine) {
+  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);
+  const SkMatrix matrix = SkMatrix::Scale(4, 4);
+  const SkM44 m44 = SkM44::Scale(4, 4);
+
+  const SkMatrix transformed_matrix =
+      SkMatrix::Concat(matrix, SkMatrix::MakeAll(2, 0, 5,  //
+                                                 0, 2, 6,  //
+                                                 0, 0, 1));
+  const SkM44 transformed_m44 = SkM44(transformed_matrix);
+  const SkRect local_cull_rect = SkRect::MakeLTRB(0, -0.5, 5, 4.5);
+
+  DisplayListMatrixClipTracker tracker1(cull_rect, matrix);
+  DisplayListMatrixClipTracker tracker2(cull_rect, m44);
+  tracker1.transform2DAffine(2, 0, 5,  //
+                             0, 2, 6);
+  tracker2.transform2DAffine(2, 0, 5,  //
+                             0, 2, 6);
+  ASSERT_FALSE(tracker1.using_4x4_matrix());
+  ASSERT_EQ(tracker1.device_cull_rect(), cull_rect);
+  ASSERT_EQ(tracker1.local_cull_rect(), local_cull_rect);
+  ASSERT_EQ(tracker1.matrix_3x3(), transformed_matrix);
+  ASSERT_EQ(tracker1.matrix_4x4(), transformed_m44);
+
+  ASSERT_FALSE(tracker2.using_4x4_matrix());
+  ASSERT_EQ(tracker2.device_cull_rect(), cull_rect);
+  ASSERT_EQ(tracker2.local_cull_rect(), local_cull_rect);
+  ASSERT_EQ(tracker2.matrix_3x3(), transformed_matrix);
+  ASSERT_EQ(tracker2.matrix_4x4(), transformed_m44);
+}
+
+TEST(DisplayListMatrixClipTracker, TransformFullPerspectiveUsing3x3Matrix) {
+  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);
+  const SkMatrix matrix = SkMatrix::Scale(4, 4);
+  const SkM44 m44 = SkM44::Scale(4, 4);
+
+  const SkMatrix transformed_matrix =
+      SkMatrix::Concat(matrix, SkMatrix::MakeAll(2, 0, 5,  //
+                                                 0, 2, 6,  //
+                                                 0, 0, 1));
+  const SkM44 transformed_m44 = SkM44(transformed_matrix);
+  const SkRect local_cull_rect = SkRect::MakeLTRB(0, -0.5, 5, 4.5);
+
+  DisplayListMatrixClipTracker tracker1(cull_rect, matrix);
+  DisplayListMatrixClipTracker tracker2(cull_rect, m44);
+  tracker1.transformFullPerspective(2, 0, 0, 5,  //
+                                    0, 2, 0, 6,  //
+                                    0, 0, 1, 0,  //
+                                    0, 0, 0, 1);
+  tracker2.transformFullPerspective(2, 0, 0, 5,  //
+                                    0, 2, 0, 6,  //
+                                    0, 0, 1, 0,  //
+                                    0, 0, 0, 1);
+  ASSERT_FALSE(tracker1.using_4x4_matrix());
+  ASSERT_EQ(tracker1.device_cull_rect(), cull_rect);
+  ASSERT_EQ(tracker1.local_cull_rect(), local_cull_rect);
+  ASSERT_EQ(tracker1.matrix_3x3(), transformed_matrix);
+  ASSERT_EQ(tracker1.matrix_4x4(), transformed_m44);
+
+  ASSERT_FALSE(tracker2.using_4x4_matrix());
+  ASSERT_EQ(tracker2.device_cull_rect(), cull_rect);
+  ASSERT_EQ(tracker2.local_cull_rect(), local_cull_rect);
+  ASSERT_EQ(tracker2.matrix_3x3(), transformed_matrix);
+  ASSERT_EQ(tracker2.matrix_4x4(), transformed_m44);
+}
+
+TEST(DisplayListMatrixClipTracker, TransformFullPerspectiveUsing4x4Matrix) {
+  const SkRect cull_rect = SkRect::MakeLTRB(20, 20, 60, 60);
+  const SkMatrix matrix = SkMatrix::Scale(4, 4);
+  const SkM44 m44 = SkM44::Scale(4, 4);
+
+  const SkM44 transformed_m44 = SkM44(m44, SkM44(2, 0, 0, 5,  //
+                                                 0, 2, 0, 6,  //
+                                                 0, 0, 1, 7,  //
+                                                 0, 0, 0, 1));
+  const SkRect local_cull_rect = SkRect::MakeLTRB(0, -0.5, 5, 4.5);
+
+  DisplayListMatrixClipTracker tracker1(cull_rect, matrix);
+  DisplayListMatrixClipTracker tracker2(cull_rect, m44);
+  tracker1.transformFullPerspective(2, 0, 0, 5,  //
+                                    0, 2, 0, 6,  //
+                                    0, 0, 1, 7,  //
+                                    0, 0, 0, 1);
+  tracker2.transformFullPerspective(2, 0, 0, 5,  //
+                                    0, 2, 0, 6,  //
+                                    0, 0, 1, 7,  //
+                                    0, 0, 0, 1);
+  ASSERT_TRUE(tracker1.using_4x4_matrix());
+  ASSERT_EQ(tracker1.device_cull_rect(), cull_rect);
+  ASSERT_EQ(tracker1.local_cull_rect(), local_cull_rect);
+  ASSERT_EQ(tracker1.matrix_4x4(), transformed_m44);
+
+  ASSERT_TRUE(tracker2.using_4x4_matrix());
+  ASSERT_EQ(tracker2.device_cull_rect(), cull_rect);
+  ASSERT_EQ(tracker2.local_cull_rect(), local_cull_rect);
+  ASSERT_EQ(tracker2.matrix_4x4(), transformed_m44);
+}
+
+TEST(DisplayListMatrixClipTracker, ClipPathWithInvertFillType) {
+  SkRect cull_rect = SkRect::MakeLTRB(0, 0, 100.0, 100.0);
+  DisplayListMatrixClipTracker builder(cull_rect, SkMatrix::I());
+  SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);
+  clip.setFillType(SkPathFillType::kInverseWinding);
+  builder.clipPath(clip, SkClipOp::kIntersect, false);
+
+  ASSERT_EQ(builder.local_cull_rect(), cull_rect);
+  ASSERT_EQ(builder.device_cull_rect(), cull_rect);
+}
+
+TEST(DisplayListMatrixClipTracker, DiffClipPathWithInvertFillType) {
+  SkRect cull_rect = SkRect::MakeLTRB(0, 0, 100.0, 100.0);
+  DisplayListMatrixClipTracker tracker(cull_rect, SkMatrix::I());
+
+  SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);
+  clip.setFillType(SkPathFillType::kInverseWinding);
+  SkRect clip_bounds = SkRect::MakeLTRB(8.2, 9.3, 22.4, 27.7);
+  tracker.clipPath(clip, SkClipOp::kDifference, false);
+
+  ASSERT_EQ(tracker.local_cull_rect(), clip_bounds);
+  ASSERT_EQ(tracker.device_cull_rect(), clip_bounds);
+}
+
 }  // namespace testing
 }  // namespace flutter
diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc
index 4c5359d..d0cd15d 100644
--- a/display_list/display_list_unittests.cc
+++ b/display_list/display_list_unittests.cc
@@ -1361,22 +1361,21 @@
 TEST(DisplayList, ClipRectAffectsClipBounds) {
   DisplayListBuilder builder;
   SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
-  SkRect clip_expanded_bounds = SkRect::MakeLTRB(10, 11, 21, 26);
   builder.clipRect(clip_bounds, SkClipOp::kIntersect, false);
 
   // Save initial return values for testing restored values
   SkRect initial_local_bounds = builder.getLocalClipBounds();
   SkRect initial_destination_bounds = builder.getDestinationClipBounds();
-  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
   builder.save();
   builder.clipRect({0, 0, 15, 15}, SkClipOp::kIntersect, false);
   // Both clip bounds have changed
-  ASSERT_NE(builder.getLocalClipBounds(), clip_expanded_bounds);
+  ASSERT_NE(builder.getLocalClipBounds(), clip_bounds);
   ASSERT_NE(builder.getDestinationClipBounds(), clip_bounds);
   // Previous return values have not changed
-  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
   builder.restore();
 
@@ -1386,10 +1385,49 @@
 
   builder.save();
   builder.scale(2, 2);
+  SkRect scaled_clip_bounds = SkRect::MakeLTRB(5.1, 5.65, 10.2, 12.85);
+  ASSERT_EQ(builder.getLocalClipBounds(), scaled_clip_bounds);
+  // Destination bounds are unaffected by transform
+  ASSERT_EQ(builder.getDestinationClipBounds(), clip_bounds);
+  builder.restore();
+
+  // save/restore returned the values to their original values
+  ASSERT_EQ(builder.getLocalClipBounds(), initial_local_bounds);
+  ASSERT_EQ(builder.getDestinationClipBounds(), initial_destination_bounds);
+}
+
+TEST(DisplayList, ClipRectDoAAAffectsClipBounds) {
+  DisplayListBuilder builder;
+  SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
+  SkRect clip_expanded_bounds = SkRect::MakeLTRB(10, 11, 21, 26);
+  builder.clipRect(clip_bounds, SkClipOp::kIntersect, true);
+
+  // Save initial return values for testing restored values
+  SkRect initial_local_bounds = builder.getLocalClipBounds();
+  SkRect initial_destination_bounds = builder.getDestinationClipBounds();
+  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
+
+  builder.save();
+  builder.clipRect({0, 0, 15, 15}, SkClipOp::kIntersect, true);
+  // Both clip bounds have changed
+  ASSERT_NE(builder.getLocalClipBounds(), clip_expanded_bounds);
+  ASSERT_NE(builder.getDestinationClipBounds(), clip_expanded_bounds);
+  // Previous return values have not changed
+  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
+  builder.restore();
+
+  // save/restore returned the values to their original values
+  ASSERT_EQ(builder.getLocalClipBounds(), initial_local_bounds);
+  ASSERT_EQ(builder.getDestinationClipBounds(), initial_destination_bounds);
+
+  builder.save();
+  builder.scale(2, 2);
   SkRect scaled_expanded_bounds = SkRect::MakeLTRB(5, 5.5, 10.5, 13);
   ASSERT_EQ(builder.getLocalClipBounds(), scaled_expanded_bounds);
   // Destination bounds are unaffected by transform
-  ASSERT_EQ(builder.getDestinationClipBounds(), clip_bounds);
+  ASSERT_EQ(builder.getDestinationClipBounds(), clip_expanded_bounds);
   builder.restore();
 
   // save/restore returned the values to their original values
@@ -1419,23 +1457,22 @@
 TEST(DisplayList, ClipRRectAffectsClipBounds) {
   DisplayListBuilder builder;
   SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
-  SkRect clip_expanded_bounds = SkRect::MakeLTRB(10, 11, 21, 26);
   SkRRect clip = SkRRect::MakeRectXY(clip_bounds, 3, 2);
   builder.clipRRect(clip, SkClipOp::kIntersect, false);
 
   // Save initial return values for testing restored values
   SkRect initial_local_bounds = builder.getLocalClipBounds();
   SkRect initial_destination_bounds = builder.getDestinationClipBounds();
-  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
   builder.save();
   builder.clipRect({0, 0, 15, 15}, SkClipOp::kIntersect, false);
   // Both clip bounds have changed
-  ASSERT_NE(builder.getLocalClipBounds(), clip_expanded_bounds);
+  ASSERT_NE(builder.getLocalClipBounds(), clip_bounds);
   ASSERT_NE(builder.getDestinationClipBounds(), clip_bounds);
   // Previous return values have not changed
-  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
   builder.restore();
 
@@ -1445,10 +1482,50 @@
 
   builder.save();
   builder.scale(2, 2);
+  SkRect scaled_clip_bounds = SkRect::MakeLTRB(5.1, 5.65, 10.2, 12.85);
+  ASSERT_EQ(builder.getLocalClipBounds(), scaled_clip_bounds);
+  // Destination bounds are unaffected by transform
+  ASSERT_EQ(builder.getDestinationClipBounds(), clip_bounds);
+  builder.restore();
+
+  // save/restore returned the values to their original values
+  ASSERT_EQ(builder.getLocalClipBounds(), initial_local_bounds);
+  ASSERT_EQ(builder.getDestinationClipBounds(), initial_destination_bounds);
+}
+
+TEST(DisplayList, ClipRRectDoAAAffectsClipBounds) {
+  DisplayListBuilder builder;
+  SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
+  SkRect clip_expanded_bounds = SkRect::MakeLTRB(10, 11, 21, 26);
+  SkRRect clip = SkRRect::MakeRectXY(clip_bounds, 3, 2);
+  builder.clipRRect(clip, SkClipOp::kIntersect, true);
+
+  // Save initial return values for testing restored values
+  SkRect initial_local_bounds = builder.getLocalClipBounds();
+  SkRect initial_destination_bounds = builder.getDestinationClipBounds();
+  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
+
+  builder.save();
+  builder.clipRect({0, 0, 15, 15}, SkClipOp::kIntersect, true);
+  // Both clip bounds have changed
+  ASSERT_NE(builder.getLocalClipBounds(), clip_expanded_bounds);
+  ASSERT_NE(builder.getDestinationClipBounds(), clip_expanded_bounds);
+  // Previous return values have not changed
+  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
+  builder.restore();
+
+  // save/restore returned the values to their original values
+  ASSERT_EQ(builder.getLocalClipBounds(), initial_local_bounds);
+  ASSERT_EQ(builder.getDestinationClipBounds(), initial_destination_bounds);
+
+  builder.save();
+  builder.scale(2, 2);
   SkRect scaled_expanded_bounds = SkRect::MakeLTRB(5, 5.5, 10.5, 13);
   ASSERT_EQ(builder.getLocalClipBounds(), scaled_expanded_bounds);
   // Destination bounds are unaffected by transform
-  ASSERT_EQ(builder.getDestinationClipBounds(), clip_bounds);
+  ASSERT_EQ(builder.getDestinationClipBounds(), clip_expanded_bounds);
   builder.restore();
 
   // save/restore returned the values to their original values
@@ -1482,22 +1559,21 @@
   DisplayListBuilder builder;
   SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);
   SkRect clip_bounds = SkRect::MakeLTRB(8.2, 9.3, 22.4, 27.7);
-  SkRect clip_expanded_bounds = SkRect::MakeLTRB(8, 9, 23, 28);
   builder.clipPath(clip, SkClipOp::kIntersect, false);
 
   // Save initial return values for testing restored values
   SkRect initial_local_bounds = builder.getLocalClipBounds();
   SkRect initial_destination_bounds = builder.getDestinationClipBounds();
-  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
   builder.save();
   builder.clipRect({0, 0, 15, 15}, SkClipOp::kIntersect, false);
   // Both clip bounds have changed
-  ASSERT_NE(builder.getLocalClipBounds(), clip_expanded_bounds);
+  ASSERT_NE(builder.getLocalClipBounds(), clip_bounds);
   ASSERT_NE(builder.getDestinationClipBounds(), clip_bounds);
   // Previous return values have not changed
-  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
   builder.restore();
 
@@ -1507,10 +1583,49 @@
 
   builder.save();
   builder.scale(2, 2);
+  SkRect scaled_clip_bounds = SkRect::MakeLTRB(4.1, 4.65, 11.2, 13.85);
+  ASSERT_EQ(builder.getLocalClipBounds(), scaled_clip_bounds);
+  // Destination bounds are unaffected by transform
+  ASSERT_EQ(builder.getDestinationClipBounds(), clip_bounds);
+  builder.restore();
+
+  // save/restore returned the values to their original values
+  ASSERT_EQ(builder.getLocalClipBounds(), initial_local_bounds);
+  ASSERT_EQ(builder.getDestinationClipBounds(), initial_destination_bounds);
+}
+
+TEST(DisplayList, ClipPathDoAAAffectsClipBounds) {
+  DisplayListBuilder builder;
+  SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);
+  SkRect clip_expanded_bounds = SkRect::MakeLTRB(8, 9, 23, 28);
+  builder.clipPath(clip, SkClipOp::kIntersect, true);
+
+  // Save initial return values for testing restored values
+  SkRect initial_local_bounds = builder.getLocalClipBounds();
+  SkRect initial_destination_bounds = builder.getDestinationClipBounds();
+  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
+
+  builder.save();
+  builder.clipRect({0, 0, 15, 15}, SkClipOp::kIntersect, true);
+  // Both clip bounds have changed
+  ASSERT_NE(builder.getLocalClipBounds(), clip_expanded_bounds);
+  ASSERT_NE(builder.getDestinationClipBounds(), clip_expanded_bounds);
+  // Previous return values have not changed
+  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
+  builder.restore();
+
+  // save/restore returned the values to their original values
+  ASSERT_EQ(builder.getLocalClipBounds(), initial_local_bounds);
+  ASSERT_EQ(builder.getDestinationClipBounds(), initial_destination_bounds);
+
+  builder.save();
+  builder.scale(2, 2);
   SkRect scaled_expanded_bounds = SkRect::MakeLTRB(4, 4.5, 11.5, 14);
   ASSERT_EQ(builder.getLocalClipBounds(), scaled_expanded_bounds);
   // Destination bounds are unaffected by transform
-  ASSERT_EQ(builder.getDestinationClipBounds(), clip_bounds);
+  ASSERT_EQ(builder.getDestinationClipBounds(), clip_expanded_bounds);
   builder.restore();
 
   // save/restore returned the values to their original values
@@ -1543,13 +1658,12 @@
   DisplayListBuilder builder;
   SkRect diff_clip = SkRect::MakeLTRB(0, 0, 15, 15);
   SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
-  SkRect clip_expanded_bounds = SkRect::MakeLTRB(10, 11, 21, 26);
   builder.clipRect(clip_bounds, SkClipOp::kIntersect, false);
 
   // Save initial return values for testing after kDifference clip
   SkRect initial_local_bounds = builder.getLocalClipBounds();
   SkRect initial_destination_bounds = builder.getDestinationClipBounds();
-  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
   builder.clipRect(diff_clip, SkClipOp::kDifference, false);
@@ -1561,14 +1675,13 @@
   DisplayListBuilder builder;
   SkRRect diff_clip = SkRRect::MakeRectXY({0, 0, 15, 15}, 1, 1);
   SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
-  SkRect clip_expanded_bounds = SkRect::MakeLTRB(10, 11, 21, 26);
   SkRRect clip = SkRRect::MakeRectXY({10.2, 11.3, 20.4, 25.7}, 3, 2);
   builder.clipRRect(clip, SkClipOp::kIntersect, false);
 
   // Save initial return values for testing after kDifference clip
   SkRect initial_local_bounds = builder.getLocalClipBounds();
   SkRect initial_destination_bounds = builder.getDestinationClipBounds();
-  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
   builder.clipRRect(diff_clip, SkClipOp::kDifference, false);
@@ -1581,13 +1694,12 @@
   SkPath diff_clip = SkPath().addRect({0, 0, 15, 15});
   SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);
   SkRect clip_bounds = SkRect::MakeLTRB(8.2, 9.3, 22.4, 27.7);
-  SkRect clip_expanded_bounds = SkRect::MakeLTRB(8, 9, 23, 28);
   builder.clipPath(clip, SkClipOp::kIntersect, false);
 
   // Save initial return values for testing after kDifference clip
   SkRect initial_local_bounds = builder.getLocalClipBounds();
   SkRect initial_destination_bounds = builder.getDestinationClipBounds();
-  ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
+  ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
   builder.clipPath(diff_clip, SkClipOp::kDifference, false);
@@ -1612,10 +1724,9 @@
   SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);
   clip.setFillType(SkPathFillType::kInverseWinding);
   SkRect clip_bounds = SkRect::MakeLTRB(8.2, 9.3, 22.4, 27.7);
-  SkRect clip_expanded_bounds = SkRect::MakeLTRB(8, 9, 23, 28);
   builder.clipPath(clip, SkClipOp::kDifference, false);
 
-  ASSERT_EQ(builder.getLocalClipBounds(), clip_expanded_bounds);
+  ASSERT_EQ(builder.getLocalClipBounds(), clip_bounds);
   ASSERT_EQ(builder.getDestinationClipBounds(), clip_bounds);
 }
 
diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart
index 5870692..69bcbc7 100644
--- a/testing/dart/canvas_test.dart
+++ b/testing/dart/canvas_test.dart
@@ -726,7 +726,7 @@
     Expect.fail('$value is too close to $expected');
   };
 
-  test('Canvas.clipRect affects canvas.getClipBounds', () async {
+  test('Canvas.clipRect(doAA=true) affects canvas.getClipBounds', () async {
     final PictureRecorder recorder = PictureRecorder();
     final Canvas canvas = Canvas(recorder);
     const Rect clipBounds = Rect.fromLTRB(10.2, 11.3, 20.4, 25.7);
@@ -737,16 +737,16 @@
     final Rect initialLocalBounds = canvas.getLocalClipBounds();
     final Rect initialDestinationBounds = canvas.getDestinationClipBounds();
     expect(initialLocalBounds, closeToRect(clipExpandedBounds));
-    expect(initialDestinationBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipExpandedBounds));
 
     canvas.save();
     canvas.clipRect(const Rect.fromLTRB(0, 0, 15, 15));
     // Both clip bounds have changed
     expect(canvas.getLocalClipBounds(), notCloseToRect(clipExpandedBounds));
-    expect(canvas.getDestinationClipBounds(), notCloseToRect(clipBounds));
+    expect(canvas.getDestinationClipBounds(), notCloseToRect(clipExpandedBounds));
     // Previous return values have not changed
     expect(initialLocalBounds, closeToRect(clipExpandedBounds));
-    expect(initialDestinationBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipExpandedBounds));
     canvas.restore();
 
     // save/restore returned the values to their original values
@@ -758,6 +758,45 @@
     const Rect scaledExpandedBounds = Rect.fromLTRB(5, 5.5, 10.5, 13);
     expect(canvas.getLocalClipBounds(), closeToRect(scaledExpandedBounds));
     // Destination bounds are unaffected by transform
+    expect(canvas.getDestinationClipBounds(), closeToRect(clipExpandedBounds));
+    canvas.restore();
+
+    // save/restore returned the values to their original values
+    expect(canvas.getLocalClipBounds(), initialLocalBounds);
+    expect(canvas.getDestinationClipBounds(), initialDestinationBounds);
+  });
+
+  test('Canvas.clipRect(doAA=false) affects canvas.getClipBounds', () async {
+    final PictureRecorder recorder = PictureRecorder();
+    final Canvas canvas = Canvas(recorder);
+    const Rect clipBounds = Rect.fromLTRB(10.2, 11.3, 20.4, 25.7);
+    canvas.clipRect(clipBounds, doAntiAlias: false);
+
+    // Save initial return values for testing restored values
+    final Rect initialLocalBounds = canvas.getLocalClipBounds();
+    final Rect initialDestinationBounds = canvas.getDestinationClipBounds();
+    expect(initialLocalBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipBounds));
+
+    canvas.save();
+    canvas.clipRect(const Rect.fromLTRB(0, 0, 15, 15));
+    // Both clip bounds have changed
+    expect(canvas.getLocalClipBounds(), notCloseToRect(clipBounds));
+    expect(canvas.getDestinationClipBounds(), notCloseToRect(clipBounds));
+    // Previous return values have not changed
+    expect(initialLocalBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipBounds));
+    canvas.restore();
+
+    // save/restore returned the values to their original values
+    expect(canvas.getLocalClipBounds(), initialLocalBounds);
+    expect(canvas.getDestinationClipBounds(), initialDestinationBounds);
+
+    canvas.save();
+    canvas.scale(2, 2);
+    const Rect scaledClipBounds = Rect.fromLTRB(5.1, 5.65, 10.2, 12.85);
+    expect(canvas.getLocalClipBounds(), closeToRect(scaledClipBounds));
+    // Destination bounds are unaffected by transform
     expect(canvas.getDestinationClipBounds(), closeToRect(clipBounds));
     canvas.restore();
 
@@ -773,21 +812,21 @@
     const Rect clipBounds2 = Rect.fromLTRB(10.0, 10.0, 20.0, 20.0);
 
     canvas.save();
-    canvas.clipRect(clipBounds1);
+    canvas.clipRect(clipBounds1, doAntiAlias: false);
     canvas.translate(0, 10.0);
-    canvas.clipRect(clipBounds1);
+    canvas.clipRect(clipBounds1, doAntiAlias: false);
     expect(canvas.getDestinationClipBounds().isEmpty, isTrue);
     canvas.restore();
 
     canvas.save();
-    canvas.clipRect(clipBounds1);
+    canvas.clipRect(clipBounds1, doAntiAlias: false);
     canvas.translate(-10.0, -10.0);
-    canvas.clipRect(clipBounds2);
+    canvas.clipRect(clipBounds2, doAntiAlias: false);
     expect(canvas.getDestinationClipBounds(), clipBounds1);
     canvas.restore();
   });
 
-  test('Canvas.clipRRect affects canvas.getClipBounds', () async {
+  test('Canvas.clipRRect(doAA=true) affects canvas.getClipBounds', () async {
     final PictureRecorder recorder = PictureRecorder();
     final Canvas canvas = Canvas(recorder);
     const Rect clipBounds = Rect.fromLTRB(10.2, 11.3, 20.4, 25.7);
@@ -799,16 +838,16 @@
     final Rect initialLocalBounds = canvas.getLocalClipBounds();
     final Rect initialDestinationBounds = canvas.getDestinationClipBounds();
     expect(initialLocalBounds, closeToRect(clipExpandedBounds));
-    expect(initialDestinationBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipExpandedBounds));
 
     canvas.save();
     canvas.clipRect(const Rect.fromLTRB(0, 0, 15, 15));
     // Both clip bounds have changed
     expect(canvas.getLocalClipBounds(), notCloseToRect(clipExpandedBounds));
-    expect(canvas.getDestinationClipBounds(), notCloseToRect(clipBounds));
+    expect(canvas.getDestinationClipBounds(), notCloseToRect(clipExpandedBounds));
     // Previous return values have not changed
     expect(initialLocalBounds, closeToRect(clipExpandedBounds));
-    expect(initialDestinationBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipExpandedBounds));
     canvas.restore();
 
     // save/restore returned the values to their original values
@@ -820,6 +859,46 @@
     const Rect scaledExpandedBounds = Rect.fromLTRB(5, 5.5, 10.5, 13);
     expect(canvas.getLocalClipBounds(), closeToRect(scaledExpandedBounds));
     // Destination bounds are unaffected by transform
+    expect(canvas.getDestinationClipBounds(), closeToRect(clipExpandedBounds));
+    canvas.restore();
+
+    // save/restore returned the values to their original values
+    expect(canvas.getLocalClipBounds(), initialLocalBounds);
+    expect(canvas.getDestinationClipBounds(), initialDestinationBounds);
+  });
+
+  test('Canvas.clipRRect(doAA=false) affects canvas.getClipBounds', () async {
+    final PictureRecorder recorder = PictureRecorder();
+    final Canvas canvas = Canvas(recorder);
+    const Rect clipBounds = Rect.fromLTRB(10.2, 11.3, 20.4, 25.7);
+    final RRect clip = RRect.fromRectAndRadius(clipBounds, const Radius.circular(3));
+    canvas.clipRRect(clip, doAntiAlias: false);
+
+    // Save initial return values for testing restored values
+    final Rect initialLocalBounds = canvas.getLocalClipBounds();
+    final Rect initialDestinationBounds = canvas.getDestinationClipBounds();
+    expect(initialLocalBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipBounds));
+
+    canvas.save();
+    canvas.clipRect(const Rect.fromLTRB(0, 0, 15, 15), doAntiAlias: false);
+    // Both clip bounds have changed
+    expect(canvas.getLocalClipBounds(), notCloseToRect(clipBounds));
+    expect(canvas.getDestinationClipBounds(), notCloseToRect(clipBounds));
+    // Previous return values have not changed
+    expect(initialLocalBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipBounds));
+    canvas.restore();
+
+    // save/restore returned the values to their original values
+    expect(canvas.getLocalClipBounds(), initialLocalBounds);
+    expect(canvas.getDestinationClipBounds(), initialDestinationBounds);
+
+    canvas.save();
+    canvas.scale(2, 2);
+    const Rect scaledClipBounds = Rect.fromLTRB(5.1, 5.65, 10.2, 12.85);
+    expect(canvas.getLocalClipBounds(), closeToRect(scaledClipBounds));
+    // Destination bounds are unaffected by transform
     expect(canvas.getDestinationClipBounds(), closeToRect(clipBounds));
     canvas.restore();
 
@@ -837,21 +916,21 @@
     final RRect clip2 = RRect.fromRectAndRadius(clipBounds2, const Radius.circular(3));
 
     canvas.save();
-    canvas.clipRRect(clip1);
+    canvas.clipRRect(clip1, doAntiAlias: false);
     canvas.translate(0, 10.0);
-    canvas.clipRRect(clip1);
+    canvas.clipRRect(clip1, doAntiAlias: false);
     expect(canvas.getDestinationClipBounds().isEmpty, isTrue);
     canvas.restore();
 
     canvas.save();
-    canvas.clipRRect(clip1);
+    canvas.clipRRect(clip1, doAntiAlias: false);
     canvas.translate(-10.0, -10.0);
-    canvas.clipRRect(clip2);
+    canvas.clipRRect(clip2, doAntiAlias: false);
     expect(canvas.getDestinationClipBounds(), clipBounds1);
     canvas.restore();
   });
 
-  test('Canvas.clipPath affects canvas.getClipBounds', () async {
+  test('Canvas.clipPath(doAA=true) affects canvas.getClipBounds', () async {
     final PictureRecorder recorder = PictureRecorder();
     final Canvas canvas = Canvas(recorder);
     const Rect clipBounds = Rect.fromLTRB(10.2, 11.3, 20.4, 25.7);
@@ -863,16 +942,16 @@
     final Rect initialLocalBounds = canvas.getLocalClipBounds();
     final Rect initialDestinationBounds = canvas.getDestinationClipBounds();
     expect(initialLocalBounds, closeToRect(clipExpandedBounds));
-    expect(initialDestinationBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipExpandedBounds));
 
     canvas.save();
     canvas.clipRect(const Rect.fromLTRB(0, 0, 15, 15));
     // Both clip bounds have changed
     expect(canvas.getLocalClipBounds(), notCloseToRect(clipExpandedBounds));
-    expect(canvas.getDestinationClipBounds(), notCloseToRect(clipBounds));
+    expect(canvas.getDestinationClipBounds(), notCloseToRect(clipExpandedBounds));
     // Previous return values have not changed
     expect(initialLocalBounds, closeToRect(clipExpandedBounds));
-    expect(initialDestinationBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipExpandedBounds));
     canvas.restore();
 
     // save/restore returned the values to their original values
@@ -884,6 +963,46 @@
     const Rect scaledExpandedBounds = Rect.fromLTRB(5, 5.5, 10.5, 13);
     expect(canvas.getLocalClipBounds(), closeToRect(scaledExpandedBounds));
     // Destination bounds are unaffected by transform
+    expect(canvas.getDestinationClipBounds(), closeToRect(clipExpandedBounds));
+    canvas.restore();
+
+    // save/restore returned the values to their original values
+    expect(canvas.getLocalClipBounds(), initialLocalBounds);
+    expect(canvas.getDestinationClipBounds(), initialDestinationBounds);
+  });
+
+  test('Canvas.clipPath(doAA=false) affects canvas.getClipBounds', () async {
+    final PictureRecorder recorder = PictureRecorder();
+    final Canvas canvas = Canvas(recorder);
+    const Rect clipBounds = Rect.fromLTRB(10.2, 11.3, 20.4, 25.7);
+    final Path clip = Path()..addRect(clipBounds)..addOval(clipBounds);
+    canvas.clipPath(clip, doAntiAlias: false);
+
+    // Save initial return values for testing restored values
+    final Rect initialLocalBounds = canvas.getLocalClipBounds();
+    final Rect initialDestinationBounds = canvas.getDestinationClipBounds();
+    expect(initialLocalBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipBounds));
+
+    canvas.save();
+    canvas.clipRect(const Rect.fromLTRB(0, 0, 15, 15), doAntiAlias: false);
+    // Both clip bounds have changed
+    expect(canvas.getLocalClipBounds(), notCloseToRect(clipBounds));
+    expect(canvas.getDestinationClipBounds(), notCloseToRect(clipBounds));
+    // Previous return values have not changed
+    expect(initialLocalBounds, closeToRect(clipBounds));
+    expect(initialDestinationBounds, closeToRect(clipBounds));
+    canvas.restore();
+
+    // save/restore returned the values to their original values
+    expect(canvas.getLocalClipBounds(), initialLocalBounds);
+    expect(canvas.getDestinationClipBounds(), initialDestinationBounds);
+
+    canvas.save();
+    canvas.scale(2, 2);
+    const Rect scaledClipBounds = Rect.fromLTRB(5.1, 5.65, 10.2, 12.85);
+    expect(canvas.getLocalClipBounds(), closeToRect(scaledClipBounds));
+    // Destination bounds are unaffected by transform
     expect(canvas.getDestinationClipBounds(), closeToRect(clipBounds));
     canvas.restore();
 
@@ -901,16 +1020,16 @@
     final Path clip2 = Path()..addRect(clipBounds2)..addOval(clipBounds2);
 
     canvas.save();
-    canvas.clipPath(clip1);
+    canvas.clipPath(clip1, doAntiAlias: false);
     canvas.translate(0, 10.0);
-    canvas.clipPath(clip1);
+    canvas.clipPath(clip1, doAntiAlias: false);
     expect(canvas.getDestinationClipBounds().isEmpty, isTrue);
     canvas.restore();
 
     canvas.save();
-    canvas.clipPath(clip1);
+    canvas.clipPath(clip1, doAntiAlias: false);
     canvas.translate(-10.0, -10.0);
-    canvas.clipPath(clip2);
+    canvas.clipPath(clip2, doAntiAlias: false);
     expect(canvas.getDestinationClipBounds(), clipBounds1);
     canvas.restore();
   });
@@ -919,16 +1038,15 @@
     final PictureRecorder recorder = PictureRecorder();
     final Canvas canvas = Canvas(recorder);
     const Rect clipBounds = Rect.fromLTRB(10.2, 11.3, 20.4, 25.7);
-    const Rect clipExpandedBounds = Rect.fromLTRB(10, 11, 21, 26);
-    canvas.clipRect(clipBounds);
+    canvas.clipRect(clipBounds, doAntiAlias: false);
 
     // Save initial return values for testing restored values
     final Rect initialLocalBounds = canvas.getLocalClipBounds();
     final Rect initialDestinationBounds = canvas.getDestinationClipBounds();
-    expect(initialLocalBounds, closeToRect(clipExpandedBounds));
+    expect(initialLocalBounds, closeToRect(clipBounds));
     expect(initialDestinationBounds, closeToRect(clipBounds));
 
-    canvas.clipRect(const Rect.fromLTRB(0, 0, 15, 15), clipOp: ClipOp.difference);
+    canvas.clipRect(const Rect.fromLTRB(0, 0, 15, 15), clipOp: ClipOp.difference, doAntiAlias: false);
     expect(canvas.getLocalClipBounds(), initialLocalBounds);
     expect(canvas.getDestinationClipBounds(), initialDestinationBounds);
   });