Make GrShape track the winding direction and starting point for rrect types.
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2042813002

Review-Url: https://codereview.chromium.org/2042813002
diff --git a/include/core/SkRRect.h b/include/core/SkRRect.h
index 5564ce0..d025412 100644
--- a/include/core/SkRRect.h
+++ b/include/core/SkRRect.h
@@ -141,12 +141,18 @@
         SkDEBUGCODE(this->validate();)
     }
 
+    static SkRRect MakeEmpty() {
+        SkRRect rr;
+        rr.setEmpty();
+        return rr;
+    }
+
     static SkRRect MakeRect(const SkRect& r) {
         SkRRect rr;
         rr.setRect(r);
         return rr;
     }
-    
+
     static SkRRect MakeOval(const SkRect& oval) {
         SkRRect rr;
         rr.setOval(oval);
diff --git a/src/gpu/GrShape.cpp b/src/gpu/GrShape.cpp
index e0ddc55..9fcc6ee 100644
--- a/src/gpu/GrShape.cpp
+++ b/src/gpu/GrShape.cpp
@@ -22,6 +22,8 @@
                 fPath.reset();
             }
             fRRect = that.fRRect;
+            fRRectDir = that.fRRectDir;
+            fRRectStart = that.fRRectStart;
             break;
         case Type::kPath:
             if (wasPath) {
@@ -69,7 +71,8 @@
         case Type::kRRect:
             SkASSERT(!fInheritedKey.count());
             SkASSERT(0 == SkRRect::kSizeInMemory % sizeof(uint32_t));
-            return SkRRect::kSizeInMemory / sizeof(uint32_t);
+            // + 1 for the direction + start index.
+            return SkRRect::kSizeInMemory / sizeof(uint32_t) + 1;
         case Type::kPath:
             if (fPath.get()->isVolatile()) {
                 return -1;
@@ -95,6 +98,9 @@
             case Type::kRRect:
                 fRRect.writeToMemory(key);
                 key += SkRRect::kSizeInMemory / sizeof(uint32_t);
+                *key = (fRRectDir == SkPath::kCCW_Direction) ? (1 << 31) : 0;
+                *key++ |= fRRectStart;
+                SkASSERT(fRRectStart < 8);
                 break;
             case Type::kPath:
                 SkASSERT(!fPath.get()->isVolatile());
@@ -217,15 +223,17 @@
                 // the simpler shape so that applying both path effect and the strokerec all at
                 // once produces the same key.
                 SkRRect rrect;
-                Type parentType = AttemptToReduceFromPathImpl(*fPath.get(), &rrect, nullptr,
-                                                              strokeRec);
+                SkPath::Direction dir;
+                unsigned start;
+                Type parentType = AttemptToReduceFromPathImpl(*fPath.get(), &rrect, &dir, &start,
+                                                              nullptr, strokeRec);
                 switch (parentType) {
                     case Type::kEmpty:
                         tmpParent.init();
                         parentForKey = tmpParent.get();
                         break;
                     case Type::kRRect:
-                        tmpParent.init(rrect, GrStyle(strokeRec, nullptr));
+                        tmpParent.init(rrect, dir, start, GrStyle(strokeRec, nullptr));
                         parentForKey = tmpParent.get();
                     case Type::kPath:
                         break;
@@ -255,3 +263,61 @@
     this->attemptToReduceFromPath();
     this->setInheritedKey(*parentForKey, apply, scale);
 }
+
+GrShape::Type GrShape::AttemptToReduceFromPathImpl(const SkPath& path, SkRRect* rrect,
+                                                   SkPath::Direction* rrectDir,
+                                                   unsigned* rrectStart,
+                                                   const SkPathEffect* pe,
+                                                   const SkStrokeRec& strokeRec) {
+    if (path.isEmpty()) {
+        return Type::kEmpty;
+    }
+    if (path.isRRect(rrect, rrectDir, rrectStart)) {
+        // Currently SkPath does not acknowledge that empty, rect, or oval subtypes as rrects.
+        SkASSERT(!rrect->isEmpty());
+        SkASSERT(rrect->getType() != SkRRect::kRect_Type);
+        SkASSERT(rrect->getType() != SkRRect::kOval_Type);
+        if (!pe) {
+            *rrectStart = DefaultRRectDirAndStartIndex(*rrect, false, rrectDir);
+        }
+        return Type::kRRect;
+    }
+    SkRect rect;
+    if (path.isOval(&rect, rrectDir, rrectStart)) {
+        rrect->setOval(rect);
+        if (!pe) {
+            *rrectDir = kDefaultRRectDir;
+            *rrectStart = kDefaultRRectStart;
+        } else {
+            // convert from oval indexing to rrect indexiing.
+            *rrectStart *= 2;
+        }
+        return Type::kRRect;
+    }
+    // When there is a path effect we restrict rect detection to the narrower API that
+    // gives us the starting position. Otherwise, we will retry with the more aggressive isRect().
+    if (SkPathPriv::IsSimpleClosedRect(path, &rect, rrectDir, rrectStart)) {
+        if (!pe) {
+            *rrectDir = kDefaultRRectDir;
+            *rrectStart = kDefaultRRectStart;
+        } else {
+            // convert from rect indexing to rrect indexiing.
+            *rrectStart *= 2;
+        }
+        rrect->setRect(rect);
+        return Type::kRRect;
+    }
+    if (!pe) {
+        bool closed;
+        if (path.isRect(&rect, &closed, nullptr)) {
+            if (closed || strokeRec.isFillStyle()) {
+                rrect->setRect(rect);
+                // Since there is no path effect the dir and start index is immaterial.
+                *rrectDir = kDefaultRRectDir;
+                *rrectStart = kDefaultRRectStart;
+                return Type::kRRect;
+            }
+        }
+    }
+    return Type::kPath;
+}
diff --git a/src/gpu/GrShape.h b/src/gpu/GrShape.h
index 12093f4..30a1b83 100644
--- a/src/gpu/GrShape.h
+++ b/src/gpu/GrShape.h
@@ -10,6 +10,7 @@
 
 #include "GrStyle.h"
 #include "SkPath.h"
+#include "SkPathPriv.h"
 #include "SkRRect.h"
 #include "SkTemplates.h"
 #include "SkTLazy.h"
@@ -27,29 +28,31 @@
  * to two shapes that reflect the same underlying geometry the computed keys of the stylized shapes
  * will be the same.
  *
- * Currently this can only be constructed from a rrect, though it can become a path by applying
- * style to the geometry. The idea is to expand this to cover most or all of the geometries that
- * have SkCanvas::draw APIs.
+ * Currently this can only be constructed from a path, rect, or rrect though it can become a path
+ * applying style to the geometry. The idea is to expand this to cover most or all of the geometries
+ * that have SkCanvas::draw APIs.
  */
 class GrShape {
 public:
     GrShape() : fType(Type::kEmpty) {}
 
     explicit GrShape(const SkPath& path)
-            : fType(Type::kPath)
-            , fPath(&path) {
+        : fType(Type::kPath)
+        , fPath(&path) {
         this->attemptToReduceFromPath();
     }
 
     explicit GrShape(const SkRRect& rrect)
         : fType(Type::kRRect)
         , fRRect(rrect) {
+        fRRectStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectDir);
         this->attemptToReduceFromRRect();
     }
 
     explicit GrShape(const SkRect& rect)
         : fType(Type::kRRect)
         , fRRect(SkRRect::MakeRect(rect)) {
+        fRRectStart = DefaultRectDirAndStartIndex(rect, false, &fRRectDir);
         this->attemptToReduceFromRRect();
     }
 
@@ -64,6 +67,20 @@
         : fType(Type::kRRect)
         , fRRect(rrect)
         , fStyle(style) {
+        fRRectStart = DefaultRRectDirAndStartIndex(rrect, style.hasPathEffect(), &fRRectDir);
+        this->attemptToReduceFromRRect();
+    }
+
+    GrShape(const SkRRect& rrect, SkPath::Direction dir, unsigned start, const GrStyle& style)
+        : fType(Type::kRRect)
+        , fRRect(rrect)
+        , fStyle(style) {
+        if (style.pathEffect()) {
+            fRRectDir = dir;
+            fRRectStart = start;
+        } else {
+            fRRectStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectDir);
+        }
         this->attemptToReduceFromRRect();
     }
 
@@ -71,6 +88,7 @@
         : fType(Type::kRRect)
         , fRRect(SkRRect::MakeRect(rect))
         , fStyle(style) {
+        fRRectStart = DefaultRectDirAndStartIndex(rect, style.hasPathEffect(), &fRRectDir);
         this->attemptToReduceFromRRect();
     }
 
@@ -85,6 +103,7 @@
         : fType(Type::kRRect)
         , fRRect(rrect)
         , fStyle(paint) {
+        fRRectStart = DefaultRRectDirAndStartIndex(rrect, fStyle.hasPathEffect(), &fRRectDir);
         this->attemptToReduceFromRRect();
     }
 
@@ -92,6 +111,7 @@
         : fType(Type::kRRect)
         , fRRect(SkRRect::MakeRect(rect))
         , fStyle(paint) {
+        fRRectStart = DefaultRectDirAndStartIndex(rect, fStyle.hasPathEffect(), &fRRectDir);
         this->attemptToReduceFromRRect();
     }
 
@@ -116,13 +136,19 @@
     }
 
     /** Returns the unstyled geometry as a rrect if possible. */
-    bool asRRect(SkRRect* rrect) const {
+    bool asRRect(SkRRect* rrect, SkPath::Direction* dir, unsigned* start) const {
         if (Type::kRRect != fType) {
             return false;
         }
         if (rrect) {
             *rrect = fRRect;
         }
+        if (dir) {
+            *dir = fRRectDir;
+        }
+        if (start) {
+            *start = fRRectStart;
+        }
         return true;
     }
 
@@ -134,7 +160,7 @@
                 break;
             case Type::kRRect:
                 out->reset();
-                out->addRRect(fRRect);
+                out->addRRect(fRRect, fRRectDir, fRRectStart);
                 break;
             case Type::kPath:
                 *out = *fPath.get();
@@ -190,7 +216,6 @@
         kPath,
     };
 
-
     /** Constructor used by the applyStyle() function */
     GrShape(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
 
@@ -202,8 +227,8 @@
 
     void attemptToReduceFromPath() {
         SkASSERT(Type::kPath == fType);
-        fType = AttemptToReduceFromPathImpl(*fPath.get(), &fRRect, fStyle.pathEffect(),
-                                            fStyle.strokeRec());
+        fType = AttemptToReduceFromPathImpl(*fPath.get(), &fRRect, &fRRectDir, &fRRectStart,
+                                            fStyle.pathEffect(), fStyle.strokeRec());
         if (Type::kPath != fType) {
             fPath.reset();
             fInheritedKey.reset(0);
@@ -219,31 +244,58 @@
     }
 
     static Type AttemptToReduceFromPathImpl(const SkPath& path, SkRRect* rrect,
-                                            const SkPathEffect* pe, const SkStrokeRec& strokeRec) {
-        if (path.isEmpty()) {
-            return Type::kEmpty;
+                                            SkPath::Direction* rrectDir, unsigned* rrectStart,
+                                            const SkPathEffect* pe, const SkStrokeRec& strokeRec);
+
+    static constexpr SkPath::Direction kDefaultRRectDir = SkPath::kCW_Direction;
+    static constexpr unsigned kDefaultRRectStart = 0;
+
+    static unsigned DefaultRectDirAndStartIndex(const SkRect& rect, bool hasPathEffect,
+                                                SkPath::Direction* dir) {
+        *dir = kDefaultRRectDir;
+        // This comes from SkPath's interface. The default for adding a SkRect is counter clockwise
+        // beginning at index 0 (which happens to correspond to rrect index 0 or 7).
+        if (!hasPathEffect) {
+            // It doesn't matter what start we use, just be consistent to avoid redundant keys.
+            return kDefaultRRectStart;
         }
-        if (path.isRRect(rrect)) {
-            SkASSERT(!rrect->isEmpty());
-            return Type::kRRect;
+        // In SkPath a rect starts at index 0 by default. This is the top left corner. However,
+        // we store rects as rrects. RRects don't preserve the invertedness, but rather sort the
+        // rect edges. Thus, we may need to modify the rrect's start index to account for the sort.
+        bool swapX = rect.fLeft > rect.fRight;
+        bool swapY = rect.fTop > rect.fBottom;
+        if (swapX && swapY) {
+            // 0 becomes start index 2 and times 2 to convert from rect the rrect indices.
+            return 2 * 2;
+        } else if (swapX) {
+            *dir = SkPath::kCCW_Direction;
+            // 0 becomes start index 1 and times 2 to convert from rect the rrect indices.
+            return 2 * 1;
+        } else if (swapY) {
+            *dir = SkPath::kCCW_Direction;
+            // 0 becomes start index 3 and times 2 to convert from rect the rrect indices.
+            return 2 * 3;
         }
-        SkRect rect;
-        if (path.isOval(&rect)) {
-            rrect->setOval(rect);
-            return Type::kRRect;
+        return 0;
+    }
+
+    static unsigned DefaultRRectDirAndStartIndex(const SkRRect& rrect, bool hasPathEffect,
+                                                 SkPath::Direction* dir) {
+        // This comes from SkPath's interface. The default for adding a SkRRect to a path is
+        // clockwise beginning at starting index 6.
+        static constexpr unsigned kPathRRectStartIdx = 6;
+        *dir = kDefaultRRectDir;
+        if (!hasPathEffect) {
+            // It doesn't matter what start we use, just be consistent to avoid redundant keys.
+            return kDefaultRRectStart;
         }
-        bool closed;
-        if (path.isRect(&rect, &closed, nullptr)) {
-            if (closed || (!pe && strokeRec.isFillStyle())) {
-                rrect->setRect(rect);
-                return Type::kRRect;
-            }
-        }
-        return Type::kPath;
+        return kPathRRectStartIdx;
     }
 
     Type                        fType;
     SkRRect                     fRRect;
+    SkPath::Direction           fRRectDir;
+    unsigned                    fRRectStart;
     SkTLazy<SkPath>             fPath;
     GrStyle                     fStyle;
     SkAutoSTArray<8, uint32_t>  fInheritedKey;
diff --git a/src/gpu/GrStyle.h b/src/gpu/GrStyle.h
index cbaa50d..5190834 100644
--- a/src/gpu/GrStyle.h
+++ b/src/gpu/GrStyle.h
@@ -115,6 +115,8 @@
 
     SkPathEffect* pathEffect() const { return fPathEffect.get(); }
 
+    bool hasPathEffect() const { return SkToBool(fPathEffect.get()); }
+
     bool hasNonDashPathEffect() const { return fPathEffect.get() && !this->isDashed(); }
 
     bool isDashed() const { return SkPathEffect::kDash_DashType == fDashInfo.fType; }
diff --git a/tests/GrShapeTest.cpp b/tests/GrShapeTest.cpp
index 0e83b69..3f23a1c 100644
--- a/tests/GrShapeTest.cpp
+++ b/tests/GrShapeTest.cpp
@@ -13,6 +13,7 @@
 #include "SkCanvas.h"
 #include "SkDashPathEffect.h"
 #include "SkPath.h"
+#include "SkPathOps.h"
 #include "SkSurface.h"
 
 using Key = SkTArray<uint32_t>;
@@ -29,6 +30,12 @@
     return true;
 }
 
+static bool paths_fill_same(const SkPath& a, const SkPath& b) {
+    SkPath pathXor;
+    Op(a, b, SkPathOp::kXOR_SkPathOp, &pathXor);
+    return pathXor.isEmpty();
+}
+
 static bool test_bounds_by_rasterizing(const SkPath& path, const SkRect& bounds) {
     static constexpr int kRes = 2000;
     // This tolerance is in units of 1/kRes fractions of the bounds width/height.
@@ -134,7 +141,17 @@
         SkPath a, b;
         fAppliedPEThenStroke.asPath(&a);
         fAppliedFull.asPath(&b);
-        REPORTER_ASSERT(r, a == b);
+        // If the output of the path effect is a rrect then it is possible for a and b to be
+        // different paths that fill identically. The reason is that fAppliedFull will do this:
+        // base -> apply path effect -> rrect_as_path -> stroke -> stroked_rrect_as_path
+        // fAppliedPEThenStroke will have converted the rrect_as_path back to a rrect. However,
+        // now that there is no longer a path effect, the direction and starting index get
+        // canonicalized before the stroke.
+        if (fAppliedPE.asRRect(nullptr, nullptr, nullptr)) {
+            REPORTER_ASSERT(r, paths_fill_same(a, b));
+        } else {
+            REPORTER_ASSERT(r, a == b);
+        }
         REPORTER_ASSERT(r, fAppliedFull.isEmpty() == fAppliedPEThenStroke.isEmpty());
 
         SkPath path;
@@ -227,53 +244,71 @@
     }
 }
 
-void TestCase::compare(skiatest::Reporter* reporter, const TestCase& that,
+void check_equivalence(skiatest::Reporter* r, const GrShape& a, const GrShape& b,
+                       const Key& keyA, const Key& keyB) {
+    // GrShape only respects the input winding direction and start point for rrect shapes
+    // when there is a path effect. Thus, if there are two GrShapes representing the same rrect
+    // but one has a path effect in its style and the other doesn't then asPath() and the unstyled
+    // key will differ. GrShape will have canonicalized the direction and start point for the shape
+    // without the path effect. If *both* have path effects then they should have both preserved
+    // the direction and starting point.
+
+    // The asRRect() output params are all initialized just to silence compiler warnings about
+    // uninitialized variables.
+    SkRRect rrectA = SkRRect::MakeEmpty(), rrectB = SkRRect::MakeEmpty();
+    SkPath::Direction dirA = SkPath::kCW_Direction, dirB = SkPath::kCW_Direction;
+    unsigned startA = ~0U, startB = ~0U;
+
+    bool aIsRRect = a.asRRect(&rrectA, &dirA, &startA);
+    bool bIsRRect = b.asRRect(&rrectB, &dirB, &startB);
+    bool aHasPE = a.style().hasPathEffect();
+    bool bHasPE = b.style().hasPathEffect();
+    bool allowSameRRectButDiffStartAndDir = (aIsRRect && bIsRRect) && (aHasPE != bHasPE);
+    SkPath pathA, pathB;
+    a.asPath(&pathA);
+    b.asPath(&pathB);
+    if (allowSameRRectButDiffStartAndDir) {
+        REPORTER_ASSERT(r, rrectA == rrectB);
+        REPORTER_ASSERT(r, paths_fill_same(pathA, pathB));
+    } else {
+        REPORTER_ASSERT(r, pathA == pathB);
+        REPORTER_ASSERT(r, keyA == keyB);
+        REPORTER_ASSERT(r, aIsRRect == bIsRRect);
+        if (aIsRRect) {
+            REPORTER_ASSERT(r, rrectA == rrectB);
+            REPORTER_ASSERT(r, dirA == dirB);
+            REPORTER_ASSERT(r, startA == startB);
+        }
+    }
+    REPORTER_ASSERT(r, a.isEmpty() == b.isEmpty());
+    REPORTER_ASSERT(r, a.knownToBeClosed() == b.knownToBeClosed());
+    REPORTER_ASSERT(r, a.bounds() == b.bounds());
+}
+
+void TestCase::compare(skiatest::Reporter* r, const TestCase& that,
                        ComparisonExpecation expectation) const {
     SkPath a, b;
     switch (expectation) {
         case kAllDifferent_ComparisonExpecation:
-            REPORTER_ASSERT(reporter, fBaseKey != that.fBaseKey);
-            REPORTER_ASSERT(reporter, fAppliedPEKey != that.fAppliedPEKey);
-            REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey);
+            REPORTER_ASSERT(r, fBaseKey != that.fBaseKey);
+            REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
+            REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
             break;
         case kSameUpToPE_ComparisonExpecation:
-            REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey);
-            fBase.asPath(&a);
-            that.fBase.asPath(&b);
-            REPORTER_ASSERT(reporter, a == b);
-            REPORTER_ASSERT(reporter, fBase.isEmpty() == that.fBase.isEmpty());
-            REPORTER_ASSERT(reporter, fAppliedPEKey != that.fAppliedPEKey);
-            REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey);
+            check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
+            REPORTER_ASSERT(r, fAppliedPEKey != that.fAppliedPEKey);
+            REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
             break;
         case kSameUpToStroke_ComparisonExpecation:
-            REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey);
-            fBase.asPath(&a);
-            that.fBase.asPath(&b);
-            REPORTER_ASSERT(reporter, a == b);
-            REPORTER_ASSERT(reporter, fBase.isEmpty() == that.fBase.isEmpty());
-            REPORTER_ASSERT(reporter, fAppliedPEKey == that.fAppliedPEKey);
-            fAppliedPE.asPath(&a);
-            that.fAppliedPE.asPath(&b);
-            REPORTER_ASSERT(reporter, a == b);
-            REPORTER_ASSERT(reporter, fAppliedPE.isEmpty() == that.fAppliedPE.isEmpty());
-            REPORTER_ASSERT(reporter, fAppliedFullKey != that.fAppliedFullKey);
+            check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
+            check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
+            REPORTER_ASSERT(r, fAppliedFullKey != that.fAppliedFullKey);
             break;
         case kAllSame_ComparisonExpecation:
-            REPORTER_ASSERT(reporter, fBaseKey == that.fBaseKey);
-            fBase.asPath(&a);
-            that.fBase.asPath(&b);
-            REPORTER_ASSERT(reporter, a == b);
-            REPORTER_ASSERT(reporter, fBase.isEmpty() == that.fBase.isEmpty());
-            REPORTER_ASSERT(reporter, fAppliedPEKey == that.fAppliedPEKey);
-            fAppliedPE.asPath(&a);
-            that.fAppliedPE.asPath(&b);
-            REPORTER_ASSERT(reporter, a == b);
-            REPORTER_ASSERT(reporter, fAppliedPE.isEmpty() == that.fAppliedPE.isEmpty());
-            REPORTER_ASSERT(reporter, fAppliedFullKey == that.fAppliedFullKey);
-            fAppliedFull.asPath(&a);
-            that.fAppliedFull.asPath(&b);
-            REPORTER_ASSERT(reporter, a == b);
-            REPORTER_ASSERT(reporter, fAppliedFull.isEmpty() == that.fAppliedFull.isEmpty());
+            check_equivalence(r, fBase, that.fBase, fBaseKey, that.fBaseKey);
+            check_equivalence(r, fAppliedPE, that.fAppliedPE, fAppliedPEKey, that.fAppliedPEKey);
+            check_equivalence(r, fAppliedFull, that.fAppliedFull, fAppliedFullKey,
+                              that.fAppliedFullKey);
             break;
     }
 }
@@ -419,10 +454,12 @@
     // Scale affects the stroke. Though, this can wind up creating a rect when the input is a rect.
     // In that case we wind up with a pure geometry key and the geometries are the same.
     SkRRect rrect;
-    if (strokeAndFillCase1.appliedFullStyleShape().asRRect(&rrect)) {
+    if (strokeAndFillCase1.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr)) {
         // We currently only expect to get here in the rect->rect case.
         REPORTER_ASSERT(reporter, rrect.isRect());
-        REPORTER_ASSERT(reporter, strokeAndFillCase1.baseShape().asRRect(&rrect) && rrect.isRect());
+        REPORTER_ASSERT(reporter,
+                        strokeAndFillCase1.baseShape().asRRect(&rrect, nullptr, nullptr) &&
+                        rrect.isRect());
         strokeAndFillCase1.compare(reporter, strokeAndFillCase2,
                                    TestCase::kAllSame_ComparisonExpecation);
     } else {
@@ -661,20 +698,22 @@
     SkRRect rrect;
     // Applying the path effect should make a SkRRect shape. There is no further stroking in the
     // geoPECase, so the full style should be the same as just the PE.
-    REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect));
+    REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr));
     REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
     REPORTER_ASSERT(reporter, geoPECase.appliedPathEffectKey() == rrectFillCase.baseKey());
 
