Store SkRRects in SkClipStack
authorcommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Sun, 16 Feb 2014 13:25:24 +0000 (13:25 +0000)
committercommit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>
Sun, 16 Feb 2014 13:25:24 +0000 (13:25 +0000)
BUG=skia:2181
R=robertphillips@google.com

Author: bsalomon@google.com

Review URL: https://codereview.chromium.org/163683002

git-svn-id: http://skia.googlecode.com/svn/trunk@13465 2bbb7eff-a529-9590-31e7-b0007b416f81

gm/complexclip2.cpp
include/core/SkCanvas.h
include/core/SkClipStack.h
src/core/SkCanvas.cpp
src/core/SkClipStack.cpp
src/gpu/GrClipMaskManager.cpp
src/gpu/GrReducedClip.cpp
src/pdf/SkPDFDevice.cpp
src/utils/SkCanvasStateUtils.cpp
tests/CanvasTest.cpp
tests/ClipStackTest.cpp

index 0e8e0bc..2b94ffd 100644 (file)
@@ -15,8 +15,14 @@ namespace skiagm {
 
 class ComplexClip2GM : public GM {
 public:
-    ComplexClip2GM(bool doPaths, bool antiAlias)
-    : fDoPaths(doPaths)
+    enum Clip {
+        kRect_Clip,
+        kRRect_Clip,
+        kPath_Clip
+    };
+
+    ComplexClip2GM(Clip clip, bool antiAlias)
+    : fClip(clip)
     , fAntiAlias(antiAlias) {
         this->setBGColor(SkColorSetRGB(0xDD,0xA0,0xDD));
 
@@ -39,23 +45,28 @@ public:
         fHeight = yF - yA;
 
         fRects[0].set(xB, yB, xE, yE);
-        fPaths[0].addRoundRect(fRects[0], SkIntToScalar(5), SkIntToScalar(5));
+        fRRects[0].setRectXY(fRects[0], 7, 7);
+        fPaths[0].addRoundRect(fRects[0], 5, 5);
         fRectColors[0] = SK_ColorRED;
 
         fRects[1].set(xA, yA, xD, yD);
-        fPaths[1].addRoundRect(fRects[1], SkIntToScalar(5), SkIntToScalar(5));
+        fRRects[1].setRectXY(fRects[1], 7, 7);
+        fPaths[1].addRoundRect(fRects[1], 5, 5);
         fRectColors[1] = SK_ColorGREEN;
 
         fRects[2].set(xC, yA, xF, yD);
-        fPaths[2].addRoundRect(fRects[2], SkIntToScalar(5), SkIntToScalar(5));
+        fRRects[2].setRectXY(fRects[2], 7, 7);
+        fPaths[2].addRoundRect(fRects[2], 5, 5);
         fRectColors[2] = SK_ColorBLUE;
 
         fRects[3].set(xA, yC, xD, yF);
-        fPaths[3].addRoundRect(fRects[3], SkIntToScalar(5), SkIntToScalar(5));
+        fRRects[3].setRectXY(fRects[3], 7, 7);
+        fPaths[3].addRoundRect(fRects[3], 5, 5);
         fRectColors[3] = SK_ColorYELLOW;
 
         fRects[4].set(xC, yC, xF, yF);
-        fPaths[4].addRoundRect(fRects[4], SkIntToScalar(5), SkIntToScalar(5));
+        fRRects[4].setRectXY(fRects[4], 7, 7);
+        fPaths[4].addRoundRect(fRects[4], 5, 5);
         fRectColors[4] = SK_ColorCYAN;
 
         fTotalWidth = kCols * fWidth + SK_Scalar1 * (kCols + 1) * kPadX;
@@ -87,14 +98,27 @@ protected:
     static const int kPadX = 20;
     static const int kPadY = 20;
 
+    static const char* ClipStr(Clip clip) {
+        switch (clip) {
+        case kRect_Clip:
+            return "rect";
+        case kRRect_Clip:
+            return "rrect";
+        case kPath_Clip:
+            return "path";
+        }
+        SkDEBUGFAIL("Unknown clip type.");
+        return "";
+    }
+
     virtual SkString onShortName() {
-        if (!fDoPaths && !fAntiAlias) {
+        if (kRect_Clip == fClip && !fAntiAlias) {
             return SkString("complexclip2");
         }
 
         SkString str;
         str.printf("complexclip2_%s_%s",
-                    fDoPaths ? "path" : "rect",
+                    ClipStr(fClip),
                     fAntiAlias ? "aa" : "bw");
         return str;
     }
@@ -123,22 +147,36 @@ protected:
                 // antialiasing on the clipped draw
                 for (int k = 0; k < 5; ++k) {
                     rectPaint.setColor(fRectColors[k]);
-                    if (fDoPaths) {
-                        canvas->drawPath(fPaths[k], rectPaint);
-                    } else {
-                        canvas->drawRect(fRects[k], rectPaint);
+                    switch (fClip) {
+                        case kRect_Clip:
+                            canvas->drawRect(fRects[k], rectPaint);
+                            break;
+                        case kRRect_Clip:
+                            canvas->drawRRect(fRRects[k], rectPaint);
+                            break;
+                        case kPath_Clip:
+                            canvas->drawPath(fPaths[k], rectPaint);
+                            break;
                     }
                 }
 
                 for (int k = 0; k < 5; ++k) {
-                    if (fDoPaths) {
-                        canvas->clipPath(fPaths[k],
-                                         fOps[j*kRows+i][k],
-                                         fAntiAlias);
-                    } else {
-                        canvas->clipRect(fRects[k],
-                                         fOps[j*kRows+i][k],
-                                         fAntiAlias);
+                    switch (fClip) {
+                        case kRect_Clip:
+                            canvas->clipRect(fRects[k],
+                                             fOps[j*kRows+i][k],
+                                             fAntiAlias);
+                            break;
+                        case kRRect_Clip:
+                            canvas->clipRRect(fRRects[k],
+                                              fOps[j*kRows+i][k],
+                                              fAntiAlias);
+                            break;
+                        case kPath_Clip:
+                            canvas->clipPath(fPaths[k],
+                                             fOps[j*kRows+i][k],
+                                             fAntiAlias);
+                            break;
                     }
                 }
                 canvas->drawRect(SkRect::MakeWH(fWidth, fHeight), fillPaint);
@@ -147,9 +185,10 @@ protected:
         }
     }
 private:
-    bool fDoPaths;
+    Clip fClip;
     bool fAntiAlias;
     SkRect fRects[5];
+    SkRRect fRRects[5];
     SkPath fPaths[5];
     SkColor fRectColors[5];
     SkRegion::Op fOps[kRows * kCols][5];
@@ -163,20 +202,14 @@ private:
 
 //////////////////////////////////////////////////////////////////////////////
 
-// bw rects
-static GM* MyFactory(void*) { return new ComplexClip2GM(false, false); }
-static GMRegistry reg(MyFactory);
-
-// bw paths
-static GM* MyFactory2(void*) { return new ComplexClip2GM(true, false); }
-static GMRegistry reg2(MyFactory2);
-
-// aa rects
-static GM* MyFactory3(void*) { return new ComplexClip2GM(false, true); }
-static GMRegistry reg3(MyFactory3);
+// bw
+DEF_GM( return new ComplexClip2GM(ComplexClip2GM::kRect_Clip, false); )
+DEF_GM( return new ComplexClip2GM(ComplexClip2GM::kRRect_Clip, false); )
+DEF_GM( return new ComplexClip2GM(ComplexClip2GM::kPath_Clip, false); )
 
-// aa paths
-static GM* MyFactory4(void*) { return new ComplexClip2GM(true, true); }
-static GMRegistry reg4(MyFactory4);
+// aa
+DEF_GM( return new ComplexClip2GM(ComplexClip2GM::kRect_Clip, true); )
+DEF_GM( return new ComplexClip2GM(ComplexClip2GM::kRRect_Clip, true); )
+DEF_GM( return new ComplexClip2GM(ComplexClip2GM::kPath_Clip, true); )
 
 }
index aaaeb3b..684541f 100644 (file)
@@ -1004,6 +1004,7 @@ public:
     public:
         virtual ~ClipVisitor();
         virtual void clipRect(const SkRect&, SkRegion::Op, bool antialias) = 0;
+        virtual void clipRRect(const SkRRect&, SkRegion::Op, bool antialias) = 0;
         virtual void clipPath(const SkPath&, SkRegion::Op, bool antialias) = 0;
     };
 
index 0d6cfb2..0896db4 100644 (file)
@@ -11,6 +11,7 @@
 #include "SkDeque.h"
 #include "SkPath.h"
 #include "SkRect.h"
+#include "SkRRect.h"
 #include "SkRegion.h"
 #include "SkTDArray.h"
 
@@ -41,6 +42,8 @@ public:
             kEmpty_Type,
             //!< This element combines a rect with the current clip using a set operation
             kRect_Type,
+            //!< This element combines a round-rect with the current clip using a set operation
+            kRRect_Type,
             //!< This element combines a path with the current clip using a set operation
             kPath_Type,
         };
@@ -54,46 +57,35 @@ public:
             this->initRect(0, rect, op, doAA);
         }
 
