Move GrPathUtils, GrRect, and GrShape into src/gpu/geometry/

Change-Id: I864d3c2452f3affdc744bf8b11ed3b3e37d6d922
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/216602
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/geometry/GrPathUtils.cpp b/src/gpu/geometry/GrPathUtils.cpp
new file mode 100644
index 0000000..3da6e13
--- /dev/null
+++ b/src/gpu/geometry/GrPathUtils.cpp
@@ -0,0 +1,859 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/geometry/GrPathUtils.h"
+
+#include "include/gpu/GrTypes.h"
+#include "src/core/SkMathPriv.h"
+#include "src/core/SkPointPriv.h"
+
+static const SkScalar gMinCurveTol = 0.0001f;
+
+SkScalar GrPathUtils::scaleToleranceToSrc(SkScalar devTol,
+                                          const SkMatrix& viewM,
+                                          const SkRect& pathBounds) {
+    // In order to tesselate the path we get a bound on how much the matrix can
+    // scale when mapping to screen coordinates.
+    SkScalar stretch = viewM.getMaxScale();
+
+    if (stretch < 0) {
+        // take worst case mapRadius amoung four corners.
+        // (less than perfect)
+        for (int i = 0; i < 4; ++i) {
+            SkMatrix mat;
+            mat.setTranslate((i % 2) ? pathBounds.fLeft : pathBounds.fRight,
+                             (i < 2) ? pathBounds.fTop : pathBounds.fBottom);
+            mat.postConcat(viewM);
+            stretch = SkMaxScalar(stretch, mat.mapRadius(SK_Scalar1));
+        }
+    }
+    SkScalar srcTol = 0;
+    if (stretch <= 0) {
+        // We have degenerate bounds or some degenerate matrix. Thus we set the tolerance to be the
+        // max of the path pathBounds width and height.
+        srcTol = SkTMax(pathBounds.width(), pathBounds.height());
+    } else {
+        srcTol = devTol / stretch;
+    }
+    if (srcTol < gMinCurveTol) {
+        srcTol = gMinCurveTol;
+    }
+    return srcTol;
+}
+
+uint32_t GrPathUtils::quadraticPointCount(const SkPoint points[], SkScalar tol) {
+    // You should have called scaleToleranceToSrc, which guarantees this
+    SkASSERT(tol >= gMinCurveTol);
+
+    SkScalar d = SkPointPriv::DistanceToLineSegmentBetween(points[1], points[0], points[2]);
+    if (!SkScalarIsFinite(d)) {
+        return kMaxPointsPerCurve;
+    } else if (d <= tol) {
+        return 1;
+    } else {
+        // Each time we subdivide, d should be cut in 4. So we need to
+        // subdivide x = log4(d/tol) times. x subdivisions creates 2^(x)
+        // points.
+        // 2^(log4(x)) = sqrt(x);
+        SkScalar divSqrt = SkScalarSqrt(d / tol);
+        if (((SkScalar)SK_MaxS32) <= divSqrt) {
+            return kMaxPointsPerCurve;
+        } else {
+            int temp = SkScalarCeilToInt(divSqrt);
+            int pow2 = GrNextPow2(temp);
+            // Because of NaNs & INFs we can wind up with a degenerate temp
+            // such that pow2 comes out negative. Also, our point generator
+            // will always output at least one pt.
+            if (pow2 < 1) {
+                pow2 = 1;
+            }
+            return SkTMin(pow2, kMaxPointsPerCurve);
+        }
+    }
+}
+
+uint32_t GrPathUtils::generateQuadraticPoints(const SkPoint& p0,
+                                              const SkPoint& p1,
+                                              const SkPoint& p2,
+                                              SkScalar tolSqd,
+                                              SkPoint** points,
+                                              uint32_t pointsLeft) {
+    if (pointsLeft < 2 ||
+        (SkPointPriv::DistanceToLineSegmentBetweenSqd(p1, p0, p2)) < tolSqd) {
+        (*points)[0] = p2;
+        *points += 1;
+        return 1;
+    }
+
+    SkPoint q[] = {
+        { SkScalarAve(p0.fX, p1.fX), SkScalarAve(p0.fY, p1.fY) },
+        { SkScalarAve(p1.fX, p2.fX), SkScalarAve(p1.fY, p2.fY) },
+    };
+    SkPoint r = { SkScalarAve(q[0].fX, q[1].fX), SkScalarAve(q[0].fY, q[1].fY) };
+
+    pointsLeft >>= 1;
+    uint32_t a = generateQuadraticPoints(p0, q[0], r, tolSqd, points, pointsLeft);
+    uint32_t b = generateQuadraticPoints(r, q[1], p2, tolSqd, points, pointsLeft);
+    return a + b;
+}
+
+uint32_t GrPathUtils::cubicPointCount(const SkPoint points[],
+                                           SkScalar tol) {
+    // You should have called scaleToleranceToSrc, which guarantees this
+    SkASSERT(tol >= gMinCurveTol);
+
+    SkScalar d = SkTMax(
+        SkPointPriv::DistanceToLineSegmentBetweenSqd(points[1], points[0], points[3]),
+        SkPointPriv::DistanceToLineSegmentBetweenSqd(points[2], points[0], points[3]));
+    d = SkScalarSqrt(d);
+    if (!SkScalarIsFinite(d)) {
+        return kMaxPointsPerCurve;
+    } else if (d <= tol) {
+        return 1;
+    } else {
+        SkScalar divSqrt = SkScalarSqrt(d / tol);
+        if (((SkScalar)SK_MaxS32) <= divSqrt) {
+            return kMaxPointsPerCurve;
+        } else {
+            int temp = SkScalarCeilToInt(SkScalarSqrt(d / tol));
+            int pow2 = GrNextPow2(temp);
+            // Because of NaNs & INFs we can wind up with a degenerate temp
+            // such that pow2 comes out negative. Also, our point generator
+            // will always output at least one pt.
+            if (pow2 < 1) {
+                pow2 = 1;
+            }
+            return SkTMin(pow2, kMaxPointsPerCurve);
+        }
+    }
+}
+
+uint32_t GrPathUtils::generateCubicPoints(const SkPoint& p0,
+                                          const SkPoint& p1,
+                                          const SkPoint& p2,
+                                          const SkPoint& p3,
+                                          SkScalar tolSqd,
+                                          SkPoint** points,
+                                          uint32_t pointsLeft) {
+    if (pointsLeft < 2 ||
+        (SkPointPriv::DistanceToLineSegmentBetweenSqd(p1, p0, p3) < tolSqd &&
+         SkPointPriv::DistanceToLineSegmentBetweenSqd(p2, p0, p3) < tolSqd)) {
+        (*points)[0] = p3;
+        *points += 1;
+        return 1;
+    }
+    SkPoint q[] = {
+        { SkScalarAve(p0.fX, p1.fX), SkScalarAve(p0.fY, p1.fY) },
+        { SkScalarAve(p1.fX, p2.fX), SkScalarAve(p1.fY, p2.fY) },
+        { SkScalarAve(p2.fX, p3.fX), SkScalarAve(p2.fY, p3.fY) }
+    };
+    SkPoint r[] = {
+        { SkScalarAve(q[0].fX, q[1].fX), SkScalarAve(q[0].fY, q[1].fY) },
+        { SkScalarAve(q[1].fX, q[2].fX), SkScalarAve(q[1].fY, q[2].fY) }
+    };
+    SkPoint s = { SkScalarAve(r[0].fX, r[1].fX), SkScalarAve(r[0].fY, r[1].fY) };
+    pointsLeft >>= 1;
+    uint32_t a = generateCubicPoints(p0, q[0], r[0], s, tolSqd, points, pointsLeft);
+    uint32_t b = generateCubicPoints(s, r[1], q[2], p3, tolSqd, points, pointsLeft);
+    return a + b;
+}
+
+int GrPathUtils::worstCasePointCount(const SkPath& path, int* subpaths, SkScalar tol) {
+    // You should have called scaleToleranceToSrc, which guarantees this
+    SkASSERT(tol >= gMinCurveTol);
+
+    int pointCount = 0;
+    *subpaths = 1;
+
+    bool first = true;
+
+    SkPath::Iter iter(path, false);
+    SkPath::Verb verb;
+
+    SkPoint pts[4];
+    while ((verb = iter.next(pts, false)) != SkPath::kDone_Verb) {
+
+        switch (verb) {
+            case SkPath::kLine_Verb:
+                pointCount += 1;
+                break;
+            case SkPath::kConic_Verb: {
+                SkScalar weight = iter.conicWeight();
+                SkAutoConicToQuads converter;
+                const SkPoint* quadPts = converter.computeQuads(pts, weight, tol);
+                for (int i = 0; i < converter.countQuads(); ++i) {
+                    pointCount += quadraticPointCount(quadPts + 2*i, tol);
+                }
+            }
+            case SkPath::kQuad_Verb:
+                pointCount += quadraticPointCount(pts, tol);
+                break;
+            case SkPath::kCubic_Verb:
+                pointCount += cubicPointCount(pts, tol);
+                break;
+            case SkPath::kMove_Verb:
+                pointCount += 1;
+                if (!first) {
+                    ++(*subpaths);
+                }
+                break;
+            default:
+                break;
+        }
+        first = false;
+    }
+    return pointCount;
+}
+
+void GrPathUtils::QuadUVMatrix::set(const SkPoint qPts[3]) {
+    SkMatrix m;
+    // We want M such that M * xy_pt = uv_pt
+    // We know M * control_pts = [0  1/2 1]
+    //                           [0  0   1]
+    //                           [1  1   1]
+    // And control_pts = [x0 x1 x2]
+    //                   [y0 y1 y2]
+    //                   [1  1  1 ]
+    // We invert the control pt matrix and post concat to both sides to get M.
+    // Using the known form of the control point matrix and the result, we can
+    // optimize and improve precision.
+
+    double x0 = qPts[0].fX;
+    double y0 = qPts[0].fY;
+    double x1 = qPts[1].fX;
+    double y1 = qPts[1].fY;
+    double x2 = qPts[2].fX;
+    double y2 = qPts[2].fY;
+    double det = x0*y1 - y0*x1 + x2*y0 - y2*x0 + x1*y2 - y1*x2;
+
+    if (!sk_float_isfinite(det)
+        || SkScalarNearlyZero((float)det, SK_ScalarNearlyZero * SK_ScalarNearlyZero)) {
+        // The quad is degenerate. Hopefully this is rare. Find the pts that are
+        // farthest apart to compute a line (unless it is really a pt).
+        SkScalar maxD = SkPointPriv::DistanceToSqd(qPts[0], qPts[1]);
+        int maxEdge = 0;
+        SkScalar d = SkPointPriv::DistanceToSqd(qPts[1], qPts[2]);
+        if (d > maxD) {
+            maxD = d;
+            maxEdge = 1;
+        }
+        d = SkPointPriv::DistanceToSqd(qPts[2], qPts[0]);
+        if (d > maxD) {
+            maxD = d;
+            maxEdge = 2;
+        }
+        // We could have a tolerance here, not sure if it would improve anything
+        if (maxD > 0) {
+            // Set the matrix to give (u = 0, v = distance_to_line)
+            SkVector lineVec = qPts[(maxEdge + 1)%3] - qPts[maxEdge];
+            // when looking from the point 0 down the line we want positive
+            // distances to be to the left. This matches the non-degenerate
+            // case.
+            lineVec = SkPointPriv::MakeOrthog(lineVec, SkPointPriv::kLeft_Side);
+            // first row
+            fM[0] = 0;
+            fM[1] = 0;
+            fM[2] = 0;
+            // second row
+            fM[3] = lineVec.fX;
+            fM[4] = lineVec.fY;
+            fM[5] = -lineVec.dot(qPts[maxEdge]);
+        } else {
+            // It's a point. It should cover zero area. Just set the matrix such
+            // that (u, v) will always be far away from the quad.
+            fM[0] = 0; fM[1] = 0; fM[2] = 100.f;
+            fM[3] = 0; fM[4] = 0; fM[5] = 100.f;
+        }
+    } else {
+        double scale = 1.0/det;
+
+        // compute adjugate matrix
+        double a2, a3, a4, a5, a6, a7, a8;
+        a2 = x1*y2-x2*y1;
+
+        a3 = y2-y0;
+        a4 = x0-x2;
+        a5 = x2*y0-x0*y2;
+
+        a6 = y0-y1;
+        a7 = x1-x0;
+        a8 = x0*y1-x1*y0;
+
+        // this performs the uv_pts*adjugate(control_pts) multiply,
+        // then does the scale by 1/det afterwards to improve precision
+        m[SkMatrix::kMScaleX] = (float)((0.5*a3 + a6)*scale);
+        m[SkMatrix::kMSkewX]  = (float)((0.5*a4 + a7)*scale);
+        m[SkMatrix::kMTransX] = (float)((0.5*a5 + a8)*scale);
+
+        m[SkMatrix::kMSkewY]  = (float)(a6*scale);
+        m[SkMatrix::kMScaleY] = (float)(a7*scale);
+        m[SkMatrix::kMTransY] = (float)(a8*scale);
+
+        // kMPersp0 & kMPersp1 should algebraically be zero
+        m[SkMatrix::kMPersp0] = 0.0f;
+        m[SkMatrix::kMPersp1] = 0.0f;
+        m[SkMatrix::kMPersp2] = (float)((a2 + a5 + a8)*scale);
+
+        // It may not be normalized to have 1.0 in the bottom right
+        float m33 = m.get(SkMatrix::kMPersp2);
+        if (1.f != m33) {
+            m33 = 1.f / m33;
+            fM[0] = m33 * m.get(SkMatrix::kMScaleX);
+            fM[1] = m33 * m.get(SkMatrix::kMSkewX);
+            fM[2] = m33 * m.get(SkMatrix::kMTransX);
+            fM[3] = m33 * m.get(SkMatrix::kMSkewY);
+            fM[4] = m33 * m.get(SkMatrix::kMScaleY);
+            fM[5] = m33 * m.get(SkMatrix::kMTransY);
+        } else {
+            fM[0] = m.get(SkMatrix::kMScaleX);
+            fM[1] = m.get(SkMatrix::kMSkewX);
+            fM[2] = m.get(SkMatrix::kMTransX);
+            fM[3] = m.get(SkMatrix::kMSkewY);
+            fM[4] = m.get(SkMatrix::kMScaleY);
+            fM[5] = m.get(SkMatrix::kMTransY);
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// k = (y2 - y0, x0 - x2, x2*y0 - x0*y2)
+// l = (y1 - y0, x0 - x1, x1*y0 - x0*y1) * 2*w
+// m = (y2 - y1, x1 - x2, x2*y1 - x1*y2) * 2*w
+void GrPathUtils::getConicKLM(const SkPoint p[3], const SkScalar weight, SkMatrix* out) {
+    SkMatrix& klm = *out;
+    const SkScalar w2 = 2.f * weight;
+    klm[0] = p[2].fY - p[0].fY;
+    klm[1] = p[0].fX - p[2].fX;
+    klm[2] = p[2].fX * p[0].fY - p[0].fX * p[2].fY;
+
+    klm[3] = w2 * (p[1].fY - p[0].fY);
+    klm[4] = w2 * (p[0].fX - p[1].fX);
+    klm[5] = w2 * (p[1].fX * p[0].fY - p[0].fX * p[1].fY);
+
+    klm[6] = w2 * (p[2].fY - p[1].fY);
+    klm[7] = w2 * (p[1].fX - p[2].fX);
+    klm[8] = w2 * (p[2].fX * p[1].fY - p[1].fX * p[2].fY);
+
+    // scale the max absolute value of coeffs to 10
+    SkScalar scale = 0.f;
+    for (int i = 0; i < 9; ++i) {
+       scale = SkMaxScalar(scale, SkScalarAbs(klm[i]));
+    }
+    SkASSERT(scale > 0.f);
+    scale = 10.f / scale;
+    for (int i = 0; i < 9; ++i) {
+        klm[i] *= scale;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+// a is the first control point of the cubic.
+// ab is the vector from a to the second control point.
+// dc is the vector from the fourth to the third control point.
+// d is the fourth control point.
+// p is the candidate quadratic control point.
+// this assumes that the cubic doesn't inflect and is simple
+bool is_point_within_cubic_tangents(const SkPoint& a,
+                                    const SkVector& ab,
+                                    const SkVector& dc,
+                                    const SkPoint& d,
+                                    SkPathPriv::FirstDirection dir,
+                                    const SkPoint p) {
+    SkVector ap = p - a;
+    SkScalar apXab = ap.cross(ab);
+    if (SkPathPriv::kCW_FirstDirection == dir) {
+        if (apXab > 0) {
+            return false;
+        }
+    } else {
+        SkASSERT(SkPathPriv::kCCW_FirstDirection == dir);
+        if (apXab < 0) {
+            return false;
+        }
+    }
+
+    SkVector dp = p - d;
+    SkScalar dpXdc = dp.cross(dc);
+    if (SkPathPriv::kCW_FirstDirection == dir) {
+        if (dpXdc < 0) {
+            return false;
+        }
+    } else {
+        SkASSERT(SkPathPriv::kCCW_FirstDirection == dir);
+        if (dpXdc > 0) {
+            return false;
+        }
+    }
+    return true;
+}
+
+void convert_noninflect_cubic_to_quads(const SkPoint p[4],
+                                       SkScalar toleranceSqd,
+                                       SkTArray<SkPoint, true>* quads,
+                                       int sublevel = 0,
+                                       bool preserveFirstTangent = true,
+                                       bool preserveLastTangent = true) {
+    // Notation: Point a is always p[0]. Point b is p[1] unless p[1] == p[0], in which case it is
+    // p[2]. Point d is always p[3]. Point c is p[2] unless p[2] == p[3], in which case it is p[1].
+    SkVector ab = p[1] - p[0];
+    SkVector dc = p[2] - p[3];
+
+    if (SkPointPriv::LengthSqd(ab) < SK_ScalarNearlyZero) {
+        if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) {
+            SkPoint* degQuad = quads->push_back_n(3);
+            degQuad[0] = p[0];
+            degQuad[1] = p[0];
+            degQuad[2] = p[3];
+            return;
+        }
+        ab = p[2] - p[0];
+    }
+    if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) {
+        dc = p[1] - p[3];
+    }
+
+    static const SkScalar kLengthScale = 3 * SK_Scalar1 / 2;
+    static const int kMaxSubdivs = 10;
+
+    ab.scale(kLengthScale);
+    dc.scale(kLengthScale);
+
+    // c0 and c1 are extrapolations along vectors ab and dc.
+    SkPoint c0 = p[0] + ab;
+    SkPoint c1 = p[3] + dc;
+
+    SkScalar dSqd = sublevel > kMaxSubdivs ? 0 : SkPointPriv::DistanceToSqd(c0, c1);
+    if (dSqd < toleranceSqd) {
+        SkPoint newC;
+        if (preserveFirstTangent == preserveLastTangent) {
+            // We used to force a split when both tangents need to be preserved and c0 != c1.
+            // This introduced a large performance regression for tiny paths for no noticeable
+            // quality improvement. However, we aren't quite fulfilling our contract of guaranteeing
+            // the two tangent vectors and this could introduce a missed pixel in
+            // GrAAHairlinePathRenderer.
+            newC = (c0 + c1) * 0.5f;
+        } else if (preserveFirstTangent) {
+            newC = c0;
+        } else {
+            newC = c1;
+        }
+
+        SkPoint* pts = quads->push_back_n(3);
+        pts[0] = p[0];
+        pts[1] = newC;
+        pts[2] = p[3];
+        return;
+    }
+    SkPoint choppedPts[7];
+    SkChopCubicAtHalf(p, choppedPts);
+    convert_noninflect_cubic_to_quads(
+            choppedPts + 0, toleranceSqd, quads, sublevel + 1, preserveFirstTangent, false);
+    convert_noninflect_cubic_to_quads(
+            choppedPts + 3, toleranceSqd, quads, sublevel + 1, false, preserveLastTangent);
+}
+
+void convert_noninflect_cubic_to_quads_with_constraint(const SkPoint p[4],
+                                                       SkScalar toleranceSqd,
+                                                       SkPathPriv::FirstDirection dir,
+                                                       SkTArray<SkPoint, true>* quads,
+                                                       int sublevel = 0) {
+    // Notation: Point a is always p[0]. Point b is p[1] unless p[1] == p[0], in which case it is
+    // p[2]. Point d is always p[3]. Point c is p[2] unless p[2] == p[3], in which case it is p[1].
+
+    SkVector ab = p[1] - p[0];
+    SkVector dc = p[2] - p[3];
+
+    if (SkPointPriv::LengthSqd(ab) < SK_ScalarNearlyZero) {
+        if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) {
+            SkPoint* degQuad = quads->push_back_n(3);
+            degQuad[0] = p[0];
+            degQuad[1] = p[0];
+            degQuad[2] = p[3];
+            return;
+        }
+        ab = p[2] - p[0];
+    }
+    if (SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero) {
+        dc = p[1] - p[3];
+    }
+
+    // When the ab and cd tangents are degenerate or nearly parallel with vector from d to a the
+    // constraint that the quad point falls between the tangents becomes hard to enforce and we are
+    // likely to hit the max subdivision count. However, in this case the cubic is approaching a
+    // line and the accuracy of the quad point isn't so important. We check if the two middle cubic
+    // control points are very close to the baseline vector. If so then we just pick quadratic
+    // points on the control polygon.
+
+    SkVector da = p[0] - p[3];
+    bool doQuads = SkPointPriv::LengthSqd(dc) < SK_ScalarNearlyZero ||
+                   SkPointPriv::LengthSqd(ab) < SK_ScalarNearlyZero;
+    if (!doQuads) {
+        SkScalar invDALengthSqd = SkPointPriv::LengthSqd(da);
+        if (invDALengthSqd > SK_ScalarNearlyZero) {
+            invDALengthSqd = SkScalarInvert(invDALengthSqd);
+            // cross(ab, da)^2/length(da)^2 == sqd distance from b to line from d to a.
+            // same goes for point c using vector cd.
+            SkScalar detABSqd = ab.cross(da);
+            detABSqd = SkScalarSquare(detABSqd);
+            SkScalar detDCSqd = dc.cross(da);
+            detDCSqd = SkScalarSquare(detDCSqd);
+            if (detABSqd * invDALengthSqd < toleranceSqd &&
+                detDCSqd * invDALengthSqd < toleranceSqd) {
+                doQuads = true;
+            }
+        }
+    }
+    if (doQuads) {
+        SkPoint b = p[0] + ab;
+        SkPoint c = p[3] + dc;
+        SkPoint mid = b + c;
+        mid.scale(SK_ScalarHalf);
+        // Insert two quadratics to cover the case when ab points away from d and/or dc
+        // points away from a.
+        if (SkVector::DotProduct(da, dc) < 0 || SkVector::DotProduct(ab, da) > 0) {
+            SkPoint* qpts = quads->push_back_n(6);
+            qpts[0] = p[0];
+            qpts[1] = b;
+            qpts[2] = mid;
+            qpts[3] = mid;
+            qpts[4] = c;
+            qpts[5] = p[3];
+        } else {
+            SkPoint* qpts = quads->push_back_n(3);
+            qpts[0] = p[0];
+            qpts[1] = mid;
+            qpts[2] = p[3];
+        }
+        return;
+    }
+
+    static const SkScalar kLengthScale = 3 * SK_Scalar1 / 2;
+    static const int kMaxSubdivs = 10;
+
+    ab.scale(kLengthScale);
+    dc.scale(kLengthScale);
+
+    // c0 and c1 are extrapolations along vectors ab and dc.
+    SkVector c0 = p[0] + ab;
+    SkVector c1 = p[3] + dc;
+
+    SkScalar dSqd = sublevel > kMaxSubdivs ? 0 : SkPointPriv::DistanceToSqd(c0, c1);
+    if (dSqd < toleranceSqd) {
+        SkPoint cAvg = (c0 + c1) * 0.5f;
+        bool subdivide = false;
+
+        if (!is_point_within_cubic_tangents(p[0], ab, dc, p[3], dir, cAvg)) {
+            // choose a new cAvg that is the intersection of the two tangent lines.
+            ab = SkPointPriv::MakeOrthog(ab);
+            SkScalar z0 = -ab.dot(p[0]);
+            dc = SkPointPriv::MakeOrthog(dc);
+            SkScalar z1 = -dc.dot(p[3]);
+            cAvg.fX = ab.fY * z1 - z0 * dc.fY;
+            cAvg.fY = z0 * dc.fX - ab.fX * z1;
+            SkScalar z = ab.fX * dc.fY - ab.fY * dc.fX;
+            z = SkScalarInvert(z);
+            cAvg.fX *= z;
+            cAvg.fY *= z;
+            if (sublevel <= kMaxSubdivs) {
+                SkScalar d0Sqd = SkPointPriv::DistanceToSqd(c0, cAvg);
+                SkScalar d1Sqd = SkPointPriv::DistanceToSqd(c1, cAvg);
+                // We need to subdivide if d0 + d1 > tolerance but we have the sqd values. We know
+                // the distances and tolerance can't be negative.
+                // (d0 + d1)^2 > toleranceSqd
+                // d0Sqd + 2*d0*d1 + d1Sqd > toleranceSqd
+                SkScalar d0d1 = SkScalarSqrt(d0Sqd * d1Sqd);
+                subdivide = 2 * d0d1 + d0Sqd + d1Sqd > toleranceSqd;
+            }
+        }
+        if (!subdivide) {
+            SkPoint* pts = quads->push_back_n(3);
+            pts[0] = p[0];
+            pts[1] = cAvg;
+            pts[2] = p[3];
+            return;
+        }
+    }
+    SkPoint choppedPts[7];
+    SkChopCubicAtHalf(p, choppedPts);
+    convert_noninflect_cubic_to_quads_with_constraint(
+            choppedPts + 0, toleranceSqd, dir, quads, sublevel + 1);
+    convert_noninflect_cubic_to_quads_with_constraint(
+            choppedPts + 3, toleranceSqd, dir, quads, sublevel + 1);
+}
+}
+
+void GrPathUtils::convertCubicToQuads(const SkPoint p[4],
+                                      SkScalar tolScale,
+                                      SkTArray<SkPoint, true>* quads) {
+    if (!p[0].isFinite() || !p[1].isFinite() || !p[2].isFinite() || !p[3].isFinite()) {
+        return;
+    }
+    if (!SkScalarIsFinite(tolScale)) {
+        return;
+    }
+    SkPoint chopped[10];
+    int count = SkChopCubicAtInflections(p, chopped);
+
+    const SkScalar tolSqd = SkScalarSquare(tolScale);
+
+    for (int i = 0; i < count; ++i) {
+        SkPoint* cubic = chopped + 3*i;
+        convert_noninflect_cubic_to_quads(cubic, tolSqd, quads);
+    }
+}
+
+void GrPathUtils::convertCubicToQuadsConstrainToTangents(const SkPoint p[4],
+                                                         SkScalar tolScale,
+                                                         SkPathPriv::FirstDirection dir,
+                                                         SkTArray<SkPoint, true>* quads) {
+    if (!p[0].isFinite() || !p[1].isFinite() || !p[2].isFinite() || !p[3].isFinite()) {
+        return;
+    }
+    if (!SkScalarIsFinite(tolScale)) {
+        return;
+    }
+    SkPoint chopped[10];
+    int count = SkChopCubicAtInflections(p, chopped);
+
+    const SkScalar tolSqd = SkScalarSquare(tolScale);
+
+    for (int i = 0; i < count; ++i) {
+        SkPoint* cubic = chopped + 3*i;
+        convert_noninflect_cubic_to_quads_with_constraint(cubic, tolSqd, dir, quads);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+using ExcludedTerm = GrPathUtils::ExcludedTerm;
+
+ExcludedTerm GrPathUtils::calcCubicInverseTransposePowerBasisMatrix(const SkPoint p[4],
+                                                                    SkMatrix* out) {
+    GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT);
+
+    // First convert the bezier coordinates p[0..3] to power basis coefficients X,Y(,W=[0 0 0 1]).
+    // M3 is the matrix that does this conversion. The homogeneous equation for the cubic becomes:
+    //
+    //                                     | X   Y   0 |
+    // C(t,s) = [t^3  t^2*s  t*s^2  s^3] * | .   .   0 |
+    //                                     | .   .   0 |
+    //                                     | .   .   1 |
+    //
+    const Sk4f M3[3] = {Sk4f(-1, 3, -3, 1),
+                        Sk4f(3, -6, 3, 0),
+                        Sk4f(-3, 3, 0, 0)};
+    // 4th col of M3 =  Sk4f(1, 0, 0, 0)};
+    Sk4f X(p[3].x(), 0, 0, 0);
+    Sk4f Y(p[3].y(), 0, 0, 0);
+    for (int i = 2; i >= 0; --i) {
+        X += M3[i] * p[i].x();
+        Y += M3[i] * p[i].y();
+    }
+
+    // The matrix is 3x4. In order to invert it, we first need to make it square by throwing out one
+    // of the middle two rows. We toss the row that leaves us with the largest absolute determinant.
+    // Since the right column will be [0 0 1], the respective determinants reduce to x0*y2 - y0*x2
+    // and x0*y1 - y0*x1.
+    SkScalar dets[4];
+    Sk4f D = SkNx_shuffle<0,0,2,1>(X) * SkNx_shuffle<2,1,0,0>(Y);
+    D -= SkNx_shuffle<2,3,0,1>(D);
+    D.store(dets);
+    ExcludedTerm skipTerm = SkScalarAbs(dets[0]) > SkScalarAbs(dets[1]) ?
+                            ExcludedTerm::kQuadraticTerm : ExcludedTerm::kLinearTerm;
+    SkScalar det = dets[ExcludedTerm::kQuadraticTerm == skipTerm ? 0 : 1];
+    if (0 == det) {
+        return ExcludedTerm::kNonInvertible;
+    }
+    SkScalar rdet = 1 / det;
+
+    // Compute the inverse-transpose of the power basis matrix with the 'skipRow'th row removed.
+    // Since W=[0 0 0 1], it follows that our corresponding solution will be equal to:
+    //
+    //             |  y1  -x1   x1*y2 - y1*x2 |
+    //     1/det * | -y0   x0  -x0*y2 + y0*x2 |
+    //             |   0    0             det |
+    //
+    SkScalar x[4], y[4], z[4];
+    X.store(x);
+    Y.store(y);
+    (X * SkNx_shuffle<3,3,3,3>(Y) - Y * SkNx_shuffle<3,3,3,3>(X)).store(z);
+
+    int middleRow = ExcludedTerm::kQuadraticTerm == skipTerm ? 2 : 1;
+    out->setAll( y[middleRow] * rdet, -x[middleRow] * rdet,  z[middleRow] * rdet,
+                        -y[0] * rdet,          x[0] * rdet,         -z[0] * rdet,
+                                   0,                    0,                    1);
+
+    return skipTerm;
+}
+
+inline static void calc_serp_kcoeffs(SkScalar tl, SkScalar sl, SkScalar tm, SkScalar sm,
+                                     ExcludedTerm skipTerm, SkScalar outCoeffs[3]) {
+    SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm);
+    outCoeffs[0] = 0;
+    outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? sl*sm : -tl*sm - tm*sl;
+    outCoeffs[2] = tl*tm;
+}
+
+inline static void calc_serp_lmcoeffs(SkScalar t, SkScalar s, ExcludedTerm skipTerm,
+                                      SkScalar outCoeffs[3]) {
+    SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm);
+    outCoeffs[0] = -s*s*s;
+    outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? 3*s*s*t : -3*s*t*t;
+    outCoeffs[2] = t*t*t;
+}
+
+inline static void calc_loop_kcoeffs(SkScalar td, SkScalar sd, SkScalar te, SkScalar se,
+                                     SkScalar tdse, SkScalar tesd, ExcludedTerm skipTerm,
+                                     SkScalar outCoeffs[3]) {
+    SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm);
+    outCoeffs[0] = 0;
+    outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? sd*se : -tdse - tesd;
+    outCoeffs[2] = td*te;
+}
+
+inline static void calc_loop_lmcoeffs(SkScalar t2, SkScalar s2, SkScalar t1, SkScalar s1,
+                                      SkScalar t2s1, SkScalar t1s2, ExcludedTerm skipTerm,
+                                      SkScalar outCoeffs[3]) {
+    SkASSERT(ExcludedTerm::kQuadraticTerm == skipTerm || ExcludedTerm::kLinearTerm == skipTerm);
+    outCoeffs[0] = -s2*s2*s1;
+    outCoeffs[1] = (ExcludedTerm::kLinearTerm == skipTerm) ? s2 * (2*t2s1 + t1s2)
+                                                           : -t2 * (t2s1 + 2*t1s2);
+    outCoeffs[2] = t2*t2*t1;
+}
+
+// For the case when a cubic bezier is actually a quadratic. We duplicate k in l so that the
+// implicit becomes:
+//
+//     k^3 - l*m == k^3 - l*k == k * (k^2 - l)
+//
+// In the quadratic case we can simply assign fixed values at each control point:
+//
+//     | ..K.. |     | pts[0]  pts[1]  pts[2]  pts[3] |      | 0   1/3  2/3  1 |
+//     | ..L.. |  *  |   .       .       .       .    |  ==  | 0     0  1/3  1 |
+//     | ..K.. |     |   1       1       1       1    |      | 0   1/3  2/3  1 |
+//
+static void calc_quadratic_klm(const SkPoint pts[4], double d3, SkMatrix* klm) {
+    SkMatrix klmAtPts;
+    klmAtPts.setAll(0,  1.f/3,  1,
+                    0,      0,  1,
+                    0,  1.f/3,  1);
+
+    SkMatrix inversePts;
+    inversePts.setAll(pts[0].x(),  pts[1].x(),  pts[3].x(),
+                      pts[0].y(),  pts[1].y(),  pts[3].y(),
+                               1,           1,           1);
+    SkAssertResult(inversePts.invert(&inversePts));
+
+    klm->setConcat(klmAtPts, inversePts);
+
+    // If d3 > 0 we need to flip the orientation of our curve
+    // This is done by negating the k and l values
+    if (d3 > 0) {
+        klm->postScale(-1, -1);
+    }
+}
+
+// For the case when a cubic bezier is actually a line. We set K=0, L=1, M=-line, which results in
+// the following implicit:
+//
+//     k^3 - l*m == 0^3 - 1*(-line) == -(-line) == line
+//
+static void calc_line_klm(const SkPoint pts[4], SkMatrix* klm) {
+    SkScalar ny = pts[0].x() - pts[3].x();
+    SkScalar nx = pts[3].y() - pts[0].y();
+    SkScalar k = nx * pts[0].x() + ny * pts[0].y();
+    klm->setAll(  0,   0, 0,
+                  0,   0, 1,
+                -nx, -ny, k);
+}
+
+SkCubicType GrPathUtils::getCubicKLM(const SkPoint src[4], SkMatrix* klm, double tt[2],
+                                     double ss[2]) {
+    double d[4];
+    SkCubicType type = SkClassifyCubic(src, tt, ss, d);
+
+    if (SkCubicType::kLineOrPoint == type) {
+        calc_line_klm(src, klm);
+        return SkCubicType::kLineOrPoint;
+    }
+
+    if (SkCubicType::kQuadratic == type) {
+        calc_quadratic_klm(src, d[3], klm);
+        return SkCubicType::kQuadratic;
+    }
+
+    SkMatrix CIT;
+    ExcludedTerm skipTerm = calcCubicInverseTransposePowerBasisMatrix(src, &CIT);
+    if (ExcludedTerm::kNonInvertible == skipTerm) {
+        // This could technically also happen if the curve were quadratic, but SkClassifyCubic
+        // should have detected that case already with tolerance.
+        calc_line_klm(src, klm);
+        return SkCubicType::kLineOrPoint;
+    }
+
+    const SkScalar t0 = static_cast<SkScalar>(tt[0]), t1 = static_cast<SkScalar>(tt[1]),
+                   s0 = static_cast<SkScalar>(ss[0]), s1 = static_cast<SkScalar>(ss[1]);
+
+    SkMatrix klmCoeffs;
+    switch (type) {
+        case SkCubicType::kCuspAtInfinity:
+            SkASSERT(1 == t1 && 0 == s1); // Infinity.
+            // fallthru.
+        case SkCubicType::kLocalCusp:
+        case SkCubicType::kSerpentine:
+            calc_serp_kcoeffs(t0, s0, t1, s1, skipTerm, &klmCoeffs[0]);
+            calc_serp_lmcoeffs(t0, s0, skipTerm, &klmCoeffs[3]);
+            calc_serp_lmcoeffs(t1, s1, skipTerm, &klmCoeffs[6]);
+            break;
+        case SkCubicType::kLoop: {
+            const SkScalar tdse = t0 * s1;
+            const SkScalar tesd = t1 * s0;
+            calc_loop_kcoeffs(t0, s0, t1, s1, tdse, tesd, skipTerm, &klmCoeffs[0]);
+            calc_loop_lmcoeffs(t0, s0, t1, s1, tdse, tesd, skipTerm, &klmCoeffs[3]);
+            calc_loop_lmcoeffs(t1, s1, t0, s0, tesd, tdse, skipTerm, &klmCoeffs[6]);
+            break;
+        }
+        default:
+            SK_ABORT("Unexpected cubic type.");
+            break;
+    }
+
+    klm->setConcat(klmCoeffs, CIT);
+    return type;
+}
+
+int GrPathUtils::chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[10], SkMatrix* klm,
+                                             int* loopIndex) {
+    SkSTArray<2, SkScalar> chops;
+    *loopIndex = -1;
+
+    double t[2], s[2];
+    if (SkCubicType::kLoop == GrPathUtils::getCubicKLM(src, klm, t, s)) {
+        SkScalar t0 = static_cast<SkScalar>(t[0] / s[0]);
+        SkScalar t1 = static_cast<SkScalar>(t[1] / s[1]);
+        SkASSERT(t0 <= t1); // Technically t0 != t1 in a loop, but there may be FP error.
+
+        if (t0 < 1 && t1 > 0) {
+            *loopIndex = 0;
+            if (t0 > 0) {
+                chops.push_back(t0);
+                *loopIndex = 1;
+            }
+            if (t1 < 1) {
+                chops.push_back(t1);
+                *loopIndex = chops.count() - 1;
+            }
+        }
+    }
+
+    SkChopCubicAt(src, dst, chops.begin(), chops.count());
+    return chops.count() + 1;
+}
diff --git a/src/gpu/geometry/GrPathUtils.h b/src/gpu/geometry/GrPathUtils.h
new file mode 100644
index 0000000..f44f2c0
--- /dev/null
+++ b/src/gpu/geometry/GrPathUtils.h
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrPathUtils_DEFINED
+#define GrPathUtils_DEFINED
+
+#include "include/core/SkRect.h"
+#include "include/private/SkTArray.h"
+#include "src/core/SkGeometry.h"
+#include "src/core/SkPathPriv.h"
+
+class SkMatrix;
+
+/**
+ *  Utilities for evaluating paths.
+ */
+namespace GrPathUtils {
+    // Very small tolerances will be increased to a minimum threshold value, to avoid division
+    // problems in subsequent math.
+    SkScalar scaleToleranceToSrc(SkScalar devTol,
+                                 const SkMatrix& viewM,
+                                 const SkRect& pathBounds);
+
+    int worstCasePointCount(const SkPath&,
+                            int* subpaths,
+                            SkScalar tol);
+
+    uint32_t quadraticPointCount(const SkPoint points[], SkScalar tol);
+
+    uint32_t generateQuadraticPoints(const SkPoint& p0,
+                                     const SkPoint& p1,
+                                     const SkPoint& p2,
+                                     SkScalar tolSqd,
+                                     SkPoint** points,
+                                     uint32_t pointsLeft);
+
+    uint32_t cubicPointCount(const SkPoint points[], SkScalar tol);
+
+    uint32_t generateCubicPoints(const SkPoint& p0,
+                                 const SkPoint& p1,
+                                 const SkPoint& p2,
+                                 const SkPoint& p3,
+                                 SkScalar tolSqd,
+                                 SkPoint** points,
+                                 uint32_t pointsLeft);
+
+    // A 2x3 matrix that goes from the 2d space coordinates to UV space where
+    // u^2-v = 0 specifies the quad. The matrix is determined by the control
+    // points of the quadratic.
+    class QuadUVMatrix {
+    public:
+        QuadUVMatrix() {}
+        // Initialize the matrix from the control pts
+        QuadUVMatrix(const SkPoint controlPts[3]) { this->set(controlPts); }
+        void set(const SkPoint controlPts[3]);
+
+        /**
+         * Applies the matrix to vertex positions to compute UV coords.
+         *
+         * vertices is a pointer to the first vertex.
+         * vertexCount is the number of vertices.
+         * stride is the size of each vertex.
+         * uvOffset is the offset of the UV values within each vertex.
+         */
+        void apply(void* vertices, int vertexCount, size_t stride, size_t uvOffset) const {
+            intptr_t xyPtr = reinterpret_cast<intptr_t>(vertices);
+            intptr_t uvPtr = reinterpret_cast<intptr_t>(vertices) + uvOffset;
+            float sx = fM[0];
+            float kx = fM[1];
+            float tx = fM[2];
+            float ky = fM[3];
+            float sy = fM[4];
+            float ty = fM[5];
+            for (int i = 0; i < vertexCount; ++i) {
+                const SkPoint* xy = reinterpret_cast<const SkPoint*>(xyPtr);
+                SkPoint* uv = reinterpret_cast<SkPoint*>(uvPtr);
+                uv->fX = sx * xy->fX + kx * xy->fY + tx;
+                uv->fY = ky * xy->fX + sy * xy->fY + ty;
+                xyPtr += stride;
+                uvPtr += stride;
+            }
+        }
+    private:
+        float fM[6];
+    };
+
+    // Input is 3 control points and a weight for a bezier conic. Calculates the
+    // three linear functionals (K,L,M) that represent the implicit equation of the
+    // conic, k^2 - lm.
+    //
+    // Output: klm holds the linear functionals K,L,M as row vectors:
+    //
+    //     | ..K.. |   | x |      | k |
+    //     | ..L.. | * | y |  ==  | l |
+    //     | ..M.. |   | 1 |      | m |
+    //
+    void getConicKLM(const SkPoint p[3], const SkScalar weight, SkMatrix* klm);
+
+    // Converts a cubic into a sequence of quads. If working in device space
+    // use tolScale = 1, otherwise set based on stretchiness of the matrix. The
+    // result is sets of 3 points in quads. This will preserve the starting and
+    // ending tangent vectors (modulo FP precision).
+    void convertCubicToQuads(const SkPoint p[4],
+                             SkScalar tolScale,
+                             SkTArray<SkPoint, true>* quads);
+
+    // When we approximate a cubic {a,b,c,d} with a quadratic we may have to
+    // ensure that the new control point lies between the lines ab and cd. The
+    // convex path renderer requires this. It starts with a path where all the
+    // control points taken together form a convex polygon. It relies on this
+    // property and the quadratic approximation of cubics step cannot alter it.
+    // This variation enforces this constraint. The cubic must be simple and dir
+    // must specify the orientation of the contour containing the cubic.
+    void convertCubicToQuadsConstrainToTangents(const SkPoint p[4],
+                                                SkScalar tolScale,
+                                                SkPathPriv::FirstDirection dir,
+                                                SkTArray<SkPoint, true>* quads);
+
+    enum class ExcludedTerm {
+        kNonInvertible,
+        kQuadraticTerm,
+        kLinearTerm
+    };
+
+    // Computes the inverse-transpose of the cubic's power basis matrix, after removing a specific
+    // row of coefficients.
+    //
+    // E.g. if the cubic is defined in power basis form as follows:
+    //
+    //                                         | x3   y3   0 |
+    //     C(t,s) = [t^3  t^2*s  t*s^2  s^3] * | x2   y2   0 |
+    //                                         | x1   y1   0 |
+    //                                         | x0   y0   1 |
+    //
+    // And the excluded term is "kQuadraticTerm", then the resulting inverse-transpose will be:
+    //
+    //     | x3   y3   0 | -1 T
+    //     | x1   y1   0 |
+    //     | x0   y0   1 |
+    //
+    // (The term to exclude is chosen based on maximizing the resulting matrix determinant.)
+    //
+    // This can be used to find the KLM linear functionals:
+    //
+    //     | ..K.. |   | ..kcoeffs.. |
+    //     | ..L.. | = | ..lcoeffs.. | * inverse_transpose_power_basis_matrix
+    //     | ..M.. |   | ..mcoeffs.. |
+    //
+    // NOTE: the same term that was excluded here must also be removed from the corresponding column
+    // of the klmcoeffs matrix.
+    //
+    // Returns which row of coefficients was removed, or kNonInvertible if the cubic was degenerate.
+    ExcludedTerm calcCubicInverseTransposePowerBasisMatrix(const SkPoint p[4], SkMatrix* out);
+
+    // Computes the KLM linear functionals for the cubic implicit form. The "right" side of the
+    // curve (when facing in the direction of increasing parameter values) will be the area that
+    // satisfies:
+    //
+    //     k^3 < l*m
+    //
+    // Output:
+    //
+    // klm: Holds the linear functionals K,L,M as row vectors:
+    //
+    //          | ..K.. |   | x |      | k |
+    //          | ..L.. | * | y |  ==  | l |
+    //          | ..M.. |   | 1 |      | m |
+    //
+    // NOTE: the KLM lines are calculated in the same space as the input control points. If you
+    // transform the points the lines will also need to be transformed. This can be done by mapping
+    // the lines with the inverse-transpose of the matrix used to map the points.
+    //
+    // t[],s[]: These are set to the two homogeneous parameter values at which points the lines L&M
+    // intersect with K (See SkClassifyCubic).
+    //
+    // Returns the cubic's classification.
+    SkCubicType getCubicKLM(const SkPoint src[4], SkMatrix* klm, double t[2], double s[2]);
+
+    // Chops the cubic bezier passed in by src, at the double point (intersection point)
+    // if the curve is a cubic loop. If it is a loop, there will be two parametric values for
+    // the double point: t1 and t2. We chop the cubic at these values if they are between 0 and 1.
+    // Return value:
+    // Value of 3: t1 and t2 are both between (0,1), and dst will contain the three cubics,
+    //             dst[0..3], dst[3..6], and dst[6..9] if dst is not nullptr
+    // Value of 2: Only one of t1 and t2 are between (0,1), and dst will contain the two cubics,
+    //             dst[0..3] and dst[3..6] if dst is not nullptr
+    // Value of 1: Neither t1 nor t2 are between (0,1), and dst will contain the one original cubic,
+    //             src[0..3]
+    //
+    // Output:
+    //
+    // klm: Holds the linear functionals K,L,M as row vectors. (See getCubicKLM().)
+    //
+    // loopIndex: This value will tell the caller which of the chopped sections (if any) are the
+    //            actual loop. A value of -1 means there is no loop section. The caller can then use
+    //            this value to decide how/if they want to flip the orientation of this section.
+    //            The flip should be done by negating the k and l values as follows:
+    //
+    //            KLM.postScale(-1, -1)
+    int chopCubicAtLoopIntersection(const SkPoint src[4], SkPoint dst[10], SkMatrix* klm,
+                                    int* loopIndex);
+
+    // When tessellating curved paths into linear segments, this defines the maximum distance
+    // in screen space which a segment may deviate from the mathmatically correct value.
+    // Above this value, the segment will be subdivided.
+    // This value was chosen to approximate the supersampling accuracy of the raster path (16
+    // samples, or one quarter pixel).
+    static const SkScalar kDefaultTolerance = SkDoubleToScalar(0.25);
+
+    // We guarantee that no quad or cubic will ever produce more than this many points
+    static const int kMaxPointsPerCurve = 1 << 10;
+};
+#endif
diff --git a/src/gpu/geometry/GrRect.h b/src/gpu/geometry/GrRect.h
new file mode 100644
index 0000000..064d539
--- /dev/null
+++ b/src/gpu/geometry/GrRect.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrRect_DEFINED
+#define GrRect_DEFINED
+
+#include "include/core/SkRect.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkTo.h"
+
+struct GrIRect16 {
+    int16_t fLeft, fTop, fRight, fBottom;
+
+    static GrIRect16 SK_WARN_UNUSED_RESULT MakeEmpty() {
+        GrIRect16 r;
+        r.setEmpty();
+        return r;
+    }
+
+    static GrIRect16 SK_WARN_UNUSED_RESULT MakeWH(int16_t w, int16_t h) {
+        GrIRect16 r;
+        r.set(0, 0, w, h);
+        return r;
+    }
+
+    static GrIRect16 SK_WARN_UNUSED_RESULT MakeXYWH(int16_t x, int16_t y, int16_t w, int16_t h) {
+        GrIRect16 r;
+        r.set(x, y, x + w, y + h);
+        return r;
+    }
+
+    int width() const { return fRight - fLeft; }
+    int height() const { return fBottom - fTop; }
+    int area() const { return this->width() * this->height(); }
+    bool isEmpty() const { return fLeft >= fRight || fTop >= fBottom; }
+
+    void setEmpty() { memset(this, 0, sizeof(*this)); }
+
+    void set(int16_t left, int16_t top, int16_t right, int16_t bottom) {
+        fLeft = left;
+        fTop = top;
+        fRight = right;
+        fBottom = bottom;
+    }
+
+    void set(const SkIRect& r) {
+        fLeft   = SkToS16(r.fLeft);
+        fTop    = SkToS16(r.fTop);
+        fRight  = SkToS16(r.fRight);
+        fBottom = SkToS16(r.fBottom);
+    }
+};
+
+/** Returns true if the rectangles have a nonzero area of overlap. It assumed that rects can be
+    infinitely small but not "inverted". */
+static inline bool GrRectsOverlap(const SkRect& a, const SkRect& b) {
+    // See skbug.com/6607 about the isFinite() checks.
+    SkASSERT(!a.isFinite() || (a.fLeft <= a.fRight && a.fTop <= a.fBottom));
+    SkASSERT(!b.isFinite() || (b.fLeft <= b.fRight && b.fTop <= b.fBottom));
+    return a.fRight > b.fLeft && a.fBottom > b.fTop && b.fRight > a.fLeft && b.fBottom > a.fTop;
+}
+
+/** Returns true if the rectangles overlap or share an edge or corner. It assumed that rects can be
+    infinitely small but not "inverted". */
+static inline bool GrRectsTouchOrOverlap(const SkRect& a, const SkRect& b) {
+    // See skbug.com/6607 about the isFinite() checks.
+    SkASSERT(!a.isFinite() || (a.fLeft <= a.fRight && a.fTop <= a.fBottom));
+    SkASSERT(!b.isFinite() || (b.fLeft <= b.fRight && b.fTop <= b.fBottom));
+    return a.fRight >= b.fLeft && a.fBottom >= b.fTop && b.fRight >= a.fLeft && b.fBottom >= a.fTop;
+}
+
+/**
+ * Apply the transform from 'inRect' to 'outRect' to each point in 'inPts', storing the mapped point
+ * into the parallel index of 'outPts'.
+ */
+static inline void GrMapRectPoints(const SkRect& inRect, const SkRect& outRect,
+                                   const SkPoint inPts[], SkPoint outPts[], int ptCount) {
+    SkMatrix rectTransform = SkMatrix::MakeRectToRect(inRect, outRect, SkMatrix::kFill_ScaleToFit);
+    rectTransform.mapPoints(outPts, inPts, ptCount);
+}
+#endif
diff --git a/src/gpu/geometry/GrShape.cpp b/src/gpu/geometry/GrShape.cpp
new file mode 100644
index 0000000..756b68f
--- /dev/null
+++ b/src/gpu/geometry/GrShape.cpp
@@ -0,0 +1,767 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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 <utility>
+
+GrShape& GrShape::operator=(const GrShape& 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;
+    }
+    fInheritedKey.reset(that.fInheritedKey.count());
+    sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(),
+                      sizeof(uint32_t) * fInheritedKey.count());
+    if (that.fInheritedPathForListeners.isValid()) {
+        fInheritedPathForListeners.set(*that.fInheritedPathForListeners.get());
+    } else {
+        fInheritedPathForListeners.reset();
+    }
+    return *this;
+}
+
+static bool flip_inversion(bool originalIsInverted, GrShape::FillInversion inversion) {
+    switch (inversion) {
+        case GrShape::FillInversion::kPreserve:
+            return false;
+        case GrShape::FillInversion::kFlip:
+            return true;
+        case GrShape::FillInversion::kForceInverted:
+            return !originalIsInverted;
+        case GrShape::FillInversion::kForceNoninverted:
+            return originalIsInverted;
+    }
+    return false;
+}
+
+static bool is_inverted(bool originalIsInverted, GrShape::FillInversion inversion) {
+    switch (inversion) {
+        case GrShape::FillInversion::kPreserve:
+            return originalIsInverted;
+        case GrShape::FillInversion::kFlip:
+            return !originalIsInverted;
+        case GrShape::FillInversion::kForceInverted:
+            return true;
+        case GrShape::FillInversion::kForceNoninverted:
+            return false;
+    }
+    return false;
+}
+
+GrShape GrShape::MakeFilled(const GrShape& original, FillInversion inversion) {
+    if (original.style().isSimpleFill() && !flip_inversion(original.inverseFilled(), inversion)) {
+        // 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;
+    }
+    GrShape result;
+    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;
+    }
+    // We don't copy the inherited key since it can contain path effect information that we just
+    // stripped.
+    return result;
+}
+
+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 (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");
+    return kInverted;
+}
+
+SkRect GrShape::styledBounds() const {
+    if (this->isEmpty() && !fStyle.hasNonDashPathEffect()) {
+        return SkRect::MakeEmpty();
+    }
+
+    SkRect bounds;
+    fStyle.adjustBounds(&bounds, this->bounds());
+    return bounds;
+}
+
+// If the path is small enough to be keyed from its data this returns key length, otherwise -1.
+static int path_key_from_data_size(const SkPath& path) {
+    const int verbCnt = path.countVerbs();
+    if (verbCnt > GrShape::kMaxKeyFromDataVerbCnt) {
+        return -1;
+    }
+    const int pointCnt = path.countPoints();
+    const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);
+
+    GR_STATIC_ASSERT(sizeof(SkPoint) == 2 * sizeof(uint32_t));
+    GR_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
+    // a uint32_t length.
+    return 2 + (SkAlign4(verbCnt) >> 2) + 2 * pointCnt + conicWeightCnt;
+}
+
+// Writes the path data key into the passed pointer.
+static void write_path_key_from_data(const SkPath& path, uint32_t* origKey) {
+    uint32_t* key = origKey;
+    // The check below should take care of negative values casted positive.
+    const int verbCnt = path.countVerbs();
+    const int pointCnt = path.countPoints();
+    const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);
+    SkASSERT(verbCnt <= GrShape::kMaxKeyFromDataVerbCnt);
+    SkASSERT(pointCnt && verbCnt);
+    *key++ = path.getFillType();
+    *key++ = verbCnt;
+    memcpy(key, SkPathPriv::VerbData(path), verbCnt * sizeof(uint8_t));
+    int verbKeySize = SkAlign4(verbCnt);
+    // pad out to uint32_t alignment using value that will stand out when debugging.
+    uint8_t* pad = reinterpret_cast<uint8_t*>(key)+ verbCnt;
+    memset(pad, 0xDE, verbKeySize - verbCnt);
+    key += verbKeySize >> 2;
+
+    memcpy(key, SkPathPriv::PointData(path), sizeof(SkPoint) * pointCnt);
+    GR_STATIC_ASSERT(sizeof(SkPoint) == 2 * sizeof(uint32_t));
+    key += 2 * pointCnt;
+    sk_careful_memcpy(key, SkPathPriv::ConicWeightData(path), sizeof(SkScalar) * conicWeightCnt);
+    GR_STATIC_ASSERT(sizeof(SkScalar) == sizeof(uint32_t));
+    SkDEBUGCODE(key += conicWeightCnt);
+    SkASSERT(key - origKey == path_key_from_data_size(path));
+}
+
+int GrShape::unstyledKeySize() const {
+    if (fInheritedKey.count()) {
+        return fInheritedKey.count();
+    }
+    switch (fType) {
+        case Type::kEmpty:
+            return 1;
+        case Type::kInvertedEmpty:
+            return 1;
+        case Type::kRRect:
+            SkASSERT(!fInheritedKey.count());
+            GR_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());
+            GR_STATIC_ASSERT(0 == sizeof(fArcData) % sizeof(uint32_t));
+            return sizeof(fArcData) / sizeof(uint32_t);
+        case Type::kLine:
+            GR_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;
+            }
+            int dataKeySize = path_key_from_data_size(fPathData.fPath);
+            if (dataKeySize >= 0) {
+                return dataKeySize;
+            }
+            // The key is the path ID and fill type.
+            return 2;
+        }
+    }
+    SK_ABORT("Should never get here.");
+    return 0;
+}
+
+void GrShape::writeUnstyledKey(uint32_t* key) const {
+    SkASSERT(this->unstyledKeySize());
+    SkDEBUGCODE(uint32_t* origKey = key;)
+    if (fInheritedKey.count()) {
+        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 == SkPath::kCCW_Direction) ? (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);
+                if (dataKeySize >= 0) {
+                    write_path_key_from_data(fPathData.fPath, key);
+                    return;
+                }
+                *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++ = this->path().getFillType();
+                break;
+            }
+        }
+    }
+    SkASSERT(key - origKey == this->unstyledKeySize());
+}
+
+void GrShape::setInheritedKey(const GrShape &parent, GrStyle::Apply apply, 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) {
+        // We want ApplyFullStyle(ApplyPathEffect(shape)) to have the same key as
+        // ApplyFullStyle(shape).
+        // The full key is structured as (geo,path_effect,stroke).
+        // If we do ApplyPathEffect we get geo,path_effect as the inherited key. If we then
+        // do ApplyFullStyle we'll memcpy geo,path_effect into the new inherited key
+        // and then append the style key (which should now be stroke only) at the end.
+        int parentCnt = parent.fInheritedKey.count();
+        bool useParentGeoKey = !parentCnt;
+        if (useParentGeoKey) {
+            parentCnt = parent.unstyledKeySize();
+            if (parentCnt < 0) {
+                // The parent's geometry has no key so we will have no key.
+                fPathData.fGenID = 0;
+                return;
+            }
+        }
+        uint32_t styleKeyFlags = 0;
+        if (parent.knownToBeClosed()) {
+            styleKeyFlags |= GrStyle::kClosed_KeyFlag;
+        }
+        if (parent.asLine(nullptr, nullptr)) {
+            styleKeyFlags |= GrStyle::kNoJoins_KeyFlag;
+        }
+        int styleCnt = GrStyle::KeySize(parent.fStyle, apply, styleKeyFlags);
+        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;
+            return;
+        }
+        fInheritedKey.reset(parentCnt + styleCnt);
+        if (useParentGeoKey) {
+            // This will be the geo key.
+            parent.writeUnstyledKey(fInheritedKey.get());
+        } else {
+            // This should be (geo,path_effect).
+            memcpy(fInheritedKey.get(), parent.fInheritedKey.get(),
+                   parentCnt * sizeof(uint32_t));
+        }
+        // Now turn (geo,path_effect) or (geo) into (geo,path_effect,stroke)
+        GrStyle::WriteKey(fInheritedKey.get() + parentCnt, parent.fStyle, apply, scale,
+                          styleKeyFlags);
+    }
+}
+
+const SkPath* GrShape::originalPathForListeners() const {
+    if (fInheritedPathForListeners.isValid()) {
+        return fInheritedPathForListeners.get();
+    } else if (Type::kPath == fType && !fPathData.fPath.isVolatile()) {
+        return &fPathData.fPath;
+    }
+    return nullptr;
+}
+
+void GrShape::addGenIDChangeListener(sk_sp<SkPathRef::GenIDChangeListener> listener) const {
+    if (const auto* lp = this->originalPathForListeners()) {
+        SkPathPriv::AddGenIDChangeListener(*lp, std::move(listener));
+    }
+}
+
+GrShape GrShape::MakeArc(const SkRect& oval, SkScalar startAngleDegrees, SkScalar sweepAngleDegrees,
+                         bool useCenter, const GrStyle& style) {
+    GrShape 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.fStyle = style;
+    result.attemptToSimplifyArc();
+    return result;
+}
+
+GrShape::GrShape(const GrShape& 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;
+    }
+    fInheritedKey.reset(that.fInheritedKey.count());
+    sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(),
+                      sizeof(uint32_t) * fInheritedKey.count());
+    if (that.fInheritedPathForListeners.isValid()) {
+        fInheritedPathForListeners.set(*that.fInheritedPathForListeners.get());
+    }
+}
+
+GrShape::GrShape(const GrShape& parent, GrStyle::Apply apply, SkScalar scale) {
+    // TODO: Add some quantization of scale for better cache performance here or leave that up
+    // to caller?
+    // TODO: For certain shapes and stroke params we could ignore the scale. (e.g. miter or bevel
+    // stroke of a rect).
+    if (!parent.style().applies() ||
+        (GrStyle::Apply::kPathEffectOnly == apply && !parent.style().pathEffect())) {
+        this->initType(Type::kEmpty);
+        *this = parent;
+        return;
+    }
+
+    SkPathEffect* pe = parent.fStyle.pathEffect();
+    SkTLazy<SkPath> tmpPath;
+    const GrShape* parentForKey = &parent;
+    SkTLazy<GrShape> tmpParent;
+    this->initType(Type::kPath);
+    fPathData.fGenID = 0;
+    if (pe) {
+        const SkPath* srcForPathEffect;
+        if (parent.fType == Type::kPath) {
+            srcForPathEffect = &parent.path();
+        } else {
+            srcForPathEffect = tmpPath.init();
+            parent.asPath(tmpPath.get());
+        }
+        // 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,
+                                                 scale)) {
+            tmpParent.init(*srcForPathEffect, GrStyle(strokeRec, nullptr));
+            *this = tmpParent.get()->applyStyle(apply, scale);
+            return;
+        }
+        // A path effect has access to change the res scale but we aren't expecting it to and it
+        // would mess up our key computation.
+        SkASSERT(scale == strokeRec.getResScale());
+        if (GrStyle::Apply::kPathEffectAndStrokeRec == apply && strokeRec.needToApply()) {
+            // The intermediate shape may not be a general path. If we we're just applying
+            // the path effect then attemptToReduceFromPath would catch it. This means that
+            // when we subsequently applied the remaining strokeRec we would have a non-path
+            // parent shape that would be used to determine the the stroked path's key.
+            // 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.get()->setInheritedKey(parent, GrStyle::Apply::kPathEffectOnly, scale);
+            if (!tmpPath.isValid()) {
+                tmpPath.init();
+            }
+            tmpParent.get()->asPath(tmpPath.get());
+            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,
+                                                                    *tmpPath.get(), scale));
+            } else if (tmpParent.get()->style().isSimpleFill()) {
+                fillOrHairline = SkStrokeRec::kFill_InitStyle;
+            } else {
+                SkASSERT(tmpParent.get()->style().isSimpleHairline());
+                fillOrHairline = SkStrokeRec::kHairline_InitStyle;
+            }
+            fStyle.resetToInitStyle(fillOrHairline);
+            parentForKey = tmpParent.get();
+        } else {
+            fStyle = GrStyle(strokeRec, nullptr);
+        }
+    } else {
+        const SkPath* srcForParentStyle;
+        if (parent.fType == Type::kPath) {
+            srcForParentStyle = &parent.path();
+        } else {
+            srcForParentStyle = tmpPath.init();
+            parent.asPath(tmpPath.get());
+        }
+        SkStrokeRec::InitStyle fillOrHairline;
+        SkASSERT(parent.fStyle.applies());
+        SkASSERT(!parent.fStyle.pathEffect());
+        SkAssertResult(parent.fStyle.applyToPath(&this->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);
+    }
+    this->attemptToSimplifyPath();
+    this->setInheritedKey(*parentForKey, apply, scale);
+}
+
+void GrShape::attemptToSimplifyPath() {
+    SkRect rect;
+    SkRRect rrect;
+    SkPath::Direction 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;
+            }
+        }
+    }
+    if (Type::kPath != fType) {
+        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();
+        }
+    } else {
+        if (fInheritedKey.count() || this->path().isVolatile()) {
+            fPathData.fGenID = 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);
+                }
+            }
+        }
+    }
+}
+
+void GrShape::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();
+            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;
+        }
+    }
+    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 GrShape::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;
+        return;
+    } else if (fStyle.hasPathEffect()) {
+        return;
+    }
+    if (fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
+        // Make stroke + fill be stroke since the fill is empty.
+        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 GrShape::attemptToSimplifyArc() {
+    SkASSERT(fType == Type::kArc);
+    SkASSERT(!fArcData.fInverted);
+    if (fArcData.fOval.isEmpty() || !fArcData.fSweepAngleDegrees) {
+        this->changeType(Type::kEmpty);
+        return;
+    }
+
+    // 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;
+            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;
+        }
+    }
+    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 GrShape::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 = SkTMin(fLineData.fPts[0].fX, fLineData.fPts[1].fX);
+        rect.fRight = SkTMax(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 = SkTMin(fLineData.fPts[0].fY, fLineData.fPts[1].fY);
+        rect.fBottom = SkTMax(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;
+    fStyle = GrStyle::SimpleFill();
+    return true;
+}
diff --git a/src/gpu/geometry/GrShape.h b/src/gpu/geometry/GrShape.h
new file mode 100644
index 0000000..f7627e6
--- /dev/null
+++ b/src/gpu/geometry/GrShape.h
@@ -0,0 +1,605 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * 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/SkRRect.h"
+#include "include/private/SkTemplates.h"
+#include "src/core/SkPathPriv.h"
+#include "src/core/SkTLazy.h"
+#include "src/gpu/GrStyle.h"
+#include <new>
+
+/**
+ * Represents a geometric shape (rrect or path) and the GrStyle that it should be rendered with.
+ * It is possible to apply the style to the GrShape to produce a new GrShape where the geometry
+ * reflects the styling information (e.g. is stroked). It is also possible to apply just the
+ * path effect from the style. In this case the resulting shape will include any remaining
+ * stroking information that is to be applied after the path effect.
+ *
+ * Shapes can produce keys that represent only the geometry information, not the style. Note that
+ * when styling information is applied to produce a new shape then the style has been converted
+ * to geometric information and is included in the new shape's key. When the same style is applied
+ * to two shapes that reflect the same underlying geometry the computed keys of the stylized shapes
+ * will be the same.
+ *
+ * Currently this can only be constructed from a path, rect, or rrect though it can become a path
+ * applying style to the geometry. The idea is to expand this to cover most or all of the geometries
+ * that have fast paths in the GPU backend.
+ */
+class GrShape {
+public:
+    // Keys for paths may be extracted from the path data for small paths. Clients aren't supposed
+    // to have to worry about this. This value is exposed for unit tests.
+    static constexpr int kMaxKeyFromDataVerbCnt = 10;
+
+    GrShape() { this->initType(Type::kEmpty); }
+
+    explicit GrShape(const SkPath& path) : GrShape(path, GrStyle::SimpleFill()) {}
+
+    explicit GrShape(const SkRRect& rrect) : GrShape(rrect, GrStyle::SimpleFill()) {}
+
+    explicit GrShape(const SkRect& rect) : GrShape(rect, GrStyle::SimpleFill()) {}
+
+    GrShape(const SkPath& path, const GrStyle& style) : fStyle(style) {
+        this->initType(Type::kPath, &path);
+        this->attemptToSimplifyPath();
+    }
+
+    GrShape(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();
+    }
+
+    GrShape(const SkRRect& rrect, SkPath::Direction 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();
+    }
+
+    GrShape(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();
+    }
+
+    GrShape(const SkPath& path, const SkPaint& paint) : fStyle(paint) {
+        this->initType(Type::kPath, &path);
+        this->attemptToSimplifyPath();
+    }
+
+    GrShape(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();
+    }
+
+    GrShape(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 GrShape MakeArc(const SkRect& oval, SkScalar startAngleDegrees,
+                           SkScalar sweepAngleDegrees, bool useCenter, const GrStyle& style);
+
+    GrShape(const GrShape&);
+    GrShape& operator=(const GrShape& that);
+
+    ~GrShape() { this->changeType(Type::kEmpty); }
+
+    /**
+     * Informs MakeFilled on how to modify that shape's fill rule when making a simple filled
+     * version of the shape.
+     */
+    enum class FillInversion {
+        kPreserve,
+        kFlip,
+        kForceNoninverted,
+        kForceInverted
+    };
+    /**
+     * Makes a filled shape from the pre-styled original shape and optionally modifies whether
+     * the fill is inverted or not. It's important to note that the original shape's geometry
+     * may already have been modified if doing so was neutral with respect to its style
+     * (e.g. filled paths are always closed when stored in a shape and dashed paths are always
+     * made non-inverted since dashing ignores inverseness).
+     */
+    static GrShape MakeFilled(const GrShape& original, FillInversion = FillInversion::kPreserve);
+
+    const GrStyle& style() const { return fStyle; }
+
+    /**
+     * Returns a shape that has either applied the path effect or path effect and stroking
+     * information from this shape's style to its geometry. Scale is used when approximating the
+     * output geometry and typically is computed from the view matrix
+     */
+    GrShape applyStyle(GrStyle::Apply apply, SkScalar scale) const {
+        return GrShape(*this, apply, scale);
+    }
+
+    bool isRect() const {
+        if (Type::kRRect != fType) {
+            return false;
+        }
+
+        return fRRectData.fRRect.isRect();
+    }
+
+    /** Returns the unstyled geometry as a rrect if possible. */
+    bool asRRect(SkRRect* rrect, SkPath::Direction* 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;
+    }
+
+    /**
+     * 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;
+    }
+
+    /** 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;
+        }
+
+        SkPath::Direction dirs[2];
+        if (!this->path().isNestedFillRects(rects, dirs)) {
+            return false;
+        }
+
+        if (SkPath::kWinding_FillType == 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;
+    }
+
+    /**
+     * 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; }
+
+    /**
+     * Gets the bounds of the geometry without reflecting the shape's styling. This ignores
+     * the inverse fill nature of the geometry.
+     */
+    SkRect bounds() const;
+
+    /**
+     * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill
+     * status).
+     */
+    SkRect styledBounds() const;
+
+    /**
+     * Is this shape known to be convex, before styling is applied. An unclosed but otherwise
+     * convex path is considered to be closed if they styling reflects a fill and not otherwise.
+     * 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;
+    }
+
+    /** 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;
+        }
+        // Dashing ignores inverseness. We should have caught this earlier. skbug.com/5421
+        SkASSERT(!(ret && this->style().isDashed()));
+        return ret;
+    }
+
+    /**
+     * Might applying the styling to the geometry produce an inverse fill. The "may" part comes in
+     * because an arbitrary path effect could produce an inverse filled path. In other cases this
+     * can be thought of as "inverseFilledAfterStyling()".
+     */
+    bool mayBeInverseFilledAfterStyling() const {
+         // An arbitrary path effect can produce an arbitrary output path, which may be inverse
+         // filled.
+        if (this->style().hasNonDashPathEffect()) {
+            return true;
+        }
+        return this->inverseFilled();
+    }
+
+    /**
+     * Is it known that the unstyled geometry has no unclosed contours. This means that it will
+     * 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;
+    }
+
+    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;
+    }
+
+    /**
+     * Gets the size of the key for the shape represented by this GrShape (ignoring its styling).
+     * A negative value is returned if the shape has no key (shouldn't be cached).
+     */
+    int unstyledKeySize() const;
+
+    bool hasUnstyledKey() const { return this->unstyledKeySize() >= 0; }
+
+    /**
+     * Writes unstyledKeySize() bytes into the provided pointer. Assumes that there is enough
+     * space allocated for the key and that unstyledKeySize() does not return a negative value
+     * for this shape.
+     */
+    void writeUnstyledKey(uint32_t* key) const;
+
+    /**
+     * Adds a listener to the *original* path. Typically used to invalidate cached resources when
+     * a path is no longer in-use. If the shape started out as something other than a path, this
+     * does nothing.
+     */
+    void addGenIDChangeListener(sk_sp<SkPathRef::GenIDChangeListener>) const;
+
+    /**
+     * Helpers that are only exposed for unit tests, to determine if the shape is a path, and get
+     * the generation ID of the *original* path. This is the path that will receive
+     * GenIDChangeListeners added to this shape.
+     */
+    uint32_t testingOnly_getOriginalGenerationID() const;
+    bool testingOnly_isPath() const;
+    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 */
+    GrShape(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
+
+    /**
+     * Determines the key we should inherit from the input shape's geometry and style when
+     * we are applying the style to create a new shape.
+     */
+    void setInheritedKey(const GrShape& parentShape, GrStyle::Apply, SkScalar scale);
+
+    void attemptToSimplifyPath();
+    void attemptToSimplifyRRect();
+    void attemptToSimplifyLine();
+    void attemptToSimplifyArc();
+
+    bool attemptToSimplifyStrokedLineToRRect();
+
+    /** 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 SkPath::FillType kDefaultPathFillType = SkPath::kEvenOdd_FillType;
+    static constexpr SkPath::FillType kDefaultPathInverseFillType =
+            SkPath::kInverseEvenOdd_FillType;
+
+    static constexpr SkPath::Direction kDefaultRRectDir = SkPath::kCW_Direction;
+    static constexpr unsigned kDefaultRRectStart = 0;
+
+    static unsigned DefaultRectDirAndStartIndex(const SkRect& rect, bool hasPathEffect,
+                                                SkPath::Direction* dir) {
+        *dir = kDefaultRRectDir;
+        // This comes from SkPath's interface. The default for adding a SkRect is counter clockwise
+        // beginning at index 0 (which happens to correspond to rrect index 0 or 7).
+        if (!hasPathEffect) {
+            // It doesn't matter what start we use, just be consistent to avoid redundant keys.
+            return kDefaultRRectStart;
+        }
+        // In SkPath a rect starts at index 0 by default. This is the top left corner. However,
+        // we store rects as rrects. RRects don't preserve the invertedness, but rather sort the
+        // rect edges. Thus, we may need to modify the rrect's start index to account for the sort.
+        bool swapX = rect.fLeft > rect.fRight;
+        bool swapY = rect.fTop > rect.fBottom;
+        if (swapX && swapY) {
+            // 0 becomes start index 2 and times 2 to convert from rect the rrect indices.
+            return 2 * 2;
+        } else if (swapX) {
+            *dir = SkPath::kCCW_Direction;
+            // 0 becomes start index 1 and times 2 to convert from rect the rrect indices.
+            return 2 * 1;
+        } else if (swapY) {
+            *dir = SkPath::kCCW_Direction;
+            // 0 becomes start index 3 and times 2 to convert from rect the rrect indices.
+            return 2 * 3;
+        }
+        return 0;
+    }
+
+    static unsigned DefaultRRectDirAndStartIndex(const SkRRect& rrect, bool hasPathEffect,
+                                                 SkPath::Direction* dir) {
+        // This comes from SkPath's interface. The default for adding a SkRRect to a path is
+        // clockwise beginning at starting index 6.
+        static constexpr unsigned kPathRRectStartIdx = 6;
+        *dir = kDefaultRRectDir;
+        if (!hasPathEffect) {
+            // It doesn't matter what start we use, just be consistent to avoid redundant keys.
+            return kDefaultRRectStart;
+        }
+        return kPathRRectStartIdx;
+    }
+
+    union {
+        struct {
+            SkRRect fRRect;
+            SkPath::Direction 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