Reland "Refactor geometry union capabilities out of GrStyledShape"

This reverts commit af312c9d404468b5ab3cd23f42d9dfdf27332420.

Reason for revert: improved performance, updated empty point cap behavior
to make chrome happy.

Because of the performance regression in the original CL, this is a bit
more to it than just updating cap behavior. Summary of changes for perf:
1. In asPath(), only call reset() if the type isn't a path or arc.
   Otherwise it was just a wasted realloc of an empty path ref.
2. Rewrote the GrShape::simplify() to not progress through every shape
   type in order, it just jumps to the appropriate type.
3. Have simplify() return whether or not the shape started out closed,
   so we don't have to call GrShape::closed(), which is costly when the
   shape is a path.
4. Expose the GrShape's type enum so GrStyledShape's key writing can use
   switches instead of a giant block of ifs (where path happened to be
   last)

The regressions showed up most heavily on desk_mapsvg and desk_chalkboard
SKPs on the Android skpbench marks. On my system, I was able to
reproduce a similar %-regression from ToT and the original CL on the
chalkboard (but not mapsvg).

Master ranged between 5.1 and 5.3ms, original CL ranged from 5.6-5.8
and after the changes listed above, I got it down to 5.3-5.5. It's not
ideal but I haven't been able to figure out anything more substantial
that it could be. At this point it may just be code layout and/or the
fact that it's now split into two types.


Original change's description:
> Revert "Refactor geometry union capabilities out of GrStyledShape"
>
> This reverts commit 2becdde0740b84eec7856c3f811fd7637e011191.
>
> Reason for revert: likely breaking cc unit test due to empty shape cap change.
>
> Original change's description:
> > Refactor geometry union capabilities out of GrStyledShape
> >
> > The geometry union part of GrStyledShape is now held in GrShape. For the
> > most part, GrShape is entirely style agnostic and focuses on storing
> > the various types of geometry, and destructing them gracefully. It also
> > provides a public API that unifies functionality across all shape types,
> > such as contains() and bounds().
> >
> > GrStyledShape now just owns a GrShape and a GrStyle, and handles the
> > additional simplification logic that relies on knowing the effects of
> > the style on the draw. This is where GrShape makes some allowances for
> > style. Its simplify() function accepts flags that enable/disable various
> > simplification optimizations. Currently these are designed around
> > what is needed to respect path effects and stroking behaviors in
> > GrStyledShape. The main other user of GrShape (the new clip stack) will
> > always provide all flags since it treats every shape as if it were
> > simply filled.
> >
> > Several other related refactorings were taken at the same time:
> > 1. The implementations for asNestedRects, asRRect, etc. were moved out
> >    of the header and into the cpp file for GrStyledShape.
> > 2. GrRenderTargetContext relies on GrStyledShape for its stroke rect
> >    fallbacks.
> > 3. GrShape can hold points, lines, and rects explicitly. This let me
> >    simplify the stroke reasoning.
> >
> > Change-Id: I9fe75613fee51c30b4049b2b5a422daf80a1a86e
> > Reviewed-on: https://skia-review.googlesource.com/c/skia/+/284803
> > Commit-Queue: Michael Ludwig <michaelludwig@google.com>
> > Reviewed-by: Brian Salomon <bsalomon@google.com>
> > Reviewed-by: Chris Dalton <csmartdalton@google.com>
>
> TBR=bsalomon@google.com,csmartdalton@google.com,michaelludwig@google.com
>
> Change-Id: I2af5782e072e0ccb4a87f903bb88cbe335b9613f
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/286039
> Reviewed-by: Michael Ludwig <michaelludwig@google.com>
> Commit-Queue: Michael Ludwig <michaelludwig@google.com>

Change-Id: I8c614573582084f2e9ee0d73f93812e0a7c13983
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/286396
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 0dbf124..2e78bd5 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -885,41 +885,11 @@
         // Fills the rect, using rect as its own local coordinates
         this->fillRectToRect(clip, std::move(paint), aa, viewMatrix, rect, rect);
         return;