+        Element(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+            this->initRRect(0, rrect, op, doAA);
+        }
+
         Element(const SkPath& path, SkRegion::Op op, bool doAA) {
             this->initPath(0, path, op, doAA);
         }
 
-        bool operator== (const Element& element) const {
-            if (this == &element) {
-                return true;
-            }
-            if (fOp != element.fOp ||
-                fType != element.fType ||
-                fDoAA != element.fDoAA ||
-                fSaveCount != element.fSaveCount) {
-                return false;
-            }
-            switch (fType) {
-                case kPath_Type:
-                    return fPath == element.fPath;
-                case kRect_Type:
-                    return fRect == element.fRect;
-                case kEmpty_Type:
-                    return true;
-                default:
-                    SkDEBUGFAIL("Unexpected type.");
-                    return false;
-            }
-        }
+        bool operator== (const Element& element) const;
         bool operator!= (const Element& element) const { return !(*this == element); }
 
         //!< Call to get the type of the clip element.
         Type getType() const { return fType; }
 
         //!< Call if getType() is kPath to get the path.
-        const SkPath& getPath() const { return fPath; }
+        const SkPath& getPath() const { SkASSERT(kPath_Type == fType); return fPath; }
+
+        //!< Call if getType() is kRRect to get the round-rect.
+        const SkRRect& getRRect() const { SkASSERT(kRRect_Type == fType); return fRRect; }
 
         //!< Call if getType() is kRect to get the rect.
-        const SkRect& getRect() const { return fRect; }
+        const SkRect& getRect() const { SkASSERT(kRect_Type == fType); return fRect; }
 
         //!< Call if getType() is not kEmpty to get the set operation used to combine this element.
         SkRegion::Op getOp() const { return fOp; }
 
+        //!< Call to get the element as a path, regardless of its type.
+        void asPath(SkPath* path) const;
+
         /** If getType() is not kEmpty this indicates whether the clip shape should be anti-aliased
             when it is rasterized. */
         bool isAA() const { return fDoAA; }
@@ -120,6 +112,8 @@ public:
             switch (fType) {
                 case kRect_Type:
                     return fRect;
+                case kRRect_Type:
+                    return fRRect.getBounds();
                 case kPath_Type:
                     return fPath.getBounds();
                 case kEmpty_Type:
@@ -138,6 +132,8 @@ public:
             switch (fType) {
                 case kRect_Type:
                     return fRect.contains(rect);
+                case kRRect_Type:
+                    return fRRect.contains(rect);
                 case kPath_Type:
                     return fPath.conservativelyContainsRect(rect);
                 case kEmpty_Type:
@@ -160,6 +156,7 @@ public:
 
         SkPath          fPath;
         SkRect          fRect;
+        SkRRect         fRRect;
         int             fSaveCount; // save count of stack when this element was added.
         SkRegion::Op    fOp;
         Type            fType;
@@ -189,6 +186,10 @@ public:
             this->setEmpty();
         }
 
+        Element(int saveCount, const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+            this->initRRect(saveCount, rrect, op, doAA);
+        }
+
         Element(int saveCount, const SkRect& rect, SkRegion::Op op, bool doAA) {
             this->initRect(saveCount, rect, op, doAA);
         }
@@ -215,20 +216,29 @@ public:
             this->initCommon(saveCount, op, doAA);
         }
 
