Roll external/skia 5ba50afeed4c..6a6de65fd933 (24 commits)

https://skia.googlesource.com/skia.git/+log/5ba50afeed4c..6a6de65fd933

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://skia-autoroll.corp.goog/r/android-master-autoroll
Please CC nifong@google.com on the revert to ensure that a human
is aware of the problem.

To report a problem with the AutoRoller itself, please file a bug:
https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+/master/autoroll/README.md

Test: Presubmit checks will test this change.
Exempt-From-Owner-Approval: The autoroll bot does not require owner approval.
Change-Id: I6827ae4c6f44ebd257196ae4930ccebf2e2ff9fc
diff --git a/Android.bp b/Android.bp
index a528fc0..d4f7a0e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -690,6 +690,7 @@
           "src/gpu/geometry/GrPathUtils.cpp",
           "src/gpu/geometry/GrQuad.cpp",
           "src/gpu/geometry/GrQuadUtils.cpp",
+          "src/gpu/geometry/GrShape.cpp",
           "src/gpu/geometry/GrStyledShape.cpp",
           "src/gpu/gl/GrGLAssembleGLESInterfaceAutogen.cpp",
           "src/gpu/gl/GrGLAssembleGLInterfaceAutogen.cpp",
diff --git a/DEPS b/DEPS
index 89049ca..2636277 100644
--- a/DEPS
+++ b/DEPS
@@ -10,11 +10,11 @@
   "third_party/externals/angle2"          : "https://chromium.googlesource.com/angle/angle.git@957417438089bfc9c263657c5b071dd264d38d50",
   # Dawn requires jinja2 and markupsafe for the code generator, and glslang and shaderc for SPIRV compilation.
   # When the Dawn revision is updated these should be updated from the Dawn DEPS as well.
-  "third_party/externals/dawn"            : "https://dawn.googlesource.com/dawn.git@2d79ef264eba950c255f62698b7fcb362b140495",
-  "third_party/externals/glslang"         : "https://chromium.googlesource.com/external/github.com/KhronosGroup/glslang@3f4e5c4563068f277141f5fb3d96ec02afc7ac95",
+  "third_party/externals/dawn"            : "https://dawn.googlesource.com/dawn.git@754c161fd366eb87e9625b9ef4152c34ff9e5ce3",
+  "third_party/externals/glslang"         : "https://chromium.googlesource.com/external/github.com/KhronosGroup/glslang@39281fb710c328998759a17132fd5a3dbe46dcf1",
   "third_party/externals/jinja2"          : "https://chromium.googlesource.com/chromium/src/third_party/jinja2@b41863e42637544c2941b574c7877d3e1f663e25",
   "third_party/externals/markupsafe"      : "https://chromium.googlesource.com/chromium/src/third_party/markupsafe@8f45f5cfa0009d2a70589bcda0349b8cb2b72783",
-  "third_party/externals/shaderc"         : "https://chromium.googlesource.com/external/github.com/google/shaderc@ced9c72d005e3002a24683e1b94b7fb978b10144",
+  "third_party/externals/shaderc"         : "https://chromium.googlesource.com/external/github.com/google/shaderc@41f271e6139ceb6a54457fb2da14571f66100a9a",
   "third_party/externals/dng_sdk"         : "https://android.googlesource.com/platform/external/dng_sdk.git@c8d0c9b1d16bfda56f15165d39e0ffa360a11123",
   "third_party/externals/egl-registry"    : "https://skia.googlesource.com/external/github.com/KhronosGroup/EGL-Registry@a0bca08de07c7d7651047bedc0b653cfaaa4f2ae",
   "third_party/externals/expat"           : "https://chromium.googlesource.com/external/github.com/libexpat/libexpat.git@e976867fb57a0cd87e3b0fe05d59e0ed63c6febb",
@@ -34,8 +34,8 @@
   "third_party/externals/piex"            : "https://android.googlesource.com/platform/external/piex.git@bb217acdca1cc0c16b704669dd6f91a1b509c406",
   "third_party/externals/sdl"             : "https://skia.googlesource.com/third_party/sdl@5d7cfcca344034aff9327f77fc181ae3754e7a90",
   "third_party/externals/sfntly"          : "https://chromium.googlesource.com/external/github.com/googlei18n/sfntly.git@b55ff303ea2f9e26702b514cf6a3196a2e3e2974",
-  "third_party/externals/spirv-cross"     : "https://chromium.googlesource.com/external/github.com/KhronosGroup/SPIRV-Cross@6637610b16aacfe43c77ad4060da62008a83cd12",
-  "third_party/externals/spirv-headers"   : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Headers.git@f8bf11a0253a32375c32cad92c841237b96696c0",
+  "third_party/externals/spirv-cross"     : "https://chromium.googlesource.com/external/github.com/KhronosGroup/SPIRV-Cross@7e0295abf81cc939ecb2644c88592d77407d18d3",
+  "third_party/externals/spirv-headers"   : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Headers.git@c0df742ec0b8178ad58c68cff3437ad4b6a06e26",
   "third_party/externals/spirv-tools"     : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Tools.git@e95fbfb1f509ad7a7fdfb72ac35fe412d72fc4a4",
   "third_party/externals/swiftshader"     : "https://swiftshader.googlesource.com/SwiftShader@7d6b5913c98c224b30ccd5a50e4d36226c69a983",
   #"third_party/externals/v8"              : "https://chromium.googlesource.com/v8/v8.git@5f1ae66d5634e43563b2d25ea652dfb94c31a3b4",
diff --git a/gm/runtimeshader.cpp b/gm/runtimeshader.cpp
index 930193b..24c5534 100644
--- a/gm/runtimeshader.cpp
+++ b/gm/runtimeshader.cpp
@@ -175,3 +175,65 @@
     }
 };
 DEF_GM(return new ThresholdRT;)
+
+class SpiralRT : public skiagm::GM {
+    sk_sp<SkRuntimeEffect> fEffect;
+    float                  fSecs = 4;    // so we get something interested when we're not animated
+
+    void onOnceBeforeDraw() override {
+        const char code[] = R"(
+            uniform float rad_scale;
+            uniform float2 in_center;
+            uniform float4 in_colors0;
+            uniform float4 in_colors1;
+
+            void main(float2 p, inout half4 color) {
+                float2 pp = p - in_center;
+                float radius = sqrt(dot(pp, pp));
+                radius = sqrt(radius);
+                float angle = atan(pp.y / pp.x);
+                float t = (angle + 3.1415926/2) / (3.1415926);
+                t += radius * rad_scale;
+                t = fract(t);
+                float4 m = in_colors0 * (1-t) + in_colors1 * t;
+                color = half4(m);
+            }
+        )";
+        auto [effect, error] = SkRuntimeEffect::Make(SkString(code));
+        if (!effect) {
+            SkDebugf("runtime error %s\n", error.c_str());
+        }
+        fEffect = effect;
+    }
+
+    bool runAsBench() const override { return true; }
+
+    SkString onShortName() override { return SkString("spiral_rt"); }
+
+    SkISize onISize() override { return {512, 512}; }
+
+    void onDraw(SkCanvas* canvas) override {
+        struct {
+            float rad_scale;
+            SkV2  in_center;
+            SkV4  in_colors0;
+            SkV4  in_colors1;
+        } uni {
+            std::sin(fSecs / 2) / 5,
+            {256, 256},       // center
+            {1, 0, 0, 1},     // color0
+            {0, 1, 0, 1},    // color1
+        };
+
+        SkPaint paint;
+        paint.setShader(fEffect->makeShader(SkData::MakeWithCopy(&uni, sizeof(uni)),
+                                            nullptr, 0, nullptr, true));
+        canvas->drawRect({0, 0, 512, 512}, paint);
+    }
+
+    bool onAnimate(double nanos) override {
+        fSecs = nanos / (1000 * 1000 * 1000);
+        return true;
+    }
+};
+DEF_GM(return new SpiralRT;)
diff --git a/gm/wacky_yuv_formats.cpp b/gm/wacky_yuv_formats.cpp
index 403405d..bb4f82c 100644
--- a/gm/wacky_yuv_formats.cpp
+++ b/gm/wacky_yuv_formats.cpp
@@ -1058,6 +1058,7 @@
             case VK_FORMAT_R8G8B8A8_UNORM:           return kRGBA_8888_SkColorType;
             case VK_FORMAT_B8G8R8A8_UNORM:           return kBGRA_8888_SkColorType;
             case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return kRGBA_1010102_SkColorType;
+            case VK_FORMAT_A2R10G10B10_UNORM_PACK32: return kBGRA_1010102_SkColorType;
             case VK_FORMAT_R16_SFLOAT:               return kA16_float_SkColorType;
             case VK_FORMAT_R16G16_SFLOAT:            return kR16G16_float_SkColorType;
             case VK_FORMAT_R16_UNORM:                return kA16_unorm_SkColorType;
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index fb09b35..04e2f8f 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -355,6 +355,10 @@
       # It'd be nice to fix these and turn this on:
       "/wd5041",  # out-of-line definition for constexpr static data member is
                   # not needed and is deprecated in C++17
+
+      # warning C4996: 'std::result_of_t': warning STL4014: std::result_of and std::result_of_t are
+      # deprecated in C++17. They are superseded by std::invoke_result and std::invoke_result_t.
+      "/wd4996",
     ]
   } else {
     cflags += [
diff --git a/gn/find_msvc.py b/gn/find_msvc.py
index ce44931..108d576 100755
--- a/gn/find_msvc.py
+++ b/gn/find_msvc.py
@@ -15,7 +15,7 @@
   if sys.platform.startswith('win'):
     default_dir = r'C:\Program Files (x86)\Microsoft Visual Studio'
     for release in ['2019', '2017']:
-      for version in ['Enterprise', 'Professional', 'Community', 'BuildTools']:
+      for version in ['Enterprise', 'Professional', 'Community', 'BuildTools', 'Preview']:
         path = os.path.join(default_dir, release, version, 'VC')
         if os.path.isdir(path):
           return path
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 4cb2825..fa3472f 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -341,6 +341,8 @@
   "$_src/gpu/geometry/GrQuadUtils.cpp",
   "$_src/gpu/geometry/GrQuadUtils.h",
   "$_src/gpu/geometry/GrRect.h",
+  "$_src/gpu/geometry/GrShape.cpp",
+  "$_src/gpu/geometry/GrShape.h",
   "$_src/gpu/geometry/GrStyledShape.cpp",
   "$_src/gpu/geometry/GrStyledShape.h",
   "$_src/gpu/ops/GrAAConvexPathRenderer.cpp",
@@ -741,6 +743,8 @@
   "$_src/gpu/d3d/GrD3DCaps.h",
   "$_src/gpu/d3d/GrD3DCommandList.cpp",
   "$_src/gpu/d3d/GrD3DCommandList.h",
+  "$_src/gpu/d3d/GrD3DDescriptorHeap.cpp",
+  "$_src/gpu/d3d/GrD3DDescriptorHeap.h",
   "$_src/gpu/d3d/GrD3DGpu.cpp",
   "$_src/gpu/d3d/GrD3DGpu.h",
   "$_src/gpu/d3d/GrD3DOpsRenderPass.cpp",
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index 5012568..e9dcb17 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -1493,9 +1493,74 @@
         Verb autoClose(SkPoint pts[2]);
     };
 
+private:
+    /** \class SkPath::RangeIter
+        Iterates through a raw range of path verbs, points, and conics. All values are returned
+        unaltered.
+
+        NOTE: This class will be moved into SkPathPriv once RangeIter is removed.
+    */
+    class RangeIter {
+    public:
+        RangeIter() = default;
+        RangeIter(const uint8_t* verbs, const SkPoint* points, const SkScalar* weights)
+                : fVerb(verbs), fPoints(points), fWeights(weights) {
+            SkDEBUGCODE(fInitialPoints = fPoints;)
+        }
+        void operator++() {
+            auto verb = static_cast<SkPathVerb>(*fVerb++);
+            fPoints += pts_advance_after_verb(verb);
+            if (verb == SkPathVerb::kConic) {
+                ++fWeights;
+            }
+        }
+        bool operator!=(const RangeIter& that) const {
+            return fVerb != that.fVerb;
+        }
+        std::tuple<SkPathVerb, const SkPoint*, const SkScalar*> operator*() const {
+            auto verb = static_cast<SkPathVerb>(*fVerb);
+            SkASSERT(verb != SkPathVerb::kDone);
+            // We provide the starting point for beziers by peeking backwards from the current
+            // point, which works fine as long as there is always a kMove before any geometry.
+            // (SkPath::injectMoveToIfNeeded should have guaranteed this to be the case.)
+            int backset = pts_backset_for_verb(verb);
+            SkASSERT(fPoints + backset >= fInitialPoints);
+            return {verb, fPoints + backset, fWeights};
+        }
+    private:
+        constexpr static int pts_advance_after_verb(SkPathVerb verb) {
+            switch (verb) {
+                case SkPathVerb::kMove: return 1;
+                case SkPathVerb::kLine: return 1;
+                case SkPathVerb::kQuad: return 2;
+                case SkPathVerb::kConic: return 2;
+                case SkPathVerb::kCubic: return 3;
+                case SkPathVerb::kClose: return 0;
+                case SkPathVerb::kDone: return 0;
+            }
+            SkUNREACHABLE;
+        }
+        constexpr static int pts_backset_for_verb(SkPathVerb verb) {
+            switch (verb) {
+                case SkPathVerb::kMove: return 0;
+                case SkPathVerb::kLine: return -1;
+                case SkPathVerb::kQuad: return -1;
+                case SkPathVerb::kConic: return -1;
+                case SkPathVerb::kCubic: return -1;
+                case SkPathVerb::kClose: return 0;
+                case SkPathVerb::kDone: return 0;
+            }
+            SkUNREACHABLE;
+        }
+        const uint8_t* fVerb = nullptr;
+        const SkPoint* fPoints = nullptr;
+        const SkScalar* fWeights = nullptr;
+        SkDEBUGCODE(const SkPoint* fInitialPoints = nullptr;)
+    };
+public:
+
     /** \class SkPath::RawIter
-        Iterates through verb array, and associated SkPoint array and conic weight.
-        verb array, SkPoint array, and conic weight are returned unaltered.
+        Use Iter instead. This class will soon be removed and RangeIter will be made private.
     */
     class SK_API RawIter {
     public:
@@ -1521,9 +1586,7 @@
 
             @param path  SkPath to iterate
         */
-        void setPath(const SkPath& path) {
-            fRawIter.setPathRef(*path.fPathRef.get());
-        }
+        void setPath(const SkPath&);
 
         /** Returns next SkPath::Verb in verb array, and advances RawIter.
             When verb array is exhausted, returns kDone_Verb.
@@ -1532,16 +1595,14 @@
             @param pts  storage for SkPoint data describing returned SkPath::Verb
             @return     next SkPath::Verb from verb array
         */
-        Verb next(SkPoint pts[4]) {
-            return (Verb) fRawIter.next(pts);
-        }
+        Verb next(SkPoint[4]);
 
         /** Returns next SkPath::Verb, but does not advance RawIter.
 
             @return  next SkPath::Verb from verb array
         */
         Verb peek() const {
-            return (Verb) fRawIter.peek();
+            return (fIter != fEnd) ? static_cast<Verb>(std::get<0>(*fIter)) : kDone_Verb;
         }
 
         /** Returns conic weight if next() returned kConic_Verb.
@@ -1552,11 +1613,13 @@
             @return  conic weight for conic SkPoint returned by next()
         */
         SkScalar conicWeight() const {
-            return fRawIter.conicWeight();
+            return fConicWeight;
         }
 
     private:
-        SkPathRef::Iter fRawIter;
+        RangeIter fIter;
+        RangeIter fEnd;
+        SkScalar fConicWeight = 0;
         friend class SkPath;
 
     };
diff --git a/include/effects/SkRuntimeEffect.h b/include/effects/SkRuntimeEffect.h
index ffab392..5b6a7c9 100644
--- a/include/effects/SkRuntimeEffect.h
+++ b/include/effects/SkRuntimeEffect.h
@@ -56,7 +56,9 @@
         };
 
         enum Flags {
-            kArray_Flag = 0x1,
+            kArray_Flag         = 0x1,
+            kMarker_Flag        = 0x2,
+            kMarkerNormals_Flag = 0x4,
         };
 
         SkString  fName;
@@ -65,12 +67,15 @@
         Type      fType;
         int       fCount;
         uint32_t  fFlags;
+        uint32_t  fMarker;
 
 #if SK_SUPPORT_GPU
         GrSLType fGPUType;
 #endif
 
         bool isArray() const { return SkToBool(fFlags & kArray_Flag); }
+        bool hasMarker() const { return SkToBool(fFlags & kMarker_Flag); }
+        bool hasNormalsMarker() const { return SkToBool(fFlags & kMarkerNormals_Flag); }
         size_t sizeInBytes() const;
     };
 
diff --git a/include/private/GrTypesPriv.h b/include/private/GrTypesPriv.h
index 0523f5d..03ac124 100644
--- a/include/private/GrTypesPriv.h
+++ b/include/private/GrTypesPriv.h
@@ -799,6 +799,7 @@
     kRG_88,
     kBGRA_8888,
     kRGBA_1010102,
+    kBGRA_1010102,
     kGray_8,
     kAlpha_F16,
     kRGBA_F16,
@@ -843,6 +844,7 @@
         case GrColorType::kRG_88:            return kR8G8_unorm_SkColorType;
         case GrColorType::kBGRA_8888:        return kBGRA_8888_SkColorType;
         case GrColorType::kRGBA_1010102:     return kRGBA_1010102_SkColorType;
+        case GrColorType::kBGRA_1010102:     return kBGRA_1010102_SkColorType;
         case GrColorType::kGray_8:           return kGray_8_SkColorType;
         case GrColorType::kAlpha_F16:        return kA16_float_SkColorType;
         case GrColorType::kRGBA_F16:         return kRGBA_F16_SkColorType;
@@ -878,7 +880,7 @@
         case kRGBA_F16_SkColorType:           return GrColorType::kRGBA_F16;
         case kRGBA_1010102_SkColorType:       return GrColorType::kRGBA_1010102;
         case kRGB_101010x_SkColorType:        return GrColorType::kUnknown;
-        case kBGRA_1010102_SkColorType:       return GrColorType::kUnknown;
+        case kBGRA_1010102_SkColorType:       return GrColorType::kBGRA_1010102;
         case kBGR_101010x_SkColorType:        return GrColorType::kUnknown;
         case kRGBA_F32_SkColorType:           return GrColorType::kRGBA_F32;
         case kR8G8_unorm_SkColorType:         return GrColorType::kRG_88;
@@ -909,6 +911,7 @@
         case GrColorType::kRG_88:            return kRG_SkColorChannelFlags;
         case GrColorType::kBGRA_8888:        return kRGBA_SkColorChannelFlags;
         case GrColorType::kRGBA_1010102:     return kRGBA_SkColorChannelFlags;
+        case GrColorType::kBGRA_1010102:     return kRGBA_SkColorChannelFlags;
         case GrColorType::kGray_8:           return kGray_SkColorChannelFlag;
         case GrColorType::kAlpha_F16:        return kAlpha_SkColorChannelFlag;
         case GrColorType::kRGBA_F16:         return kRGBA_SkColorChannelFlags;
@@ -1041,6 +1044,8 @@
             return GrColorTypeDesc::MakeRGBA(8, GrColorTypeEncoding::kUnorm);
         case GrColorType::kRGBA_1010102:
             return GrColorTypeDesc::MakeRGBA(10, 2, GrColorTypeEncoding::kUnorm);
+        case GrColorType::kBGRA_1010102:
+            return GrColorTypeDesc::MakeRGBA(10, 2, GrColorTypeEncoding::kUnorm);
         case GrColorType::kGray_8:
             return GrColorTypeDesc::MakeGray(8, GrColorTypeEncoding::kUnorm);
         case GrColorType::kAlpha_F16:
@@ -1119,6 +1124,7 @@
         case GrColorType::kRG_88:            return 2;
         case GrColorType::kBGRA_8888:        return 4;
         case GrColorType::kRGBA_1010102:     return 4;
+        case GrColorType::kBGRA_1010102:     return 4;
         case GrColorType::kGray_8:           return 1;
         case GrColorType::kAlpha_F16:        return 2;
         case GrColorType::kRGBA_F16:         return 8;
@@ -1210,6 +1216,7 @@
         case GrColorType::kRG_88:            return "kRG_88";
         case GrColorType::kBGRA_8888:        return "kBGRA_8888";
         case GrColorType::kRGBA_1010102:     return "kRGBA_1010102";
+        case GrColorType::kBGRA_1010102:     return "kBGRA_1010102";
         case GrColorType::kGray_8:           return "kGray_8";
         case GrColorType::kAlpha_F16:        return "kAlpha_F16";
         case GrColorType::kRGBA_F16:         return "kRGBA_F16";
diff --git a/include/private/SkBitmaskEnum.h b/include/private/SkBitmaskEnum.h
index 71022b2..ca254a3 100644
--- a/include/private/SkBitmaskEnum.h
+++ b/include/private/SkBitmaskEnum.h
@@ -9,40 +9,40 @@
 
 #include <type_traits>
 
