SkClipStack::isRRect succeeds if stack is  an intersection of rects.

This helps enable optimizations that rely on the entire clip reducing
to a single rectangle, such as converting a drawRect to a clear.

Bug: b/122296071

Change-Id: I1fea4565644280d57610f8585b76babb557301d8
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/209382
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/core/SkClipStack.cpp b/src/core/SkClipStack.cpp
index cc2c187..54ef867 100644
--- a/src/core/SkClipStack.cpp
+++ b/src/core/SkClipStack.cpp
@@ -960,13 +960,18 @@
 }
 
 bool SkClipStack::isRRect(const SkRect& bounds, SkRRect* rrect, bool* aa) const {
-    // We limit to 5 elements. This means the back element will be bounds checked at most 4 times if
-    // it is an rrect.
-    int cnt = fDeque.count();
-    if (!cnt || cnt > 5) {
+    const Element* back = static_cast<const Element*>(fDeque.back());
+    if (!back) {
+        // TODO: return bounds?
         return false;
     }
-    const Element* back = static_cast<const Element*>(fDeque.back());
+    // First check if the entire stack is known to be a rect by the top element.
+    if (back->fIsIntersectionOfRects && back->fFiniteBoundType == BoundsType::kNormal_BoundsType) {
+        rrect->setRect(back->fFiniteBound);
+        *aa = back->isAA();
+        return true;
+    }
+
     if (back->getDeviceSpaceType() != SkClipStack::Element::DeviceSpaceType::kRect &&
         back->getDeviceSpaceType() != SkClipStack::Element::DeviceSpaceType::kRRect) {
         return false;
@@ -982,6 +987,12 @@
         if (!backBounds.intersect(bounds, back->asDeviceSpaceRRect().rect())) {
             return false;
         }
+        // We limit to 5 elements. This means the back element will be bounds checked at most 4
+        // times if it is an rrect.
+        int cnt = fDeque.count();
+        if (cnt > 5) {
+            return false;
+        }
         if (cnt > 1) {
             SkDeque::Iter iter(fDeque, SkDeque::Iter::kBack_IterStart);
             SkAssertResult(static_cast<const Element*>(iter.prev()) == back);
diff --git a/tests/ClipStackTest.cpp b/tests/ClipStackTest.cpp
index 0c3f3b2..c95bdc7 100644
--- a/tests/ClipStackTest.cpp
+++ b/tests/ClipStackTest.cpp
@@ -1425,6 +1425,41 @@
     }
 }
 
+static void test_is_rrect_deep_rect_stack(skiatest::Reporter* reporter) {
+    static constexpr SkRect kTargetBounds = SkRect::MakeWH(1000, 500);
+    // All antialiased or all not antialiased.
+    for (bool aa : {false, true}) {
+        SkClipStack stack;
+        for (int i = 0; i <= 100; ++i) {
+            stack.save();
+            stack.clipRect(SkRect::MakeLTRB(i, 0.5, kTargetBounds.width(), kTargetBounds.height()),
+                           SkMatrix::I(), SkClipOp::kIntersect, aa);
+        }
+        SkRRect rrect;
+        bool isAA;
+        SkRRect expected = SkRRect::MakeRect(
+                SkRect::MakeLTRB(100, 0.5, kTargetBounds.width(), kTargetBounds.height()));
+        if (stack.isRRect(kTargetBounds, &rrect, &isAA)) {
+            REPORTER_ASSERT(reporter, rrect == expected);
+            REPORTER_ASSERT(reporter, aa == isAA);
+        } else {
+            ERRORF(reporter, "Expected to be an rrect.");
+        }
+    }
+    // Mixed AA and non-AA without simple containment.
+    SkClipStack stack;
+    for (int i = 0; i <= 100; ++i) {
+        bool aa = i & 0b1;
+        int j = 100 - i;
+        stack.save();
+        stack.clipRect(SkRect::MakeLTRB(i, j + 0.5, kTargetBounds.width(), kTargetBounds.height()),
+                       SkMatrix::I(), SkClipOp::kIntersect, aa);
+    }
+    SkRRect rrect;
+    bool isAA;
+    REPORTER_ASSERT(reporter, !stack.isRRect(kTargetBounds, &rrect, &isAA));
+}
+
 DEF_TEST(ClipStack, reporter) {
     SkClipStack stack;
 
@@ -1477,6 +1512,7 @@
     test_reduced_clip_stack_no_aa_crash(reporter);
     test_reduced_clip_stack_aa(reporter);
     test_tiny_query_bounds_assertion_bug(reporter);
+    test_is_rrect_deep_rect_stack(reporter);
 }
 
 //////////////////////////////////////////////////////////////////////////////