-    } else if (stroke.getStyle() == SkStrokeRec::kStroke_Style ||
-               stroke.getStyle() == SkStrokeRec::kHairline_Style) {
-        if ((!rect.width() || !rect.height()) &&
-            SkStrokeRec::kHairline_Style != stroke.getStyle()) {
-            SkScalar r = stroke.getWidth() / 2;
-            // TODO: Move these stroke->fill fallbacks to GrStyledShape?
-            switch (stroke.getJoin()) {
-                case SkPaint::kMiter_Join:
-                    this->drawRect(
-                            clip, std::move(paint), aa, viewMatrix,
-                            {rect.fLeft - r, rect.fTop - r, rect.fRight + r, rect.fBottom + r},
-                            &GrStyle::SimpleFill());
-                    return;
-                case SkPaint::kRound_Join:
-                    // Raster draws nothing when both dimensions are empty.
-                    if (rect.width() || rect.height()){
-                        SkRRect rrect = SkRRect::MakeRectXY(rect.makeOutset(r, r), r, r);
-                        this->drawRRect(clip, std::move(paint), aa, viewMatrix, rrect,
-                                        GrStyle::SimpleFill());
-                        return;
-                    }
-                case SkPaint::kBevel_Join:
-                    if (!rect.width()) {
-                        this->drawRect(clip, std::move(paint), aa, viewMatrix,
-                                       {rect.fLeft - r, rect.fTop, rect.fRight + r, rect.fBottom},
-                                       &GrStyle::SimpleFill());
-                    } else {
-                        this->drawRect(clip, std::move(paint), aa, viewMatrix,
-                                       {rect.fLeft, rect.fTop - r, rect.fRight, rect.fBottom + r},
-                                       &GrStyle::SimpleFill());
-                    }
-                    return;
-                }
-        }
-
+    } else if ((stroke.getStyle() == SkStrokeRec::kStroke_Style ||
+                stroke.getStyle() == SkStrokeRec::kHairline_Style) &&
+               (rect.width() && rect.height())) {
+        // Only use the StrokeRectOp for non-empty rectangles. Empty rectangles will be processed by
+        // GrStyledShape to handle stroke caps and dashing properly.
         std::unique_ptr<GrDrawOp> op;
 
         GrAAType aaType = this->chooseAAType(aa);
diff --git a/src/gpu/geometry/GrShape.cpp b/src/gpu/geometry/GrShape.cpp
new file mode 100644
index 0000000..d656021
--- /dev/null
+++ b/src/gpu/geometry/GrShape.cpp
@@ -0,0 +1,453 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/geometry/GrShape.h"
+
+#include "src/core/SkPathPriv.h"
+
+GrShape& GrShape::operator=(const GrShape& shape) {
+    switch(shape.type()) {
+        case Type::kEmpty:
+            this->reset();
+            break;
+        case Type::kPoint:
+            this->setPoint(shape.fPoint);
+            break;
+        case Type::kRect:
+            this->setRect(shape.fRect);
+            break;
+        case Type::kRRect:
+            this->setRRect(shape.fRRect);
+            break;
+        case Type::kPath:
+            this->setPath(shape.fPath);
+            break;
+        case Type::kArc:
+            this->setArc(shape.fArc);
+            break;
+        case Type::kLine:
+            this->setLine(shape.fLine);
+            break;
+        default:
+            SkUNREACHABLE;
+    }
+
+    fStart = shape.fStart;
+    fCW = shape.fCW;
+    fInverted = shape.fInverted;
+
+    return *this;
+}
+
+uint32_t GrShape::stateKey() const {
+    // Use the path's full fill type instead of just whether or not it's inverted.
+    uint32_t key = this->isPath() ? static_cast<uint32_t>(fPath.getFillType())
+                                  : (fInverted ? 1 : 0);
+    key |= ((uint32_t) fType) << 2; // fill type was 2 bits
+    key |= fStart             << 5; // type was 3 bits, total 5 bits so far
+    key |= (fCW ? 1 : 0)      << 8; // start was 3 bits, total 8 bits so far
+    return key;
+}
+
+bool GrShape::simplifyPath(unsigned flags) {
+    SkASSERT(this->isPath());
+
+    SkRect rect;
+    SkRRect rrect;
+    SkPoint pts[2];
+
+    SkPathDirection dir;
+    unsigned start;
+
+    if (fPath.isEmpty()) {
+        this->setType(Type::kEmpty);
+        return false;
+    } else if (fPath.isLine(pts)) {
+        this->simplifyLine(pts[0], pts[1], flags);
+        return false;
+    } else if (SkPathPriv::IsRRect(fPath, &rrect, &dir, &start)) {
+        this->simplifyRRect(rrect, dir, start, flags);
+        return true;
+    } else if (SkPathPriv::IsOval(fPath, &rect, &dir, &start)) {
+        // Convert to rrect indexing since oval is not represented explicitly
+        this->simplifyRRect(SkRRect::MakeOval(rect), dir, start * 2, flags);
+        return true;
+    } else if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) {
+        // 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().
+        this->simplifyRect(rect, dir, start, flags);
+        return true;
+    } else if (flags & kIgnoreWinding_Flag) {
+        // Attempt isRect() since we don't have to preserve any winding info
+        bool closed;
+        if (fPath.isRect(&rect, &closed) && (closed || (flags & kSimpleFill_Flag))) {
+            this->simplifyRect(rect, kDefaultDir, kDefaultStart, flags);
+            return true;
+        }
+    }
+    // No further simplification for a path. For performance reasons, we don't query the path to
+    // determine it was closed, as whether or not it was closed when it remains a path type is not
+    // important for styling.
+    return false;
+}
+
+bool GrShape::simplifyArc(unsigned flags) {
+    SkASSERT(this->isArc());
+
+    // Arcs can simplify to rrects, lines, points, or empty; regardless of what it simplifies to
+    // it was closed if went through the center point.
+    bool wasClosed = fArc.fUseCenter;
+    if (fArc.fOval.isEmpty() || !fArc.fSweepAngle) {
+        if (flags & kSimpleFill_Flag) {
+            // Go straight to empty, since the other degenerate shapes all have 0 area anyway.
+            this->setType(Type::kEmpty);
+        } else if (!fArc.fSweepAngle) {
+            SkPoint center = {fArc.fOval.centerX(), fArc.fOval.centerY()};
+            SkScalar startRad = SkDegreesToRadians(fArc.fStartAngle);
+            SkPoint start = {center.fX + 0.5f * fArc.fOval.width() * SkScalarCos(startRad),
+                                center.fY + 0.5f * fArc.fOval.height() * SkScalarSin(startRad)};
+            // Either just the starting point, or a line from the center to the start
+            if (fArc.fUseCenter) {
+                this->simplifyLine(center, start, flags);
+             } else {
+                this->simplifyPoint(start, flags);
+             }
+        } else {
+            // TODO: Theoretically, we could analyze the arc projected into the empty bounds to
+            // determine a line, but that is somewhat complex for little value (since the arc
+            // can backtrack on itself if the sweep angle is large enough).
+            this->setType(Type::kEmpty);
+        }
+    } else {
+        if ((flags & kSimpleFill_Flag) || ((flags & kIgnoreWinding_Flag) && !fArc.fUseCenter)) {
+             // Eligible to turn into an oval if it sweeps a full circle
+            if (fArc.fSweepAngle <= -360.f || fArc.fSweepAngle >= 360.f) {
+                this->simplifyRRect(SkRRect::MakeOval(fArc.fOval),
+                                    kDefaultDir, kDefaultStart, flags);
+                return true;
+            }
+        }
+
+        if (flags & kMakeCanonical_Flag) {
+            // Map start to 0 to 360, sweep is always positive
+            if (fArc.fSweepAngle < 0) {
+                fArc.fStartAngle = fArc.fStartAngle + fArc.fSweepAngle;
+                fArc.fSweepAngle = -fArc.fSweepAngle;
+            }
+
+            if (fArc.fStartAngle < 0 || fArc.fStartAngle >= 360.f) {
+                fArc.fStartAngle = SkScalarMod(fArc.fStartAngle, 360.f);
+            }
+        }
+    }
+
+    return wasClosed;
+}
+
+void GrShape::simplifyRRect(const SkRRect& rrect, SkPathDirection dir, unsigned start,
+                            unsigned flags) {
+    if (rrect.isEmpty() || rrect.isRect()) {
+        // Change index from rrect to rect
+        start = ((start + 1) / 2) % 4;
+        this->simplifyRect(rrect.rect(), dir, start, flags);
+    } else if (!this->isRRect()) {
+        this->setType(Type::kRRect);
+        fRRect = rrect;
+        this->setPathWindingParams(dir, start);
+        // A round rect is already canonical, so there's nothing more to do
+    } else {
+        // If starting as a round rect, the provided rrect/winding params should be already set
+        SkASSERT(fRRect == rrect && this->dir() == dir && this->startIndex() == start);
+    }
+}
+
+void GrShape::simplifyRect(const SkRect& rect, SkPathDirection dir, unsigned start,
+                           unsigned flags) {
+    if (!rect.width() || !rect.height()) {
+        if (flags & kSimpleFill_Flag) {
+            // A zero area, filled shape so go straight to empty
+            this->setType(Type::kEmpty);
+        } else if (!fRect.width() ^ !fRect.height()) {
+            // A line, choose the first point that best matches the starting index
+            SkPoint p1 = {rect.fLeft, rect.fTop};
+            SkPoint p2 = {rect.fRight, rect.fBottom};
+            if (start >= 2 && !(flags & kIgnoreWinding_Flag)) {
+                using std::swap;
+                swap(p1, p2);
+            }
+            this->simplifyLine(p1, p2, flags);
+        } else {
+            // A point (all edges are equal, so start+dir doesn't affect choice)
+            this->simplifyPoint({rect.fLeft, rect.fTop}, flags);
+        }
+    } else {
+        if (!this->isRect()) {
+            this->setType(Type::kRect);
+            fRect = rect;
+            this->setPathWindingParams(dir, start);
+        } else {
+            // If starting as a rect, the provided rect/winding params should already be set
+            SkASSERT(fRect == rect && this->dir() == dir && this->startIndex() == start);
+        }
+        if (flags & kMakeCanonical_Flag) {
+            fRect.sort();
+        }
+    }
+}
+
+void GrShape::simplifyLine(const SkPoint& p1, const SkPoint& p2, unsigned flags) {
+    if (flags & kSimpleFill_Flag) {
+        this->setType(Type::kEmpty);
+    } else if (p1 == p2) {
+        this->simplifyPoint(p1, false);
+    } else {
+        if (!this->isLine()) {
+            this->setType(Type::kLine);
+            fLine.fP1 = p1;
+            fLine.fP2 = p2;
+        } else {
+            // If starting as a line, the provided points should already be set
+            SkASSERT(fLine.fP1 == p1 && fLine.fP2 == p2);
+        }
+        if (flags & kMakeCanonical_Flag) {
+             // Sort the end points
+             if (fLine.fP2.fY < fLine.fP1.fY ||
+                 (fLine.fP2.fY == fLine.fP1.fY && fLine.fP2.fX < fLine.fP1.fX)) {
+                using std::swap;
+                swap(fLine.fP1, fLine.fP2);
+            }
+        }
+    }
+}
+
+void GrShape::simplifyPoint(const SkPoint& point, unsigned flags) {
+    if (flags & kSimpleFill_Flag) {
+        this->setType(Type::kEmpty);
+    } else if (!this->isPoint()) {
+        this->setType(Type::kPoint);
+        fPoint = point;
+    } else {
+        // If starting as a point, the provided position should already be set
+        SkASSERT(point == fPoint);
+    }
+}
+
+bool GrShape::simplify(unsigned flags) {
+    // Sanity check that winding parameters are valid for the current type.
+    SkASSERT((fType == Type::kRect || fType == Type::kRRect) ||
+             (this->dir() == kDefaultDir && this->startIndex() == kDefaultStart));
+
+    // The type specific functions automatically fall through to the simpler shapes, so
+    // we only need to start in the right place.
+    bool wasClosed = false;
+    switch(fType) {
+        case Type::kEmpty:
+            // do nothing
+            break;
+        case Type::kPoint:
+            this->simplifyPoint(fPoint, flags);
+            break;
+        case Type::kLine:
+            this->simplifyLine(fLine.fP1, fLine.fP2, flags);
+            break;
+        case Type::kRect:
+            this->simplifyRect(fRect, this->dir(), this->startIndex(), flags);
+            wasClosed = true;
+            break;
+        case Type::kRRect:
+            this->simplifyRRect(fRRect, this->dir(), this->startIndex(), flags);
+            wasClosed = true;
+            break;
+        case Type::kPath:
+            wasClosed = this->simplifyPath(flags);
+            break;
+        case Type::kArc:
+            wasClosed = this->simplifyArc(flags);
+            break;
+
+        default:
+            SkUNREACHABLE;
+    }
+
+    if (((flags & kIgnoreWinding_Flag) || (fType != Type::kRect && fType != Type::kRRect))) {
+        // Reset winding parameters if we don't need them anymore
+        this->setPathWindingParams(kDefaultDir, kDefaultStart);
+    }
+
+    return wasClosed;
+}
+
+bool GrShape::contains(const SkRect& rect) const {
+    switch(this->type()) {
+        case Type::kEmpty:
+        case Type::kPoint: // fall through since a point has 0 area
+        case Type::kLine:  // fall through, "" (currently choosing not to test if 'rect' == line)
+            return false;
+        case Type::kRect:
+            return fRect.contains(rect);
+        case Type::kRRect:
+            return fRRect.contains(rect);
+        case Type::kPath:
+            return fPath.conservativelyContainsRect(rect);
+        case Type::kArc:
+            if (fArc.fUseCenter) {
+                SkPath arc;
+                this->asPath(&arc);
+                return arc.conservativelyContainsRect(rect);
+            } else {
+                return false;
+            }
+        default:
+            SkUNREACHABLE;
+    }
+}
+
+bool GrShape::closed() const {
+    switch(this->type()) {
+        case Type::kEmpty: // fall through
+        case Type::kRect:  // fall through
+        case Type::kRRect:
+            return true;
+        case Type::kPath:
+            // SkPath doesn't keep track of the closed status of each contour.
+            return SkPathPriv::IsClosedSingleContour(fPath);
+        case Type::kArc:
+            return fArc.fUseCenter;
+        case Type::kPoint: // fall through
+        case Type::kLine:
+            return false;
+        default:
+            SkUNREACHABLE;
+    }
+}
+
+bool GrShape::convex(bool simpleFill) const {
+    switch(this->type()) {
+        case Type::kEmpty: // fall through
+        case Type::kRect:  // fall through
+        case Type::kRRect:
+            return true;
+        case Type::kPath:
+            // SkPath.isConvex() really means "is this path convex were it to be closed".
+            // Convex paths may only have one contour hence isLastContourClosed() is sufficient.
+            return (simpleFill || fPath.isLastContourClosed()) && fPath.isConvex();
+        case Type::kArc:
+            return SkPathPriv::DrawArcIsConvex(fArc.fSweepAngle, fArc.fUseCenter, simpleFill);
+        case Type::kPoint: // fall through
+        case Type::kLine:
+            return false;
+        default:
+            SkUNREACHABLE;
+    }
+}
+
+SkRect GrShape::bounds() const {
+    // Bounds where left == bottom or top == right can indicate a line or point shape. We return
+    // inverted bounds for a truly empty shape.
+    static constexpr SkRect kInverted = SkRect::MakeLTRB(1, 1, -1, -1);
+    switch(this->type()) {
+        case Type::kEmpty:
+            return kInverted;
+        case Type::kPoint:
+            return {fPoint.fX, fPoint.fY, fPoint.fX, fPoint.fY};
+        case Type::kRect:
+            return fRect.makeSorted();
+        case Type::kRRect:
+            return fRRect.getBounds();
+        case Type::kPath:
+            return fPath.getBounds();
+        case Type::kArc:
+            return fArc.fOval;
+        case Type::kLine: {
+            SkRect b = SkRect::MakeLTRB(fLine.fP1.fX, fLine.fP1.fY,
+                                        fLine.fP2.fX, fLine.fP2.fY);
+            b.sort();
+            return b; }
+        default:
+            SkUNREACHABLE;
+    }
+}
+
+uint32_t GrShape::segmentMask() const {
+    // In order to match what a path would report, this has to inspect the shapes slightly
+    // to reflect what they might simplify to.
+    switch(this->type()) {
+        case Type::kEmpty:
+            return 0;
+        case Type::kRRect:
+            if (fRRect.isEmpty() || fRRect.isRect()) {
+                return SkPath::kLine_SegmentMask;
+            } else if (fRRect.isOval()) {
+                return SkPath::kConic_SegmentMask;
+            } else {
+                return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask;
+            }
+        case Type::kPath:
+            return fPath.getSegmentMasks();
+        case Type::kArc:
+            if (fArc.fUseCenter) {
+                return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask;
+            } else {
+                return SkPath::kConic_SegmentMask;
+            }
+        case Type::kPoint: // fall through
+        case Type::kLine:  // ""
+        case Type::kRect:
+            return SkPath::kLine_SegmentMask;
+        default:
+            SkUNREACHABLE;
+    }
+}
+
+void GrShape::asPath(SkPath* out, bool simpleFill) const {
+    if (!this->isPath() && !this->isArc()) {
+        // When not a path, we need to set fill type on the path to match invertedness.
+        // All the non-path geometries produce equivalent shapes with either even-odd or winding
+        // so we can use the default fill type.
+        out->reset();
+        out->setFillType(kDefaultFillType);
+        if (fInverted) {
+            out->toggleInverseFillType();
+        }
+    } // Else when we're already a path, that will assign the fill type directly to 'out'.
+
+    switch(this->type()) {
+        case Type::kEmpty:
+            return;
+        case Type::kPoint:
+            // A plain moveTo() or moveTo+close() does not match the expected path for a
+            // point that is being dashed (see SkDashPath's handling of zero-length segments).
+            out->moveTo(fPoint);
+            out->lineTo(fPoint);
+            return;
+        case Type::kRect:
+            out->addRect(fRect, this->dir(), this->startIndex());
+            return;
+        case Type::kRRect:
+            out->addRRect(fRRect, this->dir(), this->startIndex());
+            return;
+        case Type::kPath:
+            *out = fPath;
+            return;
+        case Type::kArc:
+            SkPathPriv::CreateDrawArcPath(out, fArc.fOval, fArc.fStartAngle, fArc.fSweepAngle,
+                                          fArc.fUseCenter, simpleFill);
+            // CreateDrawArcPath resets the output path and configures its fill type, so we just
+            // have to ensure invertedness is correct.
+            if (fInverted) {
+                out->toggleInverseFillType();
+            }
+            return;
+        case Type::kLine:
+            out->moveTo(fLine.fP1);
+            out->lineTo(fLine.fP2);
+            return;
+        default:
+            SkUNREACHABLE;
+    }
+}
diff --git a/src/gpu/geometry/GrShape.h b/src/gpu/geometry/GrShape.h
new file mode 100644
index 0000000..857af35
--- /dev/null
+++ b/src/gpu/geometry/GrShape.h
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrShape_DEFINED
+#define GrShape_DEFINED
+
+#include "include/core/SkPath.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkRRect.h"
+#include "include/core/SkRect.h"
+
+// Represents an arc along an oval boundary, or a closed wedge of the oval.
+struct GrArc {
+    SkRect   fOval;       // The sorted, bounding box defining the oval the arc is traced along
+    SkScalar fStartAngle; // In degrees
+    SkScalar fSweepAngle; // In degrees
+    bool     fUseCenter;  // True if the arc includes the center point of the oval
+};
+
+// Represents a line segment between two points.
+struct GrLineSegment {
+    SkPoint fP1;
+    SkPoint fP2;
+};
+
+/**
+ * GrShape is a convenience class to represent the many different specialized geometries that
+ * Ganesh can handle, including rects, round rects, lines, as well as paths. It is intended as
+ * a data-only class where any additional complex behavior is handled by an owning type (e.g.
+ * GrStyledShape). However, it does include some basic utilities that unify common functionality
+ * (such as contains()) from the underlying shape types.
+ *
+ * In order to have lossless simplification of the geometry, it also tracks winding direction, start
+ * index, and fill inversion. The direction and index are match the SkPath indexing scheme for
+ * the shape's type (e.g. rect, rrect, or oval).
+ *
+ * Regarding GrShape's empty shape:
+ * - GrShape uses empty to refer to the absence of any geometric data
+ * - SkRect::isEmpty() returns true if the rect is not sorted, even if it has area. GrShape will not
+ *   simplify these shapes to an empty GrShape. Rects with actual 0 width and height will simplify
+ *   to a point or line, not empty. This is to preserve geometric data for path effects and strokes.
+ * - SkRRect::isEmpty() is true when the bounds have 0 width or height, so GrShape will simplify it
+ *   to a point or line, just like a rect. SkRRect does not have the concept of unsorted edges.
+ */
+class GrShape {
+public:
+    // The current set of types GrShape can represent directly
+    enum class Type : uint8_t {
+        kEmpty, kPoint, kRect, kRRect, kPath, kArc, kLine, kLast = kLine
+    };
+    static constexpr int kTypeCount = static_cast<int>(Type::kLast) + 1;
+
+    // The direction and start index used when a shape does not have a representable winding,
+    // or when that information was discarded during simplification (kIgnoreWinding_Flag).
+    static constexpr SkPathDirection kDefaultDir   = SkPathDirection::kCW;
+    static constexpr unsigned        kDefaultStart = 0;
+    // The fill rule that is used by asPath() for shapes that aren't already a path.
+    static constexpr SkPathFillType  kDefaultFillType = SkPathFillType::kEvenOdd;
+
+    GrShape() {}
+    explicit GrShape(const SkPoint& point) { this->setPoint(point); }
+    explicit GrShape(const SkRect& rect) { this->setRect(rect); }
+    explicit GrShape(const SkRRect& rrect) { this->setRRect(rrect); }
+    explicit GrShape(const SkPath& path) { this->setPath(path); }
+    explicit GrShape(const GrArc& arc) { this->setArc(arc); }
+    explicit GrShape(const GrLineSegment& line){ this->setLine(line); }
+
+    explicit GrShape(const GrShape& shape) { *this = shape; }
+
+    ~GrShape() { this->reset(); }
+
+    // NOTE: None of the geometry types benefit from move semantics, so we don't bother
+    // defining a move assignment operator for GrShape.
+    GrShape& operator=(const GrShape& shape);
+
+    // These type queries reflect the shape type provided when assigned, it does not incorporate
+    // any potential simplification (e.g. if isRRect() is true and rrect().isRect() is true,
+    // isRect() will still be false, until simplify() is called).
+    bool isEmpty() const { return this->type() == Type::kEmpty; }
+    bool isPoint() const { return this->type() == Type::kPoint; }
+    bool isRect() const { return this->type() == Type::kRect; }
+    bool isRRect() const { return this->type() == Type::kRRect; }
+    bool isPath() const { return this->type() == Type::kPath; }
+    bool isArc() const { return this->type() == Type::kArc; }
+    bool isLine() const { return this->type() == Type::kLine; }
+
+    Type type() const { return fType; }
+
+    // Report the shape type, winding direction, start index, and invertedness as a value suitable
+    // for use in a resource key. This does not include any geometry coordinates into the key value.
+    uint32_t stateKey() const;
+
+    // Whether or not the shape is meant to be the inverse of its geometry (i.e. its exterior).
+    bool inverted() const {
+        return this->isPath() ? fPath.isInverseFillType() : SkToBool(fInverted);
+    }
+
+    // Returns the path direction extracted from the path during simplification, if the shape's
+    // type represents a rrect, rect, or oval.
+    SkPathDirection dir() const { return fCW ? SkPathDirection::kCW : SkPathDirection::kCCW; }
+    // Returns the start index extracted from the path during simplification, if the shape's
+    // type represents a rrect, rect, or oval.
+    unsigned startIndex() const { return fStart; }
+
+    // Override the direction and start parameters for the simplified contour. These are only
+    // meaningful for rects, rrects, and ovals.
+    void setPathWindingParams(SkPathDirection dir, unsigned start) {
+        SkASSERT((this->isRect() && start < 4) || (this->isRRect() && start < 8) ||
+                 (dir == kDefaultDir && start == kDefaultStart));
+        fCW = dir == SkPathDirection::kCW;
+        fStart = static_cast<uint8_t>(start);
+    }
+
+    void setInverted(bool inverted) {
+        if (this->isPath()) {
+            if (inverted != fPath.isInverseFillType()) {
+                fPath.toggleInverseFillType();
+            }
+        } else {
+            fInverted = static_cast<uint16_t>(inverted);
+        }
+    }
+
+    // Access the actual geometric description of the shape. May only access the appropriate type
+    // based on what was last set. The type may change after simplify() is called.
+    SkPoint& point() { SkASSERT(this->isPoint()); return fPoint; }
+    const SkPoint& point() const { SkASSERT(this->isPoint()); return fPoint; }
+
+    SkRect& rect() { SkASSERT(this->isRect()); return fRect; }
+    const SkRect& rect() const { SkASSERT(this->isRect()); return fRect; }
+
+    SkRRect& rrect() { SkASSERT(this->isRRect()); return fRRect; }
+    const SkRRect& rrect() const { SkASSERT(this->isRRect()); return fRRect; }
+
+    SkPath& path() { SkASSERT(this->isPath()); return fPath; }
+    const SkPath& path() const { SkASSERT(this->isPath()); return fPath; }
+
+    GrArc& arc() { SkASSERT(this->isArc()); return fArc; }
+    const GrArc& arc() const { SkASSERT(this->isArc()); return fArc; }
+
+    GrLineSegment& line() { SkASSERT(this->isLine()); return fLine; }
+    const GrLineSegment& line() const { SkASSERT(this->isLine()); return fLine; }
+
+    // Update the geometry stored in the GrShape and update its associated type to match. This
+    // performs no simplification, so calling setRRect() with a round rect that has isRect() return
+    // true will still be considered an rrect by this shape until simplify() is called.
+    //
+    // These also reset any extracted direction, start, and inverted state from a prior simplified
+    // path, since these functions ared used to describe a new geometry.
+    void setPoint(const SkPoint& point) {
+        this->reset(Type::kPoint);
+        fPoint = point;
+    }
+    void setRect(const SkRect& rect) {
+        this->reset(Type::kRect);
+        fRect = rect;
+    }
+    void setRRect(const SkRRect& rrect) {
+        this->reset(Type::kRRect);
+        fRRect = rrect;
+    }
+    void setArc(const GrArc& arc) {
+        this->reset(Type::kArc);
+        fArc = arc;
+    }
+    void setLine(const GrLineSegment& line) {
+        this->reset(Type::kLine);
+        fLine = line;
+    }
+    void setPath(const SkPath& path) {
+        if (this->isPath()) {
+            // Assign directly
+            fPath = path;
+        } else {
+            // In-place initialize
+            this->setType(Type::kPath);
+            new (&fPath) SkPath(path);
+        }
+        // Must also set these since we didn't call reset() like other setX functions.
+        this->setPathWindingParams(kDefaultDir, kDefaultStart);
+        fInverted = path.isInverseFillType();
+    }
+    void reset() {
+        this->setType(Type::kEmpty);
+    }
+
+    // Flags that enable more aggressive, "destructive" simplifications to the geometry
+    enum SimplifyFlags : unsigned {
+        // If set, it is assumed the original shape would have been implicitly filled when drawn or
+        // clipped, so simpler shape types that are closed can still be considered. Shapes with
+        // 0 area (i.e. points and lines) can be turned into empty.
+        kSimpleFill_Flag    = 0b001,
+        // If set, simplifications that would impact a directional stroke or path effect can still
+        // be taken (e.g. dir and start are not required, arcs can be converted to ovals).
+        kIgnoreWinding_Flag = 0b010,
+        // If set, the geometry will be updated to have sorted coordinates (rects, lines), modulated
+        // sweep angles (arcs).
+        kMakeCanonical_Flag = 0b100,
+
+        kAll_Flags          = 0b111
+    };
+    // Returns true if the shape was originally closed based on type (or detected type within a
+    // path), even if the final simplification results in a point, line, or empty.
+    bool simplify(unsigned flags = kAll_Flags);
+
+    // True if the given bounding box is completely inside the shape.
+    bool contains(const SkRect& rect) const;
+
+    // True if the underlying geometry represents a closed shape, without the need for an
+    // implicit close (note that if simplified earlier with 'simpleFill' = true, a shape that was
+    // not closed may become closed).
+    bool closed() const;
+
+    // True if the underlying shape is known to be convex, assuming no other styles. If 'simpleFill'
+    // is true, it is assumed the contours will be implicitly closed when drawn or used.
+    bool convex(bool simpleFill = true) const;
+
+    // The bounding box of the shape.
+    SkRect bounds() const;
+
+    // The segment masks that describe the shape, were it to be converted to an SkPath
+    uint32_t segmentMask() const;
+
+    // Convert the shape into a path that describes the same geometry.
+    void asPath(SkPath* out, bool simpleFill = true) const;
+
+private:
+
+    void setType(Type type) {
+        if (this->isPath() && type != Type::kPath) {
+            fPath.~SkPath();
+        }
+        fType = type;
+    }
+
+    void reset(Type type) {
+        this->setType(type);
+        this->setPathWindingParams(kDefaultDir, kDefaultStart);
+        this->setInverted(false);
+    }
+
+    // Paths and arcs are root shapes, another type will never simplify to them, so they do
+    // not take the geometry to simplify as an argument. Since they are root shapes, they also
+    // return whether or not they were originally closed before being simplified.
+    bool simplifyPath(unsigned flags);
+    bool simplifyArc(unsigned flags);
+
+    // The simpler type classes do take the geometry because it may represent an in-progress
+    // simplification that hasn't been set on the GrShape yet. The simpler types do not report
+    // whether or not they were closed because it's implicit in their type.
+    void simplifyLine(const SkPoint& p1, const SkPoint& p2, unsigned flags);
+    void simplifyPoint(const SkPoint& point, unsigned flags);
+
+    // RRects and rects care about winding for path effects and will set the path winding state
+    // of the shape as well.
+    void simplifyRRect(const SkRRect& rrect, SkPathDirection dir, unsigned start, unsigned flags);
+    void simplifyRect(const SkRect& rect, SkPathDirection dir, unsigned start, unsigned flags);
+
+    union {
+        SkPoint       fPoint;
+        SkRect        fRect;
+        SkRRect       fRRect;
+        SkPath        fPath;
+        GrArc         fArc;
+        GrLineSegment fLine;
+    };
+
+    Type            fType = Type::kEmpty;
+    uint8_t         fStart; // Restricted to rrects and simpler, so this will be < 8
+    bool            fCW;
+    bool            fInverted;
+};
+
+#endif
diff --git a/src/gpu/geometry/GrStyledShape.cpp b/src/gpu/geometry/GrStyledShape.cpp
index d81b99e..a3703a0 100644
--- a/src/gpu/geometry/GrStyledShape.cpp
+++ b/src/gpu/geometry/GrStyledShape.cpp
@@ -12,26 +12,10 @@
 #include <utility>
 
 GrStyledShape& GrStyledShape::operator=(const GrStyledShape& that) {
-    fStyle = that.fStyle;
-    this->changeType(that.fType, Type::kPath == that.fType ? &that.path() : nullptr);
-    switch (fType) {
-        case Type::kEmpty:
-            break;
-        case Type::kInvertedEmpty:
-            break;
-        case Type::kRRect:
-            fRRectData = that.fRRectData;
-            break;
-        case Type::kArc:
-            fArcData = that.fArcData;
-            break;
-        case Type::kLine:
-            fLineData = that.fLineData;
-            break;
-        case Type::kPath:
-            fPathData.fGenID = that.fPathData.fGenID;
-            break;
-    }
+    fShape      = that.fShape;
+    fStyle      = that.fStyle;
+    fGenID      = that.fGenID;
+
     fInheritedKey.reset(that.fInheritedKey.count());
     sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(),
                       sizeof(uint32_t) * fInheritedKey.count());
