Move CanvasFrontend off of SkConservativeClip

Implements clip bounds tracking for intersect and difference
directly. Also adds implementations for quickReject. Switches
the separate clip/transform stack entries to use deferred
saves so that they are only allocated when modified.

Test: existing tests still pass
Change-Id: Ic2d5b87d6f686e95bc6579750ecd63cde4a13143
diff --git a/libs/hwui/canvas/CanvasFrontend.cpp b/libs/hwui/canvas/CanvasFrontend.cpp
index dd01a5b..112ac12 100644
--- a/libs/hwui/canvas/CanvasFrontend.cpp
+++ b/libs/hwui/canvas/CanvasFrontend.cpp
@@ -30,41 +30,61 @@
     mClipStack.clear();
     mTransformStack.clear();
     mSaveStack.emplace_back();
-    mClipStack.emplace_back().setRect(mInitialBounds);
+    mClipStack.emplace_back();
     mTransformStack.emplace_back();
-    mCurrentClipIndex = 0;
-    mCurrentTransformIndex = 0;
+
+    clip().bounds = mInitialBounds;
 }
 
 bool CanvasStateHelper::internalSave(SaveEntry saveEntry) {
     mSaveStack.push_back(saveEntry);
     if (saveEntry.matrix) {
-        // We need to push before accessing transform() to ensure the reference doesn't move
-        // across vector resizes
-        mTransformStack.emplace_back() = transform();
-        mCurrentTransformIndex += 1;
+        pushEntry(&mTransformStack);
     }
     if (saveEntry.clip) {
-        // We need to push before accessing clip() to ensure the reference doesn't move
-        // across vector resizes
-        mClipStack.emplace_back() = clip();
-        mCurrentClipIndex += 1;
+        pushEntry(&mClipStack);
         return true;
     }
     return false;
 }
 
-// Assert that the cast from SkClipOp to SkRegion::Op is valid.
-// SkClipOp is a subset of SkRegion::Op and only supports difference and intersect.
-static_assert(static_cast<int>(SkClipOp::kDifference) == SkRegion::Op::kDifference_Op);
-static_assert(static_cast<int>(SkClipOp::kIntersect) == SkRegion::Op::kIntersect_Op);
+void CanvasStateHelper::ConservativeClip::apply(SkClipOp op, const SkMatrix& matrix,
+                                                const SkRect& bounds, bool aa, bool fillsBounds) {
+    this->aa |= aa;
+
+    if (op == SkClipOp::kIntersect) {
+        SkRect devBounds;
+        bool rect = matrix.mapRect(&devBounds, bounds) && fillsBounds;
+        if (!this->bounds.intersect(aa ? devBounds.roundOut() : devBounds.round())) {
+            this->bounds.setEmpty();
+        }
+        this->rect &= rect;
+    } else {
+        // Difference operations subtracts a region from the clip, so conservatively
+        // the bounds remain unchanged and the shape is unlikely to remain a rect.
+        this->rect = false;
+    }
+}
 
 void CanvasStateHelper::internalClipRect(const SkRect& rect, SkClipOp op) {
-    clip().opRect(rect, transform(), mInitialBounds, (SkRegion::Op)op, false);
+    clip().apply(op, transform(), rect, /*aa=*/false, /*fillsBounds=*/true);
 }
 
 void CanvasStateHelper::internalClipPath(const SkPath& path, SkClipOp op) {
-    clip().opPath(path, transform(), mInitialBounds, (SkRegion::Op)op, true);
+    SkRect bounds = path.getBounds();
+    if (path.isInverseFillType()) {
+        // Toggle op type if the path is inverse filled
+        op = (op == SkClipOp::kIntersect ? SkClipOp::kDifference : SkClipOp::kIntersect);
+    }
+    clip().apply(op, transform(), bounds, /*aa=*/true, /*fillsBounds=*/false);
+}
+
+CanvasStateHelper::ConservativeClip& CanvasStateHelper::clip() {
+    return writableEntry(&mClipStack);
+}
+
+SkMatrix& CanvasStateHelper::transform() {
+    return writableEntry(&mTransformStack);
 }
 
 bool CanvasStateHelper::internalRestore() {
@@ -77,45 +97,47 @@
     mSaveStack.pop_back();
     bool needsRestorePropagation = entry.layer;
     if (entry.matrix) {
-        mTransformStack.pop_back();
-        mCurrentTransformIndex -= 1;
+        popEntry(&mTransformStack);
     }
     if (entry.clip) {
-        // We need to push before accessing clip() to ensure the reference doesn't move
-        // across vector resizes
-        mClipStack.pop_back();
-        mCurrentClipIndex -= 1;
+        popEntry(&mClipStack);
         needsRestorePropagation = true;
     }
     return needsRestorePropagation;
 }
 
 SkRect CanvasStateHelper::getClipBounds() const {
-    SkIRect ibounds = clip().getBounds();
-
-    if (ibounds.isEmpty()) {
-        return SkRect::MakeEmpty();
-    }
+    SkIRect bounds = clip().bounds;
 
     SkMatrix inverse;
     // if we can't invert the CTM, we can't return local clip bounds
-    if (!transform().invert(&inverse)) {
+    if (bounds.isEmpty() || !transform().invert(&inverse)) {
         return SkRect::MakeEmpty();
     }
 
-    SkRect ret = SkRect::MakeEmpty();
-    inverse.mapRect(&ret, SkRect::Make(ibounds));
-    return ret;
+    return inverse.mapRect(SkRect::Make(bounds));
+}
+
+bool CanvasStateHelper::ConservativeClip::quickReject(const SkMatrix& matrix,
+                                                      const SkRect& bounds) const {
+    SkRect devRect = matrix.mapRect(bounds);
+    return devRect.isFinite() &&
+           SkIRect::Intersects(this->bounds, aa ? devRect.roundOut() : devRect.round());
 }
 
 bool CanvasStateHelper::quickRejectRect(float left, float top, float right, float bottom) const {
-    // TODO: Implement
-    return false;
+    return clip().quickReject(transform(), SkRect::MakeLTRB(left, top, right, bottom));
 }
 
 bool CanvasStateHelper::quickRejectPath(const SkPath& path) const {
-    // TODO: Implement
-    return false;
+    if (this->isClipEmpty()) {
+        // reject everything (prioritized above path inverse fill type).
+        return true;
+    } else {
+        // Don't reject inverse-filled paths, since even if they are "empty" of points/verbs,
+        // they fill out the entire clip.
+        return !path.isInverseFillType() && clip().quickReject(transform(), path.getBounds());
+    }
 }
 
 } // namespace android::uirenderer