-    REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect));
+    REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr));
     REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
     REPORTER_ASSERT(reporter, geoPECase.appliedFullStyleKey() == rrectFillCase.baseKey());
 
     // In the PE+stroke case applying the full style should be the same as just stroking the rrect.
-    REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect));
+    REPORTER_ASSERT(reporter,
+                    geoPEStrokeCase.appliedPathEffectShape().asRRect(&rrect, nullptr, nullptr));
     REPORTER_ASSERT(reporter, rrect == RRectPathEffect::RRect());
     REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedPathEffectKey() == rrectFillCase.baseKey());
 
-    REPORTER_ASSERT(reporter, !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect));
+    REPORTER_ASSERT(reporter,
+                    !geoPEStrokeCase.appliedFullStyleShape().asRRect(&rrect, nullptr, nullptr));
     REPORTER_ASSERT(reporter, geoPEStrokeCase.appliedFullStyleKey() ==
                               rrectStrokeCase.appliedFullStyleKey());
 }
@@ -749,21 +788,25 @@
 
     TestCase peCase(geo, pe, reporter);
 
-    SkPath a, b;
+    SkPath a, b, c;
     peCase.baseShape().asPath(&a);
     peCase.appliedPathEffectShape().asPath(&b);
-    REPORTER_ASSERT(reporter, a == b);
-    peCase.appliedFullStyleShape().asPath(&b);
-    REPORTER_ASSERT(reporter, a == b);
-    REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
-    REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
+    peCase.appliedFullStyleShape().asPath(&c);
     if (isNonPath) {
-        REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey() == peCase.baseKey());
-        REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey() == peCase.baseKey());
+        // RRect types can have a change in start index or direction after the PE is applied. This
+        // is because once the PE is applied, GrShape may canonicalize the dir and index since it
+        // is not germane to the styling any longer.
+        // Instead we just check that the paths would fill the same both before and after styling.
+        REPORTER_ASSERT(reporter, paths_fill_same(a, b));
+        REPORTER_ASSERT(reporter, paths_fill_same(a, c));
     } else {
+        REPORTER_ASSERT(reporter, a == b);
+        REPORTER_ASSERT(reporter, a == c);
         REPORTER_ASSERT(reporter, peCase.appliedPathEffectKey().empty());
         REPORTER_ASSERT(reporter, peCase.appliedFullStyleKey().empty());
     }