-        void initPath(int saveCount, const SkPath& path, SkRegion::Op op, bool doAA) {
-            fPath = path;
-            fType = kPath_Type;
+        void initRRect(int saveCount, const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+            if (rrect.isRect()) {
+                fRect = rrect.getBounds();
+                fType = kRect_Type;
+            } else {
+                fRRect = rrect;
+                fType = kRRect_Type;
+            }
             this->initCommon(saveCount, op, doAA);
         }
 
+        void initPath(int saveCount, const SkPath& path, SkRegion::Op op, bool doAA);
+
         void setEmpty() {
             fType = kEmpty_Type;
             fFiniteBound.setEmpty();
             fFiniteBoundType = kNormal_BoundsType;
             fIsIntersectionOfRects = false;
             fRect.setEmpty();
+            fRRect.setEmpty();
             fPath.reset();
             fGenID = kEmptyGenID;
+            SkDEBUGCODE(this->checkEmpty();)
         }
 
         // All Element methods below are only used within SkClipStack.cpp
@@ -305,6 +315,7 @@ public:
         this->clipDevRect(r, op, false);
     }
     void clipDevRect(const SkRect&, SkRegion::Op, bool doAA);
+    void clipDevRRect(const SkRRect&, SkRegion::Op, bool doAA);
     void clipDevPath(const SkPath&, SkRegion::Op, bool doAA);
     // An optimized version of clipDevRect(emptyRect, kIntersect, ...)
     void clipEmpty();
@@ -428,6 +439,11 @@ private:
     static int32_t     gGenID;
 
     /**
+     * Helper for clipDevPath, etc.
+     */
+    void pushElement(const Element& element);
+
+    /**
      * Restore the stack back to the specified save count.
      */
     void restoreTo(int saveCount);
index 61acf26..2cc504f 100644 (file)
@@ -1295,13 +1295,7 @@ bool SkCanvas::clipPath(const SkPath& path, SkRegion::Op op, bool doAA) {
                 continue;
             }
             SkPath operand;