@@ -43,20 +27,6 @@
     return *this;
 }
 
-static bool flip_inversion(bool originalIsInverted, GrStyledShape::FillInversion inversion) {
-    switch (inversion) {
-        case GrStyledShape::FillInversion::kPreserve:
-            return false;
-        case GrStyledShape::FillInversion::kFlip:
-            return true;
-        case GrStyledShape::FillInversion::kForceInverted:
-            return !originalIsInverted;
-        case GrStyledShape::FillInversion::kForceNoninverted:
-            return originalIsInverted;
-    }
-    return false;
-}
-
 static bool is_inverted(bool originalIsInverted, GrStyledShape::FillInversion inversion) {
     switch (inversion) {
         case GrStyledShape::FillInversion::kPreserve:
@@ -72,102 +42,37 @@
 }
 
 GrStyledShape GrStyledShape::MakeFilled(const GrStyledShape& original, FillInversion inversion) {
-    if (original.style().isSimpleFill() && !flip_inversion(original.inverseFilled(), inversion)) {
+    bool newIsInverted = is_inverted(original.fShape.inverted(), inversion);
+    if (original.style().isSimpleFill() && newIsInverted == original.fShape.inverted()) {
         // By returning the original rather than falling through we can preserve any inherited style
         // key. Otherwise, we wipe it out below since the style change invalidates it.
         return original;
     }
     GrStyledShape result;
+    SkASSERT(result.fStyle.isSimpleFill());
     if (original.fInheritedPathForListeners.isValid()) {
         result.fInheritedPathForListeners.set(*original.fInheritedPathForListeners.get());
     }
-    switch (original.fType) {
-        case Type::kRRect:
-            result.fType = original.fType;
-            result.fRRectData.fRRect = original.fRRectData.fRRect;
-            result.fRRectData.fDir = kDefaultRRectDir;
-            result.fRRectData.fStart = kDefaultRRectStart;
-            result.fRRectData.fInverted = is_inverted(original.fRRectData.fInverted, inversion);
-            break;
-        case Type::kArc:
-            result.fType = original.fType;
-            result.fArcData.fOval = original.fArcData.fOval;
-            result.fArcData.fStartAngleDegrees = original.fArcData.fStartAngleDegrees;
-            result.fArcData.fSweepAngleDegrees = original.fArcData.fSweepAngleDegrees;
-            result.fArcData.fUseCenter = original.fArcData.fUseCenter;
-            result.fArcData.fInverted = is_inverted(original.fArcData.fInverted, inversion);
-            break;
-        case Type::kLine:
-            // Lines don't fill.
-            if (is_inverted(original.fLineData.fInverted, inversion)) {
-                result.fType = Type::kInvertedEmpty;
-            } else {
-                result.fType = Type::kEmpty;
-            }
-            break;
-        case Type::kEmpty:
-            result.fType = is_inverted(false, inversion) ? Type::kInvertedEmpty :  Type::kEmpty;
-            break;
-        case Type::kInvertedEmpty:
-            result.fType = is_inverted(true, inversion) ? Type::kInvertedEmpty :  Type::kEmpty;
-            break;
-        case Type::kPath:
-            result.initType(Type::kPath, &original.fPathData.fPath);
-            result.fPathData.fGenID = original.fPathData.fGenID;
-            if (flip_inversion(original.fPathData.fPath.isInverseFillType(), inversion)) {
-                result.fPathData.fPath.toggleInverseFillType();
-            }
-            if (!original.style().isSimpleFill()) {
-                // Going from a non-filled style to fill may allow additional simplifications (e.g.
-                // closing an open rect that wasn't closed in the original shape because it had
-                // stroke style).
-                result.attemptToSimplifyPath();
-            }
-            break;
+
+    result.fShape = original.fShape;
+    result.fGenID = original.fGenID;
+    result.fShape.setInverted(newIsInverted);
+
+    if (!original.style().isSimpleFill()) {
+        // Going from a non-filled style to fill may allow additional simplifications (e.g.
+        // closing an open rect that wasn't closed in the original shape because it had
+        // stroke style).
+        result.simplify();
     }
+
+    // Sanity check that lines/points were converted to empty by the style change
+    SkASSERT((!original.fShape.isLine() && !original.fShape.isPoint()) || result.fShape.isEmpty());
+
     // We don't copy the inherited key since it can contain path effect information that we just
     // stripped.
     return result;
 }
 
-SkRect GrStyledShape::bounds() const {
-    // Bounds where left == bottom or top == right can indicate a line or point shape. We return
-    // inverted bounds for a truly empty shape.
-    static constexpr SkRect kInverted = SkRect::MakeLTRB(1, 1, -1, -1);
-    switch (fType) {
-        case Type::kEmpty:
-            return kInverted;
-        case Type::kInvertedEmpty:
-            return kInverted;
-        case Type::kLine: {
-            SkRect bounds;
-            if (fLineData.fPts[0].fX < fLineData.fPts[1].fX) {
-                bounds.fLeft = fLineData.fPts[0].fX;
-                bounds.fRight = fLineData.fPts[1].fX;
-            } else {
-                bounds.fLeft = fLineData.fPts[1].fX;
-                bounds.fRight = fLineData.fPts[0].fX;
-            }
-            if (fLineData.fPts[0].fY < fLineData.fPts[1].fY) {
-                bounds.fTop = fLineData.fPts[0].fY;
-                bounds.fBottom = fLineData.fPts[1].fY;
-            } else {
-                bounds.fTop = fLineData.fPts[1].fY;
-                bounds.fBottom = fLineData.fPts[0].fY;
-            }
-            return bounds;
-        }
-        case Type::kRRect:
-            return fRRectData.fRRect.getBounds();
-        case Type::kArc:
-            // Could make this less conservative by looking at angles.
-            return fArcData.fOval;
-        case Type::kPath:
-            return this->path().getBounds();
-    }
-    SK_ABORT("Unknown shape type");
-}
-
 SkRect GrStyledShape::styledBounds() const {
     if (this->isEmpty() && !fStyle.hasNonDashPathEffect()) {
         return SkRect::MakeEmpty();
@@ -189,9 +94,9 @@
 
     static_assert(sizeof(SkPoint) == 2 * sizeof(uint32_t));
     static_assert(sizeof(SkScalar) == sizeof(uint32_t));
-    // 2 is for the verb cnt and a fill type. Each verb is a byte but we'll pad the verb data out to
+    // 1 is for the verb count. Each verb is a byte but we'll pad the verb data out to
     // a uint32_t length.
-    return 2 + (SkAlign4(verbCnt) >> 2) + 2 * pointCnt + conicWeightCnt;
+    return 1 + (SkAlign4(verbCnt) >> 2) + 2 * pointCnt + conicWeightCnt;
 }
 
 // Writes the path data key into the passed pointer.
@@ -203,7 +108,6 @@
     const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);
     SkASSERT(verbCnt <= GrStyledShape::kMaxKeyFromDataVerbCnt);
     SkASSERT(pointCnt && verbCnt);
-    *key++ = (uint32_t)path.getFillType();
     *key++ = verbCnt;
     memcpy(key, SkPathPriv::VerbData(path), verbCnt * sizeof(uint8_t));
     int verbKeySize = SkAlign4(verbCnt);
@@ -225,37 +129,45 @@
     if (fInheritedKey.count()) {
         return fInheritedKey.count();
     }
-    switch (fType) {
-        case Type::kEmpty:
-            return 1;
-        case Type::kInvertedEmpty:
-            return 1;
-        case Type::kRRect:
-            SkASSERT(!fInheritedKey.count());
+
+    int count = 1; // Every key has the state flags from the GrShape
+    switch(fShape.type()) {
+        case GrShape::Type::kPoint:
+            static_assert(0 == sizeof(SkPoint) % sizeof(uint32_t));
+            count += sizeof(SkPoint) / sizeof(uint32_t);
+            break;
+        case GrShape::Type::kRect:
+            static_assert(0 == sizeof(SkRect) % sizeof(uint32_t));
+            count += sizeof(SkRect) / sizeof(uint32_t);
+            break;
+        case GrShape::Type::kRRect:
             static_assert(0 == SkRRect::kSizeInMemory % sizeof(uint32_t));
-            // + 1 for the direction, start index, and inverseness.
-            return SkRRect::kSizeInMemory / sizeof(uint32_t) + 1;
-        case Type::kArc:
-            SkASSERT(!fInheritedKey.count());
-            static_assert(0 == sizeof(fArcData) % sizeof(uint32_t));
-            return sizeof(fArcData) / sizeof(uint32_t);
-        case Type::kLine:
-            static_assert(2 * sizeof(uint32_t) == sizeof(SkPoint));
-            // 4 for the end points and 1 for the inverseness
-            return 5;
-        case Type::kPath: {
-            if (0 == fPathData.fGenID) {
-                return -1;
+            count += SkRRect::kSizeInMemory / sizeof(uint32_t);
+            break;
+        case GrShape::Type::kArc:
+            static_assert(0 == sizeof(GrArc) % sizeof(uint32_t));
+            count += sizeof(GrArc) / sizeof(uint32_t);
+            break;
+        case GrShape::Type::kLine:
+            static_assert(0 == sizeof(GrLineSegment) % sizeof(uint32_t));
+            count += sizeof(GrLineSegment) / sizeof(uint32_t);
+            break;
+        case GrShape::Type::kPath: {
+            if (0 == fGenID) {
+                return -1; // volatile, so won't be keyed
             }
-            int dataKeySize = path_key_from_data_size(fPathData.fPath);
+            int dataKeySize = path_key_from_data_size(fShape.path());
             if (dataKeySize >= 0) {
-                return dataKeySize;
+                count += dataKeySize;
+            } else {
+                count++; // Just adds the gen ID.
             }
-            // The key is the path ID and fill type.
-            return 2;
-        }
+            break; }
+        default:
+            // else it's empty, which just needs the state flags for its key
+            SkASSERT(fShape.isEmpty());
     }
-    SK_ABORT("Should never get here.");
+    return count;
 }
 
 void GrStyledShape::writeUnstyledKey(uint32_t* key) const {
@@ -265,43 +177,57 @@
         memcpy(key, fInheritedKey.get(), sizeof(uint32_t) * fInheritedKey.count());
         SkDEBUGCODE(key += fInheritedKey.count();)
     } else {
-        switch (fType) {
-            case Type::kEmpty:
-                *key++ = 1;
-                break;
-            case Type::kInvertedEmpty:
-                *key++ = 2;
-                break;
-            case Type::kRRect:
-                fRRectData.fRRect.writeToMemory(key);
-                key += SkRRect::kSizeInMemory / sizeof(uint32_t);
-                *key = (fRRectData.fDir == SkPathDirection::kCCW) ? (1 << 31) : 0;
-                *key |= fRRectData.fInverted ? (1 << 30) : 0;
-                *key++ |= fRRectData.fStart;
-                SkASSERT(fRRectData.fStart < 8);
-                break;
-            case Type::kArc:
-                memcpy(key, &fArcData, sizeof(fArcData));
-                key += sizeof(fArcData) / sizeof(uint32_t);
-                break;
-            case Type::kLine:
-                memcpy(key, fLineData.fPts, 2 * sizeof(SkPoint));
-                key += 4;
-                *key++ = fLineData.fInverted ? 1 : 0;
-                break;
-            case Type::kPath: {
-                SkASSERT(fPathData.fGenID);
-                int dataKeySize = path_key_from_data_size(fPathData.fPath);
+        // Dir and start are only used for rect and rrect shapes, so are not included in other
+        // shape type keys. Make sure that they are the defaults for other shapes so it doesn't
+        // matter that we universally include them in the flag key value.
+        SkASSERT((fShape.isRect() || fShape.isRRect()) ||
+                 (fShape.dir() == GrShape::kDefaultDir &&
+                  fShape.startIndex() == GrShape::kDefaultStart));
+
+        // Every key starts with the state from the GrShape (this includes path fill type,
+        // and any tracked winding, start, inversion, as well as the class of geometry).
+        *key++ = fShape.stateKey();
+
+        switch(fShape.type()) {
+            case GrShape::Type::kPath: {
+                SkASSERT(fGenID != 0);
+                // Ensure that the path's inversion matches our state so that the path's key suffices.
+                SkASSERT(fShape.inverted() == fShape.path().isInverseFillType());
+
+                int dataKeySize = path_key_from_data_size(fShape.path());
                 if (dataKeySize >= 0) {
-                    write_path_key_from_data(fPathData.fPath, key);
+                    write_path_key_from_data(fShape.path(), key);
                     return;
+                } else {
+                    *key++ = fGenID;
                 }
-                *key++ = fPathData.fGenID;
-                // We could canonicalize the fill rule for paths that don't differentiate between
-                // even/odd or winding fill (e.g. convex).
-                *key++ = (uint32_t)this->path().getFillType();
+                break; }
+            case GrShape::Type::kPoint:
+                memcpy(key, &fShape.point(), sizeof(SkPoint));
+                key += sizeof(SkPoint) / sizeof(uint32_t);
                 break;
-            }
+            case GrShape::Type::kRect:
+                memcpy(key, &fShape.rect(), sizeof(SkRect));
+                key += sizeof(SkRect) / sizeof(uint32_t);
+                break;
+            case GrShape::Type::kRRect:
+                fShape.rrect().writeToMemory(key);
+                key += SkRRect::kSizeInMemory / sizeof(uint32_t);
+                break;
+            case GrShape::Type::kArc:
+                // Write dense floats first
+                memcpy(key, &fShape.arc(), sizeof(SkRect) + 2 * sizeof(float));
+                key += (sizeof(GrArc) / sizeof(uint32_t) - 1);
+                // Then write the final bool as an int, to make sure upper bits are set
+                *key++ = fShape.arc().fUseCenter ? 1 : 0;
+                break;
+            case GrShape::Type::kLine:
+                memcpy(key, &fShape.line(), sizeof(GrLineSegment));
+                key += sizeof(GrLineSegment) / sizeof(uint32_t);
+                break;
+            default:
+                // Nothing other than the flag state is needed in the key for an empty shape
+                SkASSERT(fShape.isEmpty());
         }
     }
     SkASSERT(key - origKey == this->unstyledKeySize());
@@ -311,7 +237,7 @@
                                     SkScalar scale) {
     SkASSERT(!fInheritedKey.count());
     // If the output shape turns out to be simple, then we will just use its geometric key
-    if (Type::kPath == fType) {
+    if (fShape.isPath()) {
         // We want ApplyFullStyle(ApplyPathEffect(shape)) to have the same key as
         // ApplyFullStyle(shape).
         // The full key is structured as (geo,path_effect,stroke).
@@ -324,7 +250,7 @@
             parentCnt = parent.unstyledKeySize();
             if (parentCnt < 0) {
                 // The parent's geometry has no key so we will have no key.
-                fPathData.fGenID = 0;
+                fGenID = 0;
                 return;
             }
         }
@@ -339,7 +265,7 @@
         if (styleCnt < 0) {
             // The style doesn't allow a key, set the path gen ID to 0 so that we fail when
             // we try to get a key for the shape.
-            fPathData.fGenID = 0;
+            fGenID = 0;
             return;
         }
         fInheritedKey.reset(parentCnt + styleCnt);
@@ -360,8 +286,8 @@
 const SkPath* GrStyledShape::originalPathForListeners() const {
     if (fInheritedPathForListeners.isValid()) {
         return fInheritedPathForListeners.get();
-    } else if (Type::kPath == fType && !fPathData.fPath.isVolatile()) {
-        return &fPathData.fPath;
+    } else if (fShape.isPath() && !fShape.path().isVolatile()) {
+        return &fShape.path();
     }
     return nullptr;
 }
@@ -376,38 +302,16 @@
                                      SkScalar sweepAngleDegrees, bool useCenter,
                                      const GrStyle& style) {
     GrStyledShape result;
-    result.changeType(Type::kArc);
-    result.fArcData.fOval = oval;
-    result.fArcData.fStartAngleDegrees = startAngleDegrees;
-    result.fArcData.fSweepAngleDegrees = sweepAngleDegrees;
-    result.fArcData.fUseCenter = useCenter;
-    result.fArcData.fInverted = false;
+    result.fShape.setArc({oval.makeSorted(), startAngleDegrees, sweepAngleDegrees, useCenter});
     result.fStyle = style;
-    result.attemptToSimplifyArc();
+    result.simplify();
     return result;
 }
 
-GrStyledShape::GrStyledShape(const GrStyledShape& that) : fStyle(that.fStyle) {
-    const SkPath* thatPath = Type::kPath == that.fType ? &that.fPathData.fPath : nullptr;
-    this->initType(that.fType, thatPath);
-    switch (fType) {
-        case Type::kEmpty:
-            break;
-        case Type::kInvertedEmpty:
-            break;
-        case Type::kRRect:
-            fRRectData = that.fRRectData;
-            break;
-        case Type::kArc:
-            fArcData = that.fArcData;
-            break;
-        case Type::kLine:
-            fLineData = that.fLineData;
-            break;
-        case Type::kPath:
-            fPathData.fGenID = that.fPathData.fGenID;
-            break;
-    }
+GrStyledShape::GrStyledShape(const GrStyledShape& that)
+        : fShape(that.fShape)
+        , fStyle(that.fStyle)
+        , fGenID(that.fGenID) {
     fInheritedKey.reset(that.fInheritedKey.count());
     sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(),
                       sizeof(uint32_t) * fInheritedKey.count());
@@ -423,7 +327,6 @@
     // stroke of a rect).
     if (!parent.style().applies() ||
         (GrStyle::Apply::kPathEffectOnly == apply && !parent.style().pathEffect())) {
-        this->initType(Type::kEmpty);
         *this = parent;
         return;
     }
@@ -432,12 +335,14 @@
     SkTLazy<SkPath> tmpPath;
     const GrStyledShape* parentForKey = &parent;
     SkTLazy<GrStyledShape> tmpParent;
-    this->initType(Type::kPath);
-    fPathData.fGenID = 0;
+
+    // Start out as an empty path that is filled in by the applied style
+    fShape.setPath(SkPath());
+
     if (pe) {
         const SkPath* srcForPathEffect;
-        if (parent.fType == Type::kPath) {
-            srcForPathEffect = &parent.path();
+        if (parent.fShape.isPath()) {
+            srcForPathEffect = &parent.fShape.path();
         } else {
             srcForPathEffect = tmpPath.init();
             parent.asPath(tmpPath.get());
@@ -445,7 +350,7 @@
         // Should we consider bounds? Would have to include in key, but it'd be nice to know
         // if the bounds actually modified anything before including in key.
         SkStrokeRec strokeRec = parent.fStyle.strokeRec();
-        if (!parent.fStyle.applyPathEffectToPath(&this->path(), &strokeRec, *srcForPathEffect,
+        if (!parent.fStyle.applyPathEffectToPath(&fShape.path(), &strokeRec, *srcForPathEffect,
                                                  scale)) {
             tmpParent.init(*srcForPathEffect, GrStyle(strokeRec, nullptr));
             *this = tmpParent.get()->applyStyle(apply, scale);
@@ -462,7 +367,7 @@
             // We detect that case here and change parentForKey to a temporary that represents
             // the simpler shape so that applying both path effect and the strokerec all at
             // once produces the same key.
-            tmpParent.init(this->path(), GrStyle(strokeRec, nullptr));
+            tmpParent.init(fShape.path(), GrStyle(strokeRec, nullptr));
             tmpParent.get()->setInheritedKey(parent, GrStyle::Apply::kPathEffectOnly, scale);
             if (!tmpPath.isValid()) {
                 tmpPath.init();
@@ -471,7 +376,7 @@
             SkStrokeRec::InitStyle fillOrHairline;
             // The parent shape may have simplified away the strokeRec, check for that here.
             if (tmpParent.get()->style().applies()) {
-                SkAssertResult(tmpParent.get()->style().applyToPath(&this->path(), &fillOrHairline,
+                SkAssertResult(tmpParent.get()->style().applyToPath(&fShape.path(), &fillOrHairline,
                                                                     *tmpPath.get(), scale));
             } else if (tmpParent.get()->style().isSimpleFill()) {
                 fillOrHairline = SkStrokeRec::kFill_InitStyle;
@@ -486,8 +391,8 @@
         }
     } else {
         const SkPath* srcForParentStyle;
-        if (parent.fType == Type::kPath) {
-            srcForParentStyle = &parent.path();
+        if (parent.fShape.isPath()) {
+            srcForParentStyle = &parent.fShape.path();
         } else {
             srcForParentStyle = tmpPath.init();
             parent.asPath(tmpPath.get());
@@ -495,275 +400,361 @@
         SkStrokeRec::InitStyle fillOrHairline;
         SkASSERT(parent.fStyle.applies());
         SkASSERT(!parent.fStyle.pathEffect());
-        SkAssertResult(parent.fStyle.applyToPath(&this->path(), &fillOrHairline, *srcForParentStyle,
-                                                 scale));
+        SkAssertResult(parent.fStyle.applyToPath(&fShape.path(), &fillOrHairline,
+                                                 *srcForParentStyle, scale));
         fStyle.resetToInitStyle(fillOrHairline);
     }
+
     if (parent.fInheritedPathForListeners.isValid()) {
         fInheritedPathForListeners.set(*parent.fInheritedPathForListeners.get());
-    } else if (Type::kPath == parent.fType && !parent.fPathData.fPath.isVolatile()) {
-        fInheritedPathForListeners.set(parent.fPathData.fPath);
+    } else if (parent.fShape.isPath() && !parent.fShape.path().isVolatile()) {
+        fInheritedPathForListeners.set(parent.fShape.path());
     }
-    this->attemptToSimplifyPath();
+    this->simplify();
     this->setInheritedKey(*parentForKey, apply, scale);
 }
 
-void GrStyledShape::attemptToSimplifyPath() {
-    SkRect rect;
-    SkRRect rrect;
-    SkPathDirection rrectDir;
-    unsigned rrectStart;
-    bool inverted = this->path().isInverseFillType();
-    SkPoint pts[2];
-    if (this->path().isEmpty()) {
-        // Dashing ignores inverseness skbug.com/5421.
-        this->changeType(inverted && !this->style().isDashed() ? Type::kInvertedEmpty
-                                                               : Type::kEmpty);
-    } else if (this->path().isLine(pts)) {
-        this->changeType(Type::kLine);
-        fLineData.fPts[0] = pts[0];
-        fLineData.fPts[1] = pts[1];
-        fLineData.fInverted = inverted;
-    } else if (SkPathPriv::IsRRect(this->path(), &rrect, &rrectDir, &rrectStart)) {
-        this->changeType(Type::kRRect);
-        fRRectData.fRRect = rrect;
-        fRRectData.fDir = rrectDir;
-        fRRectData.fStart = rrectStart;
-        fRRectData.fInverted = inverted;
-        SkASSERT(!fRRectData.fRRect.isEmpty());
-    } else if (SkPathPriv::IsOval(this->path(), &rect, &rrectDir, &rrectStart)) {
-        this->changeType(Type::kRRect);
-        fRRectData.fRRect.setOval(rect);
-        fRRectData.fDir = rrectDir;
-        fRRectData.fInverted = inverted;
-        // convert from oval indexing to rrect indexiing.
-        fRRectData.fStart = 2 * rrectStart;
-    } else if (SkPathPriv::IsSimpleClosedRect(this->path(), &rect, &rrectDir, &rrectStart)) {
-        this->changeType(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().
-        fRRectData.fRRect.setRect(rect);
-        fRRectData.fInverted = inverted;
-        fRRectData.fDir = rrectDir;
-        // convert from rect indexing to rrect indexiing.
-        fRRectData.fStart = 2 * rrectStart;
-    } else if (!this->style().hasPathEffect()) {
-        bool closed;
-        if (this->path().isRect(&rect, &closed, nullptr)) {
-            if (closed || this->style().isSimpleFill()) {
-                this->changeType(Type::kRRect);
-                fRRectData.fRRect.setRect(rect);
-                // Since there is no path effect the dir and start index is immaterial.
-                fRRectData.fDir = kDefaultRRectDir;
-                fRRectData.fStart = kDefaultRRectStart;
-                // There isn't dashing so we will have to preserver inverseness.
-                fRRectData.fInverted = inverted;
+bool GrStyledShape::asRRect(SkRRect* rrect, SkPathDirection* dir, unsigned* start,
+                            bool* inverted) const {
+    if (!fShape.isRRect() && !fShape.isRect()) {
+        return false;
+    }
+
+    // Sanity check here, if we don't have a path effect on the style, we should have passed
+    // appropriate flags to GrShape::simplify() to have reset these parameters.
+    SkASSERT(fStyle.hasPathEffect() || (fShape.dir() == GrShape::kDefaultDir &&
+                                        fShape.startIndex() == GrShape::kDefaultStart));
+
+    // If the shape is a regular rect, map to round rect winding parameters, including accounting
+    // for the automatic sorting of edges that SkRRect::MakeRect() performs.
+    if (fShape.isRect()) {
+        if (rrect) {
+            *rrect = SkRRect::MakeRect(fShape.rect());
+        }
+        // Don't bother mapping these if we don't have a path effect, however.
+        if (!fStyle.hasPathEffect()) {
+            if (dir) {
+                *dir = GrShape::kDefaultDir;
+            }
+            if (start) {
+                *start = GrShape::kDefaultStart;
+            }
+        } else {
+            // 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 and direction.
+            SkPathDirection rectDir = fShape.dir();
+            unsigned rectStart = fShape.startIndex();
+
+            if (fShape.rect().fLeft > fShape.rect().fRight) {
+                // Toggle direction, and modify index by mapping through the array
+                static const unsigned kMapping[] = {1, 0, 3, 2};
+                rectDir = rectDir == SkPathDirection::kCCW ? SkPathDirection::kCW
+                                                           : SkPathDirection::kCCW;
+                rectStart = kMapping[rectStart];
+            }
+            if (fShape.rect().fTop > fShape.rect().fBottom) {
+                // Toggle direction and map index by 3 - start
+                // NOTE: if we earlier flipped for X as well, this results in no net direction
+                // change and effectively flipping the start index to the diagonal corners of the
+                // rect (matching what we'd expect for a rect with both X and Y flipped).
+                rectDir = rectDir == SkPathDirection::kCCW ? SkPathDirection::kCW
+                                                           : SkPathDirection::kCCW;
+                rectStart = 3 - rectStart;
+            }
+
+            if (dir) {
+                *dir = rectDir;
+            }
+            if (start) {
+                // Convert to round rect indexing
+                *start = 2 * rectStart;
+            }
+        }
+    } else {
+        // Straight forward export
+        if (rrect) {
+            *rrect = fShape.rrect();
+        }
+        if (dir) {
+            *dir = fShape.dir();
+        }
+        if (start) {
+            *start = fShape.startIndex();
+            // Canonicalize the index if the rrect is an oval, which GrShape doesn't treat special
+            // but we do for dashing placement
+            if (fShape.rrect().isOval()) {
+                *start &= 0b110;
             }
         }
     }
-    if (Type::kPath != fType) {
+
+    if (inverted) {
+        *inverted = fShape.inverted();
+    }
+
+    return true;
+}
+
+bool GrStyledShape::asLine(SkPoint pts[2], bool* inverted) const {
+    if (!fShape.isLine()) {
+        return false;
+    }
+
+    if (pts) {
+        pts[0] = fShape.line().fP1;
+        pts[1] = fShape.line().fP2;
+    }
+    if (inverted) {
+        *inverted = fShape.inverted();
+    }
+    return true;
+}
+
+bool GrStyledShape::asNestedRects(SkRect rects[2]) const {
+    if (!fShape.isPath()) {
+        return false;
+    }
+
+    // TODO: it would be better two store DRRects natively in the shape rather than converting
+    // them to a path and then reextracting the nested rects
+    if (fShape.path().isInverseFillType()) {
+        return false;
+    }
+
+    SkPathDirection dirs[2];
+    if (!SkPathPriv::IsNestedFillRects(fShape.path(), rects, dirs)) {
+        return false;
+    }
+
+    if (SkPathFillType::kWinding == fShape.path().getFillType() && dirs[0] == dirs[1]) {
+        // The two rects need to be wound opposite to each other
+        return false;
+    }
+
+    // Right now, nested rects where the margin is not the same width
+    // all around do not render correctly
+    const SkScalar* outer = rects[0].asScalars();
+    const SkScalar* inner = rects[1].asScalars();
+
+    bool allEq = true;
+
+    SkScalar margin = SkScalarAbs(outer[0] - inner[0]);
+    bool allGoE1 = margin >= SK_Scalar1;
+
+    for (int i = 1; i < 4; ++i) {
+        SkScalar temp = SkScalarAbs(outer[i] - inner[i]);
+        if (temp < SK_Scalar1) {
+            allGoE1 = false;
+        }
+        if (!SkScalarNearlyEqual(margin, temp)) {
+            allEq = false;
+        }
+    }
+
+    return allEq || allGoE1;
+}
+
+void GrStyledShape::simplify() {
+    // Dashing ignores inverseness skbug.com/5421.
+    bool inverted = !fStyle.isDashed() && fShape.inverted();
+
+    unsigned simplifyFlags = 0;
+    if (fStyle.isSimpleFill()) {
+        simplifyFlags = GrShape::kAll_Flags;
+    } else if (!fStyle.hasPathEffect()) {
+        // Everything but arcs with caps that might extend beyond the oval edge can ignore winding
+        if (!fShape.isArc() || fStyle.strokeRec().getCap() == SkPaint::kButt_Cap) {
+            simplifyFlags |= GrShape::kIgnoreWinding_Flag;
+        }
+        simplifyFlags |= GrShape::kMakeCanonical_Flag;
+    } // else if there's a path effect, every destructive simplification is disabledd
+
+    // Remember if the original shape was closed; in the event we simplify to a point or line
+    // because of degenerate geometry, we need to update joins and caps.
+    bool wasClosed = fShape.simplify(simplifyFlags);
+
+    if (fShape.isPath()) {
+        // The shape remains a path, so configure the gen ID and canonicalize fill type if possible
+        if (fInheritedKey.count() || fShape.path().isVolatile()) {
+            fGenID = 0;
+        } else {
+            fGenID = fShape.path().getGenerationID();
+        }
+        if (!fStyle.hasNonDashPathEffect() &&
+            (fStyle.strokeRec().getStyle() == SkStrokeRec::kStroke_Style ||
+             fStyle.strokeRec().getStyle() == SkStrokeRec::kHairline_Style ||
+             fShape.path().isConvex())) {
+            // Stroke styles don't differentiate between winding and even/odd. There is no
+            // distinction between even/odd and non-zero winding count for convex paths.
+            // Moreover, dashing ignores inverseness (skbug.com/5421)
+            fShape.path().setFillType(GrShape::kDefaultFillType);
+        }
+    } else {
         fInheritedKey.reset(0);
         // Whenever we simplify to a non-path, break the chain so we no longer refer to the
         // original path. This prevents attaching genID listeners to temporary paths created when
         // drawing simple shapes.
         fInheritedPathForListeners.reset();
-        if (Type::kRRect == fType) {
-            this->attemptToSimplifyRRect();
-        } else if (Type::kLine == fType) {
-            this->attemptToSimplifyLine();
+
+        // Further simplifications to the shape based on the style
+        this->simplifyStroke(wasClosed);
+    }
+
+    // Restore invertedness after any modifications were made to the shape type
+    fShape.setInverted(inverted);
+    SkASSERT(!fShape.isPath() || inverted == fShape.path().isInverseFillType());
+}
+
+void GrStyledShape::simplifyStroke(bool originallyClosed) {
+    // For stroke+filled rects, a mitered shape becomes a larger rect and a rounded shape
+    // becomes a round rect.
+    if (!fStyle.hasPathEffect() && fShape.isRect() &&
+        fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
+        if (fStyle.strokeRec().getJoin() == SkPaint::kBevel_Join ||
+            (fStyle.strokeRec().getJoin() == SkPaint::kMiter_Join &&
+             fStyle.strokeRec().getMiter() < SK_ScalarSqrt2)) {
+            // Bevel-stroked rect needs path rendering
+            return;
         }
-    } else {
-        if (fInheritedKey.count() || this->path().isVolatile()) {
-            fPathData.fGenID = 0;
+
+        SkScalar r = fStyle.strokeRec().getWidth() / 2;
+        fShape.rect().outset(r, r);
+        if (fStyle.strokeRec().getJoin() == SkPaint::kRound_Join) {
+            // There's no dashing to worry about if we got here, so it's okay that this resets
+            // winding parameters
+            fShape.setRRect(SkRRect::MakeRectXY(fShape.rect(), r, r));
+        }
+        fStyle = GrStyle::SimpleFill();
+        return;
+    }
+
+    // Otherwise, if we're a point or a line, we might be able to explicitly apply some of the
+    // stroking (and even some of the dashing). Any other shape+style is too complicated to reduce.
+    if ((!fShape.isPoint() && !fShape.isLine()) || fStyle.hasNonDashPathEffect() ||
+        fStyle.strokeRec().isHairlineStyle()) {
+        return;
+    }
+
+    if (fStyle.isDashed()) {
+        // For dashing a point, if the first interval is on, we can drop the dash and just draw
+        // the caps. For dashing a line, if every off interval is 0 length, its a stroke.
+        bool dropDash = false;
+        if (fShape.isPoint()) {
+            dropDash = fStyle.dashIntervalCnt() > 0 &&
+                       SkToBool(fStyle.dashIntervals()[0]);
         } else {
-            fPathData.fGenID = this->path().getGenerationID();
-        }
-        if (!this->style().hasNonDashPathEffect()) {
-            if (this->style().strokeRec().getStyle() == SkStrokeRec::kStroke_Style ||
-                this->style().strokeRec().getStyle() == SkStrokeRec::kHairline_Style) {
-                // Stroke styles don't differentiate between winding and even/odd.
-                // Moreover, dashing ignores inverseness (skbug.com/5421)
-                bool inverse = !this->style().isDashed() && this->path().isInverseFillType();
-                if (inverse) {
-                    this->path().setFillType(kDefaultPathInverseFillType);
-                } else {
-                    this->path().setFillType(kDefaultPathFillType);
-                }
-            } else if (this->path().isConvex()) {
-                // There is no distinction between even/odd and non-zero winding count for convex
-                // paths.
-                if (this->path().isInverseFillType()) {
-                    this->path().setFillType(kDefaultPathInverseFillType);
-                } else {
-                    this->path().setFillType(kDefaultPathFillType);
+            dropDash = true;
+            for (int i = 1; i < fStyle.dashIntervalCnt(); i += 2) {
+                if (SkToBool(fStyle.dashIntervals()[i])) {
+                    // An off interval has non-zero length so this won't convert to a simple line
+                    dropDash = false;
+                    break;
                 }
             }
         }
-    }
-}
 
-void GrStyledShape::attemptToSimplifyRRect() {
-    SkASSERT(Type::kRRect == fType);
-    SkASSERT(!fInheritedKey.count());
-    if (fRRectData.fRRect.isEmpty()) {
-        // An empty filled rrect is equivalent to a filled empty path with inversion preserved.
-        if (fStyle.isSimpleFill()) {
-            fType = fRRectData.fInverted ? Type::kInvertedEmpty : Type::kEmpty;
-            fStyle = GrStyle::SimpleFill();
+        if (!dropDash) {
             return;
         }
-        // Dashing a rrect with no width or height is equivalent to filling an emtpy path.
-        // When skbug.com/7387 is fixed this should be modified or removed as a dashed zero length
-        // line  will produce cap geometry if the effect begins in an "on" interval.
-        if (fStyle.isDashed() && !fRRectData.fRRect.width() && !fRRectData.fRRect.height()) {
-            // Dashing ignores the inverseness (currently). skbug.com/5421.
-            fType = Type::kEmpty;
-            fStyle = GrStyle::SimpleFill();
-            return;
-        }
+        // Fall through to modifying the shape to respect the new stroke geometry
+        fStyle = GrStyle(fStyle.strokeRec(), nullptr);
+        // Since the reduced the line or point after dashing is dependent on the caps of the dashes,
+        // we reset to be unclosed so we don't override the style based on joins later.
+        originallyClosed = false;
     }
-    if (!this->style().hasPathEffect()) {
-        fRRectData.fDir = kDefaultRRectDir;
-        fRRectData.fStart = kDefaultRRectStart;
-    } else if (fStyle.isDashed()) {
-        // Dashing ignores the inverseness (currently). skbug.com/5421
-        fRRectData.fInverted = false;
-        // Possible TODO here: Check whether the dash results in a single arc or line.
-    }
-    // Turn a stroke-and-filled miter rect into a filled rect. TODO: more rrect stroke shortcuts.
-    if (!fStyle.hasPathEffect() &&
-        fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style &&
-        fStyle.strokeRec().getJoin() == SkPaint::kMiter_Join &&
-        fStyle.strokeRec().getMiter() >= SK_ScalarSqrt2 &&
-        fRRectData.fRRect.isRect()) {
-        SkScalar r = fStyle.strokeRec().getWidth() / 2;
-        fRRectData.fRRect = SkRRect::MakeRect(fRRectData.fRRect.rect().makeOutset(r, r));
-        fStyle = GrStyle::SimpleFill();
-    }
-}
 
-void GrStyledShape::attemptToSimplifyLine() {
-    SkASSERT(Type::kLine == fType);
-    SkASSERT(!fInheritedKey.count());
-    if (fStyle.isDashed()) {
-        bool allOffsZero = true;
-        for (int i = 1; i < fStyle.dashIntervalCnt() && allOffsZero; i += 2) {
-            allOffsZero = !fStyle.dashIntervals()[i];
-        }
-        if (allOffsZero && this->attemptToSimplifyStrokedLineToRRect()) {
-            return;
-        }
-        // Dashing ignores inverseness.
-        fLineData.fInverted = false;
+    // At this point, we're a line or point with no path effects. Any fill portion of the style
+    // is empty, so a fill-only style can be empty, and a stroke+fill becomes a stroke.
+    bool strokeAndFilled = false;
+    if (fStyle.isSimpleFill()) {
+        fShape.reset();
         return;
-    } else if (fStyle.hasPathEffect()) {
-        return;
-    }
-    if (fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
-        // Make stroke + fill be stroke since the fill is empty.
+    } else if (fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
+        // Stroke only
         SkStrokeRec rec = fStyle.strokeRec();
         rec.setStrokeStyle(fStyle.strokeRec().getWidth(), false);
         fStyle = GrStyle(rec, nullptr);
-    }
-    if (fStyle.isSimpleFill()) {
-        this->changeType(fLineData.fInverted ? Type::kInvertedEmpty : Type::kEmpty);
-        return;
-    }
-    if (fStyle.strokeRec().getStyle() == SkStrokeRec::kStroke_Style &&
-        this->attemptToSimplifyStrokedLineToRRect()) {
-        return;
-    }
-    // Only path effects could care about the order of the points. Otherwise canonicalize
-    // the point order.
-    SkPoint* pts = fLineData.fPts;
-    if (pts[1].fY < pts[0].fY || (pts[1].fY == pts[0].fY && pts[1].fX < pts[0].fX)) {
-        using std::swap;
-        swap(pts[0], pts[1]);
-    }
-}
-
-void GrStyledShape::attemptToSimplifyArc() {
-    SkASSERT(fType == Type::kArc);
-    SkASSERT(!fArcData.fInverted);
-    if (fArcData.fOval.isEmpty() || !fArcData.fSweepAngleDegrees) {
-        this->changeType(Type::kEmpty);
-        return;
+        strokeAndFilled = true;
     }
 
-    // Assuming no path effect, a filled, stroked, hairline, or stroke-and-filled arc that traverses
-    // the full circle and doesn't use the center point is an oval. Unless it has square or round
-    // caps. They may protrude out of the oval. Round caps can't protrude out of a circle but we're
-    // ignoring that for now.
-    if (fStyle.isSimpleFill() || (!fStyle.pathEffect() && !fArcData.fUseCenter &&
-                                  fStyle.strokeRec().getCap() == SkPaint::kButt_Cap)) {
-        if (fArcData.fSweepAngleDegrees >= 360.f || fArcData.fSweepAngleDegrees <= -360.f) {
-            auto oval = fArcData.fOval;
-            this->changeType(Type::kRRect);
-            this->fRRectData.fRRect.setOval(oval);
-            this->fRRectData.fDir = kDefaultRRectDir;
-            this->fRRectData.fStart = kDefaultRRectStart;
-            this->fRRectData.fInverted = false;
+    // A point or line that was formed by a degenerate closed shape needs its style updated to
+    // reflect the fact that it doesn't actually produce caps.
+    if (originallyClosed) {
+        SkPaint::Cap cap;
+        if (fShape.isLine() && fStyle.strokeRec().getJoin() == SkPaint::kRound_Join) {
+            // As a closed shape, the line moves from a to b and back to a, producing a 180 degree
+            // turn. With round joins, this would make a semi-circle at each end, which is visually
+            // identical to a round cap on the reduced line geometry.
+            cap = SkPaint::kRound_Cap;
+        } else if (fShape.isPoint() && fStyle.strokeRec().getJoin() == SkPaint::kMiter_Join &&
+                   !strokeAndFilled) {
+            // Use a square cap for miter join + stroked points, which matches raster's behavior and
+            // expectations from Chrome masking tests, although it could be argued to just always
+            // use a butt cap. This behavior, though, ensures that the default stroked paint draws
+            // something with empty geometry.
+            cap = SkPaint::kSquare_Cap;
+        } else {
+            // If this were a closed line, the 180 degree turn either is a miter join that exceeds
+            // the miter limit and becomes a bevel, or a bevel join. In either case, the bevel shape
+            // of a 180 degreen corner is equivalent to a butt cap.
+            //  - to match the SVG spec, the 0-length sides of an empty rectangle are skipped, so
+            //    it fits this closed line description (it is not two 90 degree turns that could
+            //    produce miter geometry).
+            cap = SkPaint::kButt_Cap;
+        }
+
+        SkStrokeRec rec = fStyle.strokeRec();
+        rec.setStrokeParams(cap, SkPaint::kDefault_Join, fStyle.strokeRec().getMiter());
+        fStyle = GrStyle(rec, nullptr);
+    }
+
+    if (fShape.isPoint()) {
+        // The drawn geometry is entirely based on the cap style and stroke width. A butt cap point
+        // doesn't draw anything, a round cap is an oval and a square cap is a square.
+        if (fStyle.strokeRec().getCap() == SkPaint::kButt_Cap) {
+            fShape.reset();
+        } else {
+            SkScalar w = fStyle.strokeRec().getWidth() / 2.f;
+            SkRect r = {fShape.point().fX, fShape.point().fY, fShape.point().fX, fShape.point().fY};
+            r.outset(w, w);
+
+            if (fStyle.strokeRec().getCap() == SkPaint::kRound_Cap) {
+                fShape.setRRect(SkRRect::MakeOval(r));
+            } else {
+                fShape.setRect(r);
+            }
+        }
+    } else {
+        // Stroked lines reduce to rectangles or round rects when they are axis-aligned. If we
+        // allowed rotation angle, this would work for any lines.
+        SkRect rect;
+        SkVector outset;
+        if (fShape.line().fP1.fY == fShape.line().fP2.fY) {
+            rect.fLeft = std::min(fShape.line().fP1.fX, fShape.line().fP2.fX);
+            rect.fRight = std::max(fShape.line().fP1.fX, fShape.line().fP2.fX);
+            rect.fTop = rect.fBottom = fShape.line().fP1.fY;
+            outset.fY = fStyle.strokeRec().getWidth() / 2.f;
+            outset.fX = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fY;
+        } else if (fShape.line().fP1.fX == fShape.line().fP2.fX) {
+            rect.fTop = std::min(fShape.line().fP1.fY, fShape.line().fP2.fY);
+            rect.fBottom = std::max(fShape.line().fP1.fY, fShape.line().fP2.fY);
+            rect.fLeft = rect.fRight = fShape.line().fP1.fX;
+            outset.fX = fStyle.strokeRec().getWidth() / 2.f;
+            outset.fY = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fX;
+        } else {
             return;
         }
-    }
-    if (!fStyle.pathEffect()) {
-        // Canonicalize the arc such that the start is always in [0, 360) and the sweep is always
-        // positive.
-        if (fArcData.fSweepAngleDegrees < 0) {
-            fArcData.fStartAngleDegrees = fArcData.fStartAngleDegrees + fArcData.fSweepAngleDegrees;
-            fArcData.fSweepAngleDegrees = -fArcData.fSweepAngleDegrees;
+        rect.outset(outset.fX, outset.fY);
+        if (rect.isEmpty()) {
+            fShape.reset();
+        } else if (fStyle.strokeRec().getCap() == SkPaint::kRound_Cap) {
+            SkASSERT(outset.fX == outset.fY);
+            fShape.setRRect(SkRRect::MakeRectXY(rect, outset.fX, outset.fY));
+        } else {
+            fShape.setRect(rect);
         }
     }
-    if (this->fArcData.fStartAngleDegrees < 0 || this->fArcData.fStartAngleDegrees >= 360.f) {
-        this->fArcData.fStartAngleDegrees = SkScalarMod(this->fArcData.fStartAngleDegrees, 360.f);
-    }
-    // Possible TODOs here: Look at whether dash pattern results in a single dash and convert to
-    // non-dashed stroke. Stroke and fill can be fill if circular and no path effect. Just stroke
-    // could as well if the stroke fills the center.
-}
-
-bool GrStyledShape::attemptToSimplifyStrokedLineToRRect() {
-    SkASSERT(Type::kLine == fType);
-    SkASSERT(fStyle.strokeRec().getStyle() == SkStrokeRec::kStroke_Style);
-
-    SkRect rect;
-    SkVector outset;
-    // If we allowed a rotation angle for rrects we could capture all cases here.
-    if (fLineData.fPts[0].fY == fLineData.fPts[1].fY) {
-        rect.fLeft = std::min(fLineData.fPts[0].fX, fLineData.fPts[1].fX);
-        rect.fRight = std::max(fLineData.fPts[0].fX, fLineData.fPts[1].fX);
-        rect.fTop = rect.fBottom = fLineData.fPts[0].fY;
-        outset.fY = fStyle.strokeRec().getWidth() / 2.f;
-        outset.fX = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fY;
-    } else if (fLineData.fPts[0].fX == fLineData.fPts[1].fX) {
-        rect.fTop = std::min(fLineData.fPts[0].fY, fLineData.fPts[1].fY);
-        rect.fBottom = std::max(fLineData.fPts[0].fY, fLineData.fPts[1].fY);
-        rect.fLeft = rect.fRight = fLineData.fPts[0].fX;
-        outset.fX = fStyle.strokeRec().getWidth() / 2.f;
-        outset.fY = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fX;
-    } else {
-        return false;
-    }
-    rect.outset(outset.fX, outset.fY);
-    if (rect.isEmpty()) {
-        this->changeType(Type::kEmpty);
-        fStyle = GrStyle::SimpleFill();
-        return true;
-    }
-    SkRRect rrect;
-    if (fStyle.strokeRec().getCap() == SkPaint::kRound_Cap) {
-        SkASSERT(outset.fX == outset.fY);
-        rrect = SkRRect::MakeRectXY(rect, outset.fX, outset.fY);
-    } else {
-        rrect = SkRRect::MakeRect(rect);
-    }
-    bool inverted = fLineData.fInverted && !fStyle.hasPathEffect();
-    this->changeType(Type::kRRect);
-    fRRectData.fRRect = rrect;
-    fRRectData.fInverted = inverted;
-    fRRectData.fDir = kDefaultRRectDir;
-    fRRectData.fStart = kDefaultRRectStart;
+    // If we made it here, the stroke was fully applied to the new shape so we can become a fill.
     fStyle = GrStyle::SimpleFill();
-    return true;
 }
diff --git a/src/gpu/geometry/GrStyledShape.h b/src/gpu/geometry/GrStyledShape.h
index 0fc7d1c..a56fda7 100644
--- a/src/gpu/geometry/GrStyledShape.h
+++ b/src/gpu/geometry/GrStyledShape.h
@@ -14,6 +14,7 @@
 #include "src/core/SkPathPriv.h"
 #include "src/core/SkTLazy.h"
 #include "src/gpu/GrStyle.h"
+#include "src/gpu/geometry/GrShape.h"
 #include <new>
 
 class SkIDChangeListener;
@@ -41,7 +42,7 @@
     // to have to worry about this. This value is exposed for unit tests.
     static constexpr int kMaxKeyFromDataVerbCnt = 10;
 
-    GrStyledShape() { this->initType(Type::kEmpty); }
+    GrStyledShape() {}
 
     explicit GrStyledShape(const SkPath& path) : GrStyledShape(path, GrStyle::SimpleFill()) {}
 
@@ -49,79 +50,40 @@
 
     explicit GrStyledShape(const SkRect& rect) : GrStyledShape(rect, GrStyle::SimpleFill()) {}
 
-    GrStyledShape(const SkPath& path, const GrStyle& style) : fStyle(style) {
-        this->initType(Type::kPath, &path);
-        this->attemptToSimplifyPath();
+    GrStyledShape(const SkPath& path, const SkPaint& paint) : GrStyledShape(path, GrStyle(paint)) {}
+
+    GrStyledShape(const SkRRect& rrect, const SkPaint& paint)
+            : GrStyledShape(rrect, GrStyle(paint)) {}
+
+    GrStyledShape(const SkRect& rect, const SkPaint& paint) : GrStyledShape(rect, GrStyle(paint)) {}
+
+    GrStyledShape(const SkPath& path, const GrStyle& style) : fShape(path), fStyle(style) {
+        this->simplify();
     }
 
-    GrStyledShape(const SkRRect& rrect, const GrStyle& style) : fStyle(style) {
-        this->initType(Type::kRRect);
-        fRRectData.fRRect = rrect;
-        fRRectData.fInverted = false;
-        fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, style.hasPathEffect(),
-                                                         &fRRectData.fDir);
-        this->attemptToSimplifyRRect();
+    GrStyledShape(const SkRRect& rrect, const GrStyle& style) : fShape(rrect), fStyle(style) {
+        this->simplify();
     }
 
     GrStyledShape(const SkRRect& rrect, SkPathDirection dir, unsigned start, bool inverted,
-            const GrStyle& style)
-        : fStyle(style) {
-        this->initType(Type::kRRect);
-        fRRectData.fRRect = rrect;
-        fRRectData.fInverted = inverted;
-        if (style.pathEffect()) {
-            fRRectData.fDir = dir;
-            fRRectData.fStart = start;
-            if (fRRectData.fRRect.getType() == SkRRect::kRect_Type) {
-                fRRectData.fStart = (fRRectData.fStart + 1) & 0b110;
-            } else if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
-                fRRectData.fStart &= 0b110;
-            }
-        } else {
-            fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectData.fDir);
-        }
-        this->attemptToSimplifyRRect();
+                  const GrStyle& style)
+            : fShape(rrect)
+            , fStyle(style) {
+        fShape.setPathWindingParams(dir, start);
+        fShape.setInverted(inverted);
+        this->simplify();
     }
 
-    GrStyledShape(const SkRect& rect, const GrStyle& style) : fStyle(style) {
-        this->initType(Type::kRRect);
-        fRRectData.fRRect = SkRRect::MakeRect(rect);
-        fRRectData.fInverted = false;
-        fRRectData.fStart = DefaultRectDirAndStartIndex(rect, style.hasPathEffect(),
-                                                        &fRRectData.fDir);
-        this->attemptToSimplifyRRect();
+    GrStyledShape(const SkRect& rect, const GrStyle& style) : fShape(rect), fStyle(style) {
+        this->simplify();
     }
 
-    GrStyledShape(const SkPath& path, const SkPaint& paint) : fStyle(paint) {
-        this->initType(Type::kPath, &path);
-        this->attemptToSimplifyPath();
-    }
-
-    GrStyledShape(const SkRRect& rrect, const SkPaint& paint) : fStyle(paint) {
-        this->initType(Type::kRRect);
-        fRRectData.fRRect = rrect;
-        fRRectData.fInverted = false;
-        fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, fStyle.hasPathEffect(),
-                                                         &fRRectData.fDir);
-        this->attemptToSimplifyRRect();
-    }
-
-    GrStyledShape(const SkRect& rect, const SkPaint& paint) : fStyle(paint) {
-        this->initType(Type::kRRect);
-        fRRectData.fRRect = SkRRect::MakeRect(rect);
-        fRRectData.fInverted = false;
-        fRRectData.fStart = DefaultRectDirAndStartIndex(rect, fStyle.hasPathEffect(),
-                                                        &fRRectData.fDir);
-        this->attemptToSimplifyRRect();
-    }
-
-    static GrStyledShape MakeArc(const SkRect& oval, SkScalar startAngleDegrees,
-                           SkScalar sweepAngleDegrees, bool useCenter, const GrStyle& style);
-
     GrStyledShape(const GrStyledShape&);
-    GrStyledShape& operator=(const GrStyledShape& that);
 
-    ~GrStyledShape() { this->changeType(Type::kEmpty); }
+    static GrStyledShape MakeArc(const SkRect& oval, SkScalar startAngleDegrees,
+                                 SkScalar sweepAngleDegrees, bool useCenter, const GrStyle& style);
+
+    GrStyledShape& operator=(const GrStyledShape& that);
 
     /**
      * Informs MakeFilled on how to modify that shape's fill rule when making a simple filled
@@ -155,153 +117,39 @@
     }
 
     bool isRect() const {
-        if (Type::kRRect != fType) {
-            return false;
-        }
-
-        return fRRectData.fRRect.isRect();
+        // Should have simplified a rrect to a rect if possible already.
+        SkASSERT(!fShape.isRRect() || !fShape.rrect().isRect());
+        return fShape.isRect();
     }
 
     /** Returns the unstyled geometry as a rrect if possible. */
-    bool asRRect(SkRRect* rrect, SkPathDirection* dir, unsigned* start, bool* inverted) const {
-        if (Type::kRRect != fType) {
-            return false;
-        }
-        if (rrect) {
-            *rrect = fRRectData.fRRect;
-        }
-        if (dir) {
-            *dir = fRRectData.fDir;
-        }
-        if (start) {
-            *start = fRRectData.fStart;
-        }
-        if (inverted) {
-            *inverted = fRRectData.fInverted;
-        }
-        return true;
-    }
+    bool asRRect(SkRRect* rrect, SkPathDirection* dir, unsigned* start, bool* inverted) const;
 
     /**
      * If the unstyled shape is a straight line segment, returns true and sets pts to the endpoints.
      * An inverse filled line path is still considered a line.
      */
-    bool asLine(SkPoint pts[2], bool* inverted) const {
-        if (fType != Type::kLine) {
-            return false;
-        }
-        if (pts) {
-            pts[0] = fLineData.fPts[0];
-            pts[1] = fLineData.fPts[1];
-        }
-        if (inverted) {
-            *inverted = fLineData.fInverted;
-        }
-        return true;
-    }
+    bool asLine(SkPoint pts[2], bool* inverted) const;
+
+    // Can this shape be drawn as a pair of filled nested rectangles?
+    bool asNestedRects(SkRect rects[2]) const;
 
     /** Returns the unstyled geometry as a path. */
     void asPath(SkPath* out) const {
-        switch (fType) {
-            case Type::kEmpty:
-                out->reset();
-                break;
-            case Type::kInvertedEmpty:
-                out->reset();
-                out->setFillType(kDefaultPathInverseFillType);
-                break;
-            case Type::kRRect:
-                out->reset();
-                out->addRRect(fRRectData.fRRect, fRRectData.fDir, fRRectData.fStart);
-                // Below matches the fill type that attemptToSimplifyPath uses.
-                if (fRRectData.fInverted) {
-                    out->setFillType(kDefaultPathInverseFillType);
-                } else {
-                    out->setFillType(kDefaultPathFillType);
-                }
-                break;
-            case Type::kArc:
-                SkPathPriv::CreateDrawArcPath(out, fArcData.fOval, fArcData.fStartAngleDegrees,
-                                              fArcData.fSweepAngleDegrees, fArcData.fUseCenter,
-                                              fStyle.isSimpleFill());
-                if (fArcData.fInverted) {
-                    out->setFillType(kDefaultPathInverseFillType);
-                } else {
-                    out->setFillType(kDefaultPathFillType);
-                }
-                break;
-            case Type::kLine:
-                out->reset();
-                out->moveTo(fLineData.fPts[0]);
-                out->lineTo(fLineData.fPts[1]);
-                if (fLineData.fInverted) {
-                    out->setFillType(kDefaultPathInverseFillType);
-                } else {
-                    out->setFillType(kDefaultPathFillType);
-                }
-                break;
-            case Type::kPath:
-                *out = this->path();
-                break;
-        }
-    }
-
-    // Can this shape be drawn as a pair of filled nested rectangles?
-    bool asNestedRects(SkRect rects[2]) const {
-        if (Type::kPath != fType) {
-            return false;
-        }
-
-        // TODO: it would be better two store DRRects natively in the shape rather than converting
-        // them to a path and then reextracting the nested rects
-        if (this->path().isInverseFillType()) {
-            return false;
-        }
-
-        SkPathDirection dirs[2];
-        if (!SkPathPriv::IsNestedFillRects(this->path(), rects, dirs)) {
-            return false;
-        }
-
-        if (SkPathFillType::kWinding == this->path().getFillType() && dirs[0] == dirs[1]) {
-            // The two rects need to be wound opposite to each other
-            return false;
-        }
-
-        // Right now, nested rects where the margin is not the same width
-        // all around do not render correctly
-        const SkScalar* outer = rects[0].asScalars();
-        const SkScalar* inner = rects[1].asScalars();
-
-        bool allEq = true;
-
-        SkScalar margin = SkScalarAbs(outer[0] - inner[0]);
-        bool allGoE1 = margin >= SK_Scalar1;
-
-        for (int i = 1; i < 4; ++i) {
-            SkScalar temp = SkScalarAbs(outer[i] - inner[i]);
-            if (temp < SK_Scalar1) {
-                allGoE1 = false;
-            }
-            if (!SkScalarNearlyEqual(margin, temp)) {
-                allEq = false;
-            }
-        }
-
-        return allEq || allGoE1;
+        fShape.asPath(out, fStyle.isSimpleFill());
     }
 
     /**
      * Returns whether the geometry is empty. Note that applying the style could produce a
      * non-empty shape. It also may have an inverse fill.
      */
-    bool isEmpty() const { return Type::kEmpty == fType || Type::kInvertedEmpty == fType; }
+    bool isEmpty() const { return fShape.isEmpty(); }
 
     /**
      * Gets the bounds of the geometry without reflecting the shape's styling. This ignores
      * the inverse fill nature of the geometry.
      */
-    SkRect bounds() const;
+    SkRect bounds() const { return fShape.bounds(); }
 
     /**
      * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill
@@ -315,28 +163,7 @@
      * This is because filling closes all contours in the path.
      */
     bool knownToBeConvex() const {
-        switch (fType) {
-            case Type::kEmpty:
-                return true;
-            case Type::kInvertedEmpty:
-                return true;
-            case Type::kRRect:
-                return true;
-            case Type::kArc:
-                return SkPathPriv::DrawArcIsConvex(fArcData.fSweepAngleDegrees,
-                                                   SkToBool(fArcData.fUseCenter),
-                                                   fStyle.isSimpleFill());
-            case Type::kLine:
-                return true;
-            case Type::kPath:
-                // SkPath.isConvex() really means "is this path convex were it to be closed" and
-                // thus doesn't give the correct answer for stroked paths, hence we also check
-                // whether the path is either filled or closed. Convex paths may only have one
-                // contour hence isLastContourClosed() is a sufficient for a convex path.
-                return (this->style().isSimpleFill() || this->path().isLastContourClosed()) &&
-                        this->path().isConvex();
-        }
-        return false;
+        return fShape.convex(fStyle.isSimpleFill());
     }
 
     /**
@@ -345,52 +172,20 @@
      * kept separate from knownToBeConvex().
      */
     bool knownDirection() const {
-        switch (fType) {
-            case Type::kEmpty:
-                return true;
-            case Type::kInvertedEmpty:
-                return true;
-            case Type::kRRect:
-                return true;
-            case Type::kArc:
-                return true;
-            case Type::kLine:
-                return true;
-            case Type::kPath:
-                // Assuming this is called after knownToBeConvex(), this should just be relying on
-                // cached convexity and direction and will be cheap.
-                return !SkPathPriv::CheapIsFirstDirection(this->path(),
-                                                          SkPathPriv::kUnknown_FirstDirection);
-        }
-        return false;
+        // Assuming this is called after knownToBeConvex(), this should just be relying on
+        // cached convexity and direction and will be cheap.
+        return !fShape.isPath() ||
+               !SkPathPriv::CheapIsFirstDirection(fShape.path(),
+                                                  SkPathPriv::kUnknown_FirstDirection);
     }
 
     /** Is the pre-styled geometry inverse filled? */
     bool inverseFilled() const {
-        bool ret = false;
-        switch (fType) {
-            case Type::kEmpty:
-                ret = false;
-                break;
-            case Type::kInvertedEmpty:
-                ret = true;
-                break;
-            case Type::kRRect:
-                ret = fRRectData.fInverted;
-                break;
-            case Type::kArc:
-                ret = fArcData.fInverted;
-                break;
-            case Type::kLine:
-                ret = fLineData.fInverted;
-                break;
-            case Type::kPath:
-                ret = this->path().isInverseFillType();
-                break;
-        }
+        // Since the path tracks inverted-fillness itself, it should match what was recorded.
+        SkASSERT(!fShape.isPath() || fShape.inverted() == fShape.path().isInverseFillType());
         // Dashing ignores inverseness. We should have caught this earlier. skbug.com/5421
-        SkASSERT(!(ret && this->style().isDashed()));
-        return ret;
+        SkASSERT(!(fShape.inverted() && this->style().isDashed()));
+        return fShape.inverted();
     }
 
     /**
@@ -412,49 +207,13 @@
      * not have any caps if stroked (modulo the effect of any path effect).
      */
     bool knownToBeClosed() const {
-        switch (fType) {
-            case Type::kEmpty:
-                return true;
-            case Type::kInvertedEmpty:
-                return true;
-            case Type::kRRect:
-                return true;
-            case Type::kArc:
-                return fArcData.fUseCenter;
-            case Type::kLine:
-                return false;
-            case Type::kPath:
-                // SkPath doesn't keep track of the closed status of each contour.
-                return SkPathPriv::IsClosedSingleContour(this->path());
-        }
-        return false;
+        // This refers to the base shape and does not depend on invertedness.
+        return fShape.closed();
     }
 
     uint32_t segmentMask() const {
-        switch (fType) {
-            case Type::kEmpty:
-                return 0;
-            case Type::kInvertedEmpty:
-                return 0;
-            case Type::kRRect:
-                if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
-                    return SkPath::kConic_SegmentMask;
-                } else if (fRRectData.fRRect.getType() == SkRRect::kRect_Type ||
-                           fRRectData.fRRect.getType() == SkRRect::kEmpty_Type) {
-                    return SkPath::kLine_SegmentMask;
-                }
-                return SkPath::kLine_SegmentMask | SkPath::kConic_SegmentMask;
-            case Type::kArc:
-                if (fArcData.fUseCenter) {
-                    return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask;
-                }
-                return SkPath::kConic_SegmentMask;
-            case Type::kLine:
-                return SkPath::kLine_SegmentMask;
-            case Type::kPath:
-                return this->path().getSegmentMasks();
-        }
-        return 0;
+        // This refers to the base shape and does not depend on invertedness.
+        return fShape.segmentMask();
     }
 
     /**
@@ -489,50 +248,6 @@
     bool testingOnly_isNonVolatilePath() const;
 
 private:
-    enum class Type {
-        kEmpty,
-        kInvertedEmpty,
-        kRRect,
-        kArc,
-        kLine,
-        kPath,
-    };
-
-    void initType(Type type, const SkPath* path = nullptr) {
-        fType = Type::kEmpty;
-        this->changeType(type, path);
-    }
-
-    void changeType(Type type, const SkPath* path = nullptr) {
-        bool wasPath = Type::kPath == fType;
-        fType = type;
-        bool isPath = Type::kPath == type;
-        SkASSERT(!path || isPath);
-        if (wasPath && !isPath) {
-            fPathData.fPath.~SkPath();
-        } else if (!wasPath && isPath) {
-            if (path) {
-                new (&fPathData.fPath) SkPath(*path);
-            } else {
-                new (&fPathData.fPath) SkPath();
-            }
-        } else if (isPath && path) {
-            fPathData.fPath = *path;
-        }
-        // Whether or not we use the path's gen ID is decided in attemptToSimplifyPath.
-        fPathData.fGenID = 0;
-    }
-
-    SkPath& path() {
-        SkASSERT(Type::kPath == fType);
-        return fPathData.fPath;
-    }
-
-    const SkPath& path() const {
-        SkASSERT(Type::kPath == fType);
-        return fPathData.fPath;
-    }
-
     /** Constructor used by the applyStyle() function */
     GrStyledShape(const GrStyledShape& parentShape, GrStyle::Apply, SkScalar scale);
 
@@ -542,92 +257,22 @@
      */
     void setInheritedKey(const GrStyledShape& parentShape, GrStyle::Apply, SkScalar scale);
 
-    void attemptToSimplifyPath();
-    void attemptToSimplifyRRect();
-    void attemptToSimplifyLine();
-    void attemptToSimplifyArc();
-
-    bool attemptToSimplifyStrokedLineToRRect();
+    // Similar to GrShape::simplify but also takes into account style and stroking, possibly
+    // applying the style explicitly to produce a new analytic shape with a simpler style.
+    void simplify();
+    // As part of the simplification process, some shapes can have stroking trivially evaluated
+    // and form a new geometry with just a fill.
+    void simplifyStroke(bool originallyClosed);
 
     /** Gets the path that gen id listeners should be added to. */
     const SkPath* originalPathForListeners() const;
 
-    // Defaults to use when there is no distinction between even/odd and winding fills.
-    static constexpr SkPathFillType kDefaultPathFillType = SkPathFillType::kEvenOdd;
-    static constexpr SkPathFillType kDefaultPathInverseFillType = SkPathFillType::kInverseEvenOdd;
+    GrShape         fShape;
+    GrStyle         fStyle;
+    // Gen ID of the original path (path may be modified or simplified away).
+    int32_t         fGenID      = 0;
 
-    static constexpr SkPathDirection kDefaultRRectDir = SkPathDirection::kCW;
-    static constexpr unsigned kDefaultRRectStart = 0;
-
-    static unsigned DefaultRectDirAndStartIndex(const SkRect& rect, bool hasPathEffect,
-                                                SkPathDirection* 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;
-        }
-        // 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 = SkPathDirection::kCCW;
-            // 0 becomes start index 1 and times 2 to convert from rect the rrect indices.
-            return 2 * 1;
-        } else if (swapY) {
-            *dir = SkPathDirection::kCCW;
-            // 0 becomes start index 3 and times 2 to convert from rect the rrect indices.
-            return 2 * 3;
-        }
-        return 0;
-    }
-
-    static unsigned DefaultRRectDirAndStartIndex(const SkRRect& rrect, bool hasPathEffect,
-                                                 SkPathDirection* 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;
-        }
-        return kPathRRectStartIdx;
-    }
-
-    union {
-        struct {
-            SkRRect fRRect;
-            SkPathDirection fDir;
-            unsigned fStart;
-            bool fInverted;
-        } fRRectData;
-        struct {
-            SkRect fOval;
-            SkScalar fStartAngleDegrees;
-            SkScalar fSweepAngleDegrees;
-            int16_t fUseCenter;
-            int16_t fInverted;
-        } fArcData;
-        struct {
-            SkPath fPath;
-            // Gen ID of the original path (fPath may be modified)
-            int32_t fGenID;
-        } fPathData;
-        struct {
-            SkPoint fPts[2];
-            bool fInverted;
-        } fLineData;
-    };
-    GrStyle fStyle;
     SkTLazy<SkPath> fInheritedPathForListeners;
     SkAutoSTArray<8, uint32_t>  fInheritedKey;
-    Type fType;
 };
 #endif