-namespace skstd {
+namespace sknonstd {
 template <typename T> struct is_bitmask_enum : std::false_type {};
 
 template <typename E>
-typename std::enable_if<skstd::is_bitmask_enum<E>::value, bool>::type constexpr Any(E e) {
-    return static_cast<typename std::underlying_type<E>::type>(e) != 0;
+std::enable_if_t<sknonstd::is_bitmask_enum<E>::value, bool> constexpr Any(E e) {
+    return static_cast<std::underlying_type_t<E>>(e) != 0;
 }
-}  // namespace skstd
+}  // namespace sknonstd
 
 template <typename E>
-typename std::enable_if<skstd::is_bitmask_enum<E>::value, E>::type constexpr operator|(E l, E r) {
-    using U = typename std::underlying_type<E>::type;
+std::enable_if_t<sknonstd::is_bitmask_enum<E>::value, E> constexpr operator|(E l, E r) {
+    using U = std::underlying_type_t<E>;
     return static_cast<E>(static_cast<U>(l) | static_cast<U>(r));
 }
 
 template <typename E>
-typename std::enable_if<skstd::is_bitmask_enum<E>::value, E&>::type constexpr operator|=(E& l, E r) {
+std::enable_if_t<sknonstd::is_bitmask_enum<E>::value, E&> constexpr operator|=(E& l, E r) {
     return l = l | r;
 }
 
 template <typename E>
-typename std::enable_if<skstd::is_bitmask_enum<E>::value, E>::type constexpr operator&(E l, E r) {
-    using U = typename std::underlying_type<E>::type;
+std::enable_if_t<sknonstd::is_bitmask_enum<E>::value, E> constexpr operator&(E l, E r) {
+    using U = std::underlying_type_t<E>;
     return static_cast<E>(static_cast<U>(l) & static_cast<U>(r));
 }
 
 template <typename E>
-typename std::enable_if<skstd::is_bitmask_enum<E>::value, E&>::type constexpr operator&=(E& l, E r) {
+std::enable_if_t<sknonstd::is_bitmask_enum<E>::value, E&> constexpr operator&=(E& l, E r) {
     return l = l & r;
 }
 
 template <typename E>
-typename std::enable_if<skstd::is_bitmask_enum<E>::value, E>::type constexpr operator~(E e) {
-    return static_cast<E>(~static_cast<typename std::underlying_type<E>::type>(e));
+std::enable_if_t<sknonstd::is_bitmask_enum<E>::value, E> constexpr operator~(E e) {
+    return static_cast<E>(~static_cast<std::underlying_type_t<E>>(e));
 }
 
 #endif  // SkEnumOperators_DEFINED
diff --git a/include/private/SkChecksum.h b/include/private/SkChecksum.h
index 553bfcd..8e2a4ba 100644
--- a/include/private/SkChecksum.h
+++ b/include/private/SkChecksum.h
@@ -54,12 +54,12 @@
 // It should be both reasonably fast and high quality.
 struct SkGoodHash {
     template <typename K>
-    SK_WHEN(sizeof(K) == 4, uint32_t) operator()(const K& k) const {
+    std::enable_if_t<sizeof(K) == 4, uint32_t> operator()(const K& k) const {
         return SkChecksum::Mix(*(const uint32_t*)&k);
     }
 
     template <typename K>
-    SK_WHEN(sizeof(K) != 4, uint32_t) operator()(const K& k) const {
+    std::enable_if_t<sizeof(K) != 4, uint32_t> operator()(const K& k) const {
         return SkOpts::hash_fn(&k, sizeof(K), 0);
     }
 
diff --git a/include/private/SkTArray.h b/include/private/SkTArray.h
index 8cf34d1..78ce51b 100644
--- a/include/private/SkTArray.h
+++ b/include/private/SkTArray.h
@@ -492,18 +492,18 @@
         }
     }
 
-    template <bool E = MEM_MOVE> SK_WHEN(E, void) move(int dst, int src) {
+    template <bool E = MEM_MOVE> std::enable_if_t<E, void> move(int dst, int src) {
         memcpy(&fItemArray[dst], &fItemArray[src], sizeof(T));
     }
-    template <bool E = MEM_MOVE> SK_WHEN(E, void) move(void* dst) {
+    template <bool E = MEM_MOVE> std::enable_if_t<E, void> move(void* dst) {
         sk_careful_memcpy(dst, fItemArray, fCount * sizeof(T));
     }
 
-    template <bool E = MEM_MOVE> SK_WHEN(!E, void) move(int dst, int src) {
+    template <bool E = MEM_MOVE> std::enable_if_t<!E, void> move(int dst, int src) {
         new (&fItemArray[dst]) T(std::move(fItemArray[src]));
         fItemArray[src].~T();
     }
-    template <bool E = MEM_MOVE> SK_WHEN(!E, void) move(void* dst) {
+    template <bool E = MEM_MOVE> std::enable_if_t<!E, void> move(void* dst) {
         for (int i = 0; i < fCount; ++i) {
             new (static_cast<char*>(dst) + sizeof(T) * i) T(std::move(fItemArray[i]));
             fItemArray[i].~T();
diff --git a/include/private/SkTLogic.h b/include/private/SkTLogic.h
index 8cf8c28..53455c5 100644
--- a/include/private/SkTLogic.h
+++ b/include/private/SkTLogic.h
@@ -5,10 +5,8 @@
  * found in the LICENSE file.
  *
  *
- * This header provides some of the helpers (like std::enable_if_t) which will
- * become available with C++14 in the type_traits header (in the skstd
- * namespace). This header also provides several Skia specific additions such
- * as SK_WHEN and the sknonstd namespace.
+ * This header provides some std:: features early in the skstd namespace
+ * and several Skia-specific additions in the sknonstd namespace.
  */
 
 #ifndef SkTLogic_DEFINED
@@ -16,55 +14,19 @@
 
 #include <cstddef>
 #include <type_traits>
+#include <utility>
 
 namespace skstd {
 
-template <bool B> using bool_constant = std::integral_constant<bool, B>;
-
-template <bool B, typename T, typename F> using conditional_t = typename std::conditional<B, T, F>::type;
-template <bool B, typename T = void> using enable_if_t = typename std::enable_if<B, T>::type;
-
-template <typename T> using remove_const_t = typename std::remove_const<T>::type;
-template <typename T> using remove_volatile_t = typename std::remove_volatile<T>::type;
-template <typename T> using remove_cv_t = typename std::remove_cv<T>::type;
-template <typename T> using remove_pointer_t = typename std::remove_pointer<T>::type;
-template <typename T> using remove_reference_t = typename std::remove_reference<T>::type;
-template <typename T> using remove_extent_t = typename std::remove_extent<T>::type;
-
-template <typename T> using add_const_t = typename std::add_const<T>::type;
-template <typename T> using add_volatile_t = typename std::add_volatile<T>::type;
-template <typename T> using add_cv_t = typename std::add_cv<T>::type;
-template <typename T> using add_pointer_t = typename std::add_pointer<T>::type;
-template <typename T> using add_lvalue_reference_t = typename std::add_lvalue_reference<T>::type;
-
-template <typename T> using result_of_t = typename std::result_of<T>::type;
-
-template <typename... T> using common_type_t = typename std::common_type<T...>::type;
-
-template <std::size_t... Ints> struct index_sequence {
-    using type = index_sequence;
-    using value_type = std::size_t;
-    static constexpr std::size_t size() noexcept { return sizeof...(Ints); }
-};
-
-template <typename S1, typename S2> struct make_index_sequence_combine;
-template <std::size_t... I1, std::size_t... I2>
-struct make_index_sequence_combine<skstd::index_sequence<I1...>, skstd::index_sequence<I2...>>
-    : skstd::index_sequence<I1..., (sizeof...(I1)+I2)...>
-{ };
-
-template <std::size_t N> struct make_index_sequence
-    : make_index_sequence_combine<typename skstd::make_index_sequence<    N/2>::type,
-                                  typename skstd::make_index_sequence<N - N/2>::type>{};
-template<> struct make_index_sequence<0> : skstd::index_sequence< >{};
-template<> struct make_index_sequence<1> : skstd::index_sequence<0>{};
-
+// C++17, <variant>
 struct monostate {};
 
+// C++17, <type_traits>
 template<typename...> struct conjunction : std::true_type { };
 template<typename T> struct conjunction<T> : T { };
 template<typename T, typename... Ts>
 struct conjunction<T, Ts...> : std::conditional<bool(T::value), conjunction<Ts...>, T>::type { };
+
 }  // namespace skstd
 
 // The sknonstd namespace contains things we would like to be proposed and feel std-ish.
@@ -75,12 +37,12 @@
 // std::experimental::propagate_const already exists for other purposes in TSv2.
 // These also follow the <dest, source> pattern used by boost.
 template <typename D, typename S> struct copy_const {
-    using type = skstd::conditional_t<std::is_const<S>::value, skstd::add_const_t<D>, D>;
+    using type = std::conditional_t<std::is_const<S>::value, std::add_const_t<D>, D>;
 };
 template <typename D, typename S> using copy_const_t = typename copy_const<D, S>::type;
 
 template <typename D, typename S> struct copy_volatile {
-    using type = skstd::conditional_t<std::is_volatile<S>::value, skstd::add_volatile_t<D>, D>;
+    using type = std::conditional_t<std::is_volatile<S>::value, std::add_volatile_t<D>, D>;
 };
 template <typename D, typename S> using copy_volatile_t = typename copy_volatile<D, S>::type;
 
@@ -92,16 +54,13 @@
 // The name 'same' here means 'overwrite'.
 // Alternate proposed names are 'replace', 'transfer', or 'qualify_from'.
 // same_xxx<D, S> can be written as copy_xxx<remove_xxx_t<D>, S>
-template <typename D, typename S> using same_const = copy_const<skstd::remove_const_t<D>, S>;
+template <typename D, typename S> using same_const = copy_const<std::remove_const_t<D>, S>;
 template <typename D, typename S> using same_const_t = typename same_const<D, S>::type;
-template <typename D, typename S> using same_volatile =copy_volatile<skstd::remove_volatile_t<D>,S>;
+template <typename D, typename S> using same_volatile =copy_volatile<std::remove_volatile_t<D>,S>;
 template <typename D, typename S> using same_volatile_t = typename same_volatile<D, S>::type;
-template <typename D, typename S> using same_cv = copy_cv<skstd::remove_cv_t<D>, S>;
+template <typename D, typename S> using same_cv = copy_cv<std::remove_cv_t<D>, S>;
 template <typename D, typename S> using same_cv_t = typename same_cv<D, S>::type;
 
 }  // namespace sknonstd
 
-// Just a pithier wrapper for enable_if_t.
-#define SK_WHEN(condition, T) skstd::enable_if_t<!!(condition), T>
-
 #endif
diff --git a/include/private/SkTemplates.h b/include/private/SkTemplates.h
index 94b335c..477bbcb 100644
--- a/include/private/SkTemplates.h
+++ b/include/private/SkTemplates.h
@@ -17,6 +17,7 @@
 #include <cstddef>
 #include <memory>
 #include <new>
+#include <type_traits>
 #include <utility>
 
 /** \file SkTemplates.h
@@ -64,10 +65,10 @@
     function.
 */
 template <typename T, void (*P)(T*)> class SkAutoTCallVProc
-    : public std::unique_ptr<T, SkFunctionWrapper<skstd::remove_pointer_t<decltype(P)>, P>> {
+    : public std::unique_ptr<T, SkFunctionWrapper<std::remove_pointer_t<decltype(P)>, P>> {
 public:
     SkAutoTCallVProc(T* obj)
-        : std::unique_ptr<T, SkFunctionWrapper<skstd::remove_pointer_t<decltype(P)>, P>>(obj) {}
+        : std::unique_ptr<T, SkFunctionWrapper<std::remove_pointer_t<decltype(P)>, P>>(obj) {}
 
     operator T*() const { return this->get(); }
 };
@@ -442,14 +443,14 @@
 using SkAutoFree = std::unique_ptr<void, SkFunctionWrapper<void(void*), sk_free>>;
 
 template<typename C, std::size_t... Is>
-constexpr auto SkMakeArrayFromIndexSequence(C c, skstd::index_sequence<Is...>)
--> std::array<skstd::result_of_t<C(std::size_t)>, sizeof...(Is)> {
+constexpr auto SkMakeArrayFromIndexSequence(C c, std::index_sequence<Is...>)
+-> std::array<std::result_of_t<C(std::size_t)>, sizeof...(Is)> {
     return {{ c(Is)... }};
 }
 
 template<size_t N, typename C> constexpr auto SkMakeArray(C c)
--> std::array<skstd::result_of_t<C(std::size_t)>, N> {
-    return SkMakeArrayFromIndexSequence(c, skstd::make_index_sequence<N>{});
+-> std::array<std::result_of_t<C(std::size_t)>, N> {
+    return SkMakeArrayFromIndexSequence(c, std::make_index_sequence<N>{});
 }
 
 #endif
diff --git a/include/private/SkVx.h b/include/private/SkVx.h
index 81cd680..cd73ab4 100644
--- a/include/private/SkVx.h
+++ b/include/private/SkVx.h
@@ -281,6 +281,7 @@
 SIT Vec<1,T> min(const Vec<1,T>& x, const Vec<1,T>& y) { return std::min(x.val, y.val); }
 SIT Vec<1,T> max(const Vec<1,T>& x, const Vec<1,T>& y) { return std::max(x.val, y.val); }
 
+SIT Vec<1,T>  atan(const Vec<1,T>& x) { return std:: atan(x.val); }
 SIT Vec<1,T>  ceil(const Vec<1,T>& x) { return std:: ceil(x.val); }
 SIT Vec<1,T> floor(const Vec<1,T>& x) { return std::floor(x.val); }
 SIT Vec<1,T> trunc(const Vec<1,T>& x) { return std::trunc(x.val); }
@@ -314,6 +315,7 @@
 SINT Vec<N,T> min(const Vec<N,T>& x, const Vec<N,T>& y) { return join(min(x.lo, y.lo), min(x.hi, y.hi)); }
 SINT Vec<N,T> max(const Vec<N,T>& x, const Vec<N,T>& y) { return join(max(x.lo, y.lo), max(x.hi, y.hi)); }
 
+SINT Vec<N,T>  atan(const Vec<N,T>& x) { return join( atan(x.lo),  atan(x.hi)); }
 SINT Vec<N,T>  ceil(const Vec<N,T>& x) { return join( ceil(x.lo),  ceil(x.hi)); }
 SINT Vec<N,T> floor(const Vec<N,T>& x) { return join(floor(x.lo), floor(x.hi)); }
 SINT Vec<N,T> trunc(const Vec<N,T>& x) { return join(trunc(x.lo), trunc(x.hi)); }
@@ -440,6 +442,12 @@
                 fma(x.hi, y.hi, z.hi));
 }
 
+template <int N>
+static inline Vec<N,float> fract(const Vec<N,float>& x) {
+    return x - floor(x);
+}
+
+
 // div255(x) = (x + 127) / 255 is a bit-exact rounding divide-by-255, packing down to 8-bit.
 template <int N>
 static inline Vec<N,uint8_t> div255(const Vec<N,uint16_t>& x) {
diff --git a/infra/bots/gen_tasks_logic/gen_tasks_logic.go b/infra/bots/gen_tasks_logic/gen_tasks_logic.go
index 0f90c7e..13331e6 100644
--- a/infra/bots/gen_tasks_logic/gen_tasks_logic.go
+++ b/infra/bots/gen_tasks_logic/gen_tasks_logic.go
@@ -433,7 +433,7 @@
 // deriveCompileTaskName returns the name of a compile task based on the given
 // job name.
 func (b *jobBuilder) deriveCompileTaskName() string {
-	if b.role("Test", "Perf") {
+	if b.role("Test", "Perf", "FM") {
 		task_os := b.parts["os"]
 		ec := []string{}
 		if val := b.parts["extra_config"]; val != "" {
@@ -1272,7 +1272,7 @@
 }
 
 // test generates a Test task.
-func (b *jobBuilder) test() {
+func (b *jobBuilder) dm() {
 	compileTaskName := ""
 	// LottieWeb doesn't require anything in Skia to be compiled.
 	if !b.extraConfig("LottieWeb") {
@@ -1373,6 +1373,24 @@
 	}
 }
 
+func (b *jobBuilder) fm() {
+	b.addTask(b.Name, func(b *taskBuilder) {
+		b.isolate("test_skia_bundled.isolate")
+		b.dep(b.buildTaskDrivers(), b.compile())
+		b.cmd("./fm_driver",
+			"--local=false",
+			"--resources=skia/resources",
+			"--project_id", "skia-swarming-bots",
+			"--task_id", specs.PLACEHOLDER_TASK_ID,
+			"--task_name", b.Name,
+			"build/fm")
+		b.serviceAccount(b.cfg.ServiceAccountCompile)
+		b.swarmDimensions()
+		b.expiration(15 * time.Minute)
+		b.attempts(1)
+	})
+}
+
 // perf generates a Perf task.
 func (b *jobBuilder) perf() {
 	compileTaskName := ""
diff --git a/infra/bots/gen_tasks_logic/job_builder.go b/infra/bots/gen_tasks_logic/job_builder.go
index 90d71e8..e0d78b9 100644
--- a/infra/bots/gen_tasks_logic/job_builder.go
+++ b/infra/bots/gen_tasks_logic/job_builder.go
@@ -180,7 +180,11 @@
 
 	// Test bots.
 	if b.role("Test") {
-		b.test()
+		b.dm()
+		return
+	}
+	if b.role("FM") {
+		b.fm()
 		return
 	}
 
diff --git a/infra/bots/jobs.json b/infra/bots/jobs.json
index 267f2f5..f3dfe53 100644
--- a/infra/bots/jobs.json
+++ b/infra/bots/jobs.json
@@ -143,6 +143,7 @@
   "BuildStats-Debian10-EMCC-wasm-Release-CanvasKit",
   "BuildStats-Debian10-EMCC-wasm-Release-CanvasKit_CPU",
   "BuildStats-Debian10-EMCC-wasm-Release-PathKit",
+  "FM-Debian10-Clang-GCE-CPU-AVX2-x86_64-Debug-All",
   "Housekeeper-Nightly-RecreateSKPs_Canary",
   "Housekeeper-Nightly-UpdateGoDeps",
   "Housekeeper-OnDemand-Presubmit",
diff --git a/infra/bots/recipe_modules/builder_name_schema/builder_name_schema.json b/infra/bots/recipe_modules/builder_name_schema/builder_name_schema.json
index 6958b16..14d8b51 100644
--- a/infra/bots/recipe_modules/builder_name_schema/builder_name_schema.json
+++ b/infra/bots/recipe_modules/builder_name_schema/builder_name_schema.json
@@ -68,6 +68,21 @@
         "extra_config"
       ]
     },
+    "FM": {
+      "keys": [
+        "os",
+        "compiler",
+        "model",
+        "cpu_or_gpu",
+        "cpu_or_gpu_value",
+        "arch",
+        "configuration",
+        "test_filter"
+      ],
+      "optional_keys": [
+        "extra_config"
+      ]
+    },
     "Upload": {
       "recurse_roles": [
         "Build",
diff --git a/infra/bots/recipe_modules/builder_name_schema/builder_name_schema.py b/infra/bots/recipe_modules/builder_name_schema/builder_name_schema.py
index fff45e9..3a77c7d 100644
--- a/infra/bots/recipe_modules/builder_name_schema/builder_name_schema.py
+++ b/infra/bots/recipe_modules/builder_name_schema/builder_name_schema.py
@@ -26,6 +26,7 @@
 BUILDER_ROLE_INFRA = 'Infra'
 BUILDER_ROLE_PERF = 'Perf'
 BUILDER_ROLE_TEST = 'Test'
+BUILDER_ROLE_FM = 'FM'
 BUILDER_ROLE_UPLOAD = 'Upload'
 BUILDER_ROLES = (BUILDER_ROLE_BUILD,
                  BUILDER_ROLE_BUILDSTATS,
@@ -33,6 +34,7 @@
                  BUILDER_ROLE_INFRA,
                  BUILDER_ROLE_PERF,
                  BUILDER_ROLE_TEST,
+                 BUILDER_ROLE_FM,
                  BUILDER_ROLE_UPLOAD)
 
 
diff --git a/infra/bots/task_drivers/fm_driver/fm_driver.go b/infra/bots/task_drivers/fm_driver/fm_driver.go
new file mode 100644
index 0000000..1976784
--- /dev/null
+++ b/infra/bots/task_drivers/fm_driver/fm_driver.go
@@ -0,0 +1,181 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package main
+
+import (
+	"bufio"
+	"flag"
+	"math/rand"
+	"runtime"
+	"strings"
+	"sync"
+	"sync/atomic"
+
+	"go.skia.org/infra/go/exec"
+	"go.skia.org/infra/go/util"
+	"go.skia.org/infra/task_driver/go/td"
+)
+
+type work struct {
+	Sources []string
+	Flags   []string
+}
+
+func main() {
+	var (
+		projectId = flag.String("project_id", "", "ID of the Google Cloud project.")
+		taskId    = flag.String("task_id", "", "ID of this task.")
+		taskName  = flag.String("task_name", "", "Name of the task.")
+		local     = flag.Bool("local", true, "True if running locally (as opposed to on the bots)")
+		output    = flag.String("o", "", "If provided, dump a JSON blob of step data to the given file. Prints to stdout if '-' is given.")
+
+		resources = flag.String("resources", "resources", "Passed to fm -i.")
+	)
+	ctx := td.StartRun(projectId, taskId, taskName, output, local)
+	defer td.EndRun(ctx)
+
+	if flag.NArg() < 1 {
+		td.Fatalf(ctx, "Please pass an fm binary.")
+	}
+	fm := flag.Arg(0)
+
+	// Run fm --flag to find the names of all linked GMs or tests.
+	query := func(flag string) []string {
+		stdout, err := exec.RunCwd(ctx, ".", fm, flag, "-i", *resources)
+		if err != nil {
+			td.Fatal(ctx, err)
+		}
+
+		lines := []string{}
+		scanner := bufio.NewScanner(strings.NewReader(stdout))
+		for scanner.Scan() {
+			lines = append(lines, scanner.Text())
+		}
+		if err := scanner.Err(); err != nil {
+			td.Fatal(ctx, err)
+		}
+		return lines
+	}
+	gms := query("--listGMs")
+	tests := query("--listTests")
+
+	parse := func(job []string) *work {
+		w := &work{}
+
+		for _, token := range job {
+			// Everything after # is a comment.
+			if strings.HasPrefix(token, "#") {
+				break
+			}
+
+			// Treat "gm" or "gms" as a shortcut for all known GMs.
+			if token == "gm" || token == "gms" {
+				w.Sources = append(w.Sources, gms...)
+				continue
+			}
+			// Same for tests.
+			if token == "test" || token == "tests" {
+				w.Sources = append(w.Sources, tests...)
+				continue
+			}
+
+			// Is this a flag to pass through to FM?
+			if parts := strings.Split(token, "="); len(parts) == 2 {
+				f := "-"
+				if len(parts[0]) > 1 {
+					f += "-"
+				}
+				f += parts[0]
+
+				w.Flags = append(w.Flags, f, parts[1])
+				continue
+			}
+
+			// Anything else must be the name of a source for FM to run.
+			w.Sources = append(w.Sources, token)
+		}
+
+		return w
+	}
+
+	// TODO: this doesn't have to be hard coded, of course.
+	// TODO: add some .skps or images to demo that.
+	script := `
+	b=cpu tests
+	b=cpu gms
+	b=cpu gms skvm=true
+
+	#b=cpu gms skvm=true gamut=p3
+	#b=cpu gms skvm=true ct=565
+	`
+	jobs := [][]string{}
+	scanner := bufio.NewScanner(strings.NewReader(script))
+	for scanner.Scan() {
+		jobs = append(jobs, strings.Fields(scanner.Text()))
+	}
+	if err := scanner.Err(); err != nil {
+		td.Fatal(ctx, err)
+	}
+
+	var failures int32 = 0
+	wg := &sync.WaitGroup{}
+
+	worker := func(queue chan work) {
+		for w := range queue {
+			cmd := []string{}
+			cmd = append(cmd, fm)
+			cmd = append(cmd, "-i", *resources)
+			cmd = append(cmd, w.Flags...)
+			cmd = append(cmd, "-s")
+			cmd = append(cmd, w.Sources...)
+
+			if _, err := exec.RunCwd(ctx, ".", cmd...); err != nil {
+				if len(w.Sources) == 1 {
+					// If a source ran alone and failed, that's just a failure.
+					atomic.AddInt32(&failures, 1)
+					td.FailStep(ctx, err)
+				} else {
+					// If a batch of sources ran and failed, split them up and try again.
+					for _, source := range w.Sources {
+						wg.Add(1)
+						queue <- work{[]string{source}, w.Flags}
+					}
+				}
+			}
+			wg.Done()
+		}
+	}
+
+	workers := runtime.NumCPU()
+	queue := make(chan work, 1<<20)
+	for i := 0; i < workers; i++ {
+		go worker(queue)
+	}
+
+	for _, job := range jobs {
+		w := parse(job)
+		if len(w.Sources) == 0 {
+			continue
+		}
+
+		// Shuffle the sources randomly as a cheap way to approximate evenly expensive batches.
+		rand.Shuffle(len(w.Sources), func(i, j int) {
+			w.Sources[i], w.Sources[j] = w.Sources[j], w.Sources[i]
+		})
+
+		// Round up so there's at least one source per batch.
+		batch := (len(w.Sources) + workers - 1) / workers
+		util.ChunkIter(len(w.Sources), batch, func(start, end int) error {
+			wg.Add(1)
+			queue <- work{w.Sources[start:end], w.Flags}
+			return nil
+		})
+	}
+	wg.Wait()
+
+	if failures > 0 {
+		td.Fatalf(ctx, "%v runs of %v failed after retries.", failures, fm)
+	}
+}
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index 7f44a35..c17929f 100755
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -727,6 +727,11 @@
       ],
       "trigger": "master"
     },
+    "FM-Debian10-Clang-GCE-CPU-AVX2-x86_64-Debug-All": {
+      "tasks": [
+        "FM-Debian10-Clang-GCE-CPU-AVX2-x86_64-Debug-All"
+      ]
+    },
     "Housekeeper-Nightly-RecreateSKPs_Canary": {
       "tasks": [
         "Housekeeper-Nightly-RecreateSKPs_Canary"
@@ -14619,6 +14624,35 @@
         "perf"
       ]
     },
+    "FM-Debian10-Clang-GCE-CPU-AVX2-x86_64-Debug-All": {
+      "command": [
+        "./fm_driver",
+        "--local=false",
+        "--resources=skia/resources",
+        "--project_id",
+        "skia-swarming-bots",
+        "--task_id",
+        "<(TASK_ID)",
+        "--task_name",
+        "FM-Debian10-Clang-GCE-CPU-AVX2-x86_64-Debug-All",
+        "build/fm"
+      ],
+      "dependencies": [
+        "Build-Debian10-Clang-x86_64-Debug",
+        "Housekeeper-PerCommit-BuildTaskDrivers"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highcpu-64",
+        "os:Debian-10.3",
+        "pool:Skia"
+      ],
+      "expiration_ns": 900000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 1,
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
     "Housekeeper-Nightly-RecreateSKPs_Canary": {
       "caches": [
         {
diff --git a/modules/skottie/src/effects/BrightnessContrastEffect.cpp b/modules/skottie/src/effects/BrightnessContrastEffect.cpp
index e480cfe..1c3e828 100644
--- a/modules/skottie/src/effects/BrightnessContrastEffect.cpp
+++ b/modules/skottie/src/effects/BrightnessContrastEffect.cpp
@@ -56,6 +56,8 @@
 // [3] https://www.desmos.com/calculator/ehem0vy3ft
 // [4] https://www.desmos.com/calculator/5t4xi10q4v
 //
+
+#ifndef SKOTTIE_ACCURATE_CONTRAST_APPROXIMATION
 static sk_sp<SkData> make_contrast_coeffs(float contrast) {
     struct { float a, b, c; } coeffs;
 
@@ -76,6 +78,28 @@
         color.rgb = ((a*color.rgb + b)*color.rgb + c)*color.rgb;
     }
 )";
+#else
+// More accurate (but slower) approximation:
+//
+//   f(x) = x + a * sin(2πx)
+//
+//   a = -contrast/3π
+//
+static sk_sp<SkData> make_contrast_coeffs(float contrast) {
+    const auto coeff_a = -contrast / (3 * SK_ScalarPI);
+
+    return SkData::MakeWithCopy(&coeff_a, sizeof(coeff_a));
+}
+
+static constexpr char CONTRAST_EFFECT[] = R"(
+    uniform half a;
+
+    void main(inout half4 color) {
+        color.rgb += a * sin(color.rgb * 6.283185);
+    }
+)";
+
+#endif
 
 class BrightnessContrastAdapter final : public DiscardableAdapterBase<BrightnessContrastAdapter,
                                                                       sksg::ExternalColorFilter> {
diff --git a/modules/skplaintexteditor/app/editor_application.cpp b/modules/skplaintexteditor/app/editor_application.cpp
index 214cbab..cf37eb6 100644
--- a/modules/skplaintexteditor/app/editor_application.cpp
+++ b/modules/skplaintexteditor/app/editor_application.cpp
@@ -181,7 +181,7 @@
         } else if (skui::InputState::kUp == state) {
             fMouseDown = false;
         }
-        bool shiftOrDrag = skstd::Any(modifiers & skui::ModifierKey::kShift) || !mouseDown;
+        bool shiftOrDrag = sknonstd::Any(modifiers & skui::ModifierKey::kShift) || !mouseDown;
         if (fMouseDown) {
             return this->move(fEditor.getPosition({x - fMargin, y + fPos - fMargin}), shiftOrDrag);
         }
@@ -189,7 +189,7 @@
     }
 
     bool onChar(SkUnichar c, skui::ModifierKey modi) override {
-        using skstd::Any;
+        using sknonstd::Any;
         modi &= ~skui::ModifierKey::kFirstPress;
         if (!Any(modi & (skui::ModifierKey::kControl |
                          skui::ModifierKey::kOption  |
@@ -306,7 +306,7 @@
             return false;  // ignore keyup
         }
         // ignore other modifiers.
-        using skstd::Any;
+        using sknonstd::Any;
         skui::ModifierKey ctrlAltCmd = modifiers & (skui::ModifierKey::kControl |
                                               skui::ModifierKey::kOption  |
                                               skui::ModifierKey::kCommand);
@@ -348,7 +348,8 @@
                 default:
                     break;
             }
-        } else if (skstd::Any(ctrlAltCmd & (skui::ModifierKey::kControl | skui::ModifierKey::kCommand))) {
+        } else if (sknonstd::Any(ctrlAltCmd & (skui::ModifierKey::kControl |
+                                               skui::ModifierKey::kCommand))) {
             switch (key) {
                 case skui::Key::kLeft:
                     return this->moveCursor(Editor::Movement::kWordLeft, shift);
diff --git a/modules/skshaper/src/SkShaper_harfbuzz.cpp b/modules/skshaper/src/SkShaper_harfbuzz.cpp
index 3386f7c..29d9fe9 100644
--- a/modules/skshaper/src/SkShaper_harfbuzz.cpp
+++ b/modules/skshaper/src/SkShaper_harfbuzz.cpp
@@ -59,7 +59,7 @@
 # define HB_FEATURE_GLOBAL_END ((unsigned int) -1)
 #endif
 
-namespace skstd {
+namespace sknonstd {
 template <> struct is_bitmask_enum<hb_buffer_flags_t> : std::true_type {};
 }
 
diff --git a/samplecode/Sample3D.cpp b/samplecode/Sample3D.cpp
index 402ec68..7525c48 100644
--- a/samplecode/Sample3D.cpp
+++ b/samplecode/Sample3D.cpp
@@ -377,8 +377,8 @@
             in fragmentProcessor color_map;
             in fragmentProcessor normal_map;
 
-            uniform float4x4 localToWorld;
-            uniform float4x4 localToWorldAdjInv;
+            layout (marker=local_to_world)          uniform float4x4 localToWorld;
+            layout (marker=normals(local_to_world)) uniform float4x4 localToWorldAdjInv;
             uniform float3   lightPos;
 
             float3 convert_normal_sample(half4 c) {
@@ -413,27 +413,11 @@
             return;
         }
 
-        auto adj_inv = [](const SkM44& m) {
-            // Normals need to be transformed by the inverse-transpose of the upper-left 3x3 portion
-            // (scale + rotate) of the local to world matrix. (If the local to world only has
-            // uniform scale, we can use its upper-left 3x3 directly, but we don't know if that's
-            // the case here, so go the extra mile.)
-            SkM44 rot_scale(m.rc(0, 0), m.rc(0, 1), m.rc(0, 2), 0,
-                            m.rc(1, 0), m.rc(1, 1), m.rc(1, 2), 0,
-                            m.rc(2, 0), m.rc(2, 1), m.rc(2, 2), 0,
-                                     0,          0,          0, 1);
-            SkM44 inv(SkM44::kUninitialized_Constructor);
-            SkAssertResult(rot_scale.invert(&inv));
-            return inv.transpose();
-        };
-
         struct Uniforms {
-            SkM44  fLocalToWorld;
-            SkM44  fLocalToWorldAdjInv;
+            SkM44  fLocalToWorld;        // Automatically populated, via layout(marker)
+            SkM44  fLocalToWorldAdjInv;  // Ditto
             SkV3   fLightPos;
         } uni;
-        uni.fLocalToWorld = this->localToWorld(canvas);
-        uni.fLocalToWorldAdjInv = adj_inv(uni.fLocalToWorld);
         uni.fLightPos = fLight.computeWorldPos(fSphere);
 
         sk_sp<SkData> data = SkData::MakeWithCopy(&uni, sizeof(uni));
diff --git a/src/core/SkAdvancedTypefaceMetrics.h b/src/core/SkAdvancedTypefaceMetrics.h
index 84fd020..0ccf562 100644
--- a/src/core/SkAdvancedTypefaceMetrics.h
+++ b/src/core/SkAdvancedTypefaceMetrics.h
@@ -65,7 +65,7 @@
     SkIRect fBBox = {0, 0, 0, 0};  // The bounding box of all glyphs (in font units).
 };
 
-namespace skstd {
+namespace sknonstd {
 template <> struct is_bitmask_enum<SkAdvancedTypefaceMetrics::FontFlags> : std::true_type {};
 template <> struct is_bitmask_enum<SkAdvancedTypefaceMetrics::StyleFlags> : std::true_type {};
 }
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index 7352117..3b6abc0 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -1542,7 +1542,7 @@
 }
 
 void SkCanvas::markCTM(const char* name) {
-    if (name && name[0]) {
+    if (SkCanvasPriv::ValidateMarker(name)) {
         fMarkerStack->setMarker(SkOpts::hash_fn(name, strlen(name), 0),
                                 this->getLocalToDevice(), fMCRec);
         this->onMarkCTM(name);
@@ -1550,7 +1550,8 @@
 }
 
 bool SkCanvas::findMarkedCTM(const char* name, SkM44* mx) const {
-    return name && name[0] && fMarkerStack->findMarker(SkOpts::hash_fn(name, strlen(name), 0), mx);
+    return SkCanvasPriv::ValidateMarker(name) &&
+           fMarkerStack->findMarker(SkOpts::hash_fn(name, strlen(name), 0), mx);
 }
 
 //////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkCanvasPriv.cpp b/src/core/SkCanvasPriv.cpp
index cf90419..dc05d6c 100644
--- a/src/core/SkCanvasPriv.cpp
+++ b/src/core/SkCanvasPriv.cpp
@@ -10,6 +10,8 @@
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkWriter32.h"
 
+#include <locale>
+
 SkAutoCanvasMatrixPaint::SkAutoCanvasMatrixPaint(SkCanvas* canvas, const SkMatrix* matrix,
                                                  const SkPaint* paint, const SkRect& bounds)
 : fCanvas(canvas)
@@ -97,3 +99,20 @@
     *totalDstClipCount = dstClipCount;
     *totalMatrixCount = maxMatrixIndex + 1;
 }
+
+bool SkCanvasPriv::ValidateMarker(const char* name) {
+    if (!name) {
+        return false;
+    }
+
+    std::locale loc(std::locale::classic());
+    if (!std::isalpha(*name, loc)) {
+        return false;
+    }
+    while (*(++name)) {
+        if (!std::isalnum(*name, loc) && *name != '_') {
+            return false;
+        }
+    }
+    return true;
+}
diff --git a/src/core/SkCanvasPriv.h b/src/core/SkCanvasPriv.h
index 5bef882..a245d87 100644
--- a/src/core/SkCanvasPriv.h
+++ b/src/core/SkCanvasPriv.h
@@ -53,6 +53,10 @@
     // computes the minimum length for these arrays that would provide index access errors.
     static void GetDstClipAndMatrixCounts(const SkCanvas::ImageSetEntry set[], int count,
                                           int* totalDstClipCount, int* totalMatrixCount);
+
+    // Checks that the marker name is an identifier ([a-zA-Z][a-zA-Z0-9_]*)
+    // Identifiers with leading underscores are reserved (not allowed).
+    static bool ValidateMarker(const char*);
 };
 
 #endif
diff --git a/src/core/SkGlyphRunPainter.cpp b/src/core/SkGlyphRunPainter.cpp
index d0071f3..8ef928d 100644
--- a/src/core/SkGlyphRunPainter.cpp
+++ b/src/core/SkGlyphRunPainter.cpp
@@ -402,7 +402,7 @@
                 textContext->fOptions, blob.get());
     }
 
-    return blob->test_makeOp(textLen, mtxProvider, drawOrigin, skPaint, filteredColor, surfaceProps,
+    return blob->test_makeOp(mtxProvider, drawOrigin, skPaint, filteredColor, surfaceProps,
                              rtc->textTarget());
 }
 
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 3974cf1..c162670 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -1847,6 +1847,35 @@
     return (Verb)verb;
 }
 
+void SkPath::RawIter::setPath(const SkPath& path) {
+    SkPathPriv::Iterate iterate(path);
+    fIter = iterate.begin();
+    fEnd = iterate.end();
+}
+
+SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) {
+    if (!(fIter != fEnd)) {
+        return kDone_Verb;
+    }
+    auto [verb, iterPts, weights] = *fIter;
+    int numPts;
+    switch (verb) {
+        case SkPathVerb::kMove: numPts = 1; break;
+        case SkPathVerb::kLine: numPts = 2; break;
+        case SkPathVerb::kQuad: numPts = 3; break;
+        case SkPathVerb::kConic:
+            numPts = 3;
+            fConicWeight = *weights;
+            break;
+        case SkPathVerb::kCubic: numPts = 4; break;
+        case SkPathVerb::kClose: numPts = 0; break;
+        case SkPathVerb::kDone: SkUNREACHABLE;
+    }
+    memcpy(pts, iterPts, sizeof(SkPoint) * numPts);
+    ++fIter;
+    return (Verb) verb;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #include "include/core/SkStream.h"
diff --git a/src/core/SkPathPriv.h b/src/core/SkPathPriv.h
index 874d174..f924907 100644
--- a/src/core/SkPathPriv.h
+++ b/src/core/SkPathPriv.h
@@ -144,6 +144,35 @@
     };
 
     /**
+     * Iterable object for traversing verbs, points, and conic weights in a path:
+     *
+     *   for (auto [verb, pts, weights] : SkPathPriv::Iterate(skPath)) {
+     *       ...
+     *   }
+     */
+    struct Iterate {
+    public:
+        Iterate(const SkPath& path)
+                : Iterate(path.fPathRef->verbsBegin(),
+                          // Don't allow iteration through non-finite points.
+                          (!path.isFinite()) ? path.fPathRef->verbsBegin()
+                                             : path.fPathRef->verbsEnd(),
+                          path.fPathRef->points(), path.fPathRef->conicWeights()) {
+        }
+        Iterate(const uint8_t* verbsBegin, const uint8_t* verbsEnd, const SkPoint* points,
+                const SkScalar* weights)
+                : fVerbsBegin(verbsBegin), fVerbsEnd(verbsEnd), fPoints(points), fWeights(weights) {
+        }
+        SkPath::RangeIter begin() { return {fVerbsBegin, fPoints, fWeights}; }
+        SkPath::RangeIter end() { return {fVerbsEnd, nullptr, nullptr}; }
+    private:
+        const uint8_t* fVerbsBegin;
+        const uint8_t* fVerbsEnd;
+        const SkPoint* fPoints;
+        const SkScalar* fWeights;
+    };
+
+    /**
      * Returns a pointer to the verb data.
      */
     static const uint8_t* VerbData(const SkPath& path) {
diff --git a/src/core/SkPictureCommon.h b/src/core/SkPictureCommon.h
index 2a10699..bd4823d 100644
--- a/src/core/SkPictureCommon.h
+++ b/src/core/SkPictureCommon.h
@@ -74,12 +74,12 @@
     }
 
     template <typename T>
-    SK_WHEN(T::kTags & SkRecords::kHasPaint_Tag, void) operator()(const T& op) {
+    std::enable_if_t<T::kTags & SkRecords::kHasPaint_Tag, void> operator()(const T& op) {
         this->checkPaint(AsPtr(op.paint));
     }
 
     template <typename T>
-    SK_WHEN(!(T::kTags & SkRecords::kHasPaint_Tag), void)
+    std::enable_if_t<!(T::kTags & SkRecords::kHasPaint_Tag), void>
       operator()(const T& op) { /* do nothing */ }
 
     int fNumSlowPathsAndDashEffects;
diff --git a/src/core/SkRecord.h b/src/core/SkRecord.h
index 68c63492..37cc058 100644
--- a/src/core/SkRecord.h
+++ b/src/core/SkRecord.h
@@ -129,13 +129,13 @@
     };
 
     template <typename T>
-    SK_WHEN(std::is_empty<T>::value, T*) allocCommand() {
+    std::enable_if_t<std::is_empty<T>::value, T*> allocCommand() {
         static T singleton = {};
         return &singleton;
     }
 
     template <typename T>
-    SK_WHEN(!std::is_empty<T>::value, T*) allocCommand() { return this->alloc<T>(); }
+    std::enable_if_t<!std::is_empty<T>::value, T*> allocCommand() { return this->alloc<T>(); }
 
     void grow();
 
diff --git a/src/core/SkRecordPattern.h b/src/core/SkRecordPattern.h
index 7e96592..96a3407 100644
--- a/src/core/SkRecordPattern.h
+++ b/src/core/SkRecordPattern.h
@@ -49,19 +49,20 @@
     type* get() { return fPaint; }
 
     template <typename T>
-    SK_WHEN((T::kTags & kDrawWithPaint_Tag) == kDrawWithPaint_Tag, bool) operator()(T* draw) {
+    std::enable_if_t<(T::kTags & kDrawWithPaint_Tag) == kDrawWithPaint_Tag, bool>
+    operator()(T* draw) {
         fPaint = AsPtr(draw->paint);
         return true;
     }
 
     template <typename T>
-    SK_WHEN((T::kTags & kDrawWithPaint_Tag) == kDraw_Tag, bool) operator()(T* draw) {
+    std::enable_if_t<(T::kTags & kDrawWithPaint_Tag) == kDraw_Tag, bool> operator()(T* draw) {
         fPaint = nullptr;
         return true;
     }
 
     template <typename T>
-    SK_WHEN(!(T::kTags & kDraw_Tag), bool) operator()(T* draw) {
+    std::enable_if_t<!(T::kTags & kDraw_Tag), bool> operator()(T* draw) {
         fPaint = nullptr;
         return false;
     }
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index b51bffb..387b31c 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -10,6 +10,8 @@
 #include "include/effects/SkRuntimeEffect.h"
 #include "include/private/SkChecksum.h"
 #include "include/private/SkMutex.h"
+#include "src/core/SkCanvasPriv.h"
+#include "src/core/SkMatrixProvider.h"
 #include "src/core/SkRasterPipeline.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkUtils.h"
@@ -52,6 +54,20 @@
 SkSL::Compiler* SharedCompiler::gCompiler = nullptr;
 }
 
+// Accepts a valid marker, or "normals(<marker>)"
+static bool parse_marker(const SkSL::StringFragment& marker, uint32_t* id, uint32_t* flags) {
+    SkString s = marker;
+    if (s.startsWith("normals(") && s.endsWith(')')) {
+        *flags |= SkRuntimeEffect::Variable::kMarkerNormals_Flag;
+        s.set(marker.fChars + 8, marker.fLength - 9);
+    }
+    if (!SkCanvasPriv::ValidateMarker(s.c_str())) {
+        return false;
+    }
+    *id = SkOpts::hash_fn(s.c_str(), s.size(), 0);
+    return true;
+}
+
 SkRuntimeEffect::EffectResult SkRuntimeEffect::Make(SkString sksl) {
     SkSL::SharedCompiler compiler;
     auto program = compiler->convertProgram(SkSL::Program::kPipelineStage_Kind,
@@ -202,6 +218,18 @@
                                 break;
                         }
 
+                        const SkSL::StringFragment& marker(var.fModifiers.fLayout.fMarker);
+                        if (marker.fLength) {
+                            // Rules that should be enforced by the IR generator:
+                            SkASSERT(v.fQualifier == Variable::Qualifier::kUniform);
+                            SkASSERT(v.fType == Variable::Type::kFloat4x4);
+                            v.fFlags |= Variable::kMarker_Flag;
+                            if (!parse_marker(marker, &v.fMarker, &v.fFlags)) {
+                                RETURN_FAILURE("Invalid 'marker' string: '%.*s'",
+                                               (int)marker.fLength, marker.fChars);
+                            }
+                        }
+
                         if (v.fType != Variable::Type::kBool) {
                             offset = SkAlign4(offset);
                         }
@@ -380,6 +408,18 @@
       //auto u16 = [&]{ auto x = sk_unaligned_load<uint16_t>(ip); ip += sizeof(x); return x; };
         auto u32 = [&]{ auto x = sk_unaligned_load<uint32_t>(ip); ip += sizeof(x); return x; };
 
+        auto apply = [&](SkSL::ByteCodeInstruction instBase, skvm::F32 (*func)(skvm::F32)) {
+            const int N = (int)inst - (int)instBase + 1;
+            SkASSERT(N <= 4);
+            skvm::F32 args[4];
+            for (int i = 0; i < N; ++i) {
+                args[i] = pop();
+            }
+            for (int i = N; i --> 0;) {
+                push(func(args[i]));
+            }
+        };
+
         switch (inst) {
             default:
                 #if 0
@@ -420,6 +460,19 @@
                 push(uniform[ix]);
             } break;
 
+            case Inst::kLoadUniform2: {
+                int ix = u8();
+                push(uniform[ix + 0]);
+                push(uniform[ix + 1]);
+            } break;
+
+            case Inst::kLoadUniform3: {
+                int ix = u8();
+                push(uniform[ix + 0]);
+                push(uniform[ix + 1]);
+                push(uniform[ix + 2]);
+            } break;
+
             case Inst::kLoadUniform4: {
                 int ix = u8();
                 push(uniform[ix + 0]);
@@ -493,6 +546,36 @@
                 push(a+x);
             } break;
 
+            case Inst::kSubtractF: {
+                skvm::F32 x = pop(),
+                          a = pop();
+                push(a-x);
+            } break;
+
+            case Inst::kSubtractF2: {
+                skvm::F32 x = pop(), y = pop(),
+                          a = pop(), b = pop();
+                push(b-y);
+                push(a-x);
+            } break;
+
+            case Inst::kSubtractF3: {
+                skvm::F32 x = pop(), y = pop(), z = pop(),
+                          a = pop(), b = pop(), c = pop();
+                push(c-z);
+                push(b-y);
+                push(a-x);
+            } break;
+
+            case Inst::kSubtractF4: {
+                skvm::F32 x = pop(), y = pop(), z = pop(), w = pop(),
+                          a = pop(), b = pop(), c = pop(), d = pop();
+                push(d-w);
+                push(c-z);
+                push(b-y);
+                push(a-x);
+            } break;
+
             case Inst::kMultiplyF: {
                 skvm::F32 x = pop(),
                           a = pop();
@@ -523,32 +606,56 @@
                 push(a*x);
             } break;
 
-            case Inst::kSin: {
-                skvm::F32 x = pop();
-                push(approx_sin(x));
+            case Inst::kDivideF: {
+                skvm::F32 x = pop(),
+                          a = pop();
+                push(a/x);
             } break;
 
-            case Inst::kSin2: {
-                skvm::F32 x = pop(), y = pop();
-                push(approx_sin(y));
-                push(approx_sin(x));
+            case Inst::kDivideF2: {
+                skvm::F32 x = pop(), y = pop(),
+                          a = pop(), b = pop();
+                push(b/y);
+                push(a/x);
             } break;
 
-            case Inst::kSin3: {
-                skvm::F32 x = pop(), y = pop(), z = pop();
-                push(approx_sin(z));
-                push(approx_sin(y));
-                push(approx_sin(x));
+            case Inst::kDivideF3: {
+                skvm::F32 x = pop(), y = pop(), z = pop(),
+                          a = pop(), b = pop(), c = pop();
+                push(c/z);
+                push(b/y);
+                push(a/x);
             } break;
 
-            case Inst::kSin4: {
-                skvm::F32 x = pop(), y = pop(), z = pop(), w = pop();
-                push(approx_sin(w));
-                push(approx_sin(z));
-                push(approx_sin(y));
-                push(approx_sin(x));
+            case Inst::kDivideF4: {
+                skvm::F32 x = pop(), y = pop(), z = pop(), w = pop(),
+                          a = pop(), b = pop(), c = pop(), d = pop();
+                push(d/w);
+                push(c/z);
+                push(b/y);
+                push(a/x);
             } break;
 
+            case Inst::kATan:
+            case Inst::kATan2:
+            case Inst::kATan3:
+            case Inst::kATan4: apply(Inst::kATan, skvm::approx_atan); break;
+
+            case Inst::kFract:
+            case Inst::kFract2:
+            case Inst::kFract3:
+            case Inst::kFract4: apply(Inst::kFract, skvm::fract); break;
+
+            case Inst::kSqrt:
+            case Inst::kSqrt2:
+            case Inst::kSqrt3:
+            case Inst::kSqrt4: apply(Inst::kSqrt, skvm::sqrt); break;
+
+            case Inst::kSin:
+            case Inst::kSin2:
+            case Inst::kSin3:
+            case Inst::kSin4: apply(Inst::kSin, skvm::approx_sin); break;
+
             // Baby steps... just leaving test conditions on the stack for now.
             case Inst::kMaskPush:   break;
             case Inst::kMaskNegate: break;
@@ -738,7 +845,35 @@
         if (!this->totalLocalMatrix(args.fPreLocalMatrix)->invert(&matrix)) {
             return nullptr;
         }
-        auto fp = GrSkSLFP::Make(args.fContext, fEffect, "runtime_shader", fInputs);
+        // If any of our uniforms are late-bound (eg, layout(marker)), we need to clone the blob
+        sk_sp<SkData> inputs = fInputs;
+
+        for (const auto& v : fEffect->inputs()) {
+            if (v.hasMarker()) {
+                if (inputs == fInputs) {
+                    inputs = SkData::MakeWithCopy(fInputs->data(), fInputs->size());
+                }
+                SkASSERT(v.fType == SkRuntimeEffect::Variable::Type::kFloat4x4);
+                SkM44* localToMarker = SkTAddOffset<SkM44>(inputs->writable_data(), v.fOffset);
+                if (!args.fMatrixProvider.getLocalToMarker(v.fMarker, localToMarker)) {
+                    // We couldn't provide a matrix that was requested by the SkSL
+                    SkDebugf("Failed to get marked matrix %u\n", v.fMarker);
+                    return nullptr;
+                }
+                if (v.hasNormalsMarker()) {
+                    // Normals need to be transformed by the inverse-transpose of the upper-left
+                    // 3x3 portion (scale + rotate) of the matrix.
+                    localToMarker->setRow(3, {0, 0, 0, 1});
+                    localToMarker->setCol(3, {0, 0, 0, 1});
+                    if (!localToMarker->invert(localToMarker)) {
+                        return nullptr;
+                    }
+                    *localToMarker = localToMarker->transpose();
+                }
+            }
+        }
+
+        auto fp = GrSkSLFP::Make(args.fContext, fEffect, "runtime_shader", std::move(inputs));
         for (const auto& child : fChildren) {
             auto childFP = child ? as_SB(child)->asFragmentProcessor(args) : nullptr;
             if (!childFP) {
diff --git a/src/core/SkVertices.cpp b/src/core/SkVertices.cpp
index 2d6992c..532bb4f 100644
--- a/src/core/SkVertices.cpp
+++ b/src/core/SkVertices.cpp
@@ -9,6 +9,7 @@
 
 #include "include/core/SkData.h"
 #include "include/private/SkTo.h"
+#include "src/core/SkCanvasPriv.h"
 #include "src/core/SkOpts.h"
 #include "src/core/SkReader32.h"
 #include "src/core/SkSafeMath.h"
@@ -31,7 +32,7 @@
 SkVertices::Attribute::Attribute(Type t, Usage u, const char* markerName)
         : fType(t)
         , fUsage(u)
-        , fMarkerName((markerName && markerName[0]) ? markerName : nullptr) {
+        , fMarkerName(markerName) {
     fMarkerID = fMarkerName ? SkOpts::hash_fn(fMarkerName, strlen(fMarkerName), 0) : 0;
     SkASSERT(!fMarkerName || fMarkerID != 0);
 }
@@ -67,6 +68,9 @@
 }
 
 bool SkVertices::Attribute::isValid() const {
+    if (fMarkerName && !SkCanvasPriv::ValidateMarker(fMarkerName)) {
+        return false;
+    }
     switch (fUsage) {
         case Usage::kRaw:
             return fMarkerID == 0;
diff --git a/src/core/SkZip.h b/src/core/SkZip.h
index d13ecaf..5cf6120 100644
--- a/src/core/SkZip.h
+++ b/src/core/SkZip.h
@@ -11,6 +11,7 @@
 #include <iterator>
 #include <tuple>
 #include <type_traits>
+#include <utility>
 
 #include "include/core/SkTypes.h"
 #include "include/private/SkTemplates.h"
@@ -105,22 +106,22 @@
     constexpr ReturnTuple index(size_t i) const {
         SkASSERT(this->size() > 0);
         SkASSERT(i < this->size());
-        return indexDetail(i, skstd::make_index_sequence<sizeof...(Ts)>{});
+        return indexDetail(i, std::make_index_sequence<sizeof...(Ts)>{});
     }
 
     template<std::size_t... Is>
-    constexpr ReturnTuple indexDetail(size_t i, skstd::index_sequence<Is...>) const {
+    constexpr ReturnTuple indexDetail(size_t i, std::index_sequence<Is...>) const {
         return ReturnTuple((std::get<Is>(fPointers))[i]...);
     }
 
     std::tuple<Ts*...> pointersAt(size_t i) const {
         SkASSERT(this->size() > 0);
         SkASSERT(i < this->size());
-        return pointersAtDetail(i, skstd::make_index_sequence<sizeof...(Ts)>{});
+        return pointersAtDetail(i, std::make_index_sequence<sizeof...(Ts)>{});
     }
 
     template<std::size_t... Is>
-    constexpr std::tuple<Ts*...> pointersAtDetail(size_t i, skstd::index_sequence<Is...>) const {
+    constexpr std::tuple<Ts*...> pointersAtDetail(size_t i, std::index_sequence<Is...>) const {
         return std::tuple<Ts*...>{&(std::get<Is>(fPointers))[i]...};
     }
 
diff --git a/src/gpu/GrDataUtils.cpp b/src/gpu/GrDataUtils.cpp
index 6efa24d..a6102fd 100644
--- a/src/gpu/GrDataUtils.cpp
+++ b/src/gpu/GrDataUtils.cpp
@@ -334,6 +334,9 @@
         case GrColorType::kRGBA_8888:        *load = SkRasterPipeline::load_8888;     break;
         case GrColorType::kRG_88:            *load = SkRasterPipeline::load_rg88;     break;
         case GrColorType::kRGBA_1010102:     *load = SkRasterPipeline::load_1010102;  break;
+        case GrColorType::kBGRA_1010102:     *load = SkRasterPipeline::load_1010102;
+                                             swizzle = GrSwizzle("bgra");
+                                             break;
         case GrColorType::kAlpha_F16:        *load = SkRasterPipeline::load_af16;     break;
         case GrColorType::kRGBA_F16_Clamped: *load = SkRasterPipeline::load_f16;      break;
         case GrColorType::kRG_1616:          *load = SkRasterPipeline::load_rg1616;   break;
@@ -396,6 +399,9 @@
         case GrColorType::kRGBA_8888:        *store = SkRasterPipeline::store_8888;     break;
         case GrColorType::kRG_88:            *store = SkRasterPipeline::store_rg88;     break;
         case GrColorType::kRGBA_1010102:     *store = SkRasterPipeline::store_1010102;  break;
+        case GrColorType::kBGRA_1010102:     swizzle = GrSwizzle("bgra");
+                                             *store = SkRasterPipeline::store_1010102;
+                                             break;
         case GrColorType::kRGBA_F16_Clamped: *store = SkRasterPipeline::store_f16;      break;
         case GrColorType::kRG_1616:          *store = SkRasterPipeline::store_rg1616;   break;
         case GrColorType::kRGBA_16161616:    *store = SkRasterPipeline::store_16161616; break;
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index 694ff4f..e1da02f 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -240,6 +240,7 @@
         case GrColorType::kABGR_4444:
         case GrColorType::kBGRA_8888:
         case GrColorType::kRGBA_1010102:
+        case GrColorType::kBGRA_1010102:
         case GrColorType::kRGBA_F16:
         case GrColorType::kRGBA_F16_Clamped:
             return GrColorType::kRGBA_8888;
@@ -884,41 +885,11 @@
         // Fills the rect, using rect as its own local coordinates
         this->fillRectToRect(clip, std::move(paint), aa, viewMatrix, rect, rect);
         return;
-    } else if (stroke.getStyle() == SkStrokeRec::kStroke_Style ||
-               stroke.getStyle() == SkStrokeRec::kHairline_Style) {
-        if ((!rect.width() || !rect.height()) &&
-            SkStrokeRec::kHairline_Style != stroke.getStyle()) {
-            SkScalar r = stroke.getWidth() / 2;
-            // TODO: Move these stroke->fill fallbacks to GrStyledShape?
-            switch (stroke.getJoin()) {
-                case SkPaint::kMiter_Join:
-                    this->drawRect(
-                            clip, std::move(paint), aa, viewMatrix,
-                            {rect.fLeft - r, rect.fTop - r, rect.fRight + r, rect.fBottom + r},
-                            &GrStyle::SimpleFill());
-                    return;
-                case SkPaint::kRound_Join:
-                    // Raster draws nothing when both dimensions are empty.
-                    if (rect.width() || rect.height()){
-                        SkRRect rrect = SkRRect::MakeRectXY(rect.makeOutset(r, r), r, r);
-                        this->drawRRect(clip, std::move(paint), aa, viewMatrix, rrect,
-                                        GrStyle::SimpleFill());
-                        return;
-                    }
-                case SkPaint::kBevel_Join:
-                    if (!rect.width()) {
-                        this->drawRect(clip, std::move(paint), aa, viewMatrix,
-                                       {rect.fLeft - r, rect.fTop, rect.fRight + r, rect.fBottom},
-                                       &GrStyle::SimpleFill());
-                    } else {
-                        this->drawRect(clip, std::move(paint), aa, viewMatrix,
-                                       {rect.fLeft, rect.fTop - r, rect.fRight, rect.fBottom + r},
-                                       &GrStyle::SimpleFill());
-                    }
-                    return;
-                }
-        }
-
+    } else if ((stroke.getStyle() == SkStrokeRec::kStroke_Style ||
+                stroke.getStyle() == SkStrokeRec::kHairline_Style) &&
+               (rect.width() && rect.height())) {
+        // Only use the StrokeRectOp for non-empty rectangles. Empty rectangles will be processed by
+        // GrStyledShape to handle stroke caps and dashing properly.
         std::unique_ptr<GrDrawOp> op;
 
         GrAAType aaType = this->chooseAAType(aa);
@@ -2297,7 +2268,9 @@
         }
     }
 
-    this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, shape);
+    // If we get here in drawShape(), we definitely need to use path rendering
+    this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, shape,
+                                     /* attempt fallback */ false);
 }
 
 bool GrRenderTargetContextPriv::drawAndStencilPath(const GrHardClip& clip,
@@ -2388,7 +2361,8 @@
                                                        GrPaint&& paint,
                                                        GrAA aa,
                                                        const SkMatrix& viewMatrix,
-                                                       const GrStyledShape& originalShape) {
+                                                       const GrStyledShape& originalShape,
+                                                       bool attemptShapeFallback) {
     ASSERT_SINGLE_OWNER
     RETURN_IF_ABANDONED
     GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "internalDrawPath", fContext);
@@ -2397,6 +2371,14 @@
         return;
     }
 
+    if (attemptShapeFallback && originalShape.simplified()) {
+        // Usually we enter drawShapeUsingPathRenderer() because the shape+style was too
+        // complex for dedicated draw ops. However, if GrStyledShape was able to reduce something
+        // we ought to try again instead of going right to path rendering.
+        this->drawShape(clip, std::move(paint), aa, viewMatrix, originalShape);
+        return;
+    }
+
     SkIRect clipConservativeBounds;
     clip.getConservativeBounds(this->width(), this->height(), &clipConservativeBounds, nullptr);
 
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index eb10b3f..ca0ac26 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -671,8 +671,10 @@
                           DrawQuad* quad,
                           const SkRect* subset = nullptr);
 
+    // If 'attemptShapeFallback' is true, and the original shape had been simplfied, this
+    // will re-route through drawShape() to see if we can avoid path rendering one more time.
     void drawShapeUsingPathRenderer(const GrClip&, GrPaint&&, GrAA, const SkMatrix&,
-                                    const GrStyledShape&);
+                                    const GrStyledShape&, bool attemptShapeFallback = true);
 
     void addOp(std::unique_ptr<GrOp>);
 
diff --git a/src/gpu/GrTRecorder.h b/src/gpu/GrTRecorder.h
index 7287d62..e3f41a7 100644
--- a/src/gpu/GrTRecorder.h
+++ b/src/gpu/GrTRecorder.h
@@ -83,7 +83,7 @@
      * operating on TItem*. Multiple inheritance may make this not true. It is runtime asserted.
      */
     template <typename TItem, typename... Args>
-    SK_WHEN((std::is_base_of<TBase, TItem>::value), TItem&)
+    std::enable_if_t<(std::is_base_of<TBase, TItem>::value), TItem&>
     emplaceWithData(size_t extraDataSize, Args... args);
 
 private:
@@ -103,7 +103,7 @@
 
 template <typename TBase>
 template <typename TItem, typename... Args>
-inline SK_WHEN((std::is_base_of<TBase, TItem>::value), TItem&)
+inline std::enable_if_t<(std::is_base_of<TBase, TItem>::value), TItem&>
 GrTRecorder<TBase>::emplaceWithData(size_t extraDataSize, Args... args) {
     static constexpr size_t kTAlign = alignof(TItem);
     static constexpr size_t kHeaderAlign = alignof(Header);
diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp
index f2cbbaa..9c66d39 100644
--- a/src/gpu/SkGr.cpp
+++ b/src/gpu/SkGr.cpp
@@ -211,6 +211,7 @@
         case GrColorType::kRG_F16:
         case GrColorType::kRGBA_8888_SRGB:
         case GrColorType::kRGBA_1010102:
+        case GrColorType::kBGRA_1010102:
         case GrColorType::kAlpha_F16:
         case GrColorType::kRGBA_F32:
         case GrColorType::kRGBA_F16:
diff --git a/src/gpu/d3d/GrD3DDescriptorHeap.cpp b/src/gpu/d3d/GrD3DDescriptorHeap.cpp
new file mode 100644
index 0000000..81c1ae6
--- /dev/null
+++ b/src/gpu/d3d/GrD3DDescriptorHeap.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/d3d/GrD3DDescriptorHeap.h"
+#include "src/gpu/d3d/GrD3DGpu.h"
+
+sk_sp<GrD3DDescriptorHeap> GrD3DDescriptorHeap::Make(GrD3DGpu* gpu, D3D12_DESCRIPTOR_HEAP_TYPE type,
+                                                     unsigned int numDescriptors,
+                                                     D3D12_DESCRIPTOR_HEAP_FLAGS flags) {
+    D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
+    heapDesc.Type = type;
+    heapDesc.NumDescriptors = numDescriptors;
+    heapDesc.Flags = flags;
+
+    ID3D12DescriptorHeap* heap;
+    gpu->device()->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&heap));
+
+    return sk_sp<GrD3DDescriptorHeap>(
+            new GrD3DDescriptorHeap(std::move(gr_cp<ID3D12DescriptorHeap>(heap)), heapDesc,
+                                    gpu->device()->GetDescriptorHandleIncrementSize(type)));
+}
+
+GrD3DDescriptorHeap::GrD3DDescriptorHeap(const gr_cp<ID3D12DescriptorHeap>& heap,
+                                         const D3D12_DESCRIPTOR_HEAP_DESC& descriptor,
+                                         unsigned int handleIncrementSize)
+    : fHeap(heap)
+    , fHandleIncrementSize(handleIncrementSize)
+    , fFreeBlocks(descriptor.NumDescriptors) {
+    // set all the bits in freeBlocks
+    for (UINT i = 0; i < descriptor.NumDescriptors; ++i) {
+        fFreeBlocks.set(i);
+    }
+}
+
+D3D12_CPU_DESCRIPTOR_HANDLE GrD3DDescriptorHeap::allocateCPUHandle() {
+    // valid only for non-shader-visible heaps
+    SkASSERT(!SkToBool(fHeap->GetDesc().Flags & D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE));
+    D3D12_CPU_DESCRIPTOR_HANDLE handle = fHeap->GetCPUDescriptorHandleForHeapStart();
+    int freeIndex = fFreeBlocks.leadingBitIndex();
+    SkASSERT(freeIndex >= 0);
+    handle.ptr += freeIndex * fHandleIncrementSize;
+    fFreeBlocks.clear(freeIndex);
+
+    return handle;
+}
+
+D3D12_GPU_DESCRIPTOR_HANDLE GrD3DDescriptorHeap::allocateGPUHandle() {
+    D3D12_GPU_DESCRIPTOR_HANDLE handle = fHeap->GetGPUDescriptorHandleForHeapStart();
+    int freeIndex = fFreeBlocks.leadingBitIndex();
+    SkASSERT(freeIndex >= 0);
+    handle.ptr += freeIndex * fHandleIncrementSize;
+    fFreeBlocks.clear(freeIndex);
+
+    return handle;
+}
+
+void GrD3DDescriptorHeap::freeCPUHandle(D3D12_CPU_DESCRIPTOR_HANDLE* handle) {
+    // valid only for non-shader-visible heaps
+    SkASSERT(!SkToBool(fHeap->GetDesc().Flags & D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE));
+    D3D12_CPU_DESCRIPTOR_HANDLE heapStart = fHeap->GetCPUDescriptorHandleForHeapStart();
+    // Make sure this handle belongs to this heap
+    SkASSERT(handle->ptr >= heapStart.ptr);
+    SIZE_T index = (handle->ptr - heapStart.ptr) / fHandleIncrementSize;
+    SkASSERT(index < fHeap->GetDesc().NumDescriptors);
+    fFreeBlocks.set(index);
+    handle->ptr = 0;
+}
+
+void GrD3DDescriptorHeap::freeGPUHandle(D3D12_GPU_DESCRIPTOR_HANDLE* handle) {
+    D3D12_GPU_DESCRIPTOR_HANDLE heapStart = fHeap->GetGPUDescriptorHandleForHeapStart();
+    // Make sure this handle belongs to this heap
+    SkASSERT(handle->ptr >= heapStart.ptr);
+    SIZE_T index = (handle->ptr - heapStart.ptr) / fHandleIncrementSize;
+    SkASSERT(index < fHeap->GetDesc().NumDescriptors);
+    fFreeBlocks.set(index);
+    handle->ptr = 0;
+}
+
+#ifdef SK_TRACE_MANAGED_RESOURCES
+    /** Output a human-readable dump of this resource's information
+     */
+void GrD3DDescriptorHeap::dumpInfo() const {
+    SkDebugf("GrD3DDescriptorHeap: %d (%d refs)\n", fHeap.get(), this->getRefCnt());
+}
+#endif
+
+
diff --git a/src/gpu/d3d/GrD3DDescriptorHeap.h b/src/gpu/d3d/GrD3DDescriptorHeap.h
new file mode 100644
index 0000000..c9c8558
--- /dev/null
+++ b/src/gpu/d3d/GrD3DDescriptorHeap.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrD3DDescriptorHeap_DEFINED
+#define GrD3DDescriptorHeap_DEFINED
+
+#include "include/gpu/d3d/GrD3DTypes.h"
+#include "src/gpu/GrManagedResource.h"
+#include "src/utils/SkBitSet.h"
+
+class GrD3DGpu;
+
+class GrD3DDescriptorHeap : public GrManagedResource {
+public:
+    static sk_sp<GrD3DDescriptorHeap> Make(GrD3DGpu* gpu, D3D12_DESCRIPTOR_HEAP_TYPE,
+                                           unsigned int numDescriptors,
+                                           D3D12_DESCRIPTOR_HEAP_FLAGS);
+
+    ~GrD3DDescriptorHeap() override = default;
+
+    D3D12_CPU_DESCRIPTOR_HANDLE allocateCPUHandle(); // valid only for non-shader-visible heaps
+    D3D12_GPU_DESCRIPTOR_HANDLE allocateGPUHandle();
+    void freeCPUHandle(D3D12_CPU_DESCRIPTOR_HANDLE*);
+    void freeGPUHandle(D3D12_GPU_DESCRIPTOR_HANDLE*);
+
+#ifdef SK_TRACE_MANAGED_RESOURCES
+    /** Output a human-readable dump of this resource's information
+     */
+    void dumpInfo() const override;
+#endif
+
+    void freeGPUData() const override {}
+
+private:
+    GrD3DDescriptorHeap(const gr_cp<ID3D12DescriptorHeap>&, const D3D12_DESCRIPTOR_HEAP_DESC&,
+                        unsigned int handleIncrementSize);
+
+    gr_cp<ID3D12DescriptorHeap> fHeap;
+    size_t fHandleIncrementSize;
+    SkBitSet fFreeBlocks;
+};
+
+#endif
diff --git a/src/gpu/dawn/GrDawnGpu.cpp b/src/gpu/dawn/GrDawnGpu.cpp
index 68fc496..d8a1ac0 100644
--- a/src/gpu/dawn/GrDawnGpu.cpp
+++ b/src/gpu/dawn/GrDawnGpu.cpp
@@ -348,7 +348,7 @@
         memset(defaultStorage.get(), 0, baseLayerSize);
     }
     wgpu::Device device = this->device();
-    wgpu::CommandEncoder copyEncoder = fDevice.CreateCommandEncoder();
+    wgpu::CommandEncoder copyEncoder = this->getCopyEncoder();
     int w = dimensions.width(), h = dimensions.height();
     for (uint32_t i = 0; i < desc.mipLevelCount; i++) {
         size_t origRowBytes = bpp * w;
@@ -369,8 +369,8 @@
         wgpu::BufferCopyView srcBuffer;
         srcBuffer.buffer = static_cast<GrDawnStagingBuffer*>(stagingBuffer.fBuffer)->buffer();
         srcBuffer.offset = stagingBuffer.fOffset;
-        srcBuffer.rowPitch = rowBytes;
-        srcBuffer.imageHeight = h;
+        srcBuffer.bytesPerRow = rowBytes;
+        srcBuffer.rowsPerImage = h;
         wgpu::TextureCopyView dstTexture;
         dstTexture.texture = tex;
         dstTexture.mipLevel = i;
@@ -380,8 +380,6 @@
         w = std::max(1, w / 2);
         h = std::max(1, h / 2);
     }
-    wgpu::CommandBuffer cmdBuf = copyEncoder.Finish();
-    fQueue.Submit(1, &cmdBuf);
     GrDawnTextureInfo info;
     info.fTexture = tex;
     info.fFormat = desc.format;
@@ -559,8 +557,8 @@
     wgpu::BufferCopyView dstBuffer;
     dstBuffer.buffer = buf;
     dstBuffer.offset = 0;
-    dstBuffer.rowPitch = rowBytes;
-    dstBuffer.imageHeight = height;
+    dstBuffer.bytesPerRow = rowBytes;
+    dstBuffer.rowsPerImage = height;
 
     wgpu::Extent3D copySize = {(uint32_t) width, (uint32_t) height, 1};
     this->getCopyEncoder().CopyTextureToBuffer(&srcTexture, &dstBuffer, &copySize);
diff --git a/src/gpu/dawn/GrDawnTexture.cpp b/src/gpu/dawn/GrDawnTexture.cpp
index 5acc963..50b1c62 100644
--- a/src/gpu/dawn/GrDawnTexture.cpp
+++ b/src/gpu/dawn/GrDawnTexture.cpp
@@ -158,8 +158,8 @@
         wgpu::BufferCopyView srcBuffer;
         srcBuffer.buffer = static_cast<GrDawnStagingBuffer*>(slice.fBuffer)->buffer();
         srcBuffer.offset = slice.fOffset;
-        srcBuffer.rowPitch = dstRowBytes;
-        srcBuffer.imageHeight = height;
+        srcBuffer.bytesPerRow = dstRowBytes;
+        srcBuffer.rowsPerImage = height;
 
         wgpu::TextureCopyView dstTexture;
         dstTexture.texture = fInfo.fTexture;
diff --git a/src/gpu/geometry/GrShape.cpp b/src/gpu/geometry/GrShape.cpp
new file mode 100644
index 0000000..d656021
--- /dev/null
+++ b/src/gpu/geometry/GrShape.cpp
@@ -0,0 +1,453 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/geometry/GrShape.h"
+
+#include "src/core/SkPathPriv.h"
+
+GrShape& GrShape::operator=(const GrShape& shape) {
+    switch(shape.type()) {
+        case Type::kEmpty:
+            this->reset();
+            break;
+        case Type::kPoint:
+            this->setPoint(shape.fPoint);
+            break;
+        case Type::kRect:
+            this->setRect(shape.fRect);
+            break;
+        case Type::kRRect:
+            this->setRRect(shape.fRRect);
+            break;
+        case Type::kPath:
+            this->setPath(shape.fPath);
+            break;
+        case Type::kArc:
+            this->setArc(shape.fArc);
+            break;
+        case Type::kLine:
+            this->setLine(shape.fLine);
+            break;
+        default:
+            SkUNREACHABLE;
+    }
+
+    fStart = shape.fStart;
+    fCW = shape.fCW;
+    fInverted = shape.fInverted;
+
+    return *this;
+}
+
+uint32_t GrShape::stateKey() const {
+    // Use the path's full fill type instead of just whether or not it's inverted.
+    uint32_t key = this->isPath() ? static_cast<uint32_t>(fPath.getFillType())
+                                  : (fInverted ? 1 : 0);
+    key |= ((uint32_t) fType) << 2; // fill type was 2 bits
+    key |= fStart             << 5; // type was 3 bits, total 5 bits so far
+    key |= (fCW ? 1 : 0)      << 8; // start was 3 bits, total 8 bits so far
+    return key;
+}
+
+bool GrShape::simplifyPath(unsigned flags) {
+    SkASSERT(this->isPath());
+
+    SkRect rect;
+    SkRRect rrect;
+    SkPoint pts[2];
+
+    SkPathDirection dir;
+    unsigned start;
+
+    if (fPath.isEmpty()) {
+        this->setType(Type::kEmpty);
+        return false;
+    } else if (fPath.isLine(pts)) {
+        this->simplifyLine(pts[0], pts[1], flags);
+        return false;
+    } else if (SkPathPriv::IsRRect(fPath, &rrect, &dir, &start)) {
+        this->simplifyRRect(rrect, dir, start, flags);
+        return true;
+    } else if (SkPathPriv::IsOval(fPath, &rect, &dir, &start)) {
+        // Convert to rrect indexing since oval is not represented explicitly
+        this->simplifyRRect(SkRRect::MakeOval(rect), dir, start * 2, flags);
+        return true;
+    } else if (SkPathPriv::IsSimpleClosedRect(fPath, &rect, &dir, &start)) {
+        // When there is a path effect we restrict rect detection to the narrower API that
+        // gives us the starting position. Otherwise, we will retry with the more aggressive
+        // isRect().
+        this->simplifyRect(rect, dir, start, flags);
+        return true;
+    } else if (flags & kIgnoreWinding_Flag) {
+        // Attempt isRect() since we don't have to preserve any winding info
+        bool closed;
+        if (fPath.isRect(&rect, &closed) && (closed || (flags & kSimpleFill_Flag))) {
+            this->simplifyRect(rect, kDefaultDir, kDefaultStart, flags);
+            return true;
+        }
+    }
+    // No further simplification for a path. For performance reasons, we don't query the path to
+    // determine it was closed, as whether or not it was closed when it remains a path type is not
+    // important for styling.
+    return false;
+}
+
+bool GrShape::simplifyArc(unsigned flags) {
+    SkASSERT(this->isArc());
+
+    // Arcs can simplify to rrects, lines, points, or empty; regardless of what it simplifies to
+    // it was closed if went through the center point.
+    bool wasClosed = fArc.fUseCenter;
+    if (fArc.fOval.isEmpty() || !fArc.fSweepAngle) {
+        if (flags & kSimpleFill_Flag) {
+            // Go straight to empty, since the other degenerate shapes all have 0 area anyway.
+            this->setType(Type::kEmpty);
+        } else if (!fArc.fSweepAngle) {
+            SkPoint center = {fArc.fOval.centerX(), fArc.fOval.centerY()};
+            SkScalar startRad = SkDegreesToRadians(fArc.fStartAngle);
+            SkPoint start = {center.fX + 0.5f * fArc.fOval.width() * SkScalarCos(startRad),
+                                center.fY + 0.5f * fArc.fOval.height() * SkScalarSin(startRad)};
+            // Either just the starting point, or a line from the center to the start
+            if (fArc.fUseCenter) {
+                this->simplifyLine(center, start, flags);
+             } else {
+                this->simplifyPoint(start, flags);
+             }
+        } else {
+            // TODO: Theoretically, we could analyze the arc projected into the empty bounds to
+            // determine a line, but that is somewhat complex for little value (since the arc
+            // can backtrack on itself if the sweep angle is large enough).
+            this->setType(Type::kEmpty);
+        }
+    } else {
+        if ((flags & kSimpleFill_Flag) || ((flags & kIgnoreWinding_Flag) && !fArc.fUseCenter)) {
+             // Eligible to turn into an oval if it sweeps a full circle
+            if (fArc.fSweepAngle <= -360.f || fArc.fSweepAngle >= 360.f) {
+                this->simplifyRRect(SkRRect::MakeOval(fArc.fOval),
+                                    kDefaultDir, kDefaultStart, flags);
+                return true;
+            }
+        }
+
+        if (flags & kMakeCanonical_Flag) {
+            // Map start to 0 to 360, sweep is always positive
+            if (fArc.fSweepAngle < 0) {
+                fArc.fStartAngle = fArc.fStartAngle + fArc.fSweepAngle;
+                fArc.fSweepAngle = -fArc.fSweepAngle;
+            }
+
+            if (fArc.fStartAngle < 0 || fArc.fStartAngle >= 360.f) {
+                fArc.fStartAngle = SkScalarMod(fArc.fStartAngle, 360.f);
+            }
+        }
+    }
+
+    return wasClosed;
+}
+
+void GrShape::simplifyRRect(const SkRRect& rrect, SkPathDirection dir, unsigned start,
+                            unsigned flags) {
+    if (rrect.isEmpty() || rrect.isRect()) {
+        // Change index from rrect to rect
+        start = ((start + 1) / 2) % 4;
+        this->simplifyRect(rrect.rect(), dir, start, flags);
+    } else if (!this->isRRect()) {
+        this->setType(Type::kRRect);
+        fRRect = rrect;
+        this->setPathWindingParams(dir, start);
+        // A round rect is already canonical, so there's nothing more to do
+    } else {
+        // If starting as a round rect, the provided rrect/winding params should be already set
+        SkASSERT(fRRect == rrect && this->dir() == dir && this->startIndex() == start);
+    }
+}
+
+void GrShape::simplifyRect(const SkRect& rect, SkPathDirection dir, unsigned start,
+                           unsigned flags) {
+    if (!rect.width() || !rect.height()) {
+        if (flags & kSimpleFill_Flag) {
+            // A zero area, filled shape so go straight to empty
+            this->setType(Type::kEmpty);
+        } else if (!fRect.width() ^ !fRect.height()) {
+            // A line, choose the first point that best matches the starting index
+            SkPoint p1 = {rect.fLeft, rect.fTop};
+            SkPoint p2 = {rect.fRight, rect.fBottom};
+            if (start >= 2 && !(flags & kIgnoreWinding_Flag)) {
+                using std::swap;
+                swap(p1, p2);
+            }
+            this->simplifyLine(p1, p2, flags);
+        } else {
+            // A point (all edges are equal, so start+dir doesn't affect choice)
+            this->simplifyPoint({rect.fLeft, rect.fTop}, flags);
+        }
+    } else {
+        if (!this->isRect()) {
+            this->setType(Type::kRect);
+            fRect = rect;
+            this->setPathWindingParams(dir, start);
+        } else {
+            // If starting as a rect, the provided rect/winding params should already be set
+            SkASSERT(fRect == rect && this->dir() == dir && this->startIndex() == start);
+        }
+        if (flags & kMakeCanonical_Flag) {
+            fRect.sort();
+        }
+    }
+}
+
+void GrShape::simplifyLine(const SkPoint& p1, const SkPoint& p2, unsigned flags) {
+    if (flags & kSimpleFill_Flag) {
+        this->setType(Type::kEmpty);
+    } else if (p1 == p2) {
+        this->simplifyPoint(p1, false);
+    } else {
+        if (!this->isLine()) {
+            this->setType(Type::kLine);
+            fLine.fP1 = p1;
+            fLine.fP2 = p2;
+        } else {
+            // If starting as a line, the provided points should already be set
+            SkASSERT(fLine.fP1 == p1 && fLine.fP2 == p2);
+        }
+        if (flags & kMakeCanonical_Flag) {
+             // Sort the end points
+             if (fLine.fP2.fY < fLine.fP1.fY ||
+                 (fLine.fP2.fY == fLine.fP1.fY && fLine.fP2.fX < fLine.fP1.fX)) {
+                using std::swap;
+                swap(fLine.fP1, fLine.fP2);
+            }
+        }
+    }
+}
+
+void GrShape::simplifyPoint(const SkPoint& point, unsigned flags) {
+    if (flags & kSimpleFill_Flag) {
+        this->setType(Type::kEmpty);
+    } else if (!this->isPoint()) {
+        this->setType(Type::kPoint);
+        fPoint = point;
+    } else {
+        // If starting as a point, the provided position should already be set
+        SkASSERT(point == fPoint);
+    }
+}
+
+bool GrShape::simplify(unsigned flags) {
+    // Sanity check that winding parameters are valid for the current type.
+    SkASSERT((fType == Type::kRect || fType == Type::kRRect) ||
+             (this->dir() == kDefaultDir && this->startIndex() == kDefaultStart));
+
+    // The type specific functions automatically fall through to the simpler shapes, so
+    // we only need to start in the right place.
+    bool wasClosed = false;
+    switch(fType) {
+        case Type::kEmpty:
+            // do nothing
+            break;
+        case Type::kPoint:
+            this->simplifyPoint(fPoint, flags);
+            break;
+        case Type::kLine:
+            this->simplifyLine(fLine.fP1, fLine.fP2, flags);
+            break;
+        case Type::kRect:
+            this->simplifyRect(fRect, this->dir(), this->startIndex(), flags);
+            wasClosed = true;
+            break;
+        case Type::kRRect:
+            this->simplifyRRect(fRRect, this->dir(), this->startIndex(), flags);
+            wasClosed = true;
+            break;
+        case Type::kPath:
+            wasClosed = this->simplifyPath(flags);
+            break;
+        case Type::kArc:
+            wasClosed = this->simplifyArc(flags);
+            break;
+
+        default:
+            SkUNREACHABLE;
+    }
+
+    if (((flags & kIgnoreWinding_Flag) || (fType != Type::kRect && fType != Type::kRRect))) {
+        // Reset winding parameters if we don't need them anymore
+        this->setPathWindingParams(kDefaultDir, kDefaultStart);
+    }
+
+    return wasClosed;
+}
+
+bool GrShape::contains(const SkRect& rect) const {
+    switch(this->type()) {
+        case Type::kEmpty:
+        case Type::kPoint: // fall through since a point has 0 area
+        case Type::kLine:  // fall through, "" (currently choosing not to test if 'rect' == line)
+            return false;
+        case Type::kRect:
+            return fRect.contains(rect);
+        case Type::kRRect:
+            return fRRect.contains(rect);
+        case Type::kPath:
+            return fPath.conservativelyContainsRect(rect);
+        case Type::kArc:
+            if (fArc.fUseCenter) {
+                SkPath arc;
+                this->asPath(&arc);
+                return arc.conservativelyContainsRect(rect);
+            } else {
+                return false;
+            }
+        default:
+            SkUNREACHABLE;
+    }
+}
+
+bool GrShape::closed() const {
+    switch(this->type()) {
+        case Type::kEmpty: // fall through
+        case Type::kRect:  // fall through
+        case Type::kRRect:
+            return true;
+        case Type::kPath:
+            // SkPath doesn't keep track of the closed status of each contour.
+            return SkPathPriv::IsClosedSingleContour(fPath);
+        case Type::kArc:
+            return fArc.fUseCenter;
+        case Type::kPoint: // fall through
+        case Type::kLine:
+            return false;
+        default:
+            SkUNREACHABLE;
+    }
+}
+
+bool GrShape::convex(bool simpleFill) const {
+    switch(this->type()) {
+        case Type::kEmpty: // fall through
+        case Type::kRect:  // fall through
+        case Type::kRRect:
+            return true;
+        case Type::kPath:
+            // SkPath.isConvex() really means "is this path convex were it to be closed".
+            // Convex paths may only have one contour hence isLastContourClosed() is sufficient.
+            return (simpleFill || fPath.isLastContourClosed()) && fPath.isConvex();
+        case Type::kArc:
+            return SkPathPriv::DrawArcIsConvex(fArc.fSweepAngle, fArc.fUseCenter, simpleFill);
+        case Type::kPoint: // fall through
+        case Type::kLine:
+            return false;
+        default:
+            SkUNREACHABLE;
+    }
+}
+
+SkRect GrShape::bounds() const {
+    // Bounds where left == bottom or top == right can indicate a line or point shape. We return
+    // inverted bounds for a truly empty shape.
+    static constexpr SkRect kInverted = SkRect::MakeLTRB(1, 1, -1, -1);
+    switch(this->type()) {
+        case Type::kEmpty:
+            return kInverted;
+        case Type::kPoint:
+            return {fPoint.fX, fPoint.fY, fPoint.fX, fPoint.fY};
+        case Type::kRect:
+            return fRect.makeSorted();
+        case Type::kRRect:
+            return fRRect.getBounds();
+        case Type::kPath:
+            return fPath.getBounds();
+        case Type::kArc:
+            return fArc.fOval;
+        case Type::kLine: {
+            SkRect b = SkRect::MakeLTRB(fLine.fP1.fX, fLine.fP1.fY,
+                                        fLine.fP2.fX, fLine.fP2.fY);
+            b.sort();
+            return b; }
+        default:
+            SkUNREACHABLE;
+    }
+}
+
+uint32_t GrShape::segmentMask() const {
+    // In order to match what a path would report, this has to inspect the shapes slightly
+    // to reflect what they might simplify to.
+    switch(this->type()) {
+        case Type::kEmpty:
+            return 0;
+        case Type::kRRect:
+            if (fRRect.isEmpty() || fRRect.isRect()) {
+                return SkPath::kLine_SegmentMask;
+            } else if (fRRect.isOval()) {
+                return SkPath::kConic_SegmentMask;
+            } else {
+                return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask;
+            }
+        case Type::kPath:
+            return fPath.getSegmentMasks();
+        case Type::kArc:
+            if (fArc.fUseCenter) {
+                return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask;
+            } else {
+                return SkPath::kConic_SegmentMask;
+            }
+        case Type::kPoint: // fall through
+        case Type::kLine:  // ""
+        case Type::kRect:
+            return SkPath::kLine_SegmentMask;
+        default:
+            SkUNREACHABLE;
+    }
+}
+
+void GrShape::asPath(SkPath* out, bool simpleFill) const {
+    if (!this->isPath() && !this->isArc()) {
+        // When not a path, we need to set fill type on the path to match invertedness.
+        // All the non-path geometries produce equivalent shapes with either even-odd or winding
+        // so we can use the default fill type.
+        out->reset();
+        out->setFillType(kDefaultFillType);
+        if (fInverted) {
+            out->toggleInverseFillType();
+        }
+    } // Else when we're already a path, that will assign the fill type directly to 'out'.
+
+    switch(this->type()) {
+        case Type::kEmpty:
+            return;
+        case Type::kPoint:
+            // A plain moveTo() or moveTo+close() does not match the expected path for a
+            // point that is being dashed (see SkDashPath's handling of zero-length segments).
+            out->moveTo(fPoint);
+            out->lineTo(fPoint);
+            return;
+        case Type::kRect:
+            out->addRect(fRect, this->dir(), this->startIndex());
+            return;
+        case Type::kRRect:
+            out->addRRect(fRRect, this->dir(), this->startIndex());
+            return;
+        case Type::kPath:
+            *out = fPath;
+            return;
+        case Type::kArc:
+            SkPathPriv::CreateDrawArcPath(out, fArc.fOval, fArc.fStartAngle, fArc.fSweepAngle,
+                                          fArc.fUseCenter, simpleFill);
+            // CreateDrawArcPath resets the output path and configures its fill type, so we just
+            // have to ensure invertedness is correct.
+            if (fInverted) {
+                out->toggleInverseFillType();
+            }
+            return;
+        case Type::kLine:
+            out->moveTo(fLine.fP1);
+            out->lineTo(fLine.fP2);
+            return;
+        default:
+            SkUNREACHABLE;
+    }
+}
diff --git a/src/gpu/geometry/GrShape.h b/src/gpu/geometry/GrShape.h
new file mode 100644
index 0000000..857af35
--- /dev/null
+++ b/src/gpu/geometry/GrShape.h
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrShape_DEFINED
+#define GrShape_DEFINED
+
+#include "include/core/SkPath.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkRRect.h"
+#include "include/core/SkRect.h"
+
+// Represents an arc along an oval boundary, or a closed wedge of the oval.
+struct GrArc {
+    SkRect   fOval;       // The sorted, bounding box defining the oval the arc is traced along
+    SkScalar fStartAngle; // In degrees
+    SkScalar fSweepAngle; // In degrees
+    bool     fUseCenter;  // True if the arc includes the center point of the oval
+};
+
+// Represents a line segment between two points.
+struct GrLineSegment {
+    SkPoint fP1;
+    SkPoint fP2;
+};
+
+/**
+ * GrShape is a convenience class to represent the many different specialized geometries that
+ * Ganesh can handle, including rects, round rects, lines, as well as paths. It is intended as
+ * a data-only class where any additional complex behavior is handled by an owning type (e.g.
+ * GrStyledShape). However, it does include some basic utilities that unify common functionality
+ * (such as contains()) from the underlying shape types.
+ *
+ * In order to have lossless simplification of the geometry, it also tracks winding direction, start
+ * index, and fill inversion. The direction and index are match the SkPath indexing scheme for
+ * the shape's type (e.g. rect, rrect, or oval).
+ *
+ * Regarding GrShape's empty shape:
+ * - GrShape uses empty to refer to the absence of any geometric data
+ * - SkRect::isEmpty() returns true if the rect is not sorted, even if it has area. GrShape will not
+ *   simplify these shapes to an empty GrShape. Rects with actual 0 width and height will simplify
+ *   to a point or line, not empty. This is to preserve geometric data for path effects and strokes.
+ * - SkRRect::isEmpty() is true when the bounds have 0 width or height, so GrShape will simplify it
+ *   to a point or line, just like a rect. SkRRect does not have the concept of unsorted edges.
+ */
+class GrShape {
+public:
+    // The current set of types GrShape can represent directly
+    enum class Type : uint8_t {
+        kEmpty, kPoint, kRect, kRRect, kPath, kArc, kLine, kLast = kLine
+    };
+    static constexpr int kTypeCount = static_cast<int>(Type::kLast) + 1;
+
+    // The direction and start index used when a shape does not have a representable winding,
+    // or when that information was discarded during simplification (kIgnoreWinding_Flag).
+    static constexpr SkPathDirection kDefaultDir   = SkPathDirection::kCW;
+    static constexpr unsigned        kDefaultStart = 0;
+    // The fill rule that is used by asPath() for shapes that aren't already a path.
+    static constexpr SkPathFillType  kDefaultFillType = SkPathFillType::kEvenOdd;
+
+    GrShape() {}
+    explicit GrShape(const SkPoint& point) { this->setPoint(point); }
+    explicit GrShape(const SkRect& rect) { this->setRect(rect); }
+    explicit GrShape(const SkRRect& rrect) { this->setRRect(rrect); }
+    explicit GrShape(const SkPath& path) { this->setPath(path); }
+    explicit GrShape(const GrArc& arc) { this->setArc(arc); }
+    explicit GrShape(const GrLineSegment& line){ this->setLine(line); }
+
+    explicit GrShape(const GrShape& shape) { *this = shape; }
+
+    ~GrShape() { this->reset(); }
+
+    // NOTE: None of the geometry types benefit from move semantics, so we don't bother
+    // defining a move assignment operator for GrShape.
+    GrShape& operator=(const GrShape& shape);
+
+    // These type queries reflect the shape type provided when assigned, it does not incorporate
+    // any potential simplification (e.g. if isRRect() is true and rrect().isRect() is true,
+    // isRect() will still be false, until simplify() is called).
+    bool isEmpty() const { return this->type() == Type::kEmpty; }
+    bool isPoint() const { return this->type() == Type::kPoint; }
+    bool isRect() const { return this->type() == Type::kRect; }
+    bool isRRect() const { return this->type() == Type::kRRect; }
+    bool isPath() const { return this->type() == Type::kPath; }
+    bool isArc() const { return this->type() == Type::kArc; }
+    bool isLine() const { return this->type() == Type::kLine; }
+
+    Type type() const { return fType; }
+
+    // Report the shape type, winding direction, start index, and invertedness as a value suitable
+    // for use in a resource key. This does not include any geometry coordinates into the key value.
+    uint32_t stateKey() const;
+
+    // Whether or not the shape is meant to be the inverse of its geometry (i.e. its exterior).
+    bool inverted() const {
+        return this->isPath() ? fPath.isInverseFillType() : SkToBool(fInverted);
+    }
+
+    // Returns the path direction extracted from the path during simplification, if the shape's
+    // type represents a rrect, rect, or oval.
+    SkPathDirection dir() const { return fCW ? SkPathDirection::kCW : SkPathDirection::kCCW; }
+    // Returns the start index extracted from the path during simplification, if the shape's
+    // type represents a rrect, rect, or oval.
+    unsigned startIndex() const { return fStart; }
+
+    // Override the direction and start parameters for the simplified contour. These are only
+    // meaningful for rects, rrects, and ovals.
+    void setPathWindingParams(SkPathDirection dir, unsigned start) {
+        SkASSERT((this->isRect() && start < 4) || (this->isRRect() && start < 8) ||
+                 (dir == kDefaultDir && start == kDefaultStart));
+        fCW = dir == SkPathDirection::kCW;
+        fStart = static_cast<uint8_t>(start);
+    }
+
+    void setInverted(bool inverted) {
+        if (this->isPath()) {
+            if (inverted != fPath.isInverseFillType()) {
+                fPath.toggleInverseFillType();
+            }
+        } else {
+            fInverted = static_cast<uint16_t>(inverted);
+        }
+    }
+
+    // Access the actual geometric description of the shape. May only access the appropriate type
+    // based on what was last set. The type may change after simplify() is called.
+    SkPoint& point() { SkASSERT(this->isPoint()); return fPoint; }
+    const SkPoint& point() const { SkASSERT(this->isPoint()); return fPoint; }
+
+    SkRect& rect() { SkASSERT(this->isRect()); return fRect; }
+    const SkRect& rect() const { SkASSERT(this->isRect()); return fRect; }
+
+    SkRRect& rrect() { SkASSERT(this->isRRect()); return fRRect; }
+    const SkRRect& rrect() const { SkASSERT(this->isRRect()); return fRRect; }
+
+    SkPath& path() { SkASSERT(this->isPath()); return fPath; }
+    const SkPath& path() const { SkASSERT(this->isPath()); return fPath; }
+
+    GrArc& arc() { SkASSERT(this->isArc()); return fArc; }
+    const GrArc& arc() const { SkASSERT(this->isArc()); return fArc; }
+
+    GrLineSegment& line() { SkASSERT(this->isLine()); return fLine; }
+    const GrLineSegment& line() const { SkASSERT(this->isLine()); return fLine; }
+
+    // Update the geometry stored in the GrShape and update its associated type to match. This
+    // performs no simplification, so calling setRRect() with a round rect that has isRect() return
+    // true will still be considered an rrect by this shape until simplify() is called.
+    //
+    // These also reset any extracted direction, start, and inverted state from a prior simplified
+    // path, since these functions ared used to describe a new geometry.
+    void setPoint(const SkPoint& point) {
+        this->reset(Type::kPoint);
+        fPoint = point;
+    }
+    void setRect(const SkRect& rect) {
+        this->reset(Type::kRect);
+        fRect = rect;
+    }
+    void setRRect(const SkRRect& rrect) {
+        this->reset(Type::kRRect);
+        fRRect = rrect;
+    }
+    void setArc(const GrArc& arc) {
+        this->reset(Type::kArc);
+        fArc = arc;
+    }
+    void setLine(const GrLineSegment& line) {
+        this->reset(Type::kLine);
+        fLine = line;
+    }
+    void setPath(const SkPath& path) {
+        if (this->isPath()) {
+            // Assign directly
+            fPath = path;
+        } else {
+            // In-place initialize
+            this->setType(Type::kPath);
+            new (&fPath) SkPath(path);
+        }
+        // Must also set these since we didn't call reset() like other setX functions.
+        this->setPathWindingParams(kDefaultDir, kDefaultStart);
+        fInverted = path.isInverseFillType();
+    }
+    void reset() {
+        this->setType(Type::kEmpty);
+    }
+
+    // Flags that enable more aggressive, "destructive" simplifications to the geometry
+    enum SimplifyFlags : unsigned {
+        // If set, it is assumed the original shape would have been implicitly filled when drawn or
+        // clipped, so simpler shape types that are closed can still be considered. Shapes with
+        // 0 area (i.e. points and lines) can be turned into empty.
+        kSimpleFill_Flag    = 0b001,
+        // If set, simplifications that would impact a directional stroke or path effect can still
+        // be taken (e.g. dir and start are not required, arcs can be converted to ovals).
+        kIgnoreWinding_Flag = 0b010,
+        // If set, the geometry will be updated to have sorted coordinates (rects, lines), modulated
+        // sweep angles (arcs).
+        kMakeCanonical_Flag = 0b100,
+
+        kAll_Flags          = 0b111
+    };
+    // Returns true if the shape was originally closed based on type (or detected type within a
+    // path), even if the final simplification results in a point, line, or empty.
+    bool simplify(unsigned flags = kAll_Flags);
+
+    // True if the given bounding box is completely inside the shape.
+    bool contains(const SkRect& rect) const;
+
+    // True if the underlying geometry represents a closed shape, without the need for an
+    // implicit close (note that if simplified earlier with 'simpleFill' = true, a shape that was
+    // not closed may become closed).
+    bool closed() const;
+
+    // True if the underlying shape is known to be convex, assuming no other styles. If 'simpleFill'
+    // is true, it is assumed the contours will be implicitly closed when drawn or used.
+    bool convex(bool simpleFill = true) const;
+
+    // The bounding box of the shape.
+    SkRect bounds() const;
+
+    // The segment masks that describe the shape, were it to be converted to an SkPath
+    uint32_t segmentMask() const;
+
+    // Convert the shape into a path that describes the same geometry.
+    void asPath(SkPath* out, bool simpleFill = true) const;
+
+private:
+
+    void setType(Type type) {
+        if (this->isPath() && type != Type::kPath) {
+            fPath.~SkPath();
+        }
+        fType = type;
+    }
+
+    void reset(Type type) {
+        this->setType(type);
+        this->setPathWindingParams(kDefaultDir, kDefaultStart);
+        this->setInverted(false);
+    }
+
+    // Paths and arcs are root shapes, another type will never simplify to them, so they do
+    // not take the geometry to simplify as an argument. Since they are root shapes, they also
+    // return whether or not they were originally closed before being simplified.
+    bool simplifyPath(unsigned flags);
+    bool simplifyArc(unsigned flags);
+
+    // The simpler type classes do take the geometry because it may represent an in-progress
+    // simplification that hasn't been set on the GrShape yet. The simpler types do not report
+    // whether or not they were closed because it's implicit in their type.
+    void simplifyLine(const SkPoint& p1, const SkPoint& p2, unsigned flags);
+    void simplifyPoint(const SkPoint& point, unsigned flags);
+
+    // RRects and rects care about winding for path effects and will set the path winding state
+    // of the shape as well.
+    void simplifyRRect(const SkRRect& rrect, SkPathDirection dir, unsigned start, unsigned flags);
+    void simplifyRect(const SkRect& rect, SkPathDirection dir, unsigned start, unsigned flags);
+
+    union {
+        SkPoint       fPoint;
+        SkRect        fRect;
+        SkRRect       fRRect;
+        SkPath        fPath;
+        GrArc         fArc;
+        GrLineSegment fLine;
+    };
+
+    Type            fType = Type::kEmpty;
+    uint8_t         fStart; // Restricted to rrects and simpler, so this will be < 8
+    bool            fCW;
+    bool            fInverted;
+};
+
+#endif
diff --git a/src/gpu/geometry/GrStyledShape.cpp b/src/gpu/geometry/GrStyledShape.cpp
index d81b99e..0e87857 100644
--- a/src/gpu/geometry/GrStyledShape.cpp
+++ b/src/gpu/geometry/GrStyledShape.cpp
@@ -12,26 +12,11 @@
 #include <utility>
 
 GrStyledShape& GrStyledShape::operator=(const GrStyledShape& that) {
-    fStyle = that.fStyle;
-    this->changeType(that.fType, Type::kPath == that.fType ? &that.path() : nullptr);
-    switch (fType) {
-        case Type::kEmpty:
-            break;
-        case Type::kInvertedEmpty:
-            break;
-        case Type::kRRect:
-            fRRectData = that.fRRectData;
-            break;
-        case Type::kArc:
-            fArcData = that.fArcData;
-            break;
-        case Type::kLine:
-            fLineData = that.fLineData;
-            break;
-        case Type::kPath:
-            fPathData.fGenID = that.fPathData.fGenID;
-            break;
-    }
+    fShape      = that.fShape;
+    fStyle      = that.fStyle;
+    fGenID      = that.fGenID;
+    fSimplified = that.fSimplified;
+
     fInheritedKey.reset(that.fInheritedKey.count());
     sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(),
                       sizeof(uint32_t) * fInheritedKey.count());
@@ -43,20 +28,6 @@
     return *this;
 }
 
-static bool flip_inversion(bool originalIsInverted, GrStyledShape::FillInversion inversion) {
-    switch (inversion) {
-        case GrStyledShape::FillInversion::kPreserve:
-            return false;
-        case GrStyledShape::FillInversion::kFlip:
-            return true;
-        case GrStyledShape::FillInversion::kForceInverted:
-            return !originalIsInverted;
-        case GrStyledShape::FillInversion::kForceNoninverted:
-            return originalIsInverted;
-    }
-    return false;
-}
-
 static bool is_inverted(bool originalIsInverted, GrStyledShape::FillInversion inversion) {
     switch (inversion) {
         case GrStyledShape::FillInversion::kPreserve:
@@ -72,102 +43,41 @@
 }
 
 GrStyledShape GrStyledShape::MakeFilled(const GrStyledShape& original, FillInversion inversion) {
-    if (original.style().isSimpleFill() && !flip_inversion(original.inverseFilled(), inversion)) {
+    bool newIsInverted = is_inverted(original.fShape.inverted(), inversion);
+    if (original.style().isSimpleFill() && newIsInverted == original.fShape.inverted()) {
         // By returning the original rather than falling through we can preserve any inherited style
         // key. Otherwise, we wipe it out below since the style change invalidates it.
         return original;
     }
     GrStyledShape result;
+    SkASSERT(result.fStyle.isSimpleFill());
     if (original.fInheritedPathForListeners.isValid()) {
         result.fInheritedPathForListeners.set(*original.fInheritedPathForListeners.get());
     }
-    switch (original.fType) {
-        case Type::kRRect:
-            result.fType = original.fType;
-            result.fRRectData.fRRect = original.fRRectData.fRRect;
-            result.fRRectData.fDir = kDefaultRRectDir;
-            result.fRRectData.fStart = kDefaultRRectStart;
-            result.fRRectData.fInverted = is_inverted(original.fRRectData.fInverted, inversion);
-            break;
-        case Type::kArc:
-            result.fType = original.fType;
-            result.fArcData.fOval = original.fArcData.fOval;
-            result.fArcData.fStartAngleDegrees = original.fArcData.fStartAngleDegrees;
-            result.fArcData.fSweepAngleDegrees = original.fArcData.fSweepAngleDegrees;
-            result.fArcData.fUseCenter = original.fArcData.fUseCenter;
-            result.fArcData.fInverted = is_inverted(original.fArcData.fInverted, inversion);
-            break;
-        case Type::kLine:
-            // Lines don't fill.
-            if (is_inverted(original.fLineData.fInverted, inversion)) {
-                result.fType = Type::kInvertedEmpty;
-            } else {
-                result.fType = Type::kEmpty;
-            }
-            break;
-        case Type::kEmpty:
-            result.fType = is_inverted(false, inversion) ? Type::kInvertedEmpty :  Type::kEmpty;
-            break;
-        case Type::kInvertedEmpty:
-            result.fType = is_inverted(true, inversion) ? Type::kInvertedEmpty :  Type::kEmpty;
-            break;
-        case Type::kPath:
-            result.initType(Type::kPath, &original.fPathData.fPath);
-            result.fPathData.fGenID = original.fPathData.fGenID;
-            if (flip_inversion(original.fPathData.fPath.isInverseFillType(), inversion)) {
-                result.fPathData.fPath.toggleInverseFillType();
-            }
-            if (!original.style().isSimpleFill()) {
-                // Going from a non-filled style to fill may allow additional simplifications (e.g.
-                // closing an open rect that wasn't closed in the original shape because it had
-                // stroke style).
-                result.attemptToSimplifyPath();
-            }
-            break;
+
+    result.fShape = original.fShape;
+    result.fGenID = original.fGenID;
+    result.fShape.setInverted(newIsInverted);
+
+    if (!original.style().isSimpleFill()) {
+        // Going from a non-filled style to fill may allow additional simplifications (e.g.
+        // closing an open rect that wasn't closed in the original shape because it had
+        // stroke style).
+        result.simplify();
+        // The above simplify() call only sets simplified to true if its geometry was changed,
+        // since it already sees its style as a simple fill. Since the original style was not a
+        // simple fill, MakeFilled always simplifies.
+        result.fSimplified = true;
     }
+
+    // Sanity check that lines/points were converted to empty by the style change
+    SkASSERT((!original.fShape.isLine() && !original.fShape.isPoint()) || result.fShape.isEmpty());
+
     // We don't copy the inherited key since it can contain path effect information that we just
     // stripped.
     return result;
 }
 
-SkRect GrStyledShape::bounds() const {
-    // Bounds where left == bottom or top == right can indicate a line or point shape. We return
-    // inverted bounds for a truly empty shape.
-    static constexpr SkRect kInverted = SkRect::MakeLTRB(1, 1, -1, -1);
-    switch (fType) {
-        case Type::kEmpty:
-            return kInverted;
-        case Type::kInvertedEmpty:
-            return kInverted;
-        case Type::kLine: {
-            SkRect bounds;
-            if (fLineData.fPts[0].fX < fLineData.fPts[1].fX) {
-                bounds.fLeft = fLineData.fPts[0].fX;
-                bounds.fRight = fLineData.fPts[1].fX;
-            } else {
-                bounds.fLeft = fLineData.fPts[1].fX;
-                bounds.fRight = fLineData.fPts[0].fX;
-            }
-            if (fLineData.fPts[0].fY < fLineData.fPts[1].fY) {
-                bounds.fTop = fLineData.fPts[0].fY;
-                bounds.fBottom = fLineData.fPts[1].fY;
-            } else {
-                bounds.fTop = fLineData.fPts[1].fY;
-                bounds.fBottom = fLineData.fPts[0].fY;
-            }
-            return bounds;
-        }
-        case Type::kRRect:
-            return fRRectData.fRRect.getBounds();
-        case Type::kArc:
-            // Could make this less conservative by looking at angles.
-            return fArcData.fOval;
-        case Type::kPath:
-            return this->path().getBounds();
-    }
-    SK_ABORT("Unknown shape type");
-}
-
 SkRect GrStyledShape::styledBounds() const {
     if (this->isEmpty() && !fStyle.hasNonDashPathEffect()) {
         return SkRect::MakeEmpty();
@@ -189,9 +99,9 @@
 
     static_assert(sizeof(SkPoint) == 2 * sizeof(uint32_t));
     static_assert(sizeof(SkScalar) == sizeof(uint32_t));
-    // 2 is for the verb cnt and a fill type. Each verb is a byte but we'll pad the verb data out to
+    // 1 is for the verb count. Each verb is a byte but we'll pad the verb data out to
     // a uint32_t length.
-    return 2 + (SkAlign4(verbCnt) >> 2) + 2 * pointCnt + conicWeightCnt;
+    return 1 + (SkAlign4(verbCnt) >> 2) + 2 * pointCnt + conicWeightCnt;
 }
 
 // Writes the path data key into the passed pointer.
@@ -203,7 +113,6 @@
     const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);
     SkASSERT(verbCnt <= GrStyledShape::kMaxKeyFromDataVerbCnt);
     SkASSERT(pointCnt && verbCnt);
-    *key++ = (uint32_t)path.getFillType();
     *key++ = verbCnt;
     memcpy(key, SkPathPriv::VerbData(path), verbCnt * sizeof(uint8_t));
     int verbKeySize = SkAlign4(verbCnt);
@@ -225,37 +134,45 @@
     if (fInheritedKey.count()) {
         return fInheritedKey.count();
     }
-    switch (fType) {
-        case Type::kEmpty:
-            return 1;
-        case Type::kInvertedEmpty:
-            return 1;
-        case Type::kRRect:
-            SkASSERT(!fInheritedKey.count());
+
+    int count = 1; // Every key has the state flags from the GrShape
+    switch(fShape.type()) {
+        case GrShape::Type::kPoint:
+            static_assert(0 == sizeof(SkPoint) % sizeof(uint32_t));
+            count += sizeof(SkPoint) / sizeof(uint32_t);
+            break;
+        case GrShape::Type::kRect:
+            static_assert(0 == sizeof(SkRect) % sizeof(uint32_t));
+            count += sizeof(SkRect) / sizeof(uint32_t);
+            break;
+        case GrShape::Type::kRRect:
             static_assert(0 == SkRRect::kSizeInMemory % sizeof(uint32_t));
-            // + 1 for the direction, start index, and inverseness.
-            return SkRRect::kSizeInMemory / sizeof(uint32_t) + 1;
-        case Type::kArc:
-            SkASSERT(!fInheritedKey.count());
-            static_assert(0 == sizeof(fArcData) % sizeof(uint32_t));
-            return sizeof(fArcData) / sizeof(uint32_t);
-        case Type::kLine:
-            static_assert(2 * sizeof(uint32_t) == sizeof(SkPoint));
-            // 4 for the end points and 1 for the inverseness
-            return 5;
-        case Type::kPath: {
-            if (0 == fPathData.fGenID) {
-                return -1;
+            count += SkRRect::kSizeInMemory / sizeof(uint32_t);
+            break;
+        case GrShape::Type::kArc:
+            static_assert(0 == sizeof(GrArc) % sizeof(uint32_t));
+            count += sizeof(GrArc) / sizeof(uint32_t);
+            break;
+        case GrShape::Type::kLine:
+            static_assert(0 == sizeof(GrLineSegment) % sizeof(uint32_t));
+            count += sizeof(GrLineSegment) / sizeof(uint32_t);
+            break;
+        case GrShape::Type::kPath: {
+            if (0 == fGenID) {
+                return -1; // volatile, so won't be keyed
             }
-            int dataKeySize = path_key_from_data_size(fPathData.fPath);
+            int dataKeySize = path_key_from_data_size(fShape.path());
             if (dataKeySize >= 0) {
-                return dataKeySize;
+                count += dataKeySize;
+            } else {
+                count++; // Just adds the gen ID.
             }
-            // The key is the path ID and fill type.
-            return 2;
-        }
+            break; }
+        default:
+            // else it's empty, which just needs the state flags for its key
+            SkASSERT(fShape.isEmpty());
     }
-    SK_ABORT("Should never get here.");
+    return count;
 }
 
 void GrStyledShape::writeUnstyledKey(uint32_t* key) const {
@@ -265,43 +182,57 @@
         memcpy(key, fInheritedKey.get(), sizeof(uint32_t) * fInheritedKey.count());
         SkDEBUGCODE(key += fInheritedKey.count();)
     } else {
-        switch (fType) {
-            case Type::kEmpty:
-                *key++ = 1;
-                break;
-            case Type::kInvertedEmpty:
-                *key++ = 2;
-                break;
-            case Type::kRRect:
-                fRRectData.fRRect.writeToMemory(key);
-                key += SkRRect::kSizeInMemory / sizeof(uint32_t);
-                *key = (fRRectData.fDir == SkPathDirection::kCCW) ? (1 << 31) : 0;
-                *key |= fRRectData.fInverted ? (1 << 30) : 0;
-                *key++ |= fRRectData.fStart;
-                SkASSERT(fRRectData.fStart < 8);
-                break;
-            case Type::kArc:
-                memcpy(key, &fArcData, sizeof(fArcData));
-                key += sizeof(fArcData) / sizeof(uint32_t);
-                break;
-            case Type::kLine:
-                memcpy(key, fLineData.fPts, 2 * sizeof(SkPoint));
-                key += 4;
-                *key++ = fLineData.fInverted ? 1 : 0;
-                break;
-            case Type::kPath: {
-                SkASSERT(fPathData.fGenID);
-                int dataKeySize = path_key_from_data_size(fPathData.fPath);
+        // Dir and start are only used for rect and rrect shapes, so are not included in other
+        // shape type keys. Make sure that they are the defaults for other shapes so it doesn't
+        // matter that we universally include them in the flag key value.
+        SkASSERT((fShape.isRect() || fShape.isRRect()) ||
+                 (fShape.dir() == GrShape::kDefaultDir &&
+                  fShape.startIndex() == GrShape::kDefaultStart));
+
+        // Every key starts with the state from the GrShape (this includes path fill type,
+        // and any tracked winding, start, inversion, as well as the class of geometry).
+        *key++ = fShape.stateKey();
+
+        switch(fShape.type()) {
+            case GrShape::Type::kPath: {
+                SkASSERT(fGenID != 0);
+                // Ensure that the path's inversion matches our state so that the path's key suffices.
+                SkASSERT(fShape.inverted() == fShape.path().isInverseFillType());
+
+                int dataKeySize = path_key_from_data_size(fShape.path());
                 if (dataKeySize >= 0) {
-                    write_path_key_from_data(fPathData.fPath, key);
+                    write_path_key_from_data(fShape.path(), key);
                     return;
+                } else {
+                    *key++ = fGenID;
                 }
-                *key++ = fPathData.fGenID;
-                // We could canonicalize the fill rule for paths that don't differentiate between
-                // even/odd or winding fill (e.g. convex).
-                *key++ = (uint32_t)this->path().getFillType();
+                break; }
+            case GrShape::Type::kPoint:
+                memcpy(key, &fShape.point(), sizeof(SkPoint));
+                key += sizeof(SkPoint) / sizeof(uint32_t);
                 break;
-            }
+            case GrShape::Type::kRect:
+                memcpy(key, &fShape.rect(), sizeof(SkRect));
+                key += sizeof(SkRect) / sizeof(uint32_t);
+                break;
+            case GrShape::Type::kRRect:
+                fShape.rrect().writeToMemory(key);
+                key += SkRRect::kSizeInMemory / sizeof(uint32_t);
+                break;
+            case GrShape::Type::kArc:
+                // Write dense floats first
+                memcpy(key, &fShape.arc(), sizeof(SkRect) + 2 * sizeof(float));
+                key += (sizeof(GrArc) / sizeof(uint32_t) - 1);
+                // Then write the final bool as an int, to make sure upper bits are set
+                *key++ = fShape.arc().fUseCenter ? 1 : 0;
+                break;
+            case GrShape::Type::kLine:
+                memcpy(key, &fShape.line(), sizeof(GrLineSegment));
+                key += sizeof(GrLineSegment) / sizeof(uint32_t);
+                break;
+            default:
+                // Nothing other than the flag state is needed in the key for an empty shape
+                SkASSERT(fShape.isEmpty());
         }
     }
     SkASSERT(key - origKey == this->unstyledKeySize());
@@ -311,7 +242,7 @@
                                     SkScalar scale) {
     SkASSERT(!fInheritedKey.count());
     // If the output shape turns out to be simple, then we will just use its geometric key
-    if (Type::kPath == fType) {
+    if (fShape.isPath()) {
         // We want ApplyFullStyle(ApplyPathEffect(shape)) to have the same key as
         // ApplyFullStyle(shape).
         // The full key is structured as (geo,path_effect,stroke).
@@ -324,7 +255,7 @@
             parentCnt = parent.unstyledKeySize();
             if (parentCnt < 0) {
                 // The parent's geometry has no key so we will have no key.
-                fPathData.fGenID = 0;
+                fGenID = 0;
                 return;
             }
         }
@@ -339,7 +270,7 @@
         if (styleCnt < 0) {
             // The style doesn't allow a key, set the path gen ID to 0 so that we fail when
             // we try to get a key for the shape.
-            fPathData.fGenID = 0;
+            fGenID = 0;
             return;
         }
         fInheritedKey.reset(parentCnt + styleCnt);
@@ -360,8 +291,8 @@
 const SkPath* GrStyledShape::originalPathForListeners() const {
     if (fInheritedPathForListeners.isValid()) {
         return fInheritedPathForListeners.get();
-    } else if (Type::kPath == fType && !fPathData.fPath.isVolatile()) {
-        return &fPathData.fPath;
+    } else if (fShape.isPath() && !fShape.path().isVolatile()) {
+        return &fShape.path();
     }
     return nullptr;
 }
@@ -376,38 +307,17 @@
                                      SkScalar sweepAngleDegrees, bool useCenter,
                                      const GrStyle& style) {
     GrStyledShape result;
-    result.changeType(Type::kArc);
-    result.fArcData.fOval = oval;
-    result.fArcData.fStartAngleDegrees = startAngleDegrees;
-    result.fArcData.fSweepAngleDegrees = sweepAngleDegrees;
-    result.fArcData.fUseCenter = useCenter;
-    result.fArcData.fInverted = false;
+    result.fShape.setArc({oval.makeSorted(), startAngleDegrees, sweepAngleDegrees, useCenter});
     result.fStyle = style;
-    result.attemptToSimplifyArc();
+    result.simplify();
     return result;
 }
 
-GrStyledShape::GrStyledShape(const GrStyledShape& that) : fStyle(that.fStyle) {
-    const SkPath* thatPath = Type::kPath == that.fType ? &that.fPathData.fPath : nullptr;
-    this->initType(that.fType, thatPath);
-    switch (fType) {
-        case Type::kEmpty:
-            break;
-        case Type::kInvertedEmpty:
-            break;
-        case Type::kRRect:
-            fRRectData = that.fRRectData;
-            break;
-        case Type::kArc:
-            fArcData = that.fArcData;
-            break;
-        case Type::kLine:
-            fLineData = that.fLineData;
-            break;
-        case Type::kPath:
-            fPathData.fGenID = that.fPathData.fGenID;
-            break;
-    }
+GrStyledShape::GrStyledShape(const GrStyledShape& that)
+        : fShape(that.fShape)
+        , fStyle(that.fStyle)
+        , fGenID(that.fGenID)
+        , fSimplified(that.fSimplified) {
     fInheritedKey.reset(that.fInheritedKey.count());
     sk_careful_memcpy(fInheritedKey.get(), that.fInheritedKey.get(),
                       sizeof(uint32_t) * fInheritedKey.count());
@@ -423,7 +333,6 @@
     // stroke of a rect).
     if (!parent.style().applies() ||
         (GrStyle::Apply::kPathEffectOnly == apply && !parent.style().pathEffect())) {
-        this->initType(Type::kEmpty);
         *this = parent;
         return;
     }
@@ -432,12 +341,14 @@
     SkTLazy<SkPath> tmpPath;
     const GrStyledShape* parentForKey = &parent;
     SkTLazy<GrStyledShape> tmpParent;
-    this->initType(Type::kPath);
-    fPathData.fGenID = 0;
+
+    // Start out as an empty path that is filled in by the applied style
+    fShape.setPath(SkPath());
+
     if (pe) {
         const SkPath* srcForPathEffect;
-        if (parent.fType == Type::kPath) {
-            srcForPathEffect = &parent.path();
+        if (parent.fShape.isPath()) {
+            srcForPathEffect = &parent.fShape.path();
         } else {
             srcForPathEffect = tmpPath.init();
             parent.asPath(tmpPath.get());
@@ -445,7 +356,7 @@
         // Should we consider bounds? Would have to include in key, but it'd be nice to know
         // if the bounds actually modified anything before including in key.
         SkStrokeRec strokeRec = parent.fStyle.strokeRec();
-        if (!parent.fStyle.applyPathEffectToPath(&this->path(), &strokeRec, *srcForPathEffect,
+        if (!parent.fStyle.applyPathEffectToPath(&fShape.path(), &strokeRec, *srcForPathEffect,
                                                  scale)) {
             tmpParent.init(*srcForPathEffect, GrStyle(strokeRec, nullptr));
             *this = tmpParent.get()->applyStyle(apply, scale);
@@ -462,7 +373,7 @@
             // We detect that case here and change parentForKey to a temporary that represents
             // the simpler shape so that applying both path effect and the strokerec all at
             // once produces the same key.
-            tmpParent.init(this->path(), GrStyle(strokeRec, nullptr));
+            tmpParent.init(fShape.path(), GrStyle(strokeRec, nullptr));
             tmpParent.get()->setInheritedKey(parent, GrStyle::Apply::kPathEffectOnly, scale);
             if (!tmpPath.isValid()) {
                 tmpPath.init();
@@ -471,7 +382,7 @@
             SkStrokeRec::InitStyle fillOrHairline;
             // The parent shape may have simplified away the strokeRec, check for that here.
             if (tmpParent.get()->style().applies()) {
-                SkAssertResult(tmpParent.get()->style().applyToPath(&this->path(), &fillOrHairline,
+                SkAssertResult(tmpParent.get()->style().applyToPath(&fShape.path(), &fillOrHairline,
                                                                     *tmpPath.get(), scale));
             } else if (tmpParent.get()->style().isSimpleFill()) {
                 fillOrHairline = SkStrokeRec::kFill_InitStyle;
@@ -486,8 +397,8 @@
         }
     } else {
         const SkPath* srcForParentStyle;
-        if (parent.fType == Type::kPath) {
-            srcForParentStyle = &parent.path();
+        if (parent.fShape.isPath()) {
+            srcForParentStyle = &parent.fShape.path();
         } else {
             srcForParentStyle = tmpPath.init();
             parent.asPath(tmpPath.get());
@@ -495,275 +406,374 @@
         SkStrokeRec::InitStyle fillOrHairline;
         SkASSERT(parent.fStyle.applies());
         SkASSERT(!parent.fStyle.pathEffect());
-        SkAssertResult(parent.fStyle.applyToPath(&this->path(), &fillOrHairline, *srcForParentStyle,
-                                                 scale));
+        SkAssertResult(parent.fStyle.applyToPath(&fShape.path(), &fillOrHairline,
+                                                 *srcForParentStyle, scale));
         fStyle.resetToInitStyle(fillOrHairline);
     }
+
     if (parent.fInheritedPathForListeners.isValid()) {
         fInheritedPathForListeners.set(*parent.fInheritedPathForListeners.get());
-    } else if (Type::kPath == parent.fType && !parent.fPathData.fPath.isVolatile()) {
-        fInheritedPathForListeners.set(parent.fPathData.fPath);
+    } else if (parent.fShape.isPath() && !parent.fShape.path().isVolatile()) {
+        fInheritedPathForListeners.set(parent.fShape.path());
     }
-    this->attemptToSimplifyPath();
+    this->simplify();
     this->setInheritedKey(*parentForKey, apply, scale);
 }
 
-void GrStyledShape::attemptToSimplifyPath() {
-    SkRect rect;
-    SkRRect rrect;
-    SkPathDirection rrectDir;
-    unsigned rrectStart;
-    bool inverted = this->path().isInverseFillType();
-    SkPoint pts[2];
-    if (this->path().isEmpty()) {
-        // Dashing ignores inverseness skbug.com/5421.
-        this->changeType(inverted && !this->style().isDashed() ? Type::kInvertedEmpty
-                                                               : Type::kEmpty);
-    } else if (this->path().isLine(pts)) {
-        this->changeType(Type::kLine);
-        fLineData.fPts[0] = pts[0];
-        fLineData.fPts[1] = pts[1];
-        fLineData.fInverted = inverted;
-    } else if (SkPathPriv::IsRRect(this->path(), &rrect, &rrectDir, &rrectStart)) {
-        this->changeType(Type::kRRect);
-        fRRectData.fRRect = rrect;
-        fRRectData.fDir = rrectDir;
-        fRRectData.fStart = rrectStart;
-        fRRectData.fInverted = inverted;
-        SkASSERT(!fRRectData.fRRect.isEmpty());
-    } else if (SkPathPriv::IsOval(this->path(), &rect, &rrectDir, &rrectStart)) {
-        this->changeType(Type::kRRect);
-        fRRectData.fRRect.setOval(rect);
-        fRRectData.fDir = rrectDir;
-        fRRectData.fInverted = inverted;
-        // convert from oval indexing to rrect indexiing.
-        fRRectData.fStart = 2 * rrectStart;
-    } else if (SkPathPriv::IsSimpleClosedRect(this->path(), &rect, &rrectDir, &rrectStart)) {
-        this->changeType(Type::kRRect);
-        // When there is a path effect we restrict rect detection to the narrower API that
-        // gives us the starting position. Otherwise, we will retry with the more aggressive
-        // isRect().
-        fRRectData.fRRect.setRect(rect);
-        fRRectData.fInverted = inverted;
-        fRRectData.fDir = rrectDir;
-        // convert from rect indexing to rrect indexiing.
-        fRRectData.fStart = 2 * rrectStart;
-    } else if (!this->style().hasPathEffect()) {
-        bool closed;
-        if (this->path().isRect(&rect, &closed, nullptr)) {
-            if (closed || this->style().isSimpleFill()) {
-                this->changeType(Type::kRRect);
-                fRRectData.fRRect.setRect(rect);
-                // Since there is no path effect the dir and start index is immaterial.
-                fRRectData.fDir = kDefaultRRectDir;
-                fRRectData.fStart = kDefaultRRectStart;
-                // There isn't dashing so we will have to preserver inverseness.
-                fRRectData.fInverted = inverted;
+bool GrStyledShape::asRRect(SkRRect* rrect, SkPathDirection* dir, unsigned* start,
+                            bool* inverted) const {
+    if (!fShape.isRRect() && !fShape.isRect()) {
+        return false;
+    }
+
+    // Sanity check here, if we don't have a path effect on the style, we should have passed
+    // appropriate flags to GrShape::simplify() to have reset these parameters.
+    SkASSERT(fStyle.hasPathEffect() || (fShape.dir() == GrShape::kDefaultDir &&
+                                        fShape.startIndex() == GrShape::kDefaultStart));
+
+    // If the shape is a regular rect, map to round rect winding parameters, including accounting
+    // for the automatic sorting of edges that SkRRect::MakeRect() performs.
+    if (fShape.isRect()) {
+        if (rrect) {
+            *rrect = SkRRect::MakeRect(fShape.rect());
+        }
+        // Don't bother mapping these if we don't have a path effect, however.
+        if (!fStyle.hasPathEffect()) {
+            if (dir) {
+                *dir = GrShape::kDefaultDir;
+            }
+            if (start) {
+                *start = GrShape::kDefaultStart;
+            }
+        } else {
+            // In SkPath a rect starts at index 0 by default. This is the top left corner. However,
+            // we store rects as rrects. RRects don't preserve the invertedness, but rather sort the
+            // rect edges. Thus, we may need to modify the rrect's start index and direction.
+            SkPathDirection rectDir = fShape.dir();
+            unsigned rectStart = fShape.startIndex();
+
+            if (fShape.rect().fLeft > fShape.rect().fRight) {
+                // Toggle direction, and modify index by mapping through the array
+                static const unsigned kMapping[] = {1, 0, 3, 2};
+                rectDir = rectDir == SkPathDirection::kCCW ? SkPathDirection::kCW
+                                                           : SkPathDirection::kCCW;
+                rectStart = kMapping[rectStart];
+            }
+            if (fShape.rect().fTop > fShape.rect().fBottom) {
+                // Toggle direction and map index by 3 - start
+                // NOTE: if we earlier flipped for X as well, this results in no net direction
+                // change and effectively flipping the start index to the diagonal corners of the
+                // rect (matching what we'd expect for a rect with both X and Y flipped).
+                rectDir = rectDir == SkPathDirection::kCCW ? SkPathDirection::kCW
+                                                           : SkPathDirection::kCCW;
+                rectStart = 3 - rectStart;
+            }
+
+            if (dir) {
+                *dir = rectDir;
+            }
+            if (start) {
+                // Convert to round rect indexing
+                *start = 2 * rectStart;
+            }
+        }
+    } else {
+        // Straight forward export
+        if (rrect) {
+            *rrect = fShape.rrect();
+        }
+        if (dir) {
+            *dir = fShape.dir();
+        }
+        if (start) {
+            *start = fShape.startIndex();
+            // Canonicalize the index if the rrect is an oval, which GrShape doesn't treat special
+            // but we do for dashing placement
+            if (fShape.rrect().isOval()) {
+                *start &= 0b110;
             }
         }
     }
-    if (Type::kPath != fType) {
+
+    if (inverted) {
+        *inverted = fShape.inverted();
+    }
+
+    return true;
+}
+
+bool GrStyledShape::asLine(SkPoint pts[2], bool* inverted) const {
+    if (!fShape.isLine()) {
+        return false;
+    }
+
+    if (pts) {
+        pts[0] = fShape.line().fP1;
+        pts[1] = fShape.line().fP2;
+    }
+    if (inverted) {
+        *inverted = fShape.inverted();
+    }
+    return true;
+}
+
+bool GrStyledShape::asNestedRects(SkRect rects[2]) const {
+    if (!fShape.isPath()) {
+        return false;
+    }
+
+    // TODO: it would be better two store DRRects natively in the shape rather than converting
+    // them to a path and then reextracting the nested rects
+    if (fShape.path().isInverseFillType()) {
+        return false;
+    }
+
+    SkPathDirection dirs[2];
+    if (!SkPathPriv::IsNestedFillRects(fShape.path(), rects, dirs)) {
+        return false;
+    }
+
+    if (SkPathFillType::kWinding == fShape.path().getFillType() && dirs[0] == dirs[1]) {
+        // The two rects need to be wound opposite to each other
+        return false;
+    }
+
+    // Right now, nested rects where the margin is not the same width
+    // all around do not render correctly
+    const SkScalar* outer = rects[0].asScalars();
+    const SkScalar* inner = rects[1].asScalars();
+
+    bool allEq = true;
+
+    SkScalar margin = SkScalarAbs(outer[0] - inner[0]);
+    bool allGoE1 = margin >= SK_Scalar1;
+
+    for (int i = 1; i < 4; ++i) {
+        SkScalar temp = SkScalarAbs(outer[i] - inner[i]);
+        if (temp < SK_Scalar1) {
+            allGoE1 = false;
+        }
+        if (!SkScalarNearlyEqual(margin, temp)) {
+            allEq = false;
+        }
+    }
+
+    return allEq || allGoE1;
+}
+
+void GrStyledShape::simplify() {
+    // Dashing ignores inverseness skbug.com/5421.
+    bool inverted = !fStyle.isDashed() && fShape.inverted();
+
+    unsigned simplifyFlags = 0;
+    if (fStyle.isSimpleFill()) {
+        simplifyFlags = GrShape::kAll_Flags;
+    } else if (!fStyle.hasPathEffect()) {
+        // Everything but arcs with caps that might extend beyond the oval edge can ignore winding
+        if (!fShape.isArc() || fStyle.strokeRec().getCap() == SkPaint::kButt_Cap) {
+            simplifyFlags |= GrShape::kIgnoreWinding_Flag;
+        }
+        simplifyFlags |= GrShape::kMakeCanonical_Flag;
+    } // else if there's a path effect, every destructive simplification is disabledd
+
+    // Remember if the original shape was closed; in the event we simplify to a point or line
+    // because of degenerate geometry, we need to update joins and caps.
+    GrShape::Type oldType = fShape.type();
+    bool wasClosed = fShape.simplify(simplifyFlags);
+    fSimplified = oldType != fShape.type();
+
+    if (fShape.isPath()) {
+        // The shape remains a path, so configure the gen ID and canonicalize fill type if possible
+        if (fInheritedKey.count() || fShape.path().isVolatile()) {
+            fGenID = 0;
+        } else {
+            fGenID = fShape.path().getGenerationID();
+        }
+        if (!fStyle.hasNonDashPathEffect() &&
+            (fStyle.strokeRec().getStyle() == SkStrokeRec::kStroke_Style ||
+             fStyle.strokeRec().getStyle() == SkStrokeRec::kHairline_Style ||
+             fShape.path().isConvex())) {
+            // Stroke styles don't differentiate between winding and even/odd. There is no
+            // distinction between even/odd and non-zero winding count for convex paths.
+            // Moreover, dashing ignores inverseness (skbug.com/5421)
+            fShape.path().setFillType(GrShape::kDefaultFillType);
+        }
+    } else {
         fInheritedKey.reset(0);
         // Whenever we simplify to a non-path, break the chain so we no longer refer to the
         // original path. This prevents attaching genID listeners to temporary paths created when
         // drawing simple shapes.
         fInheritedPathForListeners.reset();
-        if (Type::kRRect == fType) {
-            this->attemptToSimplifyRRect();
-        } else if (Type::kLine == fType) {
-            this->attemptToSimplifyLine();
-        }
-    } 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);
-                }
-            }
-        }
+
+        // Further simplifications to the shape based on the style
+        fSimplified |= this->simplifyStroke(wasClosed);
     }
+
+    // Restore invertedness after any modifications were made to the shape type
+    fShape.setInverted(inverted);
+    SkASSERT(!fShape.isPath() || inverted == fShape.path().isInverseFillType());
 }
 
-void GrStyledShape::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;
+bool GrStyledShape::simplifyStroke(bool originallyClosed) {
+    // For stroke+filled rects, a mitered shape becomes a larger rect and a rounded shape
+    // becomes a round rect.
+    if (!fStyle.hasPathEffect() && fShape.isRect() &&
+        fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
+        if (fStyle.strokeRec().getJoin() == SkPaint::kBevel_Join ||
+            (fStyle.strokeRec().getJoin() == SkPaint::kMiter_Join &&
+             fStyle.strokeRec().getMiter() < SK_ScalarSqrt2)) {
+            // Bevel-stroked rect needs path rendering
+            return false;
         }
-        // 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 GrStyledShape::attemptToSimplifyLine() {
-    SkASSERT(Type::kLine == fType);
-    SkASSERT(!fInheritedKey.count());
-    if (fStyle.isDashed()) {
-        bool allOffsZero = true;
-        for (int i = 1; i < fStyle.dashIntervalCnt() && allOffsZero; i += 2) {
-            allOffsZero = !fStyle.dashIntervals()[i];
+        fShape.rect().outset(r, r);
+        if (fStyle.strokeRec().getJoin() == SkPaint::kRound_Join) {
+            // There's no dashing to worry about if we got here, so it's okay that this resets
+            // winding parameters
+            fShape.setRRect(SkRRect::MakeRectXY(fShape.rect(), r, r));
         }
-        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 GrStyledShape::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 GrStyledShape::attemptToSimplifyStrokedLineToRRect() {
-    SkASSERT(Type::kLine == fType);
-    SkASSERT(fStyle.strokeRec().getStyle() == SkStrokeRec::kStroke_Style);
-
-    SkRect rect;
-    SkVector outset;
-    // If we allowed a rotation angle for rrects we could capture all cases here.
-    if (fLineData.fPts[0].fY == fLineData.fPts[1].fY) {
-        rect.fLeft = std::min(fLineData.fPts[0].fX, fLineData.fPts[1].fX);
-        rect.fRight = std::max(fLineData.fPts[0].fX, fLineData.fPts[1].fX);
-        rect.fTop = rect.fBottom = fLineData.fPts[0].fY;
-        outset.fY = fStyle.strokeRec().getWidth() / 2.f;
-        outset.fX = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fY;
-    } else if (fLineData.fPts[0].fX == fLineData.fPts[1].fX) {
-        rect.fTop = std::min(fLineData.fPts[0].fY, fLineData.fPts[1].fY);
-        rect.fBottom = std::max(fLineData.fPts[0].fY, fLineData.fPts[1].fY);
-        rect.fLeft = rect.fRight = fLineData.fPts[0].fX;
-        outset.fX = fStyle.strokeRec().getWidth() / 2.f;
-        outset.fY = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fX;
-    } else {
-        return false;
-    }
-    rect.outset(outset.fX, outset.fY);
-    if (rect.isEmpty()) {
-        this->changeType(Type::kEmpty);
         fStyle = GrStyle::SimpleFill();
         return true;
     }
-    SkRRect rrect;
-    if (fStyle.strokeRec().getCap() == SkPaint::kRound_Cap) {
-        SkASSERT(outset.fX == outset.fY);
-        rrect = SkRRect::MakeRectXY(rect, outset.fX, outset.fY);
-    } else {
-        rrect = SkRRect::MakeRect(rect);
+
+    // Otherwise, if we're a point or a line, we might be able to explicitly apply some of the
+    // stroking (and even some of the dashing). Any other shape+style is too complicated to reduce.
+    if ((!fShape.isPoint() && !fShape.isLine()) || fStyle.hasNonDashPathEffect() ||
+        fStyle.strokeRec().isHairlineStyle()) {
+        return false;
     }
-    bool inverted = fLineData.fInverted && !fStyle.hasPathEffect();
-    this->changeType(Type::kRRect);
-    fRRectData.fRRect = rrect;
-    fRRectData.fInverted = inverted;
-    fRRectData.fDir = kDefaultRRectDir;
-    fRRectData.fStart = kDefaultRRectStart;
+
+    // Tracks style simplifications, even if the geometry can't be further simplified.
+    bool styleSimplified = false;
+    if (fStyle.isDashed()) {
+        // For dashing a point, if the first interval is on, we can drop the dash and just draw
+        // the caps. For dashing a line, if every off interval is 0 length, its a stroke.
+        bool dropDash = false;
+        if (fShape.isPoint()) {
+            dropDash = fStyle.dashIntervalCnt() > 0 &&
+                       SkToBool(fStyle.dashIntervals()[0]);
+        } else {
+            dropDash = true;
+            for (int i = 1; i < fStyle.dashIntervalCnt(); i += 2) {
+                if (SkToBool(fStyle.dashIntervals()[i])) {
+                    // An off interval has non-zero length so this won't convert to a simple line
+                    dropDash = false;
+                    break;
+                }
+            }
+        }
+
+        if (!dropDash) {
+            return false;
+        }
+        // Fall through to modifying the shape to respect the new stroke geometry
+        fStyle = GrStyle(fStyle.strokeRec(), nullptr);
+        // Since the reduced the line or point after dashing is dependent on the caps of the dashes,
+        // we reset to be unclosed so we don't override the style based on joins later.
+        originallyClosed = false;
+        styleSimplified = true;
+    }
+
+    // At this point, we're a line or point with no path effects. Any fill portion of the style
+    // is empty, so a fill-only style can be empty, and a stroke+fill becomes a stroke.
+    bool strokeAndFilled = false;
+    if (fStyle.isSimpleFill()) {
+        fShape.reset();
+        return true;
+    } else if (fStyle.strokeRec().getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
+        // Stroke only
+        SkStrokeRec rec = fStyle.strokeRec();
+        rec.setStrokeStyle(fStyle.strokeRec().getWidth(), false);
+        fStyle = GrStyle(rec, nullptr);
+        styleSimplified = true;
+        strokeAndFilled = true;
+    }
+
+    // A point or line that was formed by a degenerate closed shape needs its style updated to
+    // reflect the fact that it doesn't actually produce caps.
+    if (originallyClosed) {
+        SkPaint::Cap cap;
+        if (fShape.isLine() && fStyle.strokeRec().getJoin() == SkPaint::kRound_Join) {
+            // As a closed shape, the line moves from a to b and back to a, producing a 180 degree
+            // turn. With round joins, this would make a semi-circle at each end, which is visually
+            // identical to a round cap on the reduced line geometry.
+            cap = SkPaint::kRound_Cap;
+        } else if (fShape.isPoint() && fStyle.strokeRec().getJoin() == SkPaint::kMiter_Join &&
+                   !strokeAndFilled) {
+            // Use a square cap for miter join + stroked points, which matches raster's behavior and
+            // expectations from Chrome masking tests, although it could be argued to just always
+            // use a butt cap. This behavior, though, ensures that the default stroked paint draws
+            // something with empty geometry.
+            cap = SkPaint::kSquare_Cap;
+        } else {
+            // If this were a closed line, the 180 degree turn either is a miter join that exceeds
+            // the miter limit and becomes a bevel, or a bevel join. In either case, the bevel shape
+            // of a 180 degreen corner is equivalent to a butt cap.
+            //  - to match the SVG spec, the 0-length sides of an empty rectangle are skipped, so
+            //    it fits this closed line description (it is not two 90 degree turns that could
+            //    produce miter geometry).
+            cap = SkPaint::kButt_Cap;
+        }
+
+        if (cap != fStyle.strokeRec().getCap() ||
+            SkPaint::kDefault_Join != fStyle.strokeRec().getJoin()) {
+            SkStrokeRec rec = fStyle.strokeRec();
+            rec.setStrokeParams(cap, SkPaint::kDefault_Join, fStyle.strokeRec().getMiter());
+            fStyle = GrStyle(rec, nullptr);
+            styleSimplified = true;
+        }
+    }
+
+    if (fShape.isPoint()) {
+        // The drawn geometry is entirely based on the cap style and stroke width. A butt cap point
+        // doesn't draw anything, a round cap is an oval and a square cap is a square.
+        if (fStyle.strokeRec().getCap() == SkPaint::kButt_Cap) {
+            fShape.reset();
+        } else {
+            SkScalar w = fStyle.strokeRec().getWidth() / 2.f;
+            SkRect r = {fShape.point().fX, fShape.point().fY, fShape.point().fX, fShape.point().fY};
+            r.outset(w, w);
+
+            if (fStyle.strokeRec().getCap() == SkPaint::kRound_Cap) {
+                fShape.setRRect(SkRRect::MakeOval(r));
+            } else {
+                fShape.setRect(r);
+            }
+        }
+    } else {
+        // Stroked lines reduce to rectangles or round rects when they are axis-aligned. If we
+        // allowed rotation angle, this would work for any lines.
+        SkRect rect;
+        SkVector outset;
+        if (fShape.line().fP1.fY == fShape.line().fP2.fY) {
+            rect.fLeft = std::min(fShape.line().fP1.fX, fShape.line().fP2.fX);
+            rect.fRight = std::max(fShape.line().fP1.fX, fShape.line().fP2.fX);
+            rect.fTop = rect.fBottom = fShape.line().fP1.fY;
+            outset.fY = fStyle.strokeRec().getWidth() / 2.f;
+            outset.fX = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fY;
+        } else if (fShape.line().fP1.fX == fShape.line().fP2.fX) {
+            rect.fTop = std::min(fShape.line().fP1.fY, fShape.line().fP2.fY);
+            rect.fBottom = std::max(fShape.line().fP1.fY, fShape.line().fP2.fY);
+            rect.fLeft = rect.fRight = fShape.line().fP1.fX;
+            outset.fX = fStyle.strokeRec().getWidth() / 2.f;
+            outset.fY = SkPaint::kButt_Cap == fStyle.strokeRec().getCap() ? 0.f : outset.fX;
+        } else {
+            // Geometrically can't apply the style and turn into a fill, but might still be simpler
+            // than before based solely on changes to fStyle.
+            return styleSimplified;
+        }
+        rect.outset(outset.fX, outset.fY);
+        if (rect.isEmpty()) {
+            fShape.reset();
+        } else if (fStyle.strokeRec().getCap() == SkPaint::kRound_Cap) {
+            SkASSERT(outset.fX == outset.fY);
+            fShape.setRRect(SkRRect::MakeRectXY(rect, outset.fX, outset.fY));
+        } else {
+            fShape.setRect(rect);
+        }
+    }
+    // If we made it here, the stroke was fully applied to the new shape so we can become a fill.
     fStyle = GrStyle::SimpleFill();
     return true;
 }
diff --git a/src/gpu/geometry/GrStyledShape.h b/src/gpu/geometry/GrStyledShape.h
index 0fc7d1c..39adad2 100644
--- a/src/gpu/geometry/GrStyledShape.h
+++ b/src/gpu/geometry/GrStyledShape.h
@@ -14,6 +14,7 @@
 #include "src/core/SkPathPriv.h"
 #include "src/core/SkTLazy.h"
 #include "src/gpu/GrStyle.h"
+#include "src/gpu/geometry/GrShape.h"
 #include <new>
 
 class SkIDChangeListener;
@@ -41,7 +42,7 @@
     // to have to worry about this. This value is exposed for unit tests.
     static constexpr int kMaxKeyFromDataVerbCnt = 10;
 
-    GrStyledShape() { this->initType(Type::kEmpty); }
+    GrStyledShape() {}
 
     explicit GrStyledShape(const SkPath& path) : GrStyledShape(path, GrStyle::SimpleFill()) {}
 
@@ -49,79 +50,40 @@
 
     explicit GrStyledShape(const SkRect& rect) : GrStyledShape(rect, GrStyle::SimpleFill()) {}
 
-    GrStyledShape(const SkPath& path, const GrStyle& style) : fStyle(style) {
-        this->initType(Type::kPath, &path);
-        this->attemptToSimplifyPath();
+    GrStyledShape(const SkPath& path, const SkPaint& paint) : GrStyledShape(path, GrStyle(paint)) {}
+
+    GrStyledShape(const SkRRect& rrect, const SkPaint& paint)
+            : GrStyledShape(rrect, GrStyle(paint)) {}
+
+    GrStyledShape(const SkRect& rect, const SkPaint& paint) : GrStyledShape(rect, GrStyle(paint)) {}
+
+    GrStyledShape(const SkPath& path, const GrStyle& style) : fShape(path), fStyle(style) {
+        this->simplify();
     }
 
-    GrStyledShape(const SkRRect& rrect, const GrStyle& style) : fStyle(style) {
-        this->initType(Type::kRRect);
-        fRRectData.fRRect = rrect;
-        fRRectData.fInverted = false;
-        fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, style.hasPathEffect(),
-                                                         &fRRectData.fDir);
-        this->attemptToSimplifyRRect();
+    GrStyledShape(const SkRRect& rrect, const GrStyle& style) : fShape(rrect), fStyle(style) {
+        this->simplify();
     }
 
     GrStyledShape(const SkRRect& rrect, SkPathDirection dir, unsigned start, bool inverted,
-            const GrStyle& style)
-        : fStyle(style) {
-        this->initType(Type::kRRect);
-        fRRectData.fRRect = rrect;
-        fRRectData.fInverted = inverted;
-        if (style.pathEffect()) {
-            fRRectData.fDir = dir;
-            fRRectData.fStart = start;
-            if (fRRectData.fRRect.getType() == SkRRect::kRect_Type) {
-                fRRectData.fStart = (fRRectData.fStart + 1) & 0b110;
-            } else if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
-                fRRectData.fStart &= 0b110;
-            }
-        } else {
-            fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, false, &fRRectData.fDir);
-        }
-        this->attemptToSimplifyRRect();
+                  const GrStyle& style)
+            : fShape(rrect)
+            , fStyle(style) {
+        fShape.setPathWindingParams(dir, start);
+        fShape.setInverted(inverted);
+        this->simplify();
     }
 
-    GrStyledShape(const SkRect& rect, const GrStyle& style) : fStyle(style) {
-        this->initType(Type::kRRect);
-        fRRectData.fRRect = SkRRect::MakeRect(rect);
-        fRRectData.fInverted = false;
-        fRRectData.fStart = DefaultRectDirAndStartIndex(rect, style.hasPathEffect(),
-                                                        &fRRectData.fDir);
-        this->attemptToSimplifyRRect();
+    GrStyledShape(const SkRect& rect, const GrStyle& style) : fShape(rect), fStyle(style) {
+        this->simplify();
     }
 
-    GrStyledShape(const SkPath& path, const SkPaint& paint) : fStyle(paint) {
-        this->initType(Type::kPath, &path);
-        this->attemptToSimplifyPath();
-    }
-
-    GrStyledShape(const SkRRect& rrect, const SkPaint& paint) : fStyle(paint) {
-        this->initType(Type::kRRect);
-        fRRectData.fRRect = rrect;
-        fRRectData.fInverted = false;
-        fRRectData.fStart = DefaultRRectDirAndStartIndex(rrect, fStyle.hasPathEffect(),
-                                                         &fRRectData.fDir);
-        this->attemptToSimplifyRRect();
-    }
-
-    GrStyledShape(const SkRect& rect, const SkPaint& paint) : fStyle(paint) {
-        this->initType(Type::kRRect);
-        fRRectData.fRRect = SkRRect::MakeRect(rect);
-        fRRectData.fInverted = false;
-        fRRectData.fStart = DefaultRectDirAndStartIndex(rect, fStyle.hasPathEffect(),
-                                                        &fRRectData.fDir);
-        this->attemptToSimplifyRRect();
-    }
-
-    static GrStyledShape MakeArc(const SkRect& oval, SkScalar startAngleDegrees,
-                           SkScalar sweepAngleDegrees, bool useCenter, const GrStyle& style);
-
     GrStyledShape(const GrStyledShape&);
-    GrStyledShape& operator=(const GrStyledShape& that);
 
-    ~GrStyledShape() { this->changeType(Type::kEmpty); }
+    static GrStyledShape MakeArc(const SkRect& oval, SkScalar startAngleDegrees,
+                                 SkScalar sweepAngleDegrees, bool useCenter, const GrStyle& style);
+
+    GrStyledShape& operator=(const GrStyledShape& that);
 
     /**
      * Informs MakeFilled on how to modify that shape's fill rule when making a simple filled
@@ -145,6 +107,9 @@
 
     const GrStyle& style() const { return fStyle; }
 
+    // True if the shape and/or style were modified into a simpler, equivalent pairing
+    bool simplified() const { return fSimplified; }
+
     /**
      * 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
@@ -155,153 +120,39 @@
     }
 
     bool isRect() const {
-        if (Type::kRRect != fType) {
-            return false;
-        }
-
-        return fRRectData.fRRect.isRect();
+        // Should have simplified a rrect to a rect if possible already.
+        SkASSERT(!fShape.isRRect() || !fShape.rrect().isRect());
+        return fShape.isRect();
     }
 
     /** Returns the unstyled geometry as a rrect if possible. */
-    bool asRRect(SkRRect* rrect, SkPathDirection* dir, unsigned* start, bool* inverted) const {
-        if (Type::kRRect != fType) {
-            return false;
-        }
-        if (rrect) {
-            *rrect = fRRectData.fRRect;
-        }
-        if (dir) {
-            *dir = fRRectData.fDir;
-        }
-        if (start) {
-            *start = fRRectData.fStart;
-        }
-        if (inverted) {
-            *inverted = fRRectData.fInverted;
-        }
-        return true;
-    }
+    bool asRRect(SkRRect* rrect, SkPathDirection* dir, unsigned* start, bool* inverted) const;
 
     /**
      * If the unstyled shape is a straight line segment, returns true and sets pts to the endpoints.
      * An inverse filled line path is still considered a line.
      */
-    bool asLine(SkPoint pts[2], bool* inverted) const {
-        if (fType != Type::kLine) {
-            return false;
-        }
-        if (pts) {
-            pts[0] = fLineData.fPts[0];
-            pts[1] = fLineData.fPts[1];
-        }
-        if (inverted) {
-            *inverted = fLineData.fInverted;
-        }
-        return true;
-    }
+    bool asLine(SkPoint pts[2], bool* inverted) const;
+
+    // Can this shape be drawn as a pair of filled nested rectangles?
+    bool asNestedRects(SkRect rects[2]) const;
 
     /** Returns the unstyled geometry as a path. */
     void asPath(SkPath* out) const {
-        switch (fType) {
-            case Type::kEmpty:
-                out->reset();
-                break;
-            case Type::kInvertedEmpty:
-                out->reset();
-                out->setFillType(kDefaultPathInverseFillType);
-                break;
-            case Type::kRRect:
-                out->reset();
-                out->addRRect(fRRectData.fRRect, fRRectData.fDir, fRRectData.fStart);
-                // Below matches the fill type that attemptToSimplifyPath uses.
-                if (fRRectData.fInverted) {
-                    out->setFillType(kDefaultPathInverseFillType);
-                } else {
-                    out->setFillType(kDefaultPathFillType);
-                }
-                break;
-            case Type::kArc:
-                SkPathPriv::CreateDrawArcPath(out, fArcData.fOval, fArcData.fStartAngleDegrees,
-                                              fArcData.fSweepAngleDegrees, fArcData.fUseCenter,
-                                              fStyle.isSimpleFill());
-                if (fArcData.fInverted) {
-                    out->setFillType(kDefaultPathInverseFillType);
-                } else {
-                    out->setFillType(kDefaultPathFillType);
-                }
-                break;
-            case Type::kLine:
-                out->reset();
-                out->moveTo(fLineData.fPts[0]);
-                out->lineTo(fLineData.fPts[1]);
-                if (fLineData.fInverted) {
-                    out->setFillType(kDefaultPathInverseFillType);
-                } else {
-                    out->setFillType(kDefaultPathFillType);
-                }
-                break;
-            case Type::kPath:
-                *out = this->path();
-                break;
-        }
-    }
-
-    // Can this shape be drawn as a pair of filled nested rectangles?
-    bool asNestedRects(SkRect rects[2]) const {
-        if (Type::kPath != fType) {
-            return false;
-        }
-
-        // TODO: it would be better two store DRRects natively in the shape rather than converting
-        // them to a path and then reextracting the nested rects
-        if (this->path().isInverseFillType()) {
-            return false;
-        }
-
-        SkPathDirection dirs[2];
-        if (!SkPathPriv::IsNestedFillRects(this->path(), rects, dirs)) {
-            return false;
-        }
-
-        if (SkPathFillType::kWinding == this->path().getFillType() && dirs[0] == dirs[1]) {
-            // The two rects need to be wound opposite to each other
-            return false;
-        }
-
-        // Right now, nested rects where the margin is not the same width
-        // all around do not render correctly
-        const SkScalar* outer = rects[0].asScalars();
-        const SkScalar* inner = rects[1].asScalars();
-
-        bool allEq = true;
-
-        SkScalar margin = SkScalarAbs(outer[0] - inner[0]);
-        bool allGoE1 = margin >= SK_Scalar1;
-
-        for (int i = 1; i < 4; ++i) {
-            SkScalar temp = SkScalarAbs(outer[i] - inner[i]);
-            if (temp < SK_Scalar1) {
-                allGoE1 = false;
-            }
-            if (!SkScalarNearlyEqual(margin, temp)) {
-                allEq = false;
-            }
-        }
-
-        return allEq || allGoE1;
+        fShape.asPath(out, fStyle.isSimpleFill());
     }
 
     /**
      * Returns whether the geometry is empty. Note that applying the style could produce a
      * non-empty shape. It also may have an inverse fill.
      */
-    bool isEmpty() const { return Type::kEmpty == fType || Type::kInvertedEmpty == fType; }
+    bool isEmpty() const { return fShape.isEmpty(); }
 
     /**
      * Gets the bounds of the geometry without reflecting the shape's styling. This ignores
      * the inverse fill nature of the geometry.
      */
-    SkRect bounds() const;
+    SkRect bounds() const { return fShape.bounds(); }
 
     /**
      * Gets the bounds of the geometry reflecting the shape's styling (ignoring inverse fill
@@ -315,28 +166,7 @@
      * This is because filling closes all contours in the path.
      */
     bool knownToBeConvex() const {
-        switch (fType) {
-            case Type::kEmpty:
-                return true;
-            case Type::kInvertedEmpty:
-                return true;
-            case Type::kRRect:
-                return true;
-            case Type::kArc:
-                return SkPathPriv::DrawArcIsConvex(fArcData.fSweepAngleDegrees,
-                                                   SkToBool(fArcData.fUseCenter),
-                                                   fStyle.isSimpleFill());
-            case Type::kLine:
-                return true;
-            case Type::kPath:
-                // SkPath.isConvex() really means "is this path convex were it to be closed" and
-                // thus doesn't give the correct answer for stroked paths, hence we also check
-                // whether the path is either filled or closed. Convex paths may only have one
-                // contour hence isLastContourClosed() is a sufficient for a convex path.
-                return (this->style().isSimpleFill() || this->path().isLastContourClosed()) &&
-                        this->path().isConvex();
-        }
-        return false;
+        return fShape.convex(fStyle.isSimpleFill());
     }
 
     /**
@@ -345,52 +175,20 @@
      * kept separate from knownToBeConvex().
      */
     bool knownDirection() const {
-        switch (fType) {
-            case Type::kEmpty:
-                return true;
-            case Type::kInvertedEmpty:
-                return true;
-            case Type::kRRect:
-                return true;
-            case Type::kArc:
-                return true;
-            case Type::kLine:
-                return true;
-            case Type::kPath:
-                // Assuming this is called after knownToBeConvex(), this should just be relying on
-                // cached convexity and direction and will be cheap.
-                return !SkPathPriv::CheapIsFirstDirection(this->path(),
-                                                          SkPathPriv::kUnknown_FirstDirection);
-        }
-        return false;
+        // Assuming this is called after knownToBeConvex(), this should just be relying on
+        // cached convexity and direction and will be cheap.
+        return !fShape.isPath() ||
+               !SkPathPriv::CheapIsFirstDirection(fShape.path(),
+                                                  SkPathPriv::kUnknown_FirstDirection);
     }
 
     /** Is the pre-styled geometry inverse filled? */
     bool inverseFilled() const {
-        bool ret = false;
-        switch (fType) {
-            case Type::kEmpty:
-                ret = false;
-                break;
-            case Type::kInvertedEmpty:
-                ret = true;
-                break;
-            case Type::kRRect:
-                ret = fRRectData.fInverted;
-                break;
-            case Type::kArc:
-                ret = fArcData.fInverted;
-                break;
-            case Type::kLine:
-                ret = fLineData.fInverted;
-                break;
-            case Type::kPath:
-                ret = this->path().isInverseFillType();
-                break;
-        }
+        // Since the path tracks inverted-fillness itself, it should match what was recorded.
+        SkASSERT(!fShape.isPath() || fShape.inverted() == fShape.path().isInverseFillType());
         // Dashing ignores inverseness. We should have caught this earlier. skbug.com/5421
-        SkASSERT(!(ret && this->style().isDashed()));
-        return ret;
+        SkASSERT(!(fShape.inverted() && this->style().isDashed()));
+        return fShape.inverted();
     }
 
     /**
@@ -412,49 +210,13 @@
      * not have any caps if stroked (modulo the effect of any path effect).
      */
     bool knownToBeClosed() const {
-        switch (fType) {
-            case Type::kEmpty:
-                return true;
-            case Type::kInvertedEmpty:
-                return true;
-            case Type::kRRect:
-                return true;
-            case Type::kArc:
-                return fArcData.fUseCenter;
-            case Type::kLine:
-                return false;
-            case Type::kPath:
-                // SkPath doesn't keep track of the closed status of each contour.
-                return SkPathPriv::IsClosedSingleContour(this->path());
-        }
-        return false;
+        // This refers to the base shape and does not depend on invertedness.
+        return fShape.closed();
     }
 
     uint32_t segmentMask() const {
-        switch (fType) {
-            case Type::kEmpty:
-                return 0;
-            case Type::kInvertedEmpty:
-                return 0;
-            case Type::kRRect:
-                if (fRRectData.fRRect.getType() == SkRRect::kOval_Type) {
-                    return SkPath::kConic_SegmentMask;
-                } else if (fRRectData.fRRect.getType() == SkRRect::kRect_Type ||
-                           fRRectData.fRRect.getType() == SkRRect::kEmpty_Type) {
-                    return SkPath::kLine_SegmentMask;
-                }
-                return SkPath::kLine_SegmentMask | SkPath::kConic_SegmentMask;
-            case Type::kArc:
-                if (fArcData.fUseCenter) {
-                    return SkPath::kConic_SegmentMask | SkPath::kLine_SegmentMask;
-                }
-                return SkPath::kConic_SegmentMask;
-            case Type::kLine:
-                return SkPath::kLine_SegmentMask;
-            case Type::kPath:
-                return this->path().getSegmentMasks();
-        }
-        return 0;
+        // This refers to the base shape and does not depend on invertedness.
+        return fShape.segmentMask();
     }
 
     /**
@@ -489,50 +251,6 @@
     bool testingOnly_isNonVolatilePath() const;
 
 private:
-    enum class Type {
-        kEmpty,
-        kInvertedEmpty,
-        kRRect,
-        kArc,
-        kLine,
-        kPath,
-    };
-
-    void initType(Type type, const SkPath* path = nullptr) {
-        fType = Type::kEmpty;
-        this->changeType(type, path);
-    }
-
-    void changeType(Type type, const SkPath* path = nullptr) {
-        bool wasPath = Type::kPath == fType;
-        fType = type;
-        bool isPath = Type::kPath == type;
-        SkASSERT(!path || isPath);
-        if (wasPath && !isPath) {
-            fPathData.fPath.~SkPath();
-        } else if (!wasPath && isPath) {
-            if (path) {
-                new (&fPathData.fPath) SkPath(*path);
-            } else {
-                new (&fPathData.fPath) SkPath();
-            }
-        } else if (isPath && path) {
-            fPathData.fPath = *path;
-        }
-        // Whether or not we use the path's gen ID is decided in attemptToSimplifyPath.
-        fPathData.fGenID = 0;
-    }
-
-    SkPath& path() {
-        SkASSERT(Type::kPath == fType);
-        return fPathData.fPath;
-    }
-
-    const SkPath& path() const {
-        SkASSERT(Type::kPath == fType);
-        return fPathData.fPath;
-    }
-
     /** Constructor used by the applyStyle() function */
     GrStyledShape(const GrStyledShape& parentShape, GrStyle::Apply, SkScalar scale);
 
@@ -542,92 +260,23 @@
      */
     void setInheritedKey(const GrStyledShape& parentShape, GrStyle::Apply, SkScalar scale);
 
-    void attemptToSimplifyPath();
-    void attemptToSimplifyRRect();
-    void attemptToSimplifyLine();
-    void attemptToSimplifyArc();
-
-    bool attemptToSimplifyStrokedLineToRRect();
+    // Similar to GrShape::simplify but also takes into account style and stroking, possibly
+    // applying the style explicitly to produce a new analytic shape with a simpler style.
+    void simplify();
+    // As part of the simplification process, some shapes can have stroking trivially evaluated
+    // and form a new geometry with just a fill.
+    bool simplifyStroke(bool originallyClosed);
 
     /** Gets the path that gen id listeners should be added to. */
     const SkPath* originalPathForListeners() const;
 
-    // Defaults to use when there is no distinction between even/odd and winding fills.
-    static constexpr SkPathFillType kDefaultPathFillType = SkPathFillType::kEvenOdd;
-    static constexpr SkPathFillType kDefaultPathInverseFillType = SkPathFillType::kInverseEvenOdd;
-
-    static constexpr SkPathDirection kDefaultRRectDir = SkPathDirection::kCW;
-    static constexpr unsigned kDefaultRRectStart = 0;
-
-    static unsigned DefaultRectDirAndStartIndex(const SkRect& rect, bool hasPathEffect,
-                                                SkPathDirection* dir) {
-        *dir = kDefaultRRectDir;
-        // This comes from SkPath's interface. The default for adding a SkRect is counter clockwise
-        // beginning at index 0 (which happens to correspond to rrect index 0 or 7).
-        if (!hasPathEffect) {
-            // It doesn't matter what start we use, just be consistent to avoid redundant keys.
-            return kDefaultRRectStart;
-        }
-        // In SkPath a rect starts at index 0 by default. This is the top left corner. However,
-        // we store rects as rrects. RRects don't preserve the invertedness, but rather sort the
-        // rect edges. Thus, we may need to modify the rrect's start index to account for the sort.
-        bool swapX = rect.fLeft > rect.fRight;
-        bool swapY = rect.fTop > rect.fBottom;
-        if (swapX && swapY) {
-            // 0 becomes start index 2 and times 2 to convert from rect the rrect indices.
-            return 2 * 2;
-        } else if (swapX) {
-            *dir = SkPathDirection::kCCW;
-            // 0 becomes start index 1 and times 2 to convert from rect the rrect indices.
-            return 2 * 1;
-        } else if (swapY) {
-            *dir = SkPathDirection::kCCW;
-            // 0 becomes start index 3 and times 2 to convert from rect the rrect indices.
-            return 2 * 3;
-        }
-        return 0;
-    }
-
-    static unsigned DefaultRRectDirAndStartIndex(const SkRRect& rrect, bool hasPathEffect,
-                                                 SkPathDirection* dir) {
-        // This comes from SkPath's interface. The default for adding a SkRRect to a path is
-        // clockwise beginning at starting index 6.
-        static constexpr unsigned kPathRRectStartIdx = 6;
-        *dir = kDefaultRRectDir;
-        if (!hasPathEffect) {
-            // It doesn't matter what start we use, just be consistent to avoid redundant keys.
-            return kDefaultRRectStart;
-        }
-        return kPathRRectStartIdx;
-    }
-
-    union {
-        struct {
-            SkRRect fRRect;
-            SkPathDirection fDir;
-            unsigned fStart;
-            bool fInverted;
-        } fRRectData;
-        struct {
-            SkRect fOval;
-            SkScalar fStartAngleDegrees;
-            SkScalar fSweepAngleDegrees;
-            int16_t fUseCenter;
-            int16_t fInverted;
-        } fArcData;
-        struct {
-            SkPath fPath;
-            // Gen ID of the original path (fPath may be modified)
-            int32_t fGenID;
-        } fPathData;
-        struct {
-            SkPoint fPts[2];
-            bool fInverted;
-        } fLineData;
-    };
+    GrShape fShape;
     GrStyle fStyle;
-    SkTLazy<SkPath> fInheritedPathForListeners;
-    SkAutoSTArray<8, uint32_t>  fInheritedKey;
-    Type fType;
+    // Gen ID of the original path (path may be modified or simplified away).
+    int32_t fGenID      = 0;
+    bool    fSimplified = false;
+
+    SkTLazy<SkPath>            fInheritedPathForListeners;
+    SkAutoSTArray<8, uint32_t> fInheritedKey;
 };
 #endif
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index fa54fcd..588e852 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -1856,7 +1856,7 @@
             info.fInternalFormatForTexImageOrStorage = bgraTexImageFormat;
         }
 
-        if (SkToBool(info.fFlags &FormatInfo::kTexturable_Flag)) {
+        if (SkToBool(info.fFlags & FormatInfo::kTexturable_Flag)) {
             info.fColorTypeInfoCount = 1;
             info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]());
             int ctIdx = 0;
@@ -2450,6 +2450,7 @@
                    ctxInfo.hasExtension("GL_EXT_texture_type_2_10_10_10_REV")) {
             info.fFlags = FormatInfo::kTexturable_Flag;
         } // No WebGL support
+
         if (texStorageSupported) {
             info.fFlags |= FormatInfo::kUseTexStorage_Flag;
             info.fInternalFormatForTexImageOrStorage = GR_GL_RGB10_A2;
@@ -2458,8 +2459,11 @@
                     texImageSupportsSizedInternalFormat ? GR_GL_RGB10_A2 : GR_GL_RGBA;
         }
 
-        if (SkToBool(info.fFlags &FormatInfo::kTexturable_Flag)) {
-            info.fColorTypeInfoCount = 1;
+        if (SkToBool(info.fFlags & FormatInfo::kTexturable_Flag)) {
+            bool supportsBGRAColorType = GR_IS_GR_GL(standard) &&
+                    (version >= GR_GL_VER(1, 2) || ctxInfo.hasExtension("GL_EXT_bgra"));
+
+            info.fColorTypeInfoCount = supportsBGRAColorType ? 2 : 1;
             info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]());
             int ctIdx = 0;
             // Format: RGB10_A2, Surface: kRGBA_1010102
@@ -2494,6 +2498,40 @@
                     ioFormat.fExternalReadFormat = GR_GL_RGBA;
                 }
             }
+            //------------------------------------------------------------------
+            // Format: RGB10_A2, Surface: kBGRA_1010102
+            if (supportsBGRAColorType) {
+                auto& ctInfo = info.fColorTypeInfos[ctIdx++];
+                ctInfo.fColorType = GrColorType::kBGRA_1010102;
+                ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
+                this->setColorTypeFormat(GrColorType::kBGRA_1010102, GrGLFormat::kRGB10_A2);
+
+                // External IO ColorTypes:
+                ctInfo.fExternalIOFormatCount = 2;
+                ctInfo.fExternalIOFormats.reset(
+                        new ColorTypeInfo::ExternalIOFormats[ctInfo.fExternalIOFormatCount]());
+                int ioIdx = 0;
+                // Format: RGB10_A2, Surface: kBGRA_1010102, Data: kBGRA_1010102
+                {
+                    auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++];
+                    ioFormat.fColorType = GrColorType::kBGRA_1010102;
+                    ioFormat.fExternalType = GR_GL_UNSIGNED_INT_2_10_10_10_REV;
+                    ioFormat.fExternalTexImageFormat = GR_GL_BGRA;
+                    ioFormat.fExternalReadFormat =
+                            formatWorkarounds.fDisallowBGRA8ReadPixels ? 0 : GR_GL_BGRA;
+                    // Not guaranteed by ES/WebGL.
+                    ioFormat.fRequiresImplementationReadQuery = !GR_IS_GR_GL(standard);
+                }
+
+                // Format: RGB10_A2, Surface: kBGRA_1010102, Data: kRGBA_8888
+                {
+                    auto& ioFormat = ctInfo.fExternalIOFormats[ioIdx++];
+                    ioFormat.fColorType = GrColorType::kRGBA_8888;
+                    ioFormat.fExternalType = GR_GL_UNSIGNED_BYTE;
+                    ioFormat.fExternalTexImageFormat = 0;
+                    ioFormat.fExternalReadFormat = GR_GL_RGBA;
+                }
+            }
         }
     }
 
@@ -4424,6 +4462,8 @@
     if (GR_IS_GR_GL(fStandard)) {
         combos.push_back({ GrColorType::kBGRA_8888,
                            GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_2D) });
+        combos.push_back({ GrColorType::kBGRA_1010102,
+                           GrBackendFormat::MakeGL(GR_GL_RGB10_A2, GR_GL_TEXTURE_2D) });
     } else {
         SkASSERT(GR_IS_GR_GL_ES(fStandard) || GR_IS_GR_WEBGL(fStandard));
 
diff --git a/src/gpu/mock/GrMockCaps.cpp b/src/gpu/mock/GrMockCaps.cpp
index e0764dc..dbaf644 100644
--- a/src/gpu/mock/GrMockCaps.cpp
+++ b/src/gpu/mock/GrMockCaps.cpp
@@ -48,6 +48,8 @@
                                                                   SkImage::CompressionType::kNone)},
         { GrColorType::kRGBA_1010102,   GrBackendFormat::MakeMock(GrColorType::kRGBA_1010102,
                                                                   SkImage::CompressionType::kNone)},
+        { GrColorType::kBGRA_1010102,   GrBackendFormat::MakeMock(GrColorType::kBGRA_1010102,
+                                                                  SkImage::CompressionType::kNone)},
         { GrColorType::kGray_8,         GrBackendFormat::MakeMock(GrColorType::kGray_8,
                                                                   SkImage::CompressionType::kNone)},
         { GrColorType::kAlpha_F16,      GrBackendFormat::MakeMock(GrColorType::kAlpha_F16,
diff --git a/src/gpu/mtl/GrMtlCaps.h b/src/gpu/mtl/GrMtlCaps.h
index 50dac59..2285a70 100644
--- a/src/gpu/mtl/GrMtlCaps.h
+++ b/src/gpu/mtl/GrMtlCaps.h
@@ -156,9 +156,9 @@
         int fColorTypeInfoCount = 0;
     };
 #ifdef SK_BUILD_FOR_IOS
-    static constexpr size_t kNumMtlFormats = 17;
+    static constexpr size_t kNumMtlFormats = 18;
 #else
-    static constexpr size_t kNumMtlFormats = 15;
+    static constexpr size_t kNumMtlFormats = 16;
 #endif
     static size_t GetFormatIndex(MTLPixelFormat);
     FormatInfo fFormatTable[kNumMtlFormats];
diff --git a/src/gpu/mtl/GrMtlCaps.mm b/src/gpu/mtl/GrMtlCaps.mm
index 51f7a26..864d539 100644
--- a/src/gpu/mtl/GrMtlCaps.mm
+++ b/src/gpu/mtl/GrMtlCaps.mm
@@ -469,6 +469,7 @@
     MTLPixelFormatR16Float,
     MTLPixelFormatRG8Unorm,
     MTLPixelFormatRGB10A2Unorm,
+    MTLPixelFormatBGR10A2Unorm,
 #ifdef SK_BUILD_FOR_IOS
     MTLPixelFormatABGR4Unorm,
 #endif
@@ -697,6 +698,26 @@
         }
     }
 
+    // Format: BGR10A2Unorm
+    {
+        info = &fFormatTable[GetFormatIndex(MTLPixelFormatBGR10A2Unorm)];
+        if (this->isMac() && fFamilyGroup == 1) {
+            info->fFlags = FormatInfo::kTexturable_Flag;
+        } else {
+            info->fFlags = FormatInfo::kAllFlags;
+        }
+        info->fBytesPerPixel = 4;
+        info->fColorTypeInfoCount = 1;
+        info->fColorTypeInfos.reset(new ColorTypeInfo[info->fColorTypeInfoCount]());
+        int ctIdx = 0;
+        // Format: BGR10A2Unorm, Surface: kBGRA_1010102
+        {
+            auto& ctInfo = info->fColorTypeInfos[ctIdx++];
+            ctInfo.fColorType = GrColorType::kBGRA_1010102;
+            ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
+        }
+    }
+
     // Format: R16Float
     {
         info = &fFormatTable[GetFormatIndex(MTLPixelFormatR16Float)];
@@ -846,6 +867,7 @@
     this->setColorType(GrColorType::kRG_88,            { MTLPixelFormatRG8Unorm });
     this->setColorType(GrColorType::kBGRA_8888,        { MTLPixelFormatBGRA8Unorm });
     this->setColorType(GrColorType::kRGBA_1010102,     { MTLPixelFormatRGB10A2Unorm });
+    this->setColorType(GrColorType::kBGRA_1010102,     { MTLPixelFormatBGR10A2Unorm });
     this->setColorType(GrColorType::kGray_8,           { MTLPixelFormatR8Unorm });
     this->setColorType(GrColorType::kAlpha_F16,        { MTLPixelFormatR16Float });
     this->setColorType(GrColorType::kRGBA_F16,         { MTLPixelFormatRGBA16Float });
@@ -1072,6 +1094,7 @@
         { GrColorType::kRG_88,            GrBackendFormat::MakeMtl(MTLPixelFormatRG8Unorm)        },
         { GrColorType::kBGRA_8888,        GrBackendFormat::MakeMtl(MTLPixelFormatBGRA8Unorm)      },
         { GrColorType::kRGBA_1010102,     GrBackendFormat::MakeMtl(MTLPixelFormatRGB10A2Unorm)    },
+        { GrColorType::kBGRA_1010102,     GrBackendFormat::MakeMtl(MTLPixelFormatBGR10A2Unorm)    },
         { GrColorType::kGray_8,           GrBackendFormat::MakeMtl(MTLPixelFormatR8Unorm)         },
         { GrColorType::kAlpha_F16,        GrBackendFormat::MakeMtl(MTLPixelFormatR16Float)        },
         { GrColorType::kRGBA_F16,         GrBackendFormat::MakeMtl(MTLPixelFormatRGBA16Float)     },
diff --git a/src/gpu/mtl/GrMtlCppUtil.h b/src/gpu/mtl/GrMtlCppUtil.h
index f78058a..f431ec8 100644
--- a/src/gpu/mtl/GrMtlCppUtil.h
+++ b/src/gpu/mtl/GrMtlCppUtil.h
@@ -18,7 +18,7 @@
 
 #if GR_TEST_UTILS
 const char* GrMtlFormatToStr(GrMTLPixelFormat mtlFormat);
-bool GrMtlFormatIsBGRA(GrMTLPixelFormat mtlFormat);
+bool GrMtlFormatIsBGRA8(GrMTLPixelFormat mtlFormat);
 #endif
 
 #endif
diff --git a/src/gpu/mtl/GrMtlGpu.mm b/src/gpu/mtl/GrMtlGpu.mm
index 16ab069..3b89047 100644
--- a/src/gpu/mtl/GrMtlGpu.mm
+++ b/src/gpu/mtl/GrMtlGpu.mm
@@ -741,6 +741,7 @@
         case MTLPixelFormatRG8Unorm:        return GrColorType::kRG_88;
         case MTLPixelFormatBGRA8Unorm:      return GrColorType::kBGRA_8888;
         case MTLPixelFormatRGB10A2Unorm:    return GrColorType::kRGBA_1010102;
+        case MTLPixelFormatBGR10A2Unorm:    return GrColorType::kBGRA_1010102;
         case MTLPixelFormatR16Float:        return GrColorType::kR_F16;
         case MTLPixelFormatRGBA16Float:     return GrColorType::kRGBA_F16;
         case MTLPixelFormatR16Unorm:        return GrColorType::kR_16;
diff --git a/src/gpu/mtl/GrMtlUtil.mm b/src/gpu/mtl/GrMtlUtil.mm
index 57ecfd9..29eac72 100644
--- a/src/gpu/mtl/GrMtlUtil.mm
+++ b/src/gpu/mtl/GrMtlUtil.mm
@@ -248,6 +248,7 @@
         case MTLPixelFormatR16Float:        return kRed_SkColorChannelFlag;
         case MTLPixelFormatRG8Unorm:        return kRG_SkColorChannelFlags;
         case MTLPixelFormatRGB10A2Unorm:    return kRGBA_SkColorChannelFlags;
+        case MTLPixelFormatBGR10A2Unorm:    return kRGBA_SkColorChannelFlags;
 #if defined(SK_BUILD_FOR_IOS) && !TARGET_OS_SIMULATOR
         case MTLPixelFormatABGR4Unorm:      return kRGBA_SkColorChannelFlags;
 #endif
@@ -294,7 +295,7 @@
 }
 
 #if GR_TEST_UTILS
-bool GrMtlFormatIsBGRA(GrMTLPixelFormat mtlFormat) {
+bool GrMtlFormatIsBGRA8(GrMTLPixelFormat mtlFormat) {
     return mtlFormat == MTLPixelFormatBGRA8Unorm;
 }
 
@@ -312,6 +313,7 @@
         case MTLPixelFormatR16Float:        return "R16Float";
         case MTLPixelFormatRG8Unorm:        return "RG8Unorm";
         case MTLPixelFormatRGB10A2Unorm:    return "RGB10A2Unorm";
+        case MTLPixelFormatBGR10A2Unorm:    return "BGR10A2Unorm";
 #ifdef SK_BUILD_FOR_IOS
         case MTLPixelFormatABGR4Unorm:      return "ABGR4Unorm";
 #endif
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index bc8b38d..80a34c6 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -25,88 +25,114 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+GrAtlasTextOp::GrAtlasTextOp(MaskType maskType,
+                             GrPaint&& paint,
+                             GrTextBlob::SubRun* subrun,
+                             const SkMatrix& drawMatrix,
+                             SkPoint drawOrigin,
+                             const SkIRect& clipRect,
+                             const SkPMColor4f& filteredColor,
+                             SkColor luminanceColor,
+                             bool useGammaCorrectDistanceTable,
+                             uint32_t DFGPFlags)
+        : INHERITED(ClassID())
+        , fMaskType{maskType}
+        , fNeedsGlyphTransform{subrun->needsTransform()}
+        , fLuminanceColor{luminanceColor}
+        , fUseGammaCorrectDistanceTable{useGammaCorrectDistanceTable}
+        , fDFGPFlags{DFGPFlags}
+        , fGeoDataAllocSize{kMinGeometryAllocated}
+        , fProcessors{std::move(paint)}
+        , fNumGlyphs{SkTo<int>(subrun->fGlyphs.size())} {
+    GrAtlasTextOp::Geometry& geometry = fGeoData[0];
+
+    // Unref handled in ~GrAtlasTextOp().
+    geometry.fBlob = SkRef(subrun->fBlob);
+    geometry.fSubRunPtr = subrun;
+    geometry.fDrawMatrix = drawMatrix;
+    geometry.fDrawOrigin = drawOrigin;
+    geometry.fClipRect = clipRect;
+    geometry.fColor = subrun->maskFormat() == kARGB_GrMaskFormat ? SK_PMColor4fWHITE
+                                                                 : filteredColor;
+    fGeoCount = 1;
+
+    SkRect bounds = subrun->deviceRect(drawMatrix, drawOrigin);
+    // We don't have tight bounds on the glyph paths in device space. For the purposes of bounds
+    // we treat this as a set of non-AA rects rendered with a texture.
+    this->setBounds(bounds, HasAABloat::kNo, IsHairline::kNo);
+}
+
 std::unique_ptr<GrAtlasTextOp> GrAtlasTextOp::MakeBitmap(GrRecordingContext* context,
                                                          GrPaint&& paint,
-                                                         GrMaskFormat maskFormat,
-                                                         int glyphCount,
-                                                         bool needsTransform) {
-        GrOpMemoryPool* pool = context->priv().opMemoryPool();
+                                                         GrTextBlob::SubRun* subrun,
+                                                         const SkMatrix& drawMatrix,
+                                                         SkPoint drawOrigin,
+                                                         const SkIRect& clipRect,
+                                                         const SkPMColor4f& filteredColor) {
+    GrOpMemoryPool* pool = context->priv().opMemoryPool();
 
-        std::unique_ptr<GrAtlasTextOp> op = pool->allocate<GrAtlasTextOp>(std::move(paint));
-
-        switch (maskFormat) {
-            case kA8_GrMaskFormat:
-                op->fMaskType = kGrayscaleCoverageMask_MaskType;
-                break;
-            case kA565_GrMaskFormat:
-                op->fMaskType = kLCDCoverageMask_MaskType;
-                break;
-            case kARGB_GrMaskFormat:
-                op->fMaskType = kColorBitmapMask_MaskType;
-                break;
+    MaskType maskType = [&]() {
+        switch (subrun->maskFormat()) {
+            case kA8_GrMaskFormat: return kGrayscaleCoverageMask_MaskType;
+            case kA565_GrMaskFormat: return kLCDCoverageMask_MaskType;
+            case kARGB_GrMaskFormat: return kColorBitmapMask_MaskType;
+            // Needed to placate some compilers.
+            default: return kGrayscaleCoverageMask_MaskType;
         }
-        op->fNumGlyphs = glyphCount;
-        op->fGeoCount = 1;
-        op->fLuminanceColor = 0;
-        op->fNeedsGlyphTransform = needsTransform;
-        return op;
-    }
+    }();
+
+    return pool->allocate<GrAtlasTextOp>(maskType,
+                                         std::move(paint),
+                                         subrun,
+                                         drawMatrix,
+                                         drawOrigin,
+                                         clipRect,
+                                         filteredColor,
+                                         0,
+                                         false,
+                                         0);
+}
 
 std::unique_ptr<GrAtlasTextOp> GrAtlasTextOp::MakeDistanceField(
                                             GrRecordingContext* context,
                                             GrPaint&& paint,
-                                            int glyphCount,
+                                            GrTextBlob::SubRun* subrun,
+                                            const SkMatrix& drawMatrix,
+                                            SkPoint drawOrigin,
+                                            const SkIRect& clipRect,
+                                            const SkPMColor4f& filteredColor,
                                             bool useGammaCorrectDistanceTable,
                                             SkColor luminanceColor,
-                                            const SkSurfaceProps& props,
-                                            bool isAntiAliased,
-                                            bool useLCD) {
-        GrOpMemoryPool* pool = context->priv().opMemoryPool();
+                                            const SkSurfaceProps& props) {
+    GrOpMemoryPool* pool = context->priv().opMemoryPool();
+    bool isBGR = SkPixelGeometryIsBGR(props.pixelGeometry());
+    bool isLCD = subrun->hasUseLCDText() && SkPixelGeometryIsH(props.pixelGeometry());
+    MaskType maskType = !subrun->isAntiAliased() ? kAliasedDistanceField_MaskType
+                                                 : isLCD ? (isBGR ? kLCDBGRDistanceField_MaskType
+                                                                  : kLCDDistanceField_MaskType)
+                                                         : kGrayscaleDistanceField_MaskType;
 
-        std::unique_ptr<GrAtlasTextOp> op = pool->allocate<GrAtlasTextOp>(std::move(paint));
+    uint32_t DFGPFlags = drawMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
+    DFGPFlags |= drawMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
+    DFGPFlags |= drawMatrix.hasPerspective() ? kPerspective_DistanceFieldEffectFlag : 0;
+    DFGPFlags |= useGammaCorrectDistanceTable ? kGammaCorrect_DistanceFieldEffectFlag : 0;
+    DFGPFlags |= kAliasedDistanceField_MaskType == maskType ? kAliased_DistanceFieldEffectFlag : 0;
 
-        bool isBGR = SkPixelGeometryIsBGR(props.pixelGeometry());
-        bool isLCD = useLCD && SkPixelGeometryIsH(props.pixelGeometry());
-        op->fMaskType = !isAntiAliased ? kAliasedDistanceField_MaskType
-                                       : isLCD ? (isBGR ? kLCDBGRDistanceField_MaskType
-                                                        : kLCDDistanceField_MaskType)
-                                               : kGrayscaleDistanceField_MaskType;
-        op->fUseGammaCorrectDistanceTable = useGammaCorrectDistanceTable;
-        op->fLuminanceColor = luminanceColor;
-        op->fNeedsGlyphTransform = true;
-        op->fNumGlyphs = glyphCount;
-        op->fGeoCount = 1;
-        return op;
+    if (isLCD) {
+        DFGPFlags |= kUseLCD_DistanceFieldEffectFlag;
+        DFGPFlags |= kLCDBGRDistanceField_MaskType == maskType ? kBGR_DistanceFieldEffectFlag : 0;
     }
 
-static const int kDistanceAdjustLumShift = 5;
-
-void GrAtlasTextOp::init() {
-    const Geometry& geo = fGeoData[0];
-    if (this->usesDistanceFields()) {
-        bool isLCD = this->isLCD();
-
-        const SkMatrix& drawMatrix = geo.fDrawMatrix;
-
-        fDFGPFlags = drawMatrix.isSimilarity() ? kSimilarity_DistanceFieldEffectFlag : 0;
-        fDFGPFlags |= drawMatrix.isScaleTranslate() ? kScaleOnly_DistanceFieldEffectFlag : 0;
-        fDFGPFlags |= drawMatrix.hasPerspective() ? kPerspective_DistanceFieldEffectFlag : 0;
-        fDFGPFlags |= fUseGammaCorrectDistanceTable ? kGammaCorrect_DistanceFieldEffectFlag : 0;
-        fDFGPFlags |= (kAliasedDistanceField_MaskType == fMaskType)
-                              ? kAliased_DistanceFieldEffectFlag
-                              : 0;
-
-        if (isLCD) {
-            fDFGPFlags |= kUseLCD_DistanceFieldEffectFlag;
-            fDFGPFlags |=
-                    (kLCDBGRDistanceField_MaskType == fMaskType) ? kBGR_DistanceFieldEffectFlag : 0;
-        }
-    }
-
-    SkRect bounds = geo.fSubRunPtr->deviceRect(geo.fDrawMatrix, geo.fDrawOrigin);
-    // We don't have tight bounds on the glyph paths in device space. For the purposes of bounds
-    // we treat this as a set of non-AA rects rendered with a texture.
-    this->setBounds(bounds, HasAABloat::kNo, IsHairline::kNo);
+    return pool->allocate<GrAtlasTextOp>(maskType,
+                                         std::move(paint),
+                                         subrun,
+                                         drawMatrix,
+                                         drawOrigin,
+                                         clipRect,
+                                         filteredColor,
+                                         luminanceColor,
+                                         useGammaCorrectDistanceTable,
+                                         DFGPFlags);
 }
 
 void GrAtlasTextOp::visitProxies(const VisitProxyFunc& func) const {
@@ -590,6 +616,8 @@
     return CombineResult::kMerged;
 }
 
+static const int kDistanceAdjustLumShift = 5;
+
 // TODO trying to figure out why lcd is so whack
 // (see comments in GrTextContext::ComputeCanonicalColor)
 GrGeometryProcessor* GrAtlasTextOp::setupDfProcessor(SkArenaAlloc* arena,
diff --git a/src/gpu/ops/GrAtlasTextOp.h b/src/gpu/ops/GrAtlasTextOp.h
index 757ed1f..451de49 100644
--- a/src/gpu/ops/GrAtlasTextOp.h
+++ b/src/gpu/ops/GrAtlasTextOp.h
@@ -36,29 +36,25 @@
         SkPMColor4f fColor;
     };
 
-    static std::unique_ptr<GrAtlasTextOp> MakeBitmap(GrRecordingContext*,
-                                                     GrPaint&&,
-                                                     GrMaskFormat,
-                                                     int glyphCount,
-                                                     bool needsTransform);
+    static std::unique_ptr<GrAtlasTextOp> MakeBitmap(GrRecordingContext* context,
+                                                     GrPaint&& paint,
+                                                     GrTextBlob::SubRun* subrun,
+                                                     const SkMatrix& drawMatrix,
+                                                     SkPoint drawOrigin,
+                                                     const SkIRect& clipRect,
+                                                     const SkPMColor4f& filteredColor);
 
     static std::unique_ptr<GrAtlasTextOp> MakeDistanceField(
             GrRecordingContext*,
             GrPaint&&,
-            int glyphCount,
+            GrTextBlob::SubRun*,
+            const SkMatrix& drawMatrix,
+            SkPoint drawOrigin,
+            const SkIRect& clipRect,
+            const SkPMColor4f& filteredColor,
             bool useGammaCorrectDistanceTable,
             SkColor luminanceColor,
-            const SkSurfaceProps&,
-            bool isAntiAliased,
-            bool useLCD);
-
-    // To avoid even the initial copy of the struct, we have a getter for the first item which
-    // is used to seed the op with its initial geometry.  After seeding, the client should call
-    // init() so the op can initialize itself
-    Geometry& geometry() { return fGeoData[0]; }
-
-    /** Called after this->geometry() has been configured. */
-    void init();
+            const SkSurfaceProps&);
 
     const char* name() const override { return "AtlasTextOp"; }
 
@@ -94,10 +90,16 @@
     // The minimum number of Geometry we will try to allocate.
     static constexpr auto kMinGeometryAllocated = 12;
 
-    GrAtlasTextOp(GrPaint&& paint)
-            : INHERITED(ClassID())
-            , fGeoDataAllocSize(kMinGeometryAllocated)
-            , fProcessors(std::move(paint)) {}
+    GrAtlasTextOp(MaskType maskType,
+                  GrPaint&& paint,
+                  GrTextBlob::SubRun* subrun,
+                  const SkMatrix& drawMatrix,
+                  SkPoint drawOrigin,
+                  const SkIRect& clipRect,
+                  const SkPMColor4f& filteredColor,
+                  SkColor luminanceColor,
+                  bool useGammaCorrectDistanceTable,
+                  uint32_t DFGPFlags);
 
     struct FlushInfo {
         sk_sp<const GrBuffer> fVertexBuffer;
@@ -176,20 +178,18 @@
                                           const GrSurfaceProxyView* views,
                                           unsigned int numActiveViews) const;
 
+    const MaskType fMaskType;
+    const bool fNeedsGlyphTransform;
+    const SkColor fLuminanceColor{0};
+    const bool fUseGammaCorrectDistanceTable{false};
+    // Distance field properties
+    const uint32_t fDFGPFlags;
     SkAutoSTMalloc<kMinGeometryAllocated, Geometry> fGeoData;
     int fGeoDataAllocSize;
     GrProcessorSet fProcessors;
-    struct {
-        uint32_t fUsesLocalCoords : 1;
-        uint32_t fUseGammaCorrectDistanceTable : 1;
-        uint32_t fNeedsGlyphTransform : 1;
-    };
+    bool fUsesLocalCoords;
     int fGeoCount;
     int fNumGlyphs;
-    MaskType fMaskType;
-    // Distance field properties
-    SkColor fLuminanceColor;
-    uint32_t fDFGPFlags = 0;
 
     typedef GrMeshDrawOp INHERITED;
 };
diff --git a/src/gpu/text/GrTextBlob.cpp b/src/gpu/text/GrTextBlob.cpp
index e62805c..e73868b 100644
--- a/src/gpu/text/GrTextBlob.cpp
+++ b/src/gpu/text/GrTextBlob.cpp
@@ -583,7 +583,7 @@
                 }
             }
 
-            auto op = this->makeOp(*subRun, glyphCount, deviceMatrix, drawOrigin, clipRect,
+            auto op = this->makeOp(*subRun, deviceMatrix, drawOrigin, clipRect,
                                    paint, filteredColor, props, target);
             if (op) {
                 if (skipClip) {
@@ -600,8 +600,7 @@
 const GrTextBlob::Key& GrTextBlob::key() const { return fKey; }
 size_t GrTextBlob::size() const { return fSize; }
 
-std::unique_ptr<GrDrawOp> GrTextBlob::test_makeOp(int glyphCount,
-                                                  const SkMatrixProvider& matrixProvider,
+std::unique_ptr<GrDrawOp> GrTextBlob::test_makeOp(const SkMatrixProvider& matrixProvider,
                                                   SkPoint drawOrigin,
                                                   const SkPaint& paint,
                                                   const SkPMColor4f& filteredColor,
@@ -609,7 +608,7 @@
                                                   GrTextTarget* target) {
     SubRun* info = fFirstSubRun;
     SkIRect emptyRect = SkIRect::MakeEmpty();
-    return this->makeOp(*info, glyphCount, matrixProvider, drawOrigin, emptyRect, paint,
+    return this->makeOp(*info, matrixProvider, drawOrigin, emptyRect, paint,
                         filteredColor, props, target);
 }
 
@@ -716,7 +715,6 @@
 }
 
 std::unique_ptr<GrAtlasTextOp> GrTextBlob::makeOp(SubRun& info,
-                                                  int glyphCount,
                                                   const SkMatrixProvider& matrixProvider,
                                                   SkPoint drawOrigin,
                                                   const SkIRect& clipRect,
@@ -724,30 +722,29 @@
                                                   const SkPMColor4f& filteredColor,
                                                   const SkSurfaceProps& props,
                                                   GrTextTarget* target) {
-    GrMaskFormat format = info.maskFormat();
-
     GrPaint grPaint;
     target->makeGrPaint(info.maskFormat(), paint, matrixProvider, &grPaint);
-    std::unique_ptr<GrAtlasTextOp> op;
     if (info.drawAsDistanceFields()) {
         // TODO: Can we be even smarter based on the dest transfer function?
-        op = GrAtlasTextOp::MakeDistanceField(
-                target->getContext(), std::move(grPaint), glyphCount,
-                target->colorInfo().isLinearlyBlended(), SkPaintPriv::ComputeLuminanceColor(paint),
-                props, info.isAntiAliased(), info.hasUseLCDText());
+        return GrAtlasTextOp::MakeDistanceField(target->getContext(),
+                                                std::move(grPaint),
+                                                &info,
+                                                matrixProvider.localToDevice(),
+                                                drawOrigin,
+                                                clipRect,
+                                                filteredColor,
+                                                target->colorInfo().isLinearlyBlended(),
+                                                SkPaintPriv::ComputeLuminanceColor(paint),
+                                                props);
     } else {
-        op = GrAtlasTextOp::MakeBitmap(target->getContext(), std::move(grPaint), format, glyphCount,
-                                       info.needsTransform());
+        return GrAtlasTextOp::MakeBitmap(target->getContext(),
+                                         std::move(grPaint),
+                                         &info,
+                                         matrixProvider.localToDevice(),
+                                         drawOrigin,
+                                         clipRect,
+                                         filteredColor);
     }
-    GrAtlasTextOp::Geometry& geometry = op->geometry();
-    geometry.fDrawMatrix = matrixProvider.localToDevice();
-    geometry.fClipRect = clipRect;
-    geometry.fBlob = SkRef(this);
-    geometry.fSubRunPtr = &info;
-    geometry.fColor = info.maskFormat() == kARGB_GrMaskFormat ? SK_PMColor4fWHITE : filteredColor;
-    geometry.fDrawOrigin = drawOrigin;
-    op->init();
-    return op;
 }
 
 void GrTextBlob::processDeviceMasks(const SkZip<SkGlyphVariant, SkPoint>& drawables,
diff --git a/src/gpu/text/GrTextBlob.h b/src/gpu/text/GrTextBlob.h
index 7b27644..98f813a 100644
--- a/src/gpu/text/GrTextBlob.h
+++ b/src/gpu/text/GrTextBlob.h
@@ -166,8 +166,7 @@
     size_t size() const;
 
     // Internal test methods
-    std::unique_ptr<GrDrawOp> test_makeOp(int glyphCount,
-                                          const SkMatrixProvider& matrixProvider,
+    std::unique_ptr<GrDrawOp> test_makeOp(const SkMatrixProvider& matrixProvider,
                                           SkPoint drawOrigin,
                                           const SkPaint& paint,
                                           const SkPMColor4f& filteredColor,
@@ -220,7 +219,6 @@
     void insertSubRun(SubRun* subRun);
 
     std::unique_ptr<GrAtlasTextOp> makeOp(SubRun& info,
-                                          int glyphCount,
                                           const SkMatrixProvider& matrixProvider,
                                           SkPoint drawOrigin,
                                           const SkIRect& clipRect,
diff --git a/src/gpu/vk/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp
index 6e97db3..3ca9787 100644
--- a/src/gpu/vk/GrVkCaps.cpp
+++ b/src/gpu/vk/GrVkCaps.cpp
@@ -89,6 +89,7 @@
         case VK_FORMAT_B8G8R8A8_UNORM:
         case VK_FORMAT_R8G8B8A8_UNORM:
         case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
+        case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
         case VK_FORMAT_R8G8B8A8_SRGB:
         case VK_FORMAT_R16G16_UNORM:
         case VK_FORMAT_R16G16_SFLOAT:
@@ -680,6 +681,7 @@
     VK_FORMAT_R8G8B8_UNORM,
     VK_FORMAT_R8G8_UNORM,
     VK_FORMAT_A2B10G10R10_UNORM_PACK32,
+    VK_FORMAT_A2R10G10B10_UNORM_PACK32,
     VK_FORMAT_B4G4R4A4_UNORM_PACK16,
     VK_FORMAT_R4G4B4A4_UNORM_PACK16,
     VK_FORMAT_R8G8B8A8_SRGB,
@@ -949,6 +951,25 @@
             }
         }
     }
+    // Format: VK_FORMAT_A2R10G10B10_UNORM_PACK32
+    {
+        constexpr VkFormat format = VK_FORMAT_A2R10G10B10_UNORM_PACK32;
+        auto& info = this->getFormatInfo(format);
+        info.init(interface, physDev, properties, format);
+        info.fBytesPerPixel = 4;
+        if (SkToBool(info.fOptimalFlags & FormatInfo::kTexturable_Flag)) {
+            info.fColorTypeInfoCount = 1;
+            info.fColorTypeInfos.reset(new ColorTypeInfo[info.fColorTypeInfoCount]());
+            int ctIdx = 0;
+            // Format: VK_FORMAT_A2R10G10B10_UNORM_PACK32, Surface: kBGRA_1010102
+            {
+                constexpr GrColorType ct = GrColorType::kBGRA_1010102;
+                auto& ctInfo = info.fColorTypeInfos[ctIdx++];
+                ctInfo.fColorType = ct;
+                ctInfo.fFlags = ColorTypeInfo::kUploadData_Flag | ColorTypeInfo::kRenderable_Flag;
+            }
+        }
+    }
     // Format: VK_FORMAT_B4G4R4A4_UNORM_PACK16
     {
         constexpr VkFormat format = VK_FORMAT_B4G4R4A4_UNORM_PACK16;
@@ -1177,6 +1198,7 @@
     this->setColorType(GrColorType::kRG_88,            { VK_FORMAT_R8G8_UNORM });
     this->setColorType(GrColorType::kBGRA_8888,        { VK_FORMAT_B8G8R8A8_UNORM });
     this->setColorType(GrColorType::kRGBA_1010102,     { VK_FORMAT_A2B10G10R10_UNORM_PACK32 });
+    this->setColorType(GrColorType::kBGRA_1010102,     { VK_FORMAT_A2R10G10B10_UNORM_PACK32 });
     this->setColorType(GrColorType::kGray_8,           { VK_FORMAT_R8_UNORM });
     this->setColorType(GrColorType::kAlpha_F16,        { VK_FORMAT_R16_SFLOAT });
     this->setColorType(GrColorType::kRGBA_F16,         { VK_FORMAT_R16G16B16A16_SFLOAT });
@@ -1729,7 +1751,8 @@
         { GrColorType::kRGB_888x,         GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8_UNORM)         },
         { GrColorType::kRG_88,            GrBackendFormat::MakeVk(VK_FORMAT_R8G8_UNORM)           },
         { GrColorType::kBGRA_8888,        GrBackendFormat::MakeVk(VK_FORMAT_B8G8R8A8_UNORM)       },
-        { GrColorType::kRGBA_1010102,     GrBackendFormat::MakeVk(VK_FORMAT_A2B10G10R10_UNORM_PACK32)},
+        { GrColorType::kRGBA_1010102,  GrBackendFormat::MakeVk(VK_FORMAT_A2B10G10R10_UNORM_PACK32)},
+        { GrColorType::kBGRA_1010102,  GrBackendFormat::MakeVk(VK_FORMAT_A2R10G10B10_UNORM_PACK32)},
         { GrColorType::kGray_8,           GrBackendFormat::MakeVk(VK_FORMAT_R8_UNORM)             },
         { GrColorType::kAlpha_F16,        GrBackendFormat::MakeVk(VK_FORMAT_R16_SFLOAT)           },
         { GrColorType::kRGBA_F16,         GrBackendFormat::MakeVk(VK_FORMAT_R16G16B16A16_SFLOAT)  },
diff --git a/src/gpu/vk/GrVkCaps.h b/src/gpu/vk/GrVkCaps.h
index 57ab258..f136817 100644
--- a/src/gpu/vk/GrVkCaps.h
+++ b/src/gpu/vk/GrVkCaps.h
@@ -294,7 +294,7 @@
         std::unique_ptr<ColorTypeInfo[]> fColorTypeInfos;
         int fColorTypeInfoCount = 0;
     };
-    static const size_t kNumVkFormats = 21;
+    static const size_t kNumVkFormats = 22;
     FormatInfo fFormatTable[kNumVkFormats];
 
     FormatInfo& getFormatInfo(VkFormat);
diff --git a/src/gpu/vk/GrVkUtil.cpp b/src/gpu/vk/GrVkUtil.cpp
index 507acf3..bd73858 100644
--- a/src/gpu/vk/GrVkUtil.cpp
+++ b/src/gpu/vk/GrVkUtil.cpp
@@ -20,6 +20,7 @@
         case VK_FORMAT_R8G8B8_UNORM:
         case VK_FORMAT_R8G8_UNORM:
         case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
+        case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
         case VK_FORMAT_R5G6B5_UNORM_PACK16:
         case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
         case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
diff --git a/src/gpu/vk/GrVkUtil.h b/src/gpu/vk/GrVkUtil.h
index 8aed18c..e66cd81 100644
--- a/src/gpu/vk/GrVkUtil.h
+++ b/src/gpu/vk/GrVkUtil.h
@@ -60,6 +60,7 @@
         case VK_FORMAT_R8G8B8_UNORM:             return kRGB_SkColorChannelFlags;
         case VK_FORMAT_R8G8_UNORM:               return kRG_SkColorChannelFlags;
         case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return kRGBA_SkColorChannelFlags;
+        case VK_FORMAT_A2R10G10B10_UNORM_PACK32: return kRGBA_SkColorChannelFlags;
         case VK_FORMAT_B4G4R4A4_UNORM_PACK16:    return kRGBA_SkColorChannelFlags;
         case VK_FORMAT_R4G4B4A4_UNORM_PACK16:    return kRGBA_SkColorChannelFlags;
         case VK_FORMAT_R32G32B32A32_SFLOAT:      return kRGBA_SkColorChannelFlags;
@@ -117,6 +118,7 @@
         case VK_FORMAT_R8G8B8_UNORM:             return "R8G8B8_UNORM";
         case VK_FORMAT_R8G8_UNORM:               return "R8G8_UNORM";
         case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return "A2B10G10R10_UNORM_PACK32";
+        case VK_FORMAT_A2R10G10B10_UNORM_PACK32: return "A2R10G10B10_UNORM_PACK32";
         case VK_FORMAT_B4G4R4A4_UNORM_PACK16:    return "B4G4R4A4_UNORM_PACK16";
         case VK_FORMAT_R4G4B4A4_UNORM_PACK16:    return "R4G4B4A4_UNORM_PACK16";
         case VK_FORMAT_R32G32B32A32_SFLOAT:      return "R32G32B32A32_SFLOAT";
diff --git a/src/pdf/SkPDFSubsetFont.cpp b/src/pdf/SkPDFSubsetFont.cpp
index df2999e..81c37ee 100644
--- a/src/pdf/SkPDFSubsetFont.cpp
+++ b/src/pdf/SkPDFSubsetFont.cpp
@@ -17,7 +17,7 @@
 #include "hb-subset.h"
 
 template <class T, void(*P)(T*)> using resource =
-    std::unique_ptr<T, SkFunctionWrapper<skstd::remove_pointer_t<decltype(P)>, P>>;
+    std::unique_ptr<T, SkFunctionWrapper<std::remove_pointer_t<decltype(P)>, P>>;
 using HBBlob = resource<hb_blob_t, &hb_blob_destroy>;
 using HBFace = resource<hb_face_t, &hb_face_destroy>;
 using HBSubsetInput = resource<hb_subset_input_t, &hb_subset_input_destroy>;
diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
index e18b286..35b87bb 100644
--- a/src/ports/SkFontHost_FreeType.cpp
+++ b/src/ports/SkFontHost_FreeType.cpp
@@ -933,7 +933,7 @@
     }
 
     using DoneFTSize = SkFunctionWrapper<decltype(FT_Done_Size), FT_Done_Size>;
-    std::unique_ptr<skstd::remove_pointer_t<FT_Size>, DoneFTSize> ftSize([this]() -> FT_Size {
+    std::unique_ptr<std::remove_pointer_t<FT_Size>, DoneFTSize> ftSize([this]() -> FT_Size {
         FT_Size size;
         FT_Error err = FT_New_Size(fFaceRec->fFace.get(), &size);
         if (err != 0) {
diff --git a/src/ports/SkFontMgr_android_parser.cpp b/src/ports/SkFontMgr_android_parser.cpp
index 6f4ec76..60b94bd 100644
--- a/src/ports/SkFontMgr_android_parser.cpp
+++ b/src/ports/SkFontMgr_android_parser.cpp
@@ -644,7 +644,7 @@
         return -1;
     }
 
-    SkAutoTCallVProc<skstd::remove_pointer_t<XML_Parser>, XML_ParserFree> parser(
+    SkAutoTCallVProc<std::remove_pointer_t<XML_Parser>, XML_ParserFree> parser(
         XML_ParserCreate_MM(nullptr, &sk_XML_alloc, nullptr));
     if (!parser) {
         SkDebugf(SK_FONTMGR_ANDROID_PARSER_PREFIX "could not create XML parser\n");
diff --git a/src/sksl/SkSLByteCode.cpp b/src/sksl/SkSLByteCode.cpp
index c2a1ab6..3af0f6f 100644
--- a/src/sksl/SkSLByteCode.cpp
+++ b/src/sksl/SkSLByteCode.cpp
@@ -51,6 +51,7 @@
         VECTOR_MATRIX_DISASSEMBLE(kAddF, "addf")
         VECTOR_DISASSEMBLE(kAddI, "addi")
         case ByteCodeInstruction::kAndB: printf("andb"); break;
+        VECTOR_DISASSEMBLE(kATan, "atan")
         case ByteCodeInstruction::kBranch: printf("branch %d", READ16()); break;
         case ByteCodeInstruction::kCall: printf("call %d", READ8()); break;
         case ByteCodeInstruction::kCallExternal: {
@@ -85,6 +86,7 @@
         VECTOR_DISASSEMBLE(kDivideS, "divideS")
         VECTOR_DISASSEMBLE(kDivideU, "divideu")
         VECTOR_MATRIX_DISASSEMBLE(kDup, "dup")
+        VECTOR_DISASSEMBLE(kFract, "fract")
         case ByteCodeInstruction::kInverse2x2: printf("inverse2x2"); break;
         case ByteCodeInstruction::kInverse3x3: printf("inverse3x3"); break;
         case ByteCodeInstruction::kInverse4x4: printf("inverse4x4"); break;
@@ -654,6 +656,8 @@
                 continue;
             }
 
+            VECTOR_UNARY_FN(kFract, skvx::fract, fFloat)
+
             case ByteCodeInstruction::kInverse2x2:
                 Inverse2x2(sp);
                 continue;
@@ -1062,6 +1066,7 @@
                 continue;
             }
 
+            VECTOR_UNARY_FN(kATan, skvx::atan, fFloat)
             VECTOR_UNARY_FN(kTan, skvx::tan, fFloat)
 
             case ByteCodeInstruction::kWriteExternal4:
diff --git a/src/sksl/SkSLByteCode.h b/src/sksl/SkSLByteCode.h
index f2acdf7..7c1f1ec 100644
--- a/src/sksl/SkSLByteCode.h
+++ b/src/sksl/SkSLByteCode.h
@@ -27,6 +27,7 @@
     VECTOR_MATRIX(kAddF),
     VECTOR(kAddI),
     kAndB,
+    VECTOR(kATan),
     kBranch,
     // Followed by a byte indicating the index of the function to call
     kCall,
@@ -60,6 +61,7 @@
     VECTOR(kDivideU),
     // Duplicates the top stack value
     VECTOR_MATRIX(kDup),
+    VECTOR(kFract),
     kInverse2x2,
     kInverse3x3,
     kInverse4x4,
diff --git a/src/sksl/SkSLByteCodeGenerator.cpp b/src/sksl/SkSLByteCodeGenerator.cpp
index 210b54e..60bff34 100644
--- a/src/sksl/SkSLByteCodeGenerator.cpp
+++ b/src/sksl/SkSLByteCodeGenerator.cpp
@@ -1,4 +1,4 @@
-/*
+/*
  * Copyright 2019 Google LLC
  *
  * Use of this source code is governed by a BSD-style license that can be
@@ -43,8 +43,10 @@
     , fContext(*context)
     , fOutput(output)
     , fIntrinsics {
+        { "atan",    ByteCodeInstruction::kATan },
         { "cos",     ByteCodeInstruction::kCos },
         { "dot",     SpecialIntrinsic::kDot },
+        { "fract",   ByteCodeInstruction::kFract },
         { "inverse", ByteCodeInstruction::kInverse2x2 },
         { "sin",     ByteCodeInstruction::kSin },
         { "sqrt",    ByteCodeInstruction::kSqrt },
@@ -217,7 +219,9 @@
         VECTOR_UNARY_OP(kConvertStoF)
         VECTOR_UNARY_OP(kConvertUtoF)
 
+        VECTOR_UNARY_OP(kATan)
         VECTOR_UNARY_OP(kCos)
+        VECTOR_UNARY_OP(kFract)
         VECTOR_UNARY_OP(kSin)
         VECTOR_UNARY_OP(kSqrt)
         VECTOR_UNARY_OP(kTan)
@@ -994,7 +998,9 @@
         }
     } else {
         switch (found->second.fValue.fInstruction) {
+            case ByteCodeInstruction::kATan:
             case ByteCodeInstruction::kCos:
+            case ByteCodeInstruction::kFract:
             case ByteCodeInstruction::kSin:
             case ByteCodeInstruction::kSqrt:
             case ByteCodeInstruction::kTan:
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index f8eb00d..8061f21 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -288,6 +288,17 @@
     if (modifiers.fLayout.fKey && (modifiers.fFlags & Modifiers::kUniform_Flag)) {
         fErrors.error(decls.fOffset, "'key' is not permitted on 'uniform' variables");
     }
+    if (modifiers.fLayout.fMarker.fLength) {
+        if (fKind != Program::kPipelineStage_Kind) {
+            fErrors.error(decls.fOffset, "'marker' is only permitted in runtime effects");
+        }
+        if (!(modifiers.fFlags & Modifiers::kUniform_Flag)) {
+            fErrors.error(decls.fOffset, "'marker' is only permitted on 'uniform' variables");
+        }
+        if (*baseType != *fContext.fFloat4x4_Type) {
+            fErrors.error(decls.fOffset, "'marker' is only permitted on float4x4 variables");
+        }
+    }
     if (modifiers.fFlags & Modifiers::kVarying_Flag) {
         if (fKind != Program::kPipelineStage_Kind) {
             fErrors.error(decls.fOffset, "'varying' is only permitted in runtime effects");
@@ -1552,21 +1563,29 @@
         int64_t leftVal  = ((IntLiteral&) left).fValue;
         int64_t rightVal = ((IntLiteral&) right).fValue;
         switch (op) {
-            case Token::Kind::TK_PLUS:       return RESULT(Int, +);
-            case Token::Kind::TK_MINUS:      return RESULT(Int, -);
-            case Token::Kind::TK_STAR:       return RESULT(Int, *);
+            case Token::Kind::TK_PLUS:       return URESULT(Int, +);
+            case Token::Kind::TK_MINUS:      return URESULT(Int, -);
+            case Token::Kind::TK_STAR:       return URESULT(Int, *);
             case Token::Kind::TK_SLASH:
-                if (rightVal) {
-                    return RESULT(Int, /);
+                if (leftVal == std::numeric_limits<int64_t>::min() && rightVal == -1) {
+                    fErrors.error(right.fOffset, "arithmetic overflow");
+                    return nullptr;
                 }
-                fErrors.error(right.fOffset, "division by zero");
-                return nullptr;
+                if (!rightVal) {
+                    fErrors.error(right.fOffset, "division by zero");
+                    return nullptr;
+                }
+                return RESULT(Int, /);
             case Token::Kind::TK_PERCENT:
-                if (rightVal) {
-                    return RESULT(Int, %);
+                if (leftVal == std::numeric_limits<int64_t>::min() && rightVal == -1) {
+                    fErrors.error(right.fOffset, "arithmetic overflow");
+                    return nullptr;
                 }
-                fErrors.error(right.fOffset, "division by zero");
-                return nullptr;
+                if (!rightVal) {
+                    fErrors.error(right.fOffset, "division by zero");
+                    return nullptr;
+                }
+                return RESULT(Int, %);
             case Token::Kind::TK_BITWISEAND: return RESULT(Int,  &);
             case Token::Kind::TK_BITWISEOR:  return RESULT(Int,  |);
             case Token::Kind::TK_BITWISEXOR: return RESULT(Int,  ^);
diff --git a/src/sksl/SkSLParser.cpp b/src/sksl/SkSLParser.cpp
index 697edc7..0037870 100644
--- a/src/sksl/SkSLParser.cpp
+++ b/src/sksl/SkSLParser.cpp
@@ -85,6 +85,7 @@
     TOKEN(TRIANGLES_ADJACENCY,          "triangles_adjacency");
     TOKEN(MAX_VERTICES,                 "max_vertices");
     TOKEN(INVOCATIONS,                  "invocations");
+    TOKEN(MARKER,                       "marker");
     TOKEN(WHEN,                         "when");
     TOKEN(KEY,                          "key");
     TOKEN(TRACKED,                      "tracked");
@@ -771,14 +772,15 @@
     Layout::Primitive primitive = Layout::kUnspecified_Primitive;
     int maxVertices = -1;
     int invocations = -1;
+    StringFragment marker;
     StringFragment when;
     Layout::Key key = Layout::kNo_Key;
     Layout::CType ctype = Layout::CType::kDefault;
     if (this->checkNext(Token::Kind::TK_LAYOUT)) {
         if (!this->expect(Token::Kind::TK_LPAREN, "'('")) {
             return Layout(flags, location, offset, binding, index, set, builtin,
-                          inputAttachmentIndex, format, primitive, maxVertices, invocations, when,
-                          key, ctype);
+                          inputAttachmentIndex, format, primitive, maxVertices, invocations, marker,
+                          when, key, ctype);
         }
         for (;;) {
             Token t = this->nextToken();
@@ -894,6 +896,9 @@
                     case LayoutToken::INVOCATIONS:
                         invocations = this->layoutInt();
                         break;
+                    case LayoutToken::MARKER:
+                        marker = this->layoutCode();
+                        break;
                     case LayoutToken::WHEN:
                         when = this->layoutCode();
                         break;
@@ -921,7 +926,7 @@
         }
     }
     return Layout(flags, location, offset, binding, index, set, builtin, inputAttachmentIndex,
-                  format, primitive, maxVertices, invocations, when, key, ctype);
+                  format, primitive, maxVertices, invocations, marker, when, key, ctype);
 }
 
 /* layout? (UNIFORM | CONST | IN | OUT | INOUT | LOWP | MEDIUMP | HIGHP | FLAT | NOPERSPECTIVE |
@@ -1508,7 +1513,11 @@
         return ASTNode::ID::Invalid();
     }
     Token t;
+    AutoDepth depth(this);
     while (this->checkNext(Token::Kind::TK_COMMA, &t)) {
+        if (!depth.increase()) {
+            return ASTNode::ID::Invalid();
+        }
         ASTNode::ID right = this->assignmentExpression();
         if (!right) {
             return ASTNode::ID::Invalid();
diff --git a/src/sksl/SkSLParser.h b/src/sksl/SkSLParser.h
index e66b950..980133f 100644
--- a/src/sksl/SkSLParser.h
+++ b/src/sksl/SkSLParser.h
@@ -68,6 +68,7 @@
         TRIANGLES_ADJACENCY,
         MAX_VERTICES,
         INVOCATIONS,
+        MARKER,
         WHEN,
         KEY,
         TRACKED,
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.cpp b/src/sksl/SkSLSPIRVCodeGenerator.cpp
index 50619cb..b01bd95 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/SkSLSPIRVCodeGenerator.cpp
@@ -1823,7 +1823,7 @@
             SkASSERT(fProgram.fSettings.fRTHeightOffset >= 0);
             fields.emplace_back(Modifiers(Layout(0, -1, fProgram.fSettings.fRTHeightOffset, -1,
                                                  -1, -1, -1, -1, Layout::Format::kUnspecified,
-                                                 Layout::kUnspecified_Primitive, -1, -1, "",
+                                                 Layout::kUnspecified_Primitive, -1, -1, "", "",
                                                  Layout::kNo_Key, Layout::CType::kDefault), 0),
                                 SKSL_RTHEIGHT_NAME, fContext.fFloat_Type.get());
             StringFragment name("sksl_synthetic_uniforms");
@@ -1834,7 +1834,7 @@
             SkASSERT(binding != -1 && set != -1);
 
             Layout layout(0, -1, -1, binding, -1, set, -1, -1, Layout::Format::kUnspecified,
-                          Layout::kUnspecified_Primitive, -1, -1, "", Layout::kNo_Key,
+                          Layout::kUnspecified_Primitive, -1, -1, "", "", Layout::kNo_Key,
                           Layout::CType::kDefault);
             Variable* intfVar = (Variable*) fSynthetics.takeOwnership(std::unique_ptr<Symbol>(
                                            new Variable(-1,
diff --git a/src/sksl/ir/SkSLLayout.h b/src/sksl/ir/SkSLLayout.h
index fd85cc5..8ec3d77 100644
--- a/src/sksl/ir/SkSLLayout.h
+++ b/src/sksl/ir/SkSLLayout.h
@@ -189,7 +189,7 @@
 
     Layout(int flags, int location, int offset, int binding, int index, int set, int builtin,
            int inputAttachmentIndex, Format format, Primitive primitive, int maxVertices,
-           int invocations, StringFragment when, Key key, CType ctype)
+           int invocations, StringFragment marker, StringFragment when, Key key, CType ctype)
     : fFlags(flags)
     , fLocation(location)
     , fOffset(offset)
@@ -202,6 +202,7 @@
     , fPrimitive(primitive)
     , fMaxVertices(maxVertices)
     , fInvocations(invocations)
+    , fMarker(marker)
     , fWhen(when)
     , fKey(key)
     , fCType(ctype) {}
@@ -377,6 +378,10 @@
             result += separator + "invocations = " + to_string(fInvocations);
             separator = ", ";
         }
+        if (fMarker.fLength) {
+            result += separator + "marker = " + fMarker;
+            separator = ", ";
+        }
         if (fWhen.fLength) {
             result += separator + "when = " + fWhen;
             separator = ", ";
@@ -403,6 +408,7 @@
                fPrimitive            == other.fPrimitive &&
                fMaxVertices          == other.fMaxVertices &&
                fInvocations          == other.fInvocations &&
+               fMarker               == other.fMarker &&
                fWhen                 == other.fWhen &&
                fKey                  == other.fKey &&
                fCType                == other.fCType;
@@ -428,6 +434,8 @@
     Primitive fPrimitive;
     int fMaxVertices;
     int fInvocations;
+    // marker refers to matrices tagged on the SkCanvas with markCTM
+    StringFragment fMarker;
     StringFragment fWhen;
     Key fKey;
     CType fCType;
diff --git a/src/utils/SkBitSet.h b/src/utils/SkBitSet.h
index 770b0d8..b5c7cb2 100644
--- a/src/utils/SkBitSet.h
+++ b/src/utils/SkBitSet.h
@@ -9,6 +9,7 @@
 #define SkBitSet_DEFINED
 
 #include "include/private/SkTemplates.h"
+#include "src/core/SkMathPriv.h"
 
 class SkBitSet {
 public:
@@ -28,6 +29,14 @@
         *chunk |= mask;
     }
 
+    /** Set the value of the index-th bit to false.  */
+    void clear(int index) {
+        uint32_t mask = ~(1 << (index & 31));
+        uint32_t* chunk = this->internalGet(index);
+        SkASSERT(chunk);
+        *chunk &= mask;
+    }
+
     bool has(int index) const {
         const uint32_t* chunk = this->internalGet(index);
         uint32_t mask = 1 << (index & 31);
@@ -50,6 +59,19 @@
         }
     }
 
+    // Returns the index of the first set bit
+    // Returns -1 if no bits are set
+    int leadingBitIndex() {
+        const uint32_t* data = fBitData.get();
+        for (unsigned i = 0; i < fDwordCount; ++i) {
+            if (uint32_t value = data[i]) {  // There are set bits
+                int index = SkPrevLog2(value);
+                return index + i * 32;
+            }
+        }
+        return -1;
+    }
+
 private:
     std::unique_ptr<uint32_t, SkFunctionWrapper<void(void*), sk_free>> fBitData;
     size_t fDwordCount;  // Dword (32-bit) count of the bitset.
diff --git a/src/utils/mac/SkUniqueCFRef.h b/src/utils/mac/SkUniqueCFRef.h
index 80640de..f5086ac 100644
--- a/src/utils/mac/SkUniqueCFRef.h
+++ b/src/utils/mac/SkUniqueCFRef.h
@@ -11,14 +11,14 @@
 #include "include/core/SkTypes.h"
 #if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
 
-#include "include/private/SkTLogic.h"
 #include "include/private/SkTemplates.h"
 
 #include <CoreFoundation/CoreFoundation.h>
 #include <memory>
+#include <type_traits>
 
 template <typename CFRef> using SkUniqueCFRef =
-    std::unique_ptr<skstd::remove_pointer_t<CFRef>,
+    std::unique_ptr<std::remove_pointer_t<CFRef>,
                     SkFunctionWrapper<decltype(CFRelease), CFRelease>>;
 
 #endif
diff --git a/src/xml/SkXMLParser.cpp b/src/xml/SkXMLParser.cpp
index 2e258da..5cf2ed8 100644
--- a/src/xml/SkXMLParser.cpp
+++ b/src/xml/SkXMLParser.cpp
@@ -80,7 +80,7 @@
     }
 
     SkXMLParser* fParser;
-    SkAutoTCallVProc<skstd::remove_pointer_t<XML_Parser>, XML_ParserFree> fXMLParser;
+    SkAutoTCallVProc<std::remove_pointer_t<XML_Parser>, XML_ParserFree> fXMLParser;
 
 private:
     SkString fBufferedText;
diff --git a/tests/BackendAllocationTest.cpp b/tests/BackendAllocationTest.cpp
index 2b8a906..ca98152 100644
--- a/tests/BackendAllocationTest.cpp
+++ b/tests/BackendAllocationTest.cpp
@@ -65,8 +65,8 @@
                                                                   skColorType,
                                                                   nullptr, nullptr);
         if (!surf) {
-            ERRORF(reporter, "Couldn't make surface from backendTexture for colorType %d\n",
-                   skColorType);
+            ERRORF(reporter, "Couldn't make surface from backendTexture for %s\n",
+                   ToolUtils::colortype_name(skColorType));
         } else {
             REPORTER_ASSERT(reporter, initialCount+1 == cache->getResourceCount());
         }
@@ -80,8 +80,8 @@
                                                       kPremul_SkAlphaType,
                                                       nullptr);
         if (!img) {
-            ERRORF(reporter, "Couldn't make image from backendTexture for skColorType %d\n",
-                   skColorType);
+            ERRORF(reporter, "Couldn't make image from backendTexture for %s\n",
+                   ToolUtils::colortype_name(skColorType));
         } else {
             SkImage_Base* ib = as_IB(img);
 
@@ -101,7 +101,7 @@
     context->deleteBackendTexture(backendTex);
 }
 
-static bool isBGRA(const GrBackendFormat& format) {
+static bool isBGRA8(const GrBackendFormat& format) {
     switch (format.backend()) {
         case GrBackendApi::kOpenGL:
 #ifdef SK_GL
@@ -120,7 +120,7 @@
         }
         case GrBackendApi::kMetal:
 #ifdef SK_METAL
-            return GrMtlFormatIsBGRA(format.asMtlFormat());
+            return GrMtlFormatIsBGRA8(format.asMtlFormat());
 #else
             return false;
 #endif
@@ -440,7 +440,7 @@
         return;
     }
 
-    if (skColorType == kBGRA_8888_SkColorType && !isBGRA(backendTex.getBackendFormat())) {
+    if (skColorType == kBGRA_8888_SkColorType && !isBGRA8(backendTex.getBackendFormat())) {
         // When kBGRA is backed by an RGBA something goes wrong in the swizzling
         return;
     }
@@ -601,11 +601,11 @@
         { kRGB_888x_SkColorType,          SkColors::kCyan          },
         // TODO: readback is busted when alpha = 0.5f (perhaps premul vs. unpremul)
         { kBGRA_8888_SkColorType,         { 1, 0, 0, 1.0f }        },
-        // TODO: readback is busted when alpha = 0.5f (perhaps premul vs. unpremul)
-        { kRGBA_1010102_SkColorType,      { .25f, .5f, .75f, 1.0f }},
-        // RGB/BGR 101010x and BGRA 1010102 have no Ganesh correlate
+        // TODO: readback is busted for *10A2 when alpha = 0.5f (perhaps premul vs. unpremul)
+        { kRGBA_1010102_SkColorType,      { 0.25f, 0.5f, 0.75f, 1.0f }},
+        { kBGRA_1010102_SkColorType,      { 0.25f, 0.5f, 0.75f, 1.0f }},
+        // RGB/BGR 101010x have no Ganesh correlate
         { kRGB_101010x_SkColorType,       { 0, 0.5f, 0, 0.5f }     },
-        { kBGRA_1010102_SkColorType,      { 0, 0.5f, 0, 0.5f }     },
         { kBGR_101010x_SkColorType,       { 0, 0.5f, 0, 0.5f }     },
         { kGray_8_SkColorType,            kGrayCol                 },
         { kRGBA_F16Norm_SkColorType,      SkColors::kLtGray        },
@@ -713,26 +713,28 @@
                                     renderable);
                 }
 
-                auto createWithSrcDataMtd = [](GrContext* context,
-                                               const SkPixmap srcData[],
-                                               int numLevels,
-                                               GrRenderable renderable) {
-                    SkASSERT(srcData && numLevels);
-                    auto result = context->createBackendTexture(srcData, numLevels, renderable,
-                                                                GrProtected::kNo);
-                    check_vk_layout(result, VkLayout::kReadOnlyOptimal);
+                {
+                    auto createWithSrcDataMtd = [](GrContext* context,
+                                                   const SkPixmap srcData[],
+                                                   int numLevels,
+                                                   GrRenderable renderable) {
+                        SkASSERT(srcData && numLevels);
+                        auto result = context->createBackendTexture(srcData, numLevels, renderable,
+                                                                    GrProtected::kNo);
+                        check_vk_layout(result, VkLayout::kReadOnlyOptimal);
 #ifdef SK_DEBUG
-                    {
-                        auto format =
-                                context->defaultBackendFormat(srcData[0].colorType(), renderable);
-                        SkASSERT(format == result.getBackendFormat());
-                    }
+                        {
+                            auto format = context->defaultBackendFormat(srcData[0].colorType(),
+                                                                        renderable);
+                            SkASSERT(format == result.getBackendFormat());
+                        }
 #endif
-                    return result;
-                };
+                        return result;
+                    };
 
-                test_pixmap_init(context, reporter, createWithSrcDataMtd, colorType, mipMapped,
-                                 renderable);
+                    test_pixmap_init(context, reporter, createWithSrcDataMtd, colorType, mipMapped,
+                                     renderable);
+                }
             }
         }
     }
@@ -764,7 +766,8 @@
         { GrColorType::kBGRA_8888,        GR_GL_RGBA8,                SkColors::kBlue      },
         { GrColorType::kBGRA_8888,        GR_GL_BGRA8,                SkColors::kBlue      },
         // TODO: readback is busted when alpha = 0.5f (perhaps premul vs. unpremul)
-        { GrColorType::kRGBA_1010102,     GR_GL_RGB10_A2,             { 0.5f, 0, 0, 1.0f } },
+        { GrColorType::kRGBA_1010102,     GR_GL_RGB10_A2,             { 0.25f, 0.5f, 0.75f, 1.f }},
+        { GrColorType::kBGRA_1010102,     GR_GL_RGB10_A2,             { 0.25f, 0.5f, 0.75f, 1.f }},
         { GrColorType::kBGR_565,          GR_GL_RGB565,               SkColors::kRed       },
         { GrColorType::kABGR_4444,        GR_GL_RGBA4,                SkColors::kGreen     },
 
@@ -797,9 +800,11 @@
             continue;
         }
 
-        if (GrColorType::kBGRA_8888 == combo.fColorType) {
-            // We allow using a GL_RGBA8 texture as BGRA on desktop GL but not ES.
-            if (GR_GL_RGBA8 == combo.fFormat && kGL_GrGLStandard != standard) {
+        if (GrColorType::kBGRA_8888 == combo.fColorType ||
+            GrColorType::kBGRA_1010102 == combo.fColorType) {
+            // We allow using a GL_RGBA8 or GR_GL_RGB10_A2 texture as BGRA on desktop GL but not ES
+            if (kGL_GrGLStandard != standard &&
+                (GR_GL_RGBA8 == combo.fFormat || GR_GL_RGB10_A2 == combo.fFormat)) {
                 continue;
             }
         }
@@ -915,7 +920,10 @@
 
         { GrColorType::kBGRA_8888,        VK_FORMAT_B8G8R8A8_UNORM,           SkColors::kBlue     },
 
-        { GrColorType::kRGBA_1010102,     VK_FORMAT_A2B10G10R10_UNORM_PACK32, { 0.5f, 0, 0, 1.0f }},
+        { GrColorType::kRGBA_1010102,     VK_FORMAT_A2B10G10R10_UNORM_PACK32,
+                                                                      { 0.25f, 0.5f, 0.75f, 1.0f }},
+        { GrColorType::kBGRA_1010102,     VK_FORMAT_A2R10G10B10_UNORM_PACK32,
+                                                                      { 0.25f, 0.5f, 0.75f, 1.0f }},
         { GrColorType::kBGR_565,          VK_FORMAT_R5G6B5_UNORM_PACK16,      SkColors::kRed      },
 
         { GrColorType::kABGR_4444,        VK_FORMAT_R4G4B4A4_UNORM_PACK16,    SkColors::kCyan     },
diff --git a/tests/GrStyledShapeTest.cpp b/tests/GrStyledShapeTest.cpp
index 712f351..b5a7cf2 100644
--- a/tests/GrStyledShapeTest.cpp
+++ b/tests/GrStyledShapeTest.cpp
@@ -27,11 +27,11 @@
 }
 
 bool GrStyledShape::testingOnly_isPath() const {
-    return Type::kPath == fType;
+    return fShape.isPath();
 }
 
 bool GrStyledShape::testingOnly_isNonVolatilePath() const {
-    return Type::kPath == fType && !fPathData.fPath.isVolatile();
+    return fShape.isPath() && !fShape.path().isVolatile();
 }
 
 using Key = SkTArray<uint32_t>;
@@ -360,9 +360,10 @@
 
     bool strokeAndFillIsConvertedToFill(const SkPaint& paint) const override {
         SkASSERT(paint.getStyle() == SkPaint::kStrokeAndFill_Style);
-        // Converted to an outset rectangle.
-        return paint.getStrokeJoin() == SkPaint::kMiter_Join &&
-               paint.getStrokeMiter() >= SK_ScalarSqrt2;
+        // Converted to an outset rectangle or round rect
+        return (paint.getStrokeJoin() == SkPaint::kMiter_Join &&
+                paint.getStrokeMiter() >= SK_ScalarSqrt2) ||
+               paint.getStrokeJoin() == SkPaint::kRound_Join;
     }
 
 private:
@@ -1569,9 +1570,9 @@
     dashAndStrokeInvertexEmptyCase.compare(reporter, fillEmptyCase,
                                            TestCase::kAllSame_ComparisonExpecation);
 
-    // A shape made from an empty rrect should behave the same as an empty path when filled but not
-    // when stroked. However, dashing an empty rrect produces an empty path leaving nothing to
-    // stroke - so equivalent to filling an empty path.
+    // A shape made from an empty rrect should behave the same as an empty path when filled and
+    // when stroked. The shape is closed so it does not produce caps when stroked. When dashed there
+    // is no path to dash along, making it equivalent as well.
     SkRRect emptyRRect = SkRRect::MakeEmpty();
     REPORTER_ASSERT(reporter, emptyRRect.getType() == SkRRect::kEmpty_Type);
 
@@ -1580,7 +1581,7 @@
 
     TestCase strokeEmptyRRectCase(reporter, emptyRRect, stroke);
     strokeEmptyRRectCase.compare(reporter, strokeEmptyCase,
-                                 TestCase::kAllDifferent_ComparisonExpecation);
+                                 TestCase::kAllSame_ComparisonExpecation);
 
     TestCase dashAndStrokeEmptyRRectCase(reporter, emptyRRect, dashAndStroke);
     dashAndStrokeEmptyRRectCase.compare(reporter, fillEmptyCase,
@@ -1596,7 +1597,7 @@
     TestCase strokeInvertedEmptyRRectCase(reporter, emptyRRect, kDir, kStart, true,
                                           GrStyle(stroke));
     strokeInvertedEmptyRRectCase.compare(reporter, strokeInvertedEmptyCase,
-                                         TestCase::kAllDifferent_ComparisonExpecation);
+                                         TestCase::kAllSame_ComparisonExpecation);
 
     TestCase dashAndStrokeEmptyInvertedRRectCase(reporter, emptyRRect, kDir, kStart, true,
                                                  GrStyle(dashAndStroke));
diff --git a/tests/MtlBackendAllocationTest.mm b/tests/MtlBackendAllocationTest.mm
index f7ea5b5..68b8e0c 100644
--- a/tests/MtlBackendAllocationTest.mm
+++ b/tests/MtlBackendAllocationTest.mm
@@ -49,7 +49,10 @@
 
         { GrColorType::kBGRA_8888,        MTLPixelFormatBGRA8Unorm,      SkColors::kBlue      },
 
-        { GrColorType::kRGBA_1010102,     MTLPixelFormatRGB10A2Unorm,    { 0.5f, 0, 0, 1.0f } },
+        { GrColorType::kRGBA_1010102,     MTLPixelFormatRGB10A2Unorm,
+                                                                    { 0.25f, 0.5f, 0.75f, 1.0f } },
+        { GrColorType::kBGRA_1010102,     MTLPixelFormatBGR10A2Unorm,
+                                                                    { 0.25f, 0.5f, 0.75f, 1.0f } },
 #ifdef SK_BUILD_FOR_IOS
         { GrColorType::kBGR_565,          MTLPixelFormatB5G6R5Unorm,     SkColors::kRed       },
         { GrColorType::kABGR_4444,        MTLPixelFormatABGR4Unorm,      SkColors::kGreen     },
diff --git a/tests/ReadWriteAlphaTest.cpp b/tests/ReadWriteAlphaTest.cpp
index 3c7fac3..df18ab9 100644
--- a/tests/ReadWriteAlphaTest.cpp
+++ b/tests/ReadWriteAlphaTest.cpp
@@ -31,8 +31,9 @@
         for (int x = 0; x < w; ++x) {
             uint8_t a = actual[y * actualRowBytes + x];
             uint8_t e = expected[y * w + x];
-            if (GrColorType::kRGBA_1010102 == colorType) {
-                // This config only preserves two bits of alpha
+            if (GrColorType::kRGBA_1010102 == colorType ||
+                GrColorType::kBGRA_1010102 == colorType) {
+                // These configs only preserves two bits of alpha
                 a >>= 6;
                 e >>= 6;
             }
diff --git a/tests/TransferPixelsTest.cpp b/tests/TransferPixelsTest.cpp
index 9a6539f..91f8206 100644
--- a/tests/TransferPixelsTest.cpp
+++ b/tests/TransferPixelsTest.cpp
@@ -182,8 +182,9 @@
     auto error = std::function<ComparePixmapsErrorReporter>(
             [reporter, colorType](int x, int y, const float diffs[4]) {
                 ERRORF(reporter,
-                       "Error at (%d %d) in transfer, color type: %d, diffs: (%f, %f, %f, %f)", x,
-                       y, colorType, diffs[0], diffs[1], diffs[2], diffs[3]);
+                       "Error at (%d %d) in transfer, color type: %s, diffs: (%f, %f, %f, %f)",
+                       x, y, GrColorTypeToStr(colorType),
+                       diffs[0], diffs[1], diffs[2], diffs[3]);
             });
     GrImageInfo srcInfo(allowedSrc.fColorType, kUnpremul_SkAlphaType, nullptr, tex->width(),
                         tex->height());
@@ -352,8 +353,9 @@
     auto error = std::function<ComparePixmapsErrorReporter>(
             [reporter, colorType](int x, int y, const float diffs[4]) {
                 ERRORF(reporter,
-                       "Error at (%d %d) in transfer, color type: %d, diffs: (%f, %f, %f, %f)", x,
-                       y, colorType, diffs[0], diffs[1], diffs[2], diffs[3]);
+                       "Error at (%d %d) in transfer, color type: %s, diffs: (%f, %f, %f, %f)",
+                       x, y, GrColorTypeToStr(colorType),
+                       diffs[0], diffs[1], diffs[2], diffs[3]);
             });
     GrImageInfo textureDataInfo(colorType, kUnpremul_SkAlphaType, nullptr, kTexDims);
     ComparePixels(textureDataInfo, textureData.get(), textureDataRowBytes, transferInfo,
@@ -412,6 +414,7 @@
                      GrColorType::kRG_88,
                      GrColorType::kBGRA_8888,
                      GrColorType::kRGBA_1010102,
+                     GrColorType::kBGRA_1010102,
                      GrColorType::kGray_8,
                      GrColorType::kAlpha_F16,
                      GrColorType::kRGBA_F16,
@@ -444,6 +447,7 @@
                      GrColorType::kRG_88,
                      GrColorType::kBGRA_8888,
                      GrColorType::kRGBA_1010102,
+                     GrColorType::kBGRA_1010102,
                      GrColorType::kGray_8,
                      GrColorType::kAlpha_F16,
                      GrColorType::kRGBA_F16,
diff --git a/tools/ToolUtils.cpp b/tools/ToolUtils.cpp
index b41e985..027f8f8 100644
--- a/tools/ToolUtils.cpp
+++ b/tools/ToolUtils.cpp
@@ -58,8 +58,8 @@
         case kRGB_888x_SkColorType:           return "RGB_888x";
         case kBGRA_8888_SkColorType:          return "BGRA_8888";
         case kRGBA_1010102_SkColorType:       return "RGBA_1010102";
-        case kRGB_101010x_SkColorType:        return "RGB_101010x";
         case kBGRA_1010102_SkColorType:       return "BGRA_1010102";
+        case kRGB_101010x_SkColorType:        return "RGB_101010x";
         case kBGR_101010x_SkColorType:        return "BGR_101010x";
         case kGray_8_SkColorType:             return "Gray_8";
         case kRGBA_F16Norm_SkColorType:       return "RGBA_F16Norm";
@@ -86,8 +86,8 @@
         case kRGB_888x_SkColorType:           return "888";
         case kBGRA_8888_SkColorType:          return "8888";
         case kRGBA_1010102_SkColorType:       return "1010102";
-        case kRGB_101010x_SkColorType:        return "101010";
         case kBGRA_1010102_SkColorType:       return "1010102";
+        case kRGB_101010x_SkColorType:        return "101010";
         case kBGR_101010x_SkColorType:        return "101010";
         case kGray_8_SkColorType:             return "G8";
         case kRGBA_F16Norm_SkColorType:       return "F16Norm";  // TODO: "F16"?
diff --git a/tools/skui/ModifierKey.h b/tools/skui/ModifierKey.h
index 09c5536..ffb358c 100644
--- a/tools/skui/ModifierKey.h
+++ b/tools/skui/ModifierKey.h
@@ -16,7 +16,7 @@
 };
 }
 
-namespace skstd {
+namespace sknonstd {
 template <> struct is_bitmask_enum<skui::ModifierKey> : std::true_type {};
 }
 #endif  // skui_modifierkey_defined
diff --git a/tools/viewer/SlideDir.cpp b/tools/viewer/SlideDir.cpp
index 95b1f4a..017febe 100644
--- a/tools/viewer/SlideDir.cpp
+++ b/tools/viewer/SlideDir.cpp
@@ -396,7 +396,7 @@
 bool SlideDir::onMouse(SkScalar x, SkScalar y, skui::InputState state,
                        skui::ModifierKey modifiers) {
     modifiers &= ~skui::ModifierKey::kFirstPress;
-    if (state == skui::InputState::kMove || skstd::Any(modifiers))
+    if (state == skui::InputState::kMove || sknonstd::Any(modifiers))
         return false;
 
     if (fFocusController->hasFocus()) {