diff --git a/libs/hwui/canvas/CanvasFrontend.h b/libs/hwui/canvas/CanvasFrontend.h
index f9a6101..9f22b90 100644
--- a/libs/hwui/canvas/CanvasFrontend.h
+++ b/libs/hwui/canvas/CanvasFrontend.h
@@ -21,7 +21,6 @@
 #include "CanvasOpBuffer.h"
 #include <SaveFlags.h>
 
-#include <SkRasterClip.h>
 #include <ui/FatVector.h>
 
 #include <optional>
@@ -40,6 +39,26 @@
         bool layer : 1 = false;
     };
 
+    template <typename T>
+    struct DeferredEntry {
+        T entry;
+        int deferredSaveCount = 0;
+
+        DeferredEntry() = default;
+        DeferredEntry(const T& t) : entry(t) {}
+    };
+
+    struct ConservativeClip {
+        SkIRect bounds = SkIRect::MakeEmpty();
+        bool rect = true;
+        bool aa = false;
+
+        bool quickReject(const SkMatrix& matrix, const SkRect& bounds) const;
+
+        void apply(SkClipOp op, const SkMatrix& matrix, const SkRect& bounds, bool aa,
+                   bool fillsBounds);
+    };
+
     constexpr SaveEntry saveEntryForLayer() {
         return {
             .clip = true,
@@ -72,24 +91,48 @@
     void internalClipRect(const SkRect& rect, SkClipOp op);
     void internalClipPath(const SkPath& path, SkClipOp op);
 
+    // The canvas' clip will never expand beyond these bounds since intersect
+    // and difference operations only subtract pixels.
     SkIRect mInitialBounds;
+    // Every save() gets a SaveEntry to track what needs to be restored.
     FatVector<SaveEntry, 6> mSaveStack;
-    FatVector<SkMatrix, 6> mTransformStack;
-    FatVector<SkConservativeClip, 6> mClipStack;
+    // Transform and clip entries record a deferred save count and do not
+    // make a new entry until that particular state is modified.
+    FatVector<DeferredEntry<SkMatrix>, 6> mTransformStack;
+    FatVector<DeferredEntry<ConservativeClip>, 6> mClipStack;
 
-    size_t mCurrentTransformIndex;
-    size_t mCurrentClipIndex;
+    const ConservativeClip& clip() const { return mClipStack.back().entry; }
 
-    const SkConservativeClip& clip() const {
-        return mClipStack[mCurrentClipIndex];
-    }
-
-    SkConservativeClip& clip() {
-        return mClipStack[mCurrentClipIndex];
-    }
+    ConservativeClip& clip();
 
     void resetState(int width, int height);
 
+    // Stack manipulation for transform and clip stacks
+    template <typename T, size_t N>
+    void pushEntry(FatVector<DeferredEntry<T>, N>* stack) {
+        stack->back().deferredSaveCount += 1;
+    }
+
+    template <typename T, size_t N>
+    void popEntry(FatVector<DeferredEntry<T>, N>* stack) {
+        if (!(stack->back().deferredSaveCount--)) {
+            stack->pop_back();
+        }
+    }
+
+    template <typename T, size_t N>
+    T& writableEntry(FatVector<DeferredEntry<T>, N>* stack) {
+        DeferredEntry<T>& back = stack->back();
+        if (back.deferredSaveCount == 0) {
+            return back.entry;
+        } else {
+            back.deferredSaveCount -= 1;
+            // saved in case references move when re-allocating vector storage
+            T state = back.entry;
+            return stack->emplace_back(state).entry;
+        }
+    }
+
 public:
     int saveCount() const { return mSaveStack.size(); }
 
@@ -97,13 +140,14 @@
     bool quickRejectRect(float left, float top, float right, float bottom) const;
     bool quickRejectPath(const SkPath& path) const;
 
-    const SkMatrix& transform() const {
-        return mTransformStack[mCurrentTransformIndex];
-    }
+    bool isClipAA() const { return clip().aa; }
+    bool isClipEmpty() const { return clip().bounds.isEmpty(); }
+    bool isClipRect() const { return clip().rect; }
+    bool isClipComplex() const { return !isClipEmpty() && (isClipAA() || !isClipRect()); }
 
-    SkMatrix& transform() {
-        return mTransformStack[mCurrentTransformIndex];
-    }
+    const SkMatrix& transform() const { return mTransformStack.back().entry; }
+
+    SkMatrix& transform();
 
     // For compat with existing HWUI Canvas interface
     void getMatrix(SkMatrix* outMatrix) const {