-            if (type == SkClipStack::Element::kRect_Type) {
-                operand.addRect(element->getRect());
-            } else if (type == SkClipStack::Element::kPath_Type) {
-                operand = element->getPath();
-            } else {
-                SkDEBUGFAIL("Unexpected type.");
-            }
+            element->asPath(&operand);
             SkRegion::Op elementOp = element->getOp();
             if (elementOp == SkRegion::kReplace_Op) {
                 devPath = operand;
@@ -1458,6 +1452,9 @@ void SkCanvas::replayClips(ClipVisitor* visitor) const {
             case SkClipStack::Element::kPath_Type:
                 visitor->clipPath(element->getPath(), element->getOp(), element->isAA());
                 break;
+            case SkClipStack::Element::kRRect_Type:
+                visitor->clipRRect(element->getRRect(), element->getOp(), element->isAA());
+                break;
             case SkClipStack::Element::kRect_Type:
                 visitor->clipRect(element->getRect(), element->getOp(), element->isAA());
                 break;
index c66a219..17eb6f9 100644 (file)
 static const int32_t kFirstUnreservedGenID = 3;
 int32_t SkClipStack::gGenID = kFirstUnreservedGenID;
 
+bool SkClipStack::Element::operator== (const Element& element) const {
+    if (this == &element) {
+        return true;
+    }
+    if (fOp != element.fOp ||
+        fType != element.fType ||
+        fDoAA != element.fDoAA ||
+        fSaveCount != element.fSaveCount) {
+        return false;
+    }
+    switch (fType) {
+        case kPath_Type:
+            return fPath == element.fPath;
+        case kRRect_Type:
+            return fRRect == element.fRRect;
+        case kRect_Type:
+            return fRect == element.fRect;
+        case kEmpty_Type:
+            return true;
+        default:
+            SkDEBUGFAIL("Unexpected type.");
+            return false;
+    }
+}
+
 void SkClipStack::Element::invertShapeFillType() {
     switch (fType) {
         case kRect_Type:
             fPath.reset();
             fPath.addRect(fRect);
-            fPath.setFillType(SkPath::kInverseWinding_FillType);
+            fPath.setFillType(SkPath::kInverseEvenOdd_FillType);
+            fType = kPath_Type;
+            break;
+        case kRRect_Type:
+            fPath.reset();
+            fPath.addRRect(fRRect);
+            fPath.setFillType(SkPath::kInverseEvenOdd_FillType);
             fType = kPath_Type;
             break;
         case kPath_Type:
             fPath.toggleInverseFillType();
+            break;
         case kEmpty_Type:
+            // Should this set to an empty, inverse filled path?
+            break;
+    }
+}
+
+void SkClipStack::Element::initPath(int saveCount, const SkPath& path, SkRegion::Op op,
+                                    bool doAA) {
+    if (!path.isInverseFillType()) {
+        if (SkPath::kNone_PathAsRect != path.asRect()) {
+            this->initRect(saveCount, path.getBounds(), op, doAA);
+            return;
+        }
+        SkRect ovalRect;
+        if (path.isOval(&ovalRect)) {
+            SkRRect rrect;
+            rrect.setOval(ovalRect);
+            this->initRRect(saveCount, rrect, op, doAA);
+            return;
+        }
+    }
+    fPath = path;
+    fType = kPath_Type;
+    this->initCommon(saveCount, op, doAA);
+}
+
+void SkClipStack::Element::asPath(SkPath* path) const {
+    switch (fType) {
+        case kEmpty_Type:
+            path->reset();
+            break;
+        case kRect_Type:
+            path->reset();
+            path->addRect(fRect);
+            break;
+        case kRRect_Type:
+            path->reset();
+            path->addRRect(fRRect);
+            break;
+        case kPath_Type:
+            *path = fPath;
             break;
     }
 }
@@ -100,10 +172,10 @@ void SkClipStack::Element::combineBoundsDiff(FillCombo combination, const SkRect
             // is erased, so the only pixels that can remain set
             // occur w/in the intersection of the two finite bounds
             if (!fFiniteBound.intersect(prevFinite)) {
-                fFiniteBound.setEmpty();
-                fGenID = kEmptyGenID;
+                this->setEmpty();
+            } else {
+                fFiniteBoundType = kNormal_BoundsType;
             }
-            fFiniteBoundType = kNormal_BoundsType;
             break;
         case kPrev_Cur_FillCombo:
             // The most conservative result bound is that of the
@@ -205,8 +277,7 @@ void SkClipStack::Element::combineBoundsIntersection(int combination, const SkRe
             break;
         case kPrev_Cur_FillCombo:
             if (!fFiniteBound.intersect(prevFinite)) {
-                fFiniteBound.setEmpty();
-                fGenID = kEmptyGenID;
+                this->setEmpty();
             }
             break;
         default:
@@ -228,10 +299,10 @@ void SkClipStack::Element::combineBoundsRevDiff(int combination, const SkRect& p
             break;
         case kInvPrev_Cur_FillCombo:
             if (!fFiniteBound.intersect(prevFinite)) {
-                fFiniteBound.setEmpty();
-                fGenID = kEmptyGenID;
+                this->setEmpty();
+            } else {
+                fFiniteBoundType = kNormal_BoundsType;
             }
-            fFiniteBoundType = kNormal_BoundsType;
             break;
         case kPrev_InvCur_FillCombo:
             fFiniteBound.join(prevFinite);
@@ -258,27 +329,34 @@ void SkClipStack::Element::updateBoundAndGenID(const Element* prior) {
     // First, optimistically update the current Element's bound information
     // with the current clip's bound
     fIsIntersectionOfRects = false;
-    if (kRect_Type == fType) {
-        fFiniteBound = fRect;
-        fFiniteBoundType = kNormal_BoundsType;
-
-        if (SkRegion::kReplace_Op == fOp ||
-            (SkRegion::kIntersect_Op == fOp && NULL == prior) ||
-            (SkRegion::kIntersect_Op == fOp && prior->fIsIntersectionOfRects &&
-                prior->rectRectIntersectAllowed(fRect, fDoAA))) {
-            fIsIntersectionOfRects = true;
-        }
-
-    } else {
-        SkASSERT(kPath_Type == fType);
-
-        fFiniteBound = fPath.getBounds();
+    switch (fType) {
+        case kRect_Type:
+            fFiniteBound = fRect;
+            fFiniteBoundType = kNormal_BoundsType;
 
-        if (fPath.isInverseFillType()) {
-            fFiniteBoundType = kInsideOut_BoundsType;
-        } else {
+            if (SkRegion::kReplace_Op == fOp ||
+                (SkRegion::kIntersect_Op == fOp && NULL == prior) ||
+                (SkRegion::kIntersect_Op == fOp && prior->fIsIntersectionOfRects &&
+                    prior->rectRectIntersectAllowed(fRect, fDoAA))) {
+                fIsIntersectionOfRects = true;
+            }
+            break;
+        case kRRect_Type:
+            fFiniteBound = fRRect.getBounds();
             fFiniteBoundType = kNormal_BoundsType;
-        }
+            break;
+        case kPath_Type:
+            fFiniteBound = fPath.getBounds();
+
+            if (fPath.isInverseFillType()) {
+                fFiniteBoundType = kInsideOut_BoundsType;
+            } else {
+                fFiniteBoundType = kNormal_BoundsType;
+            }
+            break;
+        case kEmpty_Type:
+            SkDEBUGFAIL("We shouldn't get here with an empty element.");
+            break;
     }
 
     if (!fDoAA) {
@@ -344,7 +422,7 @@ void SkClipStack::Element::updateBoundAndGenID(const Element* prior) {
             // so nothing to do
             break;
         default:
-            SkDebugf("SkRegion::Op error/n");
+            SkDebugf("SkRegion::Op error\n");
             SkASSERT(0);
             break;
     }
@@ -528,97 +606,69 @@ bool SkClipStack::quickContains(const SkRect& rect) const {
     return true;
 }
 
-void SkClipStack::clipDevRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
-
+void SkClipStack::pushElement(const Element& element) {
     // Use reverse iterator instead of back because Rect path may need previous
     SkDeque::Iter iter(fDeque, SkDeque::Iter::kBack_IterStart);
-    Element* element = (Element*) iter.prev();
+    Element* prior = (Element*) iter.prev();
 
-    if (NULL != element) {
-        if (element->canBeIntersectedInPlace(fSaveCount, op)) {
-            switch (element->fType) {
+    if (NULL != prior) {
+        if (prior->canBeIntersectedInPlace(fSaveCount, element.getOp())) {
+            switch (prior->fType) {
                 case Element::kEmpty_Type:
-                    element->checkEmpty();
+                    SkDEBUGCODE(prior->checkEmpty();)
                     return;
                 case Element::kRect_Type:
-                    if (element->rectRectIntersectAllowed(rect, doAA)) {
-                        if (!element->fRect.intersect(rect)) {
-                            element->setEmpty();
+                    if (Element::kRect_Type == element.getType()) {
+                        if (prior->rectRectIntersectAllowed(element.getRect(), element.isAA())) {
+                            if (!prior->fRect.intersect(element.getRect())) {
+                                prior->setEmpty();
+                                return;
+                            }
+
+                            prior->fDoAA = element.isAA();
+                            Element* priorPrior = (Element*) iter.prev();
+                            prior->updateBoundAndGenID(priorPrior);
                             return;
                         }
-
-                        element->fDoAA = doAA;
-                        Element* prev = (Element*) iter.prev();
-                        element->updateBoundAndGenID(prev);
-                        return;
+                        break;
                     }
-                    break;
-                case Element::kPath_Type:
-                    if (!SkRect::Intersects(element->fPath.getBounds(), rect)) {
-                        element->setEmpty();
+                    // fallthrough
+                default:
+                    if (!SkRect::Intersects(prior->getBounds(), element.getBounds())) {
+                        prior->setEmpty();
                         return;
                     }
                     break;
             }
-        } else if (SkRegion::kReplace_Op == op) {
+        } else if (SkRegion::kReplace_Op == element.getOp()) {
             this->restoreTo(fSaveCount - 1);
-            element = (Element*) fDeque.back();
+            prior = (Element*) fDeque.back();
         }
     }
-    new (fDeque.push_back()) Element(fSaveCount, rect, op, doAA);
-    ((Element*) fDeque.back())->updateBoundAndGenID(element);
+    Element* newElement = SkNEW_PLACEMENT_ARGS(fDeque.push_back(), Element, (element));
+    newElement->updateBoundAndGenID(prior);
 }
 
-void SkClipStack::clipDevPath(const SkPath& path, SkRegion::Op op, bool doAA) {
-    SkRect alt;
-    if (path.isRect(&alt) && !path.isInverseFillType()) {
-        return this->clipDevRect(alt, op, doAA);
-    }
+void SkClipStack::clipDevRRect(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
+    Element element(fSaveCount, rrect, op, doAA);
+    this->pushElement(element);
+}
 
-    Element* element = (Element*)fDeque.back();
-    if (NULL != element) {
-        if (element->canBeIntersectedInPlace(fSaveCount, op)) {
-            const SkRect& pathBounds = path.getBounds();
-            switch (element->fType) {
-                case Element::kEmpty_Type:
-                    element->checkEmpty();
-                    return;
-                case Element::kRect_Type:
-                    if (!SkRect::Intersects(element->fRect, pathBounds)) {
-                        element->setEmpty();
-                        return;
-                    }
-                    break;
-                case Element::kPath_Type:
-                    if (!SkRect::Intersects(element->fPath.getBounds(), pathBounds)) {
-                        element->setEmpty();
-                        return;
-                    }
-                    break;
-            }
-        } else if (SkRegion::kReplace_Op == op) {
-            this->restoreTo(fSaveCount - 1);
-            element = (Element*) fDeque.back();
-        }
-    }
-    new (fDeque.push_back()) Element(fSaveCount, path, op, doAA);
-    ((Element*) fDeque.back())->updateBoundAndGenID(element);
+void SkClipStack::clipDevRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
+    Element element(fSaveCount, rect, op, doAA);
+    this->pushElement(element);
 }
 
-void SkClipStack::clipEmpty() {
+void SkClipStack::clipDevPath(const SkPath& path, SkRegion::Op op, bool doAA) {
+    Element element(fSaveCount, path, op, doAA);
+    this->pushElement(element);
+}
 
+void SkClipStack::clipEmpty() {
     Element* element = (Element*) fDeque.back();
 
     if (element && element->canBeIntersectedInPlace(fSaveCount, SkRegion::kIntersect_Op)) {
-        switch (element->fType) {
-            case Element::kEmpty_Type:
-                element->checkEmpty();
-                return;
-            case Element::kRect_Type:
-            case Element::kPath_Type:
-                element->setEmpty();
-                return;
-        }
+        element->setEmpty();
     }
     new (fDeque.push_back()) Element(fSaveCount);
 
index 83b3b01..8a7aac2 100644 (file)
@@ -94,13 +94,13 @@ bool GrClipMaskManager::useSWOnlyPath(const ElementList& elements) {
     for (ElementList::Iter iter(elements.headIter()); iter.get(); iter.next()) {
         const Element* element = iter.get();
         // rects can always be drawn directly w/o using the software path
-        // so only paths need to be checked
-        if (Element::kPath_Type == element->getType() &&
-            path_needs_SW_renderer(this->getContext(), fGpu,
-                                   element->getPath(),
-                                   stroke,
-                                   element->isAA())) {
-            return true;
+        // Skip rrects once we're drawing them directly.
+        if (Element::kRect_Type != element->getType()) {
+            SkPath path;
+            element->asPath(&path);
+            if (path_needs_SW_renderer(this->getContext(), fGpu, path, stroke, element->isAA())) {
+                return true;
+            }
         }
     }
     return false;
@@ -169,10 +169,11 @@ bool GrClipMaskManager::setupClipping(const GrClipData* clipDataIn,
                 return true;
             }
         }
+        Element::Type type = elements.tail()->getType();
+        bool isAA = GR_AA_CLIP && elements.tail()->isAA();
         SkAutoTUnref<GrEffectRef> effect;
-        if (SkClipStack::Element::kPath_Type == elements.tail()->getType()) {
+        if (SkClipStack::Element::kPath_Type == type) {
             const SkPath& path = elements.tail()->getPath();
-            bool isAA = GR_AA_CLIP && elements.tail()->isAA();
             if (rt->isMultisampled()) {
                 // A coverage effect for AA clipping won't play nicely with MSAA.
                 if (!isAA) {
@@ -188,13 +189,12 @@ bool GrClipMaskManager::setupClipping(const GrClipData* clipDataIn,
                                                            GrConvexPolyEffect::kFillNoAA_EdgeType;
                 effect.reset(GrConvexPolyEffect::Create(type, path, &offset));
             }
-        } else if (GR_AA_CLIP && elements.tail()->isAA() && !rt->isMultisampled()) {
+        } else if (isAA && SkClipStack::Element::kRect_Type == type && !rt->isMultisampled()) {
             // We only handle AA/non-MSAA rects here. Coverage effect AA isn't MSAA friendly and
             // non-AA rect clips are handled by the scissor.
-            SkASSERT(SkClipStack::Element::kRect_Type == elements.tail()->getType());
             SkRect rect = elements.tail()->getRect();
             SkVector offset = { SkIntToScalar(-clipDataIn->fOrigin.fX),
-                SkIntToScalar(-clipDataIn->fOrigin.fY) };
+                                SkIntToScalar(-clipDataIn->fOrigin.fY) };
             rect.offset(offset);
             effect.reset(GrConvexPolyEffect::CreateForAAFillRect(rect));
             // This should never fail.
@@ -332,7 +332,11 @@ bool GrClipMaskManager::drawElement(GrTexture* target,
 
     drawState->setRenderTarget(target->asRenderTarget());
 
+    // TODO: Draw rrects directly here.
     switch (element->getType()) {
+        case Element::kEmpty_Type:
+            SkDEBUGFAIL("Should never get here with an empty element.");
+            break;
         case Element::kRect_Type:
             // TODO: Do rects directly to the accumulator using a aa-rect GrEffect that covers the
             // entire mask bounds and writes 0 outside the rect.
@@ -347,28 +351,25 @@ bool GrClipMaskManager::drawElement(GrTexture* target,
                 fGpu->drawSimpleRect(element->getRect(), NULL);
             }
             return true;
-        case Element::kPath_Type: {
-            SkTCopyOnFirstWrite<SkPath> path(element->getPath());
-            if (path->isInverseFillType()) {
-                path.writable()->toggleInverseFillType();
+        default: {
+            SkPath path;
+            element->asPath(&path);
+            if (path.isInverseFillType()) {
+                path.toggleInverseFillType();
             }
             SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
             if (NULL == pr) {
                 GrPathRendererChain::DrawType type;
                 type = element->isAA() ? GrPathRendererChain::kColorAntiAlias_DrawType :
                                          GrPathRendererChain::kColor_DrawType;
-                pr = this->getContext()->getPathRenderer(*path, stroke, fGpu, false, type);
+                pr = this->getContext()->getPathRenderer(path, stroke, fGpu, false, type);
             }
             if (NULL == pr) {
                 return false;
             }
-            pr->drawPath(element->getPath(), stroke, fGpu, element->isAA());
+            pr->drawPath(path, stroke, fGpu, element->isAA());
             break;
         }
-        default:
-            // something is wrong if we're trying to draw an empty element.
-            GrCrash("Unexpected element type");
-            return false;
     }
     return true;
 }
@@ -379,25 +380,22 @@ bool GrClipMaskManager::canStencilAndDrawElement(GrTexture* target,
     GrDrawState* drawState = fGpu->drawState();
     drawState->setRenderTarget(target->asRenderTarget());
 
-    switch (element->getType()) {
-        case Element::kRect_Type:
-            return true;
-        case Element::kPath_Type: {
-            SkTCopyOnFirstWrite<SkPath> path(element->getPath());
-            if (path->isInverseFillType()) {
-                path.writable()->toggleInverseFillType();
-            }
-            SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
-            GrPathRendererChain::DrawType type = element->isAA() ?
-                GrPathRendererChain::kStencilAndColorAntiAlias_DrawType :
-                GrPathRendererChain::kStencilAndColor_DrawType;
-            *pr = this->getContext()->getPathRenderer(*path, stroke, fGpu, false, type);
-            return NULL != *pr;
+    if (Element::kRect_Type == element->getType()) {
+        return true;
+    } else {
+        // We shouldn't get here with an empty clip element.
+        SkASSERT(Element::kEmpty_Type != element->getType());
+        SkPath path;
+        element->asPath(&path);
+        if (path.isInverseFillType()) {
+            path.toggleInverseFillType();
         }
-        default:
-            // something is wrong if we're trying to draw an empty element.
-            GrCrash("Unexpected element type");
-            return false;
+        SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
+        GrPathRendererChain::DrawType type = element->isAA() ?
+            GrPathRendererChain::kStencilAndColorAntiAlias_DrawType :
+            GrPathRendererChain::kStencilAndColor_DrawType;
+        *pr = this->getContext()->getPathRenderer(path, stroke, fGpu, false, type);
+        return NULL != *pr;
     }
 }
 
@@ -700,18 +698,17 @@ bool GrClipMaskManager::createStencilClipMask(int32_t elementsGenID,
             SkRegion::Op op = element->getOp();
 
             GrPathRenderer* pr = NULL;
-            SkTCopyOnFirstWrite<SkPath> clipPath;
+            SkPath clipPath;
             if (Element::kRect_Type == element->getType()) {
                 stencilSupport = GrPathRenderer::kNoRestriction_StencilSupport;
                 fillInverted = false;
             } else {
-                SkASSERT(Element::kPath_Type == element->getType());
-                clipPath.init(element->getPath());
-                fillInverted = clipPath->isInverseFillType();
+                element->asPath(&clipPath);
+                fillInverted = clipPath.isInverseFillType();
                 if (fillInverted) {
-                    clipPath.writable()->toggleInverseFillType();
+                    clipPath.toggleInverseFillType();
                 }
-                pr = this->getContext()->getPathRenderer(*clipPath,
+                pr = this->getContext()->getPathRenderer(clipPath,
                                                          stroke,
                                                          fGpu,
                                                          false,
@@ -752,13 +749,12 @@ bool GrClipMaskManager::createStencilClipMask(int32_t elementsGenID,
                     *drawState->stencil() = gDrawToStencil;
                     fGpu->drawSimpleRect(element->getRect(), NULL);
                 } else {
-                    SkASSERT(Element::kPath_Type == element->getType());
-                    if (!clipPath->isEmpty()) {
+                    if (!clipPath.isEmpty()) {
                         if (canRenderDirectToStencil) {
                             *drawState->stencil() = gDrawToStencil;
-                            pr->drawPath(*clipPath, stroke, fGpu, false);
+                            pr->drawPath(clipPath, stroke, fGpu, false);
                         } else {
-                            pr->stencilPath(*clipPath, stroke, fGpu);
+                            pr->stencilPath(clipPath, stroke, fGpu);
                         }
                     }
                 }
@@ -774,9 +770,8 @@ bool GrClipMaskManager::createStencilClipMask(int32_t elementsGenID,
                         SET_RANDOM_COLOR
                         fGpu->drawSimpleRect(element->getRect(), NULL);
                     } else {
-                        SkASSERT(Element::kPath_Type == element->getType());
                         SET_RANDOM_COLOR
-                        pr->drawPath(*clipPath, stroke, fGpu, false);
+                        pr->drawPath(clipPath, stroke, fGpu, false);
                     }
                 } else {
                     SET_RANDOM_COLOR
index 8480e04..6ad9cc6 100644 (file)
@@ -72,6 +72,8 @@ void ReduceClipStack(const SkClipStack& stack,
                *requiresAA = false;
             }
         } else if (isectRect.intersect(stackBounds, scalarQueryBounds)) {
+            // If the caller asked for tighter integer bounds we may be able to
+            // return kAllIn and give the bounds with no elements
             if (NULL != tighterBounds) {
                 isectRect.roundOut(tighterBounds);
                 SkRect scalarTighterBounds = SkRect::Make(*tighterBounds);
@@ -83,14 +85,14 @@ void ReduceClipStack(const SkClipStack& stack,
                     *initialState = kAllIn_InitialState;
                     return;
                 }
-                *initialState = kAllOut_InitialState;
-                // iior should only be true if aa/non-aa status matches among all elements.
-                SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
-                bool doAA = iter.prev()->isAA();
-                SkNEW_INSERT_AT_LLIST_HEAD(result, Element, (isectRect, SkRegion::kReplace_Op, doAA));
-                if (NULL != requiresAA) {
-                    *requiresAA = doAA;
-                }
+            }
+            *initialState = kAllOut_InitialState;
+            // iior should only be true if aa/non-aa status matches among all elements.
+            SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
+            bool doAA = iter.prev()->isAA();
+            SkNEW_INSERT_AT_LLIST_HEAD(result, Element, (isectRect, SkRegion::kReplace_Op, doAA));
+            if (NULL != requiresAA) {
+                *requiresAA = doAA;
             }
         } else {
             *initialState = kAllOut_InitialState;
index 9487dfd..ce036d1 100644 (file)
@@ -404,10 +404,8 @@ static bool get_clip_stack_path(const SkMatrix& transform,
             outClipPath->reset();
             outClipPath->setFillType(SkPath::kInverseWinding_FillType);
             continue;
-        } else if (SkClipStack::Element::kRect_Type == clipEntry->getType()) {
-            entryPath.addRect(clipEntry->getRect());
-        } else if (SkClipStack::Element::kPath_Type == clipEntry->getType()) {
-            entryPath = clipEntry->getPath();
+        } else {
+            clipEntry->asPath(&entryPath);
         }
         entryPath.transform(transform);
 
index eb92c37..77b0e20 100644 (file)
@@ -130,6 +130,10 @@ public:
         fFailed |= antialias;
     }
 
+    virtual void clipRRect(const SkRRect& rrect, SkRegion::Op op, bool antialias) SK_OVERRIDE {
+        fFailed |= antialias;
+    }
+
     virtual void clipPath(const SkPath&, SkRegion::Op, bool antialias) SK_OVERRIDE {
         fFailed |= antialias;
     }
index b3b1a37..5f3d79c 100644 (file)
@@ -69,10 +69,13 @@ class Canvas2CanvasClipVisitor : public SkCanvas::ClipVisitor {
 public:
     Canvas2CanvasClipVisitor(SkCanvas* target) : fTarget(target) {}
 
-    virtual void clipRect(const SkRect& r, SkRegion::Op op, bool aa) {
+    virtual void clipRect(const SkRect& r, SkRegion::Op op, bool aa) SK_OVERRIDE {
         fTarget->clipRect(r, op, aa);
     }
-    virtual void clipPath(const SkPath& p, SkRegion::Op op, bool aa) {
+    virtual void clipRRect(const SkRRect& r, SkRegion::Op op, bool aa) SK_OVERRIDE {
+        fTarget->clipRRect(r, op, aa);
+    }
+    virtual void clipPath(const SkPath& p, SkRegion::Op op, bool aa) SK_OVERRIDE {
         fTarget->clipPath(p, op, aa);
     }
 
index 1150431..0620df9 100644 (file)
@@ -58,7 +58,6 @@ static void test_assign_and_comparison(skiatest::Reporter* reporter) {
     // Test that an equal, but not copied version is equal.
     s.save();
     REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
-
     r = SkRect::MakeLTRB(14, 15, 16, 17);
     s.clipDevRect(r, SkRegion::kUnion_Op, doAA);
     REPORTER_ASSERT(reporter, s == copy);
@@ -68,23 +67,17 @@ static void test_assign_and_comparison(skiatest::Reporter* reporter) {
     REPORTER_ASSERT(reporter, 2 == s.getSaveCount());
     s.save();
     REPORTER_ASSERT(reporter, 3 == s.getSaveCount());
-
     r = SkRect::MakeLTRB(14, 15, 16, 17);
     s.clipDevRect(r, SkRegion::kIntersect_Op, doAA);
     REPORTER_ASSERT(reporter, s != copy);
 
-    // Test that different state (clip type) triggers not equal.
-    // NO LONGER VALID: if a path contains only a rect, we turn
-    // it into a bare rect for performance reasons (working
-    // around Chromium/JavaScript bad pattern).
-/*
+    // Test that version constructed with rect-path rather than a rect is still considered equal.
     s.restore();
     s.save();
     SkPath rp;
     rp.addRect(r);
     s.clipDevPath(rp, SkRegion::kUnion_Op, doAA);
-    REPORTER_ASSERT(reporter, s != copy);
-*/
+    REPORTER_ASSERT(reporter, s == copy);
 
     // Test that different rects triggers not equal.
     s.restore();
@@ -192,8 +185,7 @@ static void test_iterators(skiatest::Reporter* reporter) {
 }
 
 // Exercise the SkClipStack's getConservativeBounds computation
-static void test_bounds(skiatest::Reporter* reporter, bool useRects) {
-
+static void test_bounds(skiatest::Reporter* reporter, SkClipStack::Element::Type primType) {
     static const int gNumCases = 20;
     static const SkRect gAnswerRectsBW[gNumCases] = {
         // A op B
@@ -238,17 +230,21 @@ static void test_bounds(skiatest::Reporter* reporter, bool useRects) {
     rectA.iset(10, 10, 50, 50);
     rectB.iset(40, 40, 80, 80);
 
-    SkPath clipA, clipB;
+    SkRRect rrectA, rrectB;
+    rrectA.setOval(rectA);
+    rrectB.setRectXY(rectB, SkIntToScalar(1), SkIntToScalar(2));
 
-    clipA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5));
-    clipB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5));
+    SkPath pathA, pathB;
+
+    pathA.addRoundRect(rectA, SkIntToScalar(5), SkIntToScalar(5));
+    pathB.addRoundRect(rectB, SkIntToScalar(5), SkIntToScalar(5));
 
     SkClipStack stack;
     SkRect devClipBound;
     bool isIntersectionOfRects = false;
 
     int testCase = 0;
-    int numBitTests = useRects ? 1 : 4;
+    int numBitTests = SkClipStack::Element::kPath_Type == primType ? 4 : 1;
     for (int invBits = 0; invBits < numBitTests; ++invBits) {
         for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); ++op) {
 
@@ -256,17 +252,27 @@ static void test_bounds(skiatest::Reporter* reporter, bool useRects) {
             bool doInvA = SkToBool(invBits & 1);
             bool doInvB = SkToBool(invBits & 2);
 
-            clipA.setFillType(doInvA ? SkPath::kInverseEvenOdd_FillType :
+            pathA.setFillType(doInvA ? SkPath::kInverseEvenOdd_FillType :
                                        SkPath::kEvenOdd_FillType);
-            clipB.setFillType(doInvB ? SkPath::kInverseEvenOdd_FillType :
+            pathB.setFillType(doInvB ? SkPath::kInverseEvenOdd_FillType :
                                        SkPath::kEvenOdd_FillType);
 
-            if (useRects) {
-                stack.clipDevRect(rectA, SkRegion::kIntersect_Op, false);
-                stack.clipDevRect(rectB, gOps[op], false);
-            } else {
-                stack.clipDevPath(clipA, SkRegion::kIntersect_Op, false);
-                stack.clipDevPath(clipB, gOps[op], false);
+            switch (primType) {
+                case SkClipStack::Element::kEmpty_Type:
+                    SkDEBUGFAIL("Don't call this with kEmpty.");
+                    break;
+                case SkClipStack::Element::kRect_Type:
+                    stack.clipDevRect(rectA, SkRegion::kIntersect_Op, false);
+                    stack.clipDevRect(rectB, gOps[op], false);
+                    break;
+                case SkClipStack::Element::kRRect_Type:
+                    stack.clipDevRRect(rrectA, SkRegion::kIntersect_Op, false);
+                    stack.clipDevRRect(rrectB, gOps[op], false);
+                    break;
+                case SkClipStack::Element::kPath_Type:
+                    stack.clipDevPath(pathA, SkRegion::kIntersect_Op, false);
+                    stack.clipDevPath(pathB, gOps[op], false);
+                    break;
             }
 
             REPORTER_ASSERT(reporter, !stack.isWideOpen());
@@ -275,7 +281,7 @@ static void test_bounds(skiatest::Reporter* reporter, bool useRects) {
             stack.getConservativeBounds(0, 0, 100, 100, &devClipBound,
                                         &isIntersectionOfRects);
 
-            if (useRects) {
+            if (SkClipStack::Element::kRect_Type == primType) {
                 REPORTER_ASSERT(reporter, isIntersectionOfRects ==
                         (gOps[op] == SkRegion::kIntersect_Op));
             } else {
@@ -801,14 +807,18 @@ typedef void (*AddElementFunc) (const SkRect& rect,
                                 SkClipStack* stack);
 
 static void add_round_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
-    SkPath path;
     SkScalar rx = rect.width() / 10;
     SkScalar ry = rect.height() / 20;
-    path.addRoundRect(rect, rx, ry);
     if (invert) {
+        SkPath path;
+        path.addRoundRect(rect, rx, ry);
         path.setFillType(SkPath::kInverseWinding_FillType);
+        stack->clipDevPath(path, op, false);
+    } else {
+        SkRRect rrect;
+        rrect.setRectXY(rect, rx, ry);
+        stack->clipDevRRect(rrect, op, false);
     }
-    stack->clipDevPath(path, op, false);
 };
 
 static void add_rect(const SkRect& rect, bool invert, SkRegion::Op op, SkClipStack* stack) {
@@ -836,6 +846,9 @@ static void add_elem_to_stack(const SkClipStack::Element& element, SkClipStack*
         case SkClipStack::Element::kRect_Type:
             stack->clipDevRect(element.getRect(), element.getOp(), element.isAA());
             break;
+        case SkClipStack::Element::kRRect_Type:
+            stack->clipDevRRect(element.getRRect(), element.getOp(), element.isAA());
+            break;
         case SkClipStack::Element::kPath_Type:
             stack->clipDevPath(element.getPath(), element.getOp(), element.isAA());
             break;
@@ -851,21 +864,16 @@ static void add_elem_to_region(const SkClipStack::Element& element,
                                SkRegion* region) {
     SkRegion elemRegion;
     SkRegion boundsRgn(bounds);
+    SkPath path;
 
     switch (element.getType()) {
-        case SkClipStack::Element::kRect_Type: {
-            SkPath path;
-            path.addRect(element.getRect());
-            elemRegion.setPath(path, boundsRgn);
+        case SkClipStack::Element::kEmpty_Type:
+            elemRegion.setEmpty();
             break;
-        }
-        case SkClipStack::Element::kPath_Type:
-            elemRegion.setPath(element.getPath(), boundsRgn);
+        default:
+            element.asPath(&path);
+            elemRegion.setPath(path, boundsRgn);
             break;
-        case SkClipStack::Element::kEmpty_Type:
-            //
-            region->setEmpty();
-            return;
     }
     region->op(elemRegion, element.getOp());
 }
@@ -938,6 +946,7 @@ static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
             SkRect rect = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight);
 
             bool invert = r.nextBiasedBool(kFractionInverted);
+
             kElementFuncs[r.nextULessThan(SK_ARRAY_COUNT(kElementFuncs))](rect, invert, op, &stack);
             if (doSave) {
                 stack.save();
@@ -996,8 +1005,9 @@ static void test_reduced_clip_stack(skiatest::Reporter* reporter) {
         while ((element = iter.next())) {
             add_elem_to_region(*element, inflatedIBounds, &reducedRegion);
         }
-
-        REPORTER_ASSERT(reporter, region == reducedRegion);
+        SkString testCase;
+        testCase.printf("Iteration %d", i);
+        REPORTER_ASSERT_MESSAGE(reporter, region == reducedRegion, testCase.c_str());
     }
 }
 
@@ -1194,8 +1204,9 @@ DEF_TEST(ClipStack, reporter) {
 
     test_assign_and_comparison(reporter);
     test_iterators(reporter);
-    test_bounds(reporter, true);        // once with rects
-    test_bounds(reporter, false);       // once with paths
+    test_bounds(reporter, SkClipStack::Element::kRect_Type);
+    test_bounds(reporter, SkClipStack::Element::kRRect_Type);
+    test_bounds(reporter, SkClipStack::Element::kPath_Type);
     test_isWideOpen(reporter);
     test_rect_merging(reporter);
     test_rect_replace(reporter);