+    REPORTER_ASSERT(reporter, peCase.appliedPathEffectShape().style().isSimpleHairline());
+    REPORTER_ASSERT(reporter, peCase.appliedFullStyleShape().style().isSimpleHairline());
 }
 
 /**
@@ -894,10 +937,34 @@
 }
 
 DEF_TEST(GrShape, reporter) {
-    sk_sp<SkPathEffect> dashPE = make_dash();
+    for (auto r : { SkRect::MakeWH(10, 20),
+                    SkRect::MakeWH(-10, -20),
+                    SkRect::MakeWH(-10, 20),
+                    SkRect::MakeWH(10, -20)}) {
+        test_basic(reporter, r);
+        test_scale(reporter, r);
+        test_dash_fill(reporter, r);
+        test_null_dash(reporter, r);
+        // Test modifying various stroke params.
+        test_stroke_param<SkRect, SkScalar>(
+                reporter, r,
+                [](SkPaint* p, SkScalar w) { p->setStrokeWidth(w);},
+                SkIntToScalar(2), SkIntToScalar(4));
+        test_stroke_param<SkRect, SkPaint::Join>(
+                reporter, r,
+                [](SkPaint* p, SkPaint::Join j) { p->setStrokeJoin(j);},
+                SkPaint::kMiter_Join, SkPaint::kRound_Join);
+        test_stroke_cap(reporter, r);
+        test_miter_limit(reporter, r);
+        test_path_effect_makes_rrect(reporter, r);
+        test_unknown_path_effect(reporter, r);
+        test_path_effect_makes_empty_shape(reporter, r);
+        test_make_hairline_path_effect(reporter, r, true);
+    }
 
     for (auto rr : { SkRRect::MakeRect(SkRect::MakeWH(10, 10)),
-                     SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4)}) {
+                     SkRRect::MakeRectXY(SkRect::MakeWH(10, 10), 3, 4),
+                     SkRRect::MakeOval(SkRect::MakeWH(20, 20))}) {
         test_basic(reporter, rr);
         test_scale(reporter, rr);
         test_dash_fill(reporter, rr);
@@ -984,11 +1051,12 @@
         TestCase fillPathCase(path, fillPaint, reporter);
         SkRRect rrect;
         REPORTER_ASSERT(reporter, testPath.fIsRRectForFill ==
-                                  fillPathCase.baseShape().asRRect(&rrect));
+                                  fillPathCase.baseShape().asRRect(&rrect, nullptr, nullptr));
         if (testPath.fIsRRectForFill) {
+            TestCase fillPathCase2(path, fillPaint, reporter);
             REPORTER_ASSERT(reporter, rrect == testPath.fRRect);
             TestCase fillRRectCase(rrect, fillPaint, reporter);
-            fillPathCase.compare(reporter, fillRRectCase, TestCase::kAllSame_ComparisonExpecation);
+            fillPathCase2.compare(reporter, fillRRectCase, TestCase::kAllSame_ComparisonExpecation);
         }
 
         SkPaint strokePaint;
@@ -996,12 +1064,12 @@
         strokePaint.setStyle(SkPaint::kStroke_Style);
         TestCase strokePathCase(path, strokePaint, reporter);
         REPORTER_ASSERT(reporter, testPath.fIsRRectForStroke ==
-                                  strokePathCase.baseShape().asRRect(&rrect));
+                                  strokePathCase.baseShape().asRRect(&rrect, nullptr, nullptr));
         if (testPath.fIsRRectForStroke) {
             REPORTER_ASSERT(reporter, rrect == testPath.fRRect);
             TestCase strokeRRectCase(rrect, strokePaint, reporter);
             strokePathCase.compare(reporter, strokeRRectCase,
-                                 TestCase::kAllSame_ComparisonExpecation);
+                                   TestCase::kAllSame_ComparisonExpecation);
         }
     }