Cut SkQP 2019-01-09

No-Try: true
Change-Id: I779fae73ac743c30a3b7e7ee066f4c08ac32a5b2
diff --git a/.clang-format b/.clang-format
index 018b06f..eb798ed 100644
--- a/.clang-format
+++ b/.clang-format
@@ -28,7 +28,7 @@
 AlwaysBreakAfterDefinitionReturnType: None
 AlwaysBreakAfterReturnType: None
 AlwaysBreakBeforeMultilineStrings: true
-AlwaysBreakTemplateDeclarations: true
+AlwaysBreakTemplateDeclarations: false
 BinPackArguments: true
 BinPackParameters: true
 BraceWrapping:
@@ -119,7 +119,7 @@
 AlwaysBreakAfterDefinitionReturnType: None
 AlwaysBreakAfterReturnType: None
 AlwaysBreakBeforeMultilineStrings: true
-AlwaysBreakTemplateDeclarations: true
+AlwaysBreakTemplateDeclarations: false
 BinPackArguments: true
 BinPackParameters: true
 BraceWrapping:
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..3b54cd1
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1 @@
+Checks: '-*,bugprone-use-after-move,google-build-namespaces'
diff --git a/AUTHORS b/AUTHORS
index aa6a5e6..a0fd225 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -15,6 +15,7 @@
 Amazon, Inc <*@amazon.com>
 Anthony Catel <paraboul@gmail.com>
 ARM <*@arm.com>
+Dawson Coleman <dawsonmcoleman@gmail.com>
 Ehsan Akhgari <ehsan.akhgari@gmail.com>
 Facebook, Inc. <*fb.com>
 George Wright <george@mozilla.com>
@@ -29,6 +30,7 @@
 Lee Salzman <lsalzman@mozilla.com>
 Marcin Kazmierczak <mar.kazmierczak@gmail.com>
 Matthew Leibowitz <mattleibow@live.com>
+Microsoft <*@microsoft.com>
 MIPS <*@imgtec.com>
 NVIDIA <*@nvidia.com>
 Opera Software ASA <*@opera.com>
diff --git a/BUILD.gn b/BUILD.gn
index b4cf3d3..60158c7 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -69,15 +69,18 @@
   skia_use_sfntly = skia_use_icu
   skia_enable_atlas_text = is_skia_dev_build && skia_enable_gpu
   skia_enable_fontmgr_empty = false
-  skia_enable_fontmgr_custom = (is_linux || target_cpu == "wasm") &&
-                               skia_use_freetype && !skia_use_fontconfig
+  skia_enable_fontmgr_custom =
+      is_linux && skia_use_freetype && !skia_use_fontconfig
   skia_enable_fontmgr_custom_empty = is_fuchsia && skia_use_freetype
   skia_enable_fontmgr_android = skia_use_expat && skia_use_freetype
+  skia_enable_fontmgr_fuchsia = is_fuchsia
 
   if (is_android) {
     skia_use_vulkan = defined(ndk_api) && ndk_api >= 24
   } else if (is_fuchsia) {
     skia_use_vulkan = fuchsia_use_vulkan
+  } else if (is_win && target_cpu == "arm64") {
+    skia_use_vulkan = false
   } else {
     skia_use_vulkan = (defined(skia_vulkan_sdk) && skia_vulkan_sdk != "") ||
                       (defined(skia_moltenvk_path) && skia_moltenvk_path != "")
@@ -186,7 +189,6 @@
   if (skia_enable_gpu) {
     include_dirs += [ "src/gpu" ]
     if (is_skia_dev_build && skia_use_vulkan) {
-      include_dirs += [ "third_party/vulkan" ]
       include_dirs += [ "tools/gpu/vk" ]
     }
   }
@@ -411,6 +413,32 @@
   ]
 }
 
+optional("fontmgr_wasm") {
+  enabled = target_cpu == "wasm"
+
+  deps = [
+    ":typeface_freetype",
+  ]
+  sources = [
+    "src/ports/SkFontMgr_custom.cpp",
+    "src/ports/SkFontMgr_custom.h",
+    "src/ports/SkFontMgr_custom_embedded.cpp",
+    "src/ports/SkFontMgr_custom_embedded_factory.cpp",
+  ]
+}
+
+optional("fontmgr_fuchsia") {
+  enabled = skia_enable_fontmgr_fuchsia
+
+  deps = [
+    "//garnet/public/fidl/fuchsia.fonts",
+  ]
+  sources = [
+    "src/ports/SkFontMgr_fuchsia.cpp",
+    "src/ports/SkFontMgr_fuchsia.h",
+  ]
+}
+
 optional("fontmgr_empty") {
   enabled = skia_enable_fontmgr_empty
   sources = [
@@ -612,7 +640,9 @@
     sources += [ "src/gpu/gl/iOS/GrGLMakeNativeInterface_iOS.cpp" ]
   } else if (is_win) {
     sources += [ "src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp" ]
-    libs += [ "OpenGL32.lib" ]
+    if (target_cpu != "arm64") {
+      libs += [ "OpenGL32.lib" ]
+    }
   } else {
     sources += [ "src/gpu/gl/GrGLMakeNativeInterface_none.cpp" ]
   }
@@ -624,6 +654,10 @@
     if (skia_enable_vulkan_debug_layers) {
       public_defines += [ "SK_ENABLE_VK_LAYERS" ]
     }
+    if (is_fuchsia) {
+      public_deps +=
+          [ "//third_party/vulkan_loader_and_validation_layers:vulkan" ]
+    }
   }
 
   if (skia_enable_spirv_validation) {
@@ -854,6 +888,8 @@
     ":fontmgr_custom_empty",
     ":fontmgr_empty",
     ":fontmgr_fontconfig",
+    ":fontmgr_fuchsia",
+    ":fontmgr_wasm",
     ":heif",
     ":hsw",
     ":jpeg",
@@ -983,9 +1019,7 @@
   }
 
   if (skia_use_fonthost_mac) {
-    sources += [
-      "src/ports/SkFontHost_mac.cpp",
-    ]
+    sources += [ "src/ports/SkFontHost_mac.cpp" ]
   }
 
   if (is_mac) {
@@ -1158,7 +1192,7 @@
 
     # We add this directory to simulate the client already have
     # vulkan/vulkan_core.h on their path.
-    include_dirs = [ "third_party/vulkan" ]
+    include_dirs = [ "include/third_party/vulkan" ]
   }
 
   template("test_lib") {
@@ -1404,10 +1438,10 @@
       sources += [ "tools/gpu/gl/mac/CreatePlatformGLTestContext_mac.cpp" ]
     } else if (is_win) {
       sources += [ "tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp" ]
-      libs += [
-        "Gdi32.lib",
-        "OpenGL32.lib",
-      ]
+      libs += [ "Gdi32.lib" ]
+      if (target_cpu != "arm64") {
+        libs += [ "OpenGL32.lib" ]
+      }
     }
 
     cflags_objcc = [ "-fobjc-arc" ]
@@ -1416,7 +1450,8 @@
       deps += [ "//third_party/angle2" ]
       sources += [ "tools/gpu/gl/angle/GLTestContext_angle.cpp" ]
     }
-    public_include_dirs += [ "third_party/vulkan" ]
+
+    public_include_dirs += [ "include/third_party/vulkan" ]
     if (skia_use_vulkan) {
       sources += [ "tools/gpu/vk/VkTestContext.cpp" ]
       sources += [ "tools/gpu/vk/VkTestUtils.cpp" ]
@@ -1716,7 +1751,6 @@
         "dm/DMJsonWriter.cpp",
         "dm/DMSrcSink.cpp",
       ]
-      include_dirs = [ "tests" ]
       deps = [
         ":common_flags",
         ":experimental_svg_model",
@@ -1837,7 +1871,7 @@
     ]
   }
 
-  if (!is_ios && target_cpu != "wasm") {
+  if (!is_ios && target_cpu != "wasm" && !(is_win && target_cpu == "arm64")) {
     test_app("skiaserve") {
       sources = [
         "tools/skiaserve/Request.cpp",
@@ -1875,6 +1909,7 @@
     include_dirs = [
       "tools",
       "tools/debugger",
+      "tools/fonts",
     ]
     sources = [
       "fuzz/Fuzz.cpp",
diff --git a/DEPS b/DEPS
index 85ced6f..340d5f2 100644
--- a/DEPS
+++ b/DEPS
@@ -7,13 +7,13 @@
 deps = {
   "buildtools"                            : "https://chromium.googlesource.com/chromium/buildtools.git@505de88083136eefd056e5ee4ca0f01fe9b33de8",
   "common"                                : "https://skia.googlesource.com/common.git@9737551d7a52c3db3262db5856e6bcd62c462b92",
-  "third_party/externals/angle2"          : "https://chromium.googlesource.com/angle/angle.git@e3e680ca47dc179a291915e6396b5616e9113c8a",
+  "third_party/externals/angle2"          : "https://chromium.googlesource.com/angle/angle.git@4f3b207d049a3d3ae25aabf280176b392fd0c533",
   "third_party/externals/dng_sdk"         : "https://android.googlesource.com/platform/external/dng_sdk.git@96443b262250c390b0caefbf3eed8463ba35ecae",
   "third_party/externals/egl-registry"    : "https://skia.googlesource.com/external/github.com/KhronosGroup/EGL-Registry@a0bca08de07c7d7651047bedc0b653cfaaa4f2ae",
   "third_party/externals/expat"           : "https://android.googlesource.com/platform/external/expat.git@android-6.0.1_r55",
   "third_party/externals/freetype"        : "https://skia.googlesource.com/third_party/freetype2.git@7edc937fe679d14d66f55cf6f7fa607925d38f3c",
   "third_party/externals/harfbuzz"        : "https://skia.googlesource.com/third_party/harfbuzz.git@8be74d85534534dbdd39a0a6f496e26e9f3e661d",
-  "third_party/externals/icu"             : "https://chromium.googlesource.com/chromium/deps/icu.git@ec9c1133693148470ffe2e5e53576998e3650c1d",
+  "third_party/externals/icu"             : "https://chromium.googlesource.com/chromium/deps/icu.git@407b39301e71006b68bd38e770f35d32398a7b14",
   "third_party/externals/imgui"           : "https://skia.googlesource.com/external/github.com/ocornut/imgui.git@bc6ac8b2aee0614debd940e45bc9cd0d9b355c86",
   # TODO: remove jsoncpp after migrating clients to SkJSON
   "third_party/externals/jsoncpp"         : "https://chromium.googlesource.com/external/github.com/open-source-parsers/jsoncpp.git@1.0.0",
@@ -26,13 +26,13 @@
   "third_party/externals/opengl-registry" : "https://skia.googlesource.com/external/github.com/KhronosGroup/OpenGL-Registry@14b80ebeab022b2c78f84a573f01028c96075553",
   "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@b18b09b6114b9b7fe6fc2f96d8b15e8a72f66916",
+  "third_party/externals/sfntly"          : "https://chromium.googlesource.com/external/github.com/googlei18n/sfntly.git@b55ff303ea2f9e26702b514cf6a3196a2e3e2974",
   "third_party/externals/spirv-headers"   : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Headers.git@661ad91124e6af2272afd00f804d8aa276e17107",
   "third_party/externals/spirv-tools"     : "https://skia.googlesource.com/external/github.com/KhronosGroup/SPIRV-Tools.git@e9e4393b1c5aad7553c05782acefbe32b42644bd",
-  "third_party/externals/swiftshader"     : "https://swiftshader.googlesource.com/SwiftShader@bd49ad09c5cab6b51934138460d88eaa830fa970",
+  "third_party/externals/swiftshader"     : "https://swiftshader.googlesource.com/SwiftShader@24e71928441e008fc49485cc795f578552df5bcc",
   #"third_party/externals/v8"              : "https://chromium.googlesource.com/v8/v8.git@5f1ae66d5634e43563b2d25ea652dfb94c31a3b4",
-  "third_party/externals/wuffs"           : "https://github.com/google/wuffs.git@b5c47e273f7f8862bcf04976453d0ec81e6e6650",
-  "third_party/externals/zlib"            : "https://chromium.googlesource.com/chromium/src/third_party/zlib@ea3ba903faac98b64b2bf8de5e98cd97b335a474",
+  "third_party/externals/wuffs"           : "https://github.com/google/wuffs.git@bdd31a2dc880d06c6ec8f4c64ca94074d0427350",
+  "third_party/externals/zlib"            : "https://chromium.googlesource.com/chromium/src/third_party/zlib@47af7c547f8551bd25424e56354a2ae1e9062859",
   "third_party/externals/Nima-Cpp"      : "https://github.com/2d-inc/Nima-Cpp.git@4bd02269d7d1d2e650950411325eafa15defb084",
   "third_party/externals/Nima-Math-Cpp" : "https://github.com/2d-inc/Nima-Math-Cpp.git@e0c12772093fa8860f55358274515b86885f0108",
 
diff --git a/bench/BlendmodeBench.cpp b/bench/BlendmodeBench.cpp
index d38807a..c89e7ea 100644
--- a/bench/BlendmodeBench.cpp
+++ b/bench/BlendmodeBench.cpp
@@ -8,9 +8,11 @@
 #include "Benchmark.h"
 #include "SkBlendModePriv.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkPaint.h"
 #include "SkRandom.h"
 #include "SkString.h"
+#include "SkTextBlob.h"
 
 // Benchmark that draws non-AA rects or AA text with an SkXfermode::Mode.
 class XfermodeBench : public Benchmark {
@@ -34,12 +36,13 @@
             paint.setColor(random.nextU());
             if (fAA) {
                 // Draw text to exercise AA code paths.
-                paint.setAntiAlias(true);
-                paint.setTextSize(random.nextRangeScalar(12, 96));
+                SkFont font;
+                font.setSize(random.nextRangeScalar(12, 96));
                 SkScalar x = random.nextRangeScalar(0, (SkScalar)size.fWidth),
                          y = random.nextRangeScalar(0, (SkScalar)size.fHeight);
+                auto blob = SkTextBlob::MakeFromText(text, len, font, kUTF8_SkTextEncoding);
                 for (int j = 0; j < 1000; ++j) {
-                    canvas->drawText(text, len, x, y, paint);
+                    canvas->drawTextBlob(blob, x, y, paint);
                 }
             } else {
                 // Draw rects to exercise non-AA code paths.
diff --git a/bench/ColorCanvasDrawBitmapBench.cpp b/bench/ColorCanvasDrawBitmapBench.cpp
index c150e79..a7877f4 100644
--- a/bench/ColorCanvasDrawBitmapBench.cpp
+++ b/bench/ColorCanvasDrawBitmapBench.cpp
@@ -54,5 +54,5 @@
 DEF_BENCH(return new ColorCanvasDrawBitmap(SkColorSpace::MakeSRGB(), SkColorSpace::MakeSRGB(),
         "sRGB_to_sRGB");)
 DEF_BENCH(return new ColorCanvasDrawBitmap(
-        SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kAdobeRGB_Gamut),
+        SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kAdobeRGB),
         SkColorSpace::MakeSRGB(), "AdobeRGB_to_sRGB");)
diff --git a/bench/ColorSpaceXformBench.cpp b/bench/ColorSpaceXformBench.cpp
index fc95e8e..b7b6b7d68 100644
--- a/bench/ColorSpaceXformBench.cpp
+++ b/bench/ColorSpaceXformBench.cpp
@@ -34,8 +34,8 @@
 
     void onDelayedSetup() override {
         sk_sp<SkColorSpace> src = SkColorSpace::MakeSRGB(),
-                            dst = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                                        SkColorSpace::kDCIP3_D65_Gamut);
+                            dst = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
+                                                        SkNamedGamut::kDCIP3);
 
         fSteps = skstd::make_unique<SkColorSpaceXformSteps>(src.get(), kOpaque_SkAlphaType,
                                                             dst.get(), kPremul_SkAlphaType);
diff --git a/bench/DisplacementBench.cpp b/bench/DisplacementBench.cpp
index 3dd7965..37d53d1 100644
--- a/bench/DisplacementBench.cpp
+++ b/bench/DisplacementBench.cpp
@@ -7,6 +7,7 @@
 
 #include "Benchmark.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkDisplacementMapEffect.h"
 #include "SkImageSource.h"
 #include "SkSurface.h"
@@ -36,11 +37,11 @@
         SkCanvas canvas(fBitmap);
         canvas.clear(0x00000000);
         SkPaint paint;
-        paint.setAntiAlias(true);
         paint.setColor(0xFF884422);
-        paint.setTextSize(SkIntToScalar(96));
-        const char* str = "g";
-        canvas.drawString(str, SkIntToScalar(15), SkIntToScalar(55), paint);
+
+        SkFont font;
+        font.setSize(SkIntToScalar(96));
+        canvas.drawSimpleText("g", 1, kUTF8_SkTextEncoding, SkIntToScalar(15), SkIntToScalar(55), font, paint);
     }
 
     void makeCheckerboard() {
diff --git a/bench/FontScalerBench.cpp b/bench/FontScalerBench.cpp
deleted file mode 100644
index 366c54c..0000000
--- a/bench/FontScalerBench.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "Benchmark.h"
-#include "SkCanvas.h"
-#include "SkGraphics.h"
-#include "SkPaint.h"
-#include "SkRandom.h"
-#include "SkString.h"
-
-class FontScalerBench : public Benchmark {
-    SkString fName;
-    SkString fText;
-    bool     fDoLCD;
-public:
-    FontScalerBench(bool doLCD)  {
-        fName.printf("fontscaler_%s", doLCD ? "lcd" : "aa");
-        fText.set("abcdefghijklmnopqrstuvwxyz01234567890");
-        fDoLCD = doLCD;
-    }
-
-protected:
-    virtual const char* onGetName() { return fName.c_str(); }
-    virtual void onDraw(int loops, SkCanvas* canvas) {
-        SkPaint paint;
-        this->setupPaint(&paint);
-        paint.setLCDRenderText(fDoLCD);
-
-        for (int i = 0; i < loops; i++) {
-            // this is critical - we want to time the creation process, so we
-            // explicitly flush our cache before each run
-            SkGraphics::PurgeFontCache();
-
-            for (int ps = 9; ps <= 24; ps += 2) {
-                paint.setTextSize(SkIntToScalar(ps));
-                canvas->drawString(fText,
-                        0, SkIntToScalar(20), paint);
-            }
-        }
-    }
-private:
-    typedef Benchmark INHERITED;
-};
-
-///////////////////////////////////////////////////////////////////////////////
-
-DEF_BENCH(return new FontScalerBench(false);)
-DEF_BENCH(return new FontScalerBench(true);)
diff --git a/bench/MergeBench.cpp b/bench/MergeBench.cpp
index 10aa8b8..dd0e07c 100644
--- a/bench/MergeBench.cpp
+++ b/bench/MergeBench.cpp
@@ -7,6 +7,7 @@
 
 #include "Benchmark.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkImageSource.h"
 #include "SkMergeImageFilter.h"
 #include "SkSurface.h"
@@ -20,11 +21,10 @@
     sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(80, 80));
     surface->getCanvas()->clear(0x00000000);
     SkPaint paint;
-    paint.setAntiAlias(true);
     paint.setColor(0xFF884422);
-    paint.setTextSize(SkIntToScalar(96));
-    const char* str = "g";
-    surface->getCanvas()->drawString(str, 15, 55, paint);
+    SkFont font;
+    font.setSize(SkIntToScalar(96));
+    surface->getCanvas()->drawSimpleText("g", 1, kUTF8_SkTextEncoding, 15, 55, font, paint);
     return surface->makeImageSnapshot();
 }
 
diff --git a/bench/PDFBench.cpp b/bench/PDFBench.cpp
index f612357..751a828 100644
--- a/bench/PDFBench.cpp
+++ b/bench/PDFBench.cpp
@@ -10,9 +10,11 @@
 #include "Resources.h"
 #include "SkAutoPixmapStorage.h"
 #include "SkData.h"
+#include "SkExecutor.h"
 #include "SkFloatToDecimal.h"
 #include "SkGradientShader.h"
 #include "SkImage.h"
+#include "SkPDFUnion.h"
 #include "SkPixmap.h"
 #include "SkRandom.h"
 #include "SkStream.h"
@@ -79,20 +81,6 @@
 #include "SkPDFUtils.h"
 
 namespace {
-static void test_pdf_object_serialization(const sk_sp<SkPDFObject> object) {
-    // SkDebugWStream wStream;
-    SkNullWStream wStream;
-    SkPDFObjNumMap objNumMap;
-    objNumMap.addObjectRecursively(object.get());
-    for (size_t i = 0; i < objNumMap.objects().size(); ++i) {
-        SkPDFObject* object = objNumMap.objects()[i].get();
-        wStream.writeDecAsText(i + 1);
-        wStream.writeText(" 0 obj\n");
-        object->emitObject(&wStream);
-        wStream.writeText("\nendobj\n");
-    }
-}
-
 class PDFImageBench : public Benchmark {
 public:
     PDFImageBench() {}
@@ -184,11 +172,11 @@
         SkASSERT(fAsset);
         if (!fAsset) { return; }
         while (loops-- > 0) {
-            sk_sp<SkPDFObject> object =
-                sk_make_sp<SkPDFSharedStream>(
-                        std::unique_ptr<SkStreamAsset>(fAsset->duplicate()));
-            test_pdf_object_serialization(object);
-        }
+            SkNullWStream wStream;
+            SkPDFDocument doc(&wStream, SkPDF::Metadata());
+            doc.beginPage(256, 256);
+            (void)SkPDFStreamOut(nullptr, fAsset->duplicate(), &doc, true);
+       }
     }
 
 private:
@@ -229,8 +217,9 @@
         while (loops-- > 0) {
             SkNullWStream nullStream;
             SkPDFDocument doc(&nullStream, SkPDF::Metadata());
-            sk_sp<SkPDFObject> shader = SkPDFMakeShader(&doc, fShader.get(), SkMatrix::I(),
-                                                        {0, 0, 400, 400}, SK_ColorBLACK);
+            doc.beginPage(256, 256);
+            (void) SkPDFMakeShader(&doc, fShader.get(), SkMatrix::I(),
+                                   {0, 0, 400, 400}, SK_ColorBLACK);
         }
     }
 };
@@ -262,5 +251,125 @@
 DEF_BENCH(return new PDFShaderBench;)
 DEF_BENCH(return new WritePDFTextBenchmark;)
 
+#if 0
+#include "SkExecutor.h"
+namespace {
+void big_pdf_test(const SkBitmap& background) {
+    static const char* kText[] = {
+        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do",
+        "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad",
+        "minim veniam, quis nostrud exercitation ullamco laboris nisi ut",
+        "aliquip ex ea commodo consequat. Duis aute irure dolor in",
+        "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla",
+        "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in",
+        "culpa qui officia deserunt mollit anim id est laborum.",
+        "",
+        "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem",
+        "accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae",
+        "ab illo inventore veritatis et quasi architecto beatae vitae dicta",
+        "sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit,",
+        "aspernatur aut odit aut fugit, sed quia consequuntur magni dolores",
+        "eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est,",
+        "qui dolorem ipsum, quia dolor sit amet consectetur adipiscing velit,",
+        "sed quia non numquam do eius modi tempora incididunt, ut labore et",
+        "dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam,",
+        "quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi",
+        "ut aliquid ex ea commodi consequatur? Quis autem vel eum iure",
+        "reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae",
+        "consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla",
+        "pariatur?",
+        "",
+        "At vero eos et accusamus et iusto odio dignissimos ducimus, qui",
+        "blanditiis praesentium voluptatum deleniti atque corrupti, quos",
+        "dolores et quas molestias excepturi sint, obcaecati cupiditate non",
+        "provident, similique sunt in culpa, qui officia deserunt mollitia",
+        "animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis",
+        "est et expedita distinctio. Nam libero tempore, cum soluta nobis est",
+        "eligendi optio, cumque nihil impedit, quo minus id, quod maxime",
+        "placeat, facere possimus, omnis voluptas assumenda est, omnis dolor",
+        "repellendus. Temporibus autem quibusdam et aut officiis debitis aut",
+        "rerum necessitatibus saepe eveniet, ut et voluptates repudiandae sint",
+        "et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente",
+        "delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut",
+        "perferendis doloribus asperiores repellat",
+        "",
+        "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem",
+        "accusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae",
+        "ab illo inventore veritatis et quasi architecto beatae vitae dicta",
+        "sunt, explicabo. Nemo enim ipsam voluptatem, quia voluptas sit,",
+        "aspernatur aut odit aut fugit, sed quia consequuntur magni dolores",
+        "eos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est,",
+        "qui dolorem ipsum, quia dolor sit amet consectetur adipiscing velit,",
+        "sed quia non numquam do eius modi tempora incididunt, ut labore et",
+        "dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam,",
+        "quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi",
+        "ut aliquid ex ea commodi consequatur? Quis autem vel eum iure",
+        "reprehenderit, qui in ea voluptate velit esse, quam nihil molestiae",
+        "consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla",
+        "pariatur?",
+        "",
+    };
+    //SkFILEWStream wStream("/tmp/big_pdf.pdf");
+    SkNullWStream wStream;
+    SkPDF::Metadata metadata;
+    //std::unique_ptr<SkExecutor> executor = SkExecutor::MakeFIFOThreadPool();
+    //metadata.fExecutor = executor.get();
+    sk_sp<SkDocument> doc = SkPDF::MakeDocument(&wStream, metadata);
+
+    SkCanvas* canvas = nullptr;
+    float x = 36;
+    float y = 36;
+    constexpr size_t kLineCount = SK_ARRAY_COUNT(kText);
+    constexpr int kLoopCount = 200;
+    SkPaint paint;
+    paint.setTextEncoding(kUTF8_SkTextEncoding);
+    for (int loop = 0; loop < kLoopCount; ++loop) {
+        for (size_t line = 0; line < kLineCount; ++line) {
+            y += paint.getFontSpacing();
+            if (!canvas || y > 792 - 36) {
+                y = 36 + paint.getFontSpacing();
+                canvas = doc->beginPage(612, 792);
+                background.notifyPixelsChanged();
+                canvas->drawBitmap(background, 0, 0);
+            }
+            canvas->drawText(kText[line], strlen(kText[line]), x, y, paint);
+        }
+    }
+}
+
+SkBitmap make_background() {
+    SkBitmap background;
+    SkBitmap bitmap;
+    bitmap.allocN32Pixels(32, 32);
+    bitmap.eraseColor(SK_ColorWHITE);
+    SkCanvas tmp(bitmap);
+    SkPaint gray;
+    gray.setColor(SkColorSetARGB(0xFF, 0xEE, 0xEE, 0xEE));
+    tmp.drawRect({0,0,16,16}, gray);
+    tmp.drawRect({16,16,32,32}, gray);
+    SkPaint shader;
+    shader.setShader(
+            SkShader::MakeBitmapShader(
+                bitmap, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode));
+    background.allocN32Pixels(612, 792);
+    SkCanvas tmp2(background);
+    tmp2.drawPaint(shader);
+    return background;
+}
+
+struct PDFBigDocBench : public Benchmark {
+    SkBitmap fBackground;
+    void onDelayedSetup() override { fBackground = make_background(); }
+    const char* onGetName() override { return "PDFBigDocBench"; }
+    bool isSuitableFor(Backend backend) override {
+        return backend == kNonRendering_Backend;
+    }
+    void onDraw(int loops, SkCanvas*) override {
+        while (loops-- > 0) { big_pdf_test(fBackground); }
+    }
+};
+}  // namespace
+DEF_BENCH(return new PDFBigDocBench;)
 #endif
 
+#endif // SK_SUPPORT_PDF
diff --git a/bench/PathBench.cpp b/bench/PathBench.cpp
index 6ceb1cf..b065c0c 100644
--- a/bench/PathBench.cpp
+++ b/bench/PathBench.cpp
@@ -1191,3 +1191,54 @@
 DEF_BENCH( return new ConicBench_asQuadTol() )
 DEF_BENCH( return new ConicBench_quadPow2() )
 */
+
+class CommonConvexBench : public Benchmark {
+protected:
+    SkString    fName;
+    SkPath      fPath;
+    const bool  fAA;
+
+public:
+    CommonConvexBench(int w, int h, bool forceConcave, bool aa) : fAA(aa) {
+        fName.printf("convex_path_%d_%d_%d_%d", w, h, forceConcave, aa);
+
+        SkRect r = SkRect::MakeXYWH(10, 10, w*1.0f, h*1.0f);
+        fPath.addRRect(SkRRect::MakeRectXY(r, w/8.0f, h/8.0f));
+
+        if (forceConcave) {
+            fPath.setConvexity(SkPath::kConcave_Convexity);
+            SkASSERT(!fPath.isConvex());
+        } else {
+            SkASSERT(fPath.isConvex());
+        }
+    }
+
+protected:
+    const char* onGetName() override {
+        return fName.c_str();
+    }
+
+    void onDraw(int loops, SkCanvas* canvas) override {
+        SkPaint paint;
+        paint.setAntiAlias(fAA);
+
+        for (int i = 0; i < loops; ++i) {
+            for (int inner = 0; inner < 100; ++inner) {
+                canvas->drawPath(fPath, paint);
+            }
+        }
+    }
+
+private:
+    typedef Benchmark INHERITED;
+};
+
+DEF_BENCH( return new CommonConvexBench( 16, 16, false, false); )
+DEF_BENCH( return new CommonConvexBench( 16, 16, true,  false); )
+DEF_BENCH( return new CommonConvexBench( 16, 16, false, true); )
+DEF_BENCH( return new CommonConvexBench( 16, 16, true,  true); )
+
+DEF_BENCH( return new CommonConvexBench(200, 16, false, false); )
+DEF_BENCH( return new CommonConvexBench(200, 16, true,  false); )
+DEF_BENCH( return new CommonConvexBench(200, 16, false, true); )
+DEF_BENCH( return new CommonConvexBench(200, 16, true,  true); )
diff --git a/bench/RefCntBench.cpp b/bench/RefCntBench.cpp
index f6aa3de..39b3a73 100644
--- a/bench/RefCntBench.cpp
+++ b/bench/RefCntBench.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 #include "Benchmark.h"
-#include "SkAtomics.h"
 #include "SkRefCnt.h"
 #include "SkWeakRefCnt.h"
 #include <memory>
@@ -15,30 +14,6 @@
     M = 2
 };
 
-class AtomicInc32 : public Benchmark {
-public:
-    AtomicInc32() : fX(0) {}
-
-    bool isSuitableFor(Backend backend) override {
-        return backend == kNonRendering_Backend;
-    }
-
-protected:
-    const char* onGetName() override {
-        return "atomic_inc_32";
-    }
-
-    void onDraw(int loops, SkCanvas*) override {
-        for (int i = 0; i < loops; ++i) {
-            sk_atomic_inc(&fX);
-        }
-    }
-
-private:
-    int32_t fX;
-    typedef Benchmark INHERITED;
-};
-
 class RefCntBench_Stack : public Benchmark {
 public:
     bool isSuitableFor(Backend backend) override {
@@ -214,8 +189,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-DEF_BENCH( return new AtomicInc32(); )
-
 DEF_BENCH( return new RefCntBench_Stack(); )
 DEF_BENCH( return new RefCntBench_Heap(); )
 DEF_BENCH( return new RefCntBench_New(); )
diff --git a/bench/ShaderMaskBench.cpp b/bench/ShaderMaskBench.cpp
deleted file mode 100644
index 1cc2347..0000000
--- a/bench/ShaderMaskBench.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "Benchmark.h"
-#include "SkCanvas.h"
-#include "SkPaint.h"
-#include "SkRandom.h"
-#include "SkShader.h"
-#include "SkString.h"
-#include "SkTemplates.h"
-
-#define STR     "Hamburgefons"
-
-enum FontQuality {
-    kBW,
-    kAA,
-    kLCD
-};
-
-static const char* fontQualityName(const SkPaint& paint) {
-    if (!paint.isAntiAlias()) {
-        return "BW";
-    }
-    if (paint.isLCDRenderText()) {
-        return "LCD";
-    }
-    return "AA";
-}
-
-class ShaderMaskBench : public Benchmark {
-    SkPaint     fPaint;
-    SkString    fText;
-    SkString    fName;
-    FontQuality fFQ;
-public:
-    ShaderMaskBench(bool isOpaque, FontQuality fq)  {
-        fFQ = fq;
-        fText.set(STR);
-
-        fPaint.setAntiAlias(kBW != fq);
-        fPaint.setLCDRenderText(kLCD == fq);
-        fPaint.setShader(SkShader::MakeColorShader(isOpaque ? 0xFFFFFFFF : 0x80808080));
-    }
-
-protected:
-    virtual const char* onGetName() {
-        fName.printf("shadermask");
-        fName.appendf("_%s", fontQualityName(fPaint));
-        fName.appendf("_%02X", fPaint.getAlpha());
-        return fName.c_str();
-    }
-
-    virtual void onDraw(int loops, SkCanvas* canvas) {
-        const SkIPoint dim = this->getSize();
-        SkRandom rand;
-
-        SkPaint paint(fPaint);
-        this->setupPaint(&paint);
-        // explicitly need these
-        paint.setAlpha(fPaint.getAlpha());
-        paint.setAntiAlias(kBW != fFQ);
-        paint.setLCDRenderText(kLCD == fFQ);
-
-        const SkScalar x0 = SkIntToScalar(-10);
-        const SkScalar y0 = SkIntToScalar(-10);
-
-        paint.setTextSize(SkIntToScalar(12));
-        for (int i = 0; i < loops; i++) {
-            SkScalar x = x0 + rand.nextUScalar1() * dim.fX;
-            SkScalar y = y0 + rand.nextUScalar1() * dim.fY;
-            canvas->drawString(fText, x, y, paint);
-        }
-
-        paint.setTextSize(SkIntToScalar(48));
-        for (int i = 0; i < loops / 4 ; i++) {
-            SkScalar x = x0 + rand.nextUScalar1() * dim.fX;
-            SkScalar y = y0 + rand.nextUScalar1() * dim.fY;
-            canvas->drawString(fText, x, y, paint);
-        }
-    }
-
-private:
-    typedef Benchmark INHERITED;
-};
-
-///////////////////////////////////////////////////////////////////////////////
-
-DEF_BENCH( return new ShaderMaskBench(true,  kBW); )
-DEF_BENCH( return new ShaderMaskBench(false, kBW); )
-DEF_BENCH( return new ShaderMaskBench(true,  kAA); )
-DEF_BENCH( return new ShaderMaskBench(false, kAA); )
-DEF_BENCH( return new ShaderMaskBench(true,  kLCD); )
-DEF_BENCH( return new ShaderMaskBench(false, kLCD); )
diff --git a/bench/TextBench.cpp b/bench/TextBench.cpp
deleted file mode 100644
index 881e78a..0000000
--- a/bench/TextBench.cpp
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "Benchmark.h"
-#include "Resources.h"
-#include "SkCanvas.h"
-#include "SkPaint.h"
-#include "SkRandom.h"
-#include "SkStream.h"
-#include "SkString.h"
-#include "SkTemplates.h"
-#include "SkTypeface.h"
-
-#ifdef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
-
-enum FontQuality {
-    kBW,
-    kAA,
-    kLCD,
-};
-
-static const char* fontQualityName(const SkPaint& paint) {
-    if (!paint.isAntiAlias()) {
-        return "BW";
-    }
-    if (paint.isLCDRenderText()) {
-        return "LCD";
-    }
-    return "AA";
-}
-
-/*  Some considerations for performance:
-        short -vs- long strings (measuring overhead)
-        tiny -vs- large pointsize (measure blit -vs- overhead)
-        1 -vs- many point sizes (measure cache lookup)
-        normal -vs- subpixel -vs- lineartext (minor)
-        force purge after each draw to measure scaler
-        textencoding?
-        text -vs- postext - pathtext
- */
-class TextBench : public Benchmark {
-    SkPaint     fPaint;
-    SkString    fText;
-    SkString    fName;
-    FontQuality fFQ;
-    bool        fDoPos;
-    bool        fDoColorEmoji;
-    sk_sp<SkTypeface> fColorEmojiTypeface;
-    SkPoint*    fPos;
-public:
-    TextBench(const char text[], int ps,
-              SkColor color, FontQuality fq, bool doColorEmoji = false, bool doPos = false)
-        : fText(text)
-        , fFQ(fq)
-        , fDoPos(doPos)
-        , fDoColorEmoji(doColorEmoji)
-        , fPos(nullptr) {
-        fPaint.setAntiAlias(kBW != fq);
-        fPaint.setLCDRenderText(kLCD == fq);
-        fPaint.setTextSize(SkIntToScalar(ps));
-        fPaint.setColor(color);
-    }
-
-    ~TextBench() override {
-        delete[] fPos;
-    }
-
-protected:
-    void onDelayedSetup() override {
-        if (fDoColorEmoji) {
-            SkASSERT(kBW == fFQ);
-            fColorEmojiTypeface = MakeResourceAsTypeface("fonts/Funkster.ttf");
-        }
-
-        if (fDoPos) {
-            size_t len = fText.size();
-            SkScalar* adv = new SkScalar[len];
-            fPaint.getTextWidths(fText.c_str(), len, adv);
-            fPos = new SkPoint[len];
-            SkScalar x = 0;
-            for (size_t i = 0; i < len; ++i) {
-                fPos[i].set(x, SkIntToScalar(50));
-                x += adv[i];
-            }
-            delete[] adv;
-        }
-    }
-
-
-    const char* onGetName() override {
-        fName.printf("text_%g", SkScalarToFloat(fPaint.getTextSize()));
-        if (fDoPos) {
-            fName.append("_pos");
-        }
-        fName.appendf("_%s", fontQualityName(fPaint));
-        if (SK_ColorBLACK == fPaint.getColor()) {
-            fName.append("_BK");
-        } else if (SK_ColorWHITE == fPaint.getColor()) {
-            fName.append("_WT");
-        } else {
-            fName.appendf("_%02X", fPaint.getAlpha());
-        }
-
-        if (fDoColorEmoji) {
-            fName.append("_ColorEmoji");
-        }
-
-        return fName.c_str();
-    }
-
-    void onDraw(int loops, SkCanvas* canvas) override {
-        const SkIPoint dim = this->getSize();
-        SkRandom rand;
-
-        SkPaint paint(fPaint);
-        this->setupPaint(&paint);
-        // explicitly need these
-        paint.setColor(fPaint.getColor());
-        paint.setAntiAlias(kBW != fFQ);
-        paint.setLCDRenderText(kLCD == fFQ);
-
-        if (fDoColorEmoji && fColorEmojiTypeface) {
-            paint.setTypeface(fColorEmojiTypeface);
-        }
-
-        const SkScalar x0 = SkIntToScalar(-10);
-        const SkScalar y0 = SkIntToScalar(-10);
-
-        if (fDoPos) {
-            // realistically, the matrix is often at least translated, so we
-            // do that since it exercises different code in drawPosText.
-            canvas->translate(SK_Scalar1, SK_Scalar1);
-        }
-
-        for (int i = 0; i < loops; i++) {
-            if (fDoPos) {
-                canvas->drawPosText(fText.c_str(), fText.size(), fPos, paint);
-            } else {
-                SkScalar x = x0 + rand.nextUScalar1() * dim.fX;
-                SkScalar y = y0 + rand.nextUScalar1() * dim.fY;
-                canvas->drawString(fText, x, y, paint);
-            }
-        }
-    }
-
-private:
-    typedef Benchmark INHERITED;
-};
-
-///////////////////////////////////////////////////////////////////////////////
-
-#define STR     "Hamburgefons"
-
-DEF_BENCH( return new TextBench(STR, 16, 0xFFFFFFFF, kBW); )
-DEF_BENCH( return new TextBench(STR, 16, 0xFF000000, kBW); )
-DEF_BENCH( return new TextBench(STR, 16, 0xFFFF0000, kBW); )
-DEF_BENCH( return new TextBench(STR, 16, 0x88FF0000, kBW); )
-
-DEF_BENCH( return new TextBench(STR, 16, 0xFFFFFFFF, kAA); )
-DEF_BENCH( return new TextBench(STR, 16, 0xFF000000, kAA); )
-DEF_BENCH( return new TextBench(STR, 16, 0xFFFF0000, kAA); )
-DEF_BENCH( return new TextBench(STR, 16, 0x88FF0000, kAA); )
-
-DEF_BENCH( return new TextBench(STR, 16, 0xFFFFFFFF, kLCD); )
-DEF_BENCH( return new TextBench(STR, 16, 0xFF000000, kLCD); )
-DEF_BENCH( return new TextBench(STR, 16, 0xFFFF0000, kLCD); )
-DEF_BENCH( return new TextBench(STR, 16, 0x88FF0000, kLCD); )
-
-DEF_BENCH( return new TextBench(STR, 16, 0xFFFFFFFF, kBW, true); )
-DEF_BENCH( return new TextBench(STR, 16, 0xFF000000, kBW, true); )
-DEF_BENCH( return new TextBench(STR, 16, 0xFFFF0000, kBW, true); )
-DEF_BENCH( return new TextBench(STR, 16, 0x88FF0000, kBW, true); )
-
-DEF_BENCH( return new TextBench(STR, 16, 0xFF000000, kBW, true, true); )
-DEF_BENCH( return new TextBench(STR, 16, 0xFF000000, kAA, false, true); )
-
-#endif
diff --git a/bench/TextBlobBench.cpp b/bench/TextBlobBench.cpp
index 05f61cc..3f543362 100644
--- a/bench/TextBlobBench.cpp
+++ b/bench/TextBlobBench.cpp
@@ -34,14 +34,17 @@
         const char* text = "Keep your sentences short, but not overly so.";
 
         fGlyphs.setCount(fFont.countText(text, strlen(text), kUTF8_SkTextEncoding));
+        fXPos.setCount(fGlyphs.count());
+
         fFont.textToGlyphs(text, strlen(text), kUTF8_SkTextEncoding, fGlyphs.begin(), fGlyphs.count());
+        fFont.getXPos(&fGlyphs[0], fGlyphs.count(), fXPos.begin());
     }
 
     sk_sp<SkTextBlob> makeBlob() {
         const SkTextBlobBuilder::RunBuffer& run =
             fBuilder.allocRunPosH(fFont, fGlyphs.count(), 10, nullptr);
         memcpy(run.glyphs, &fGlyphs[0], fGlyphs.count() * sizeof(uint16_t));
-        fFont.getXPos(&fGlyphs[0], fGlyphs.count(), run.pos);
+        memcpy(run.pos, &fXPos[0], fXPos.count() * sizeof(SkScalar));
         return fBuilder.make();
     }
 
@@ -49,6 +52,7 @@
     SkTextBlobBuilder   fBuilder;
     SkFont              fFont;
     SkTDArray<uint16_t> fGlyphs;
+    SkTDArray<SkScalar> fXPos;
 
     typedef Benchmark INHERITED;
 };
@@ -69,6 +73,7 @@
         }
     }
 };
+DEF_BENCH( return new TextBlobCachedBench(); )
 
 class TextBlobFirstTimeBench : public SkTextBlobBench {
     const char* onGetName() override {
@@ -84,6 +89,23 @@
         }
     }
 };
-
-DEF_BENCH( return new TextBlobCachedBench(); )
 DEF_BENCH( return new TextBlobFirstTimeBench(); )
+
+class TextBlobMakeBench : public SkTextBlobBench {
+    const char* onGetName() override {
+        return "TextBlobMakeBench";
+    }
+
+    bool isSuitableFor(Backend backend) override {
+        return backend == kNonRendering_Backend;
+    }
+
+    void onDraw(int loops, SkCanvas*) override {
+        for (int i = 0; i < loops; i++) {
+            for (int inner = 0; inner < 1000; ++inner) {
+                this->makeBlob();
+            }
+        }
+    }
+};
+DEF_BENCH( return new TextBlobMakeBench(); )
diff --git a/bench/VertexColorSpaceBench.cpp b/bench/VertexColorSpaceBench.cpp
index 129d0ff..164a2b4 100644
--- a/bench/VertexColorSpaceBench.cpp
+++ b/bench/VertexColorSpaceBench.cpp
@@ -257,8 +257,8 @@
 
         GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
 
-        auto p3 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                        SkColorSpace::kDCIP3_D65_Gamut);
+        auto p3 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
+                                        SkNamedGamut::kDCIP3);
         auto xform = GrColorSpaceXform::Make(sk_srgb_singleton(), kUnpremul_SkAlphaType,
                                              p3.get(),            kUnpremul_SkAlphaType);
 
diff --git a/bench/nanobench.cpp b/bench/nanobench.cpp
index 21bed94..35fcefa 100644
--- a/bench/nanobench.cpp
+++ b/bench/nanobench.cpp
@@ -478,10 +478,7 @@
     CPU_CONFIG(565,  kRaster_Backend, kRGB_565_SkColorType, kOpaque_SkAlphaType, nullptr)
 
     // 'narrow' has a gamut narrower than sRGB, and different transfer function.
-    SkMatrix44 narrow_gamut;
-    narrow_gamut.set3x3RowMajorf(gNarrow_toXYZD50);
-
-    auto narrow = SkColorSpace::MakeRGB(k2Dot2Curve_SkGammaNamed, narrow_gamut),
+    auto narrow = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, gNarrow_toXYZD50),
            srgb = SkColorSpace::MakeSRGB(),
      srgbLinear = SkColorSpace::MakeSRGBLinear();
 
diff --git a/bin/gerrit-number b/bin/gerrit-number
new file mode 100755
index 0000000..d9707c0
--- /dev/null
+++ b/bin/gerrit-number
@@ -0,0 +1,61 @@
+#!/usr/bin/env python2
+# Copyright 2017 Google Inc.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import json
+import re
+import subprocess
+import sys
+import urllib
+
+# TODO(halcanary): document functions and script usage.
+
+def retrieve_changeid(commit_or_branch):
+  try:
+    cmd = ['git', 'log', '-1', '--format=%B', commit_or_branch, '--']
+    body = subprocess.check_output(cmd)
+  except OSError:
+    raise Exception('git not found')
+  except subprocess.CalledProcessError:
+    raise Exception('`%s` failed' % ' '.join(cmd))
+  match = re.search(r'^Change-Id: *(.*) *$', body, re.MULTILINE)
+  if match is None:
+    raise Exception('Change-Id field missing from commit %s' % commit_or_branch)
+  return match.group(1)
+
+
+def gerrit_change_id_to_number(site, cid):
+  url = 'https://%s/changes/?q=change:%s' % (site, cid)
+  try:
+    content = urllib.urlopen(url).read()
+  except IOError:
+    raise Exception('error reading "%s"' % url)
+  try:
+    parsed = json.loads(content[content.find('['):])
+  except ValueError:
+    raise Exception('unable to parse content\n"""\n%s\n"""' % content)
+  try:
+    return parsed[0]['_number']
+  except (IndexError, KeyError):
+    raise Exception('Content missing\n"""\n%s\n"""' %
+                    json.dumps(parsed, indent=2))
+
+
+def args_to_changeid(argv):
+  if len(argv) == 2 and len(argv[1]) == 41 and argv[1][0] == 'I':
+    return argv[1]
+  else:
+    return retrieve_changeid(argv[1] if len(argv) == 2 else 'HEAD')
+
+
+if __name__ == '__main__':
+  try:
+    sys.stdout.write('%d\n' %
+        gerrit_change_id_to_number('skia-review.googlesource.com',
+                                   args_to_changeid(sys.argv)))
+  except Exception as e:
+    sys.stderr.write('%s\n' % e)
+    sys.exit(1)
+
+
diff --git a/tools/skqp/sysopen.py b/bin/sysopen
similarity index 100%
rename from tools/skqp/sysopen.py
rename to bin/sysopen
diff --git a/dm/DM.cpp b/dm/DM.cpp
index 043e553..6f63b36 100644
--- a/dm/DM.cpp
+++ b/dm/DM.cpp
@@ -833,7 +833,7 @@
 
 static sk_sp<SkColorSpace> rec2020() {
     return SkColorSpace::MakeRGB({2.22222f, 0.909672f, 0.0903276f, 0.222222f, 0.0812429f, 0, 0},
-                                 SkColorSpace::kRec2020_Gamut);
+                                 SkNamedGamut::kRec2020);
 }
 
 static void push_sink(const SkCommandLineConfig& config, Sink* s) {
@@ -925,14 +925,10 @@
         // Configs relevant to color management testing (and 8888 for reference).
 
         // 'narrow' has a gamut narrower than sRGB, and different transfer function.
-        SkMatrix44 narrow_gamut;
-        narrow_gamut.set3x3RowMajorf(gNarrow_toXYZD50);
-
-        auto narrow = SkColorSpace::MakeRGB(k2Dot2Curve_SkGammaNamed, narrow_gamut),
+        auto narrow = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, gNarrow_toXYZD50),
                srgb = SkColorSpace::MakeSRGB(),
          srgbLinear = SkColorSpace::MakeSRGBLinear(),
-                 p3 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                            SkColorSpace::kDCIP3_D65_Gamut);
+                 p3 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
 
         SINK(     "f16",  RasterSink,  kRGBA_F16_SkColorType, srgbLinear);
         SINK(    "srgb",  RasterSink, kRGBA_8888_SkColorType, srgb      );
@@ -958,8 +954,7 @@
 #define VIA(t, via, ...) if (tag.equals(t)) return new via(__VA_ARGS__)
     VIA("gbr",       ViaCSXform,           wrapped, rgb_to_gbr(), true);
     VIA("p3",        ViaCSXform,           wrapped,
-                     SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                           SkColorSpace::kDCIP3_D65_Gamut), false);
+                     SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3), false);
     VIA("lite",      ViaLite,              wrapped);
 #ifdef TEST_VIA_SVG
     VIA("svg",       ViaSVG,               wrapped);
@@ -969,7 +964,8 @@
     VIA("tiles",     ViaTiles, 256, 256, nullptr,            wrapped);
     VIA("tiles_rt",  ViaTiles, 256, 256, new SkRTreeFactory, wrapped);
 
-    VIA("ddl",       ViaDDL, 3,            wrapped);
+    VIA("ddl",       ViaDDL, 1, 3,         wrapped);
+    VIA("ddl2",      ViaDDL, 2, 3,         wrapped);
 
     if (FLAGS_matrix.count() == 4) {
         SkMatrix m;
diff --git a/dm/DMSrcSink.cpp b/dm/DMSrcSink.cpp
index 14311c4..46c1675 100644
--- a/dm/DMSrcSink.cpp
+++ b/dm/DMSrcSink.cpp
@@ -1517,6 +1517,9 @@
             context->contextPriv().getGpu()->deleteTestingOnlyBackendRenderTarget(backendRT);
         }
     }
+    if (grOptions.fPersistentCache) {
+        context->storeVkPipelineCacheData();
+    }
     return "";
 }
 
@@ -1638,6 +1641,10 @@
     metadata.fCreator = "Skia/DM";
     metadata.fRasterDPI = fRasterDpi;
     metadata.fPDFA = fPDFA;
+#if SK_PDF_TEST_EXECUTOR
+    std::unique_ptr<SkExecutor> executor = SkExecutor::MakeFIFOThreadPool();
+    metadata.fExecutor = executor.get();
+#endif
     sk_sp<SkDocument> doc = SkPDF::MakeDocument(dst, metadata);
     if (!doc) {
         return "SkPDF::MakeDocument() returned nullptr";
@@ -1939,10 +1946,8 @@
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 
-ViaDDL::ViaDDL(int numDivisions, Sink* sink)
-    : Via(sink)
-    , fNumDivisions(numDivisions) {
-}
+ViaDDL::ViaDDL(int numReplays, int numDivisions, Sink* sink)
+        : Via(sink), fNumReplays(numReplays), fNumDivisions(numDivisions) {}
 
 Error ViaDDL::draw(const Src& src, SkBitmap* bitmap, SkWStream* stream, SkString* log) const {
     auto size = src.size();
@@ -1962,42 +1967,49 @@
     if (!compressedPictureData) {
         return SkStringPrintf("ViaDDL: Couldn't deflate SkPicture");
     }
+    auto draw = [&](SkCanvas* canvas) -> Error {
+        GrContext* context = canvas->getGrContext();
+        if (!context || !context->contextPriv().getGpu()) {
+            return SkStringPrintf("DDLs are GPU only");
+        }
 
-    return draw_to_canvas(fSink.get(), bitmap, stream, log, size,
-                [&](SkCanvas* canvas) -> Error {
-                    GrContext* context = canvas->getGrContext();
-                    if (!context || !context->contextPriv().getGpu()) {
-                        return SkStringPrintf("DDLs are GPU only");
-                    }
+        // This is here bc this is the first point where we have access to the context
+        promiseImageHelper.uploadAllToGPU(context);
+        // We draw N times, with a clear between.
+        for (int replay = 0; replay < fNumReplays; ++replay) {
+            if (replay > 0) {
+                // Clear the drawing of the previous replay
+                canvas->clear(SK_ColorTRANSPARENT);
+            }
+            // First, create all the tiles (including their individual dest surfaces)
+            DDLTileHelper tiles(canvas, viewport, fNumDivisions);
 
-                    // This is here bc this is the first point where we have access to the context
-                    promiseImageHelper.uploadAllToGPU(context);
+            // Second, reinflate the compressed picture individually for each thread
+            tiles.createSKPPerTile(compressedPictureData.get(), promiseImageHelper);
 
-                    // First, create all the tiles (including their individual dest surfaces)
-                    DDLTileHelper tiles(canvas, viewport, fNumDivisions);
+            // Third, create the DDLs in parallel
+            tiles.createDDLsInParallel();
 
-                    // Second, reinflate the compressed picture individually for each thread
-                    tiles.createSKPPerTile(compressedPictureData.get(), promiseImageHelper);
+            if (replay == fNumReplays - 1) {
+                // This drops the promiseImageHelper's refs on all the promise images if we're in
+                // the last run.
+                promiseImageHelper.reset();
+            }
 
-                    // Third, create the DDLs in parallel
-                    tiles.createDDLsInParallel();
+            // Fourth, synchronously render the display lists into the dest tiles
+            // TODO: it would be cool to not wait until all the tiles are drawn to begin
+            // drawing to the GPU and composing to the final surface
+            tiles.drawAllTilesAndFlush(context, false);
 
-                    // This drops the promiseImageHelper's refs on all the promise images
-                    promiseImageHelper.reset();
-
-                    // Fourth, synchronously render the display lists into the dest tiles
-                    // TODO: it would be cool to not wait until all the tiles are drawn to begin
-                    // drawing to the GPU and composing to the final surface
-                    tiles.drawAllTilesAndFlush(context, false);
-
-                    // Finally, compose the drawn tiles into the result
-                    // Note: the separation between the tiles and the final composition better
-                    // matches Chrome but costs us a copy
-                    tiles.composeAllTiles(canvas);
-
-                    context->flush();
-                    return "";
-                });
+            // Finally, compose the drawn tiles into the result
+            // Note: the separation between the tiles and the final composition better
+            // matches Chrome but costs us a copy
+            tiles.composeAllTiles(canvas);
+            context->flush();
+        }
+        return "";
+    };
+    return draw_to_canvas(fSink.get(), bitmap, stream, log, size, draw);
 }
 
 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
diff --git a/dm/DMSrcSink.h b/dm/DMSrcSink.h
index 954aff3..5875b15 100644
--- a/dm/DMSrcSink.h
+++ b/dm/DMSrcSink.h
@@ -543,9 +543,10 @@
 
 class ViaDDL : public Via {
 public:
-    ViaDDL(int numDivisions, Sink* sink);
+    ViaDDL(int numReplays, int numDivisions, Sink* sink);
     Error draw(const Src&, SkBitmap*, SkWStream*, SkString*) const override;
 private:
+    const int fNumReplays;
     const int fNumDivisions;
 };
 
diff --git a/docs/SkCanvas_Reference.bmh b/docs/SkCanvas_Reference.bmh
index 46d5336..790d3f7 100644
--- a/docs/SkCanvas_Reference.bmh
+++ b/docs/SkCanvas_Reference.bmh
@@ -1006,7 +1006,7 @@
 Clip describes the area that may be drawn to.
 Matrix transforms the geometry.
 
-save(), saveLayer, saveLayerPreserveLCDTextRequests, and saveLayerAlpha
+save(), saveLayer, and saveLayerAlpha
 save state and return the depth of the stack.
 
 restore(), restoreToCount, and ~SkCanvas() revert state to its value when saved.
@@ -1062,7 +1062,7 @@
 }
 ##
 
-#SeeAlso save saveLayer saveLayerPreserveLCDTextRequests saveLayerAlpha restore() restoreToCount
+#SeeAlso save saveLayer saveLayerAlpha restore() restoreToCount
 
 #Method int save()
 
@@ -1090,7 +1090,7 @@
 }
 ##
 
-#SeeAlso saveLayer saveLayerPreserveLCDTextRequests saveLayerAlpha restore restoreToCount
+#SeeAlso saveLayer saveLayerAlpha restore restoreToCount
 
 ##
 
@@ -1111,7 +1111,7 @@
 }
 ##
 
-#SeeAlso save saveLayer saveLayerPreserveLCDTextRequests saveLayerAlpha restoreToCount
+#SeeAlso save saveLayer saveLayerAlpha restoreToCount
 
 ##
 
@@ -1223,7 +1223,7 @@
 }
 ##
 
-#SeeAlso save restore saveLayer saveLayerPreserveLCDTextRequests saveLayerAlpha SaveLayerRec
+#SeeAlso save restore saveLayer saveLayerAlpha SaveLayerRec
 
 ##
 
@@ -1258,40 +1258,7 @@
 }
 ##
 
-#SeeAlso save restore saveLayerPreserveLCDTextRequests saveLayerAlpha SaveLayerRec
-
-##
-
-#Method int saveLayerPreserveLCDTextRequests(const SkRect* bounds, const SkPaint* paint)
-
-#In Layer
-#Line # saves Clip and Matrix on stack; creates Layer for LCD text ##
-#Populate
-
-#Example
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setLCDRenderText(true);
-    paint.setTextSize(20);
-    for (auto preserve : { false, true } ) {
-        preserve ? canvas->saveLayerPreserveLCDTextRequests(nullptr, nullptr)
-                 : canvas->saveLayer(nullptr, nullptr);
-        SkPaint p;
-        p.setColor(SK_ColorWHITE);
-        // Comment out the next line to draw on a non-opaque background.
-        canvas->drawRect(SkRect::MakeLTRB(25, 40, 200, 70), p);
-        canvas->drawString("Hamburgefons", 30, 60, paint);
-
-        p.setColor(0xFFCCCCCC);
-        canvas->drawRect(SkRect::MakeLTRB(25, 70, 200, 100), p);
-        canvas->drawString("Hamburgefons", 30, 90, paint);
-
-        canvas->restore();
-        canvas->translate(0, 80);
-    }
-    ##
-
-#SeeAlso save restore saveLayer saveLayerAlpha SaveLayerRec
+#SeeAlso save restore saveLayerAlpha SaveLayerRec
 
 ##
 
@@ -1314,7 +1281,7 @@
     canvas->restore();
 ##
 
-#SeeAlso save restore saveLayer saveLayerPreserveLCDTextRequests SaveLayerRec
+#SeeAlso save restore saveLayer SaveLayerRec
 
 ##
 
@@ -1330,14 +1297,8 @@
 ##
 
 SaveLayerFlags provides options that may be used in any combination in SaveLayerRec,
-defining how Layer allocated by saveLayer operates. It may be set to zero,
-kPreserveLCDText_SaveLayerFlag, kInitWithPrevious_SaveLayerFlag, or both flags.
-
-#Const kPreserveLCDText_SaveLayerFlag 2
-#Line # creates Layer for LCD text ##
-  Creates Layer for LCD text. Flag is ignored if Layer Paint contains
-  Image_Filter or Color_Filter.
-##
+defining how Layer allocated by saveLayer operates. It may be set to zero or
+kInitWithPrevious_SaveLayerFlag.
 
 #Const kInitWithPrevious_SaveLayerFlag 4
 #Line # initializes with previous contents ##
@@ -1368,7 +1329,7 @@
 }
 ##
 
-#SeeAlso save restore saveLayer saveLayerPreserveLCDTextRequests saveLayerAlpha SaveLayerRec
+#SeeAlso save restore saveLayer saveLayerAlpha SaveLayerRec
 
 #Enum ##
 
@@ -1419,10 +1380,9 @@
 ##
 
 #Member SaveLayerFlags          fSaveLayerFlags
-#Line # preserves LCD Text, creates with prior Layer contents ##
+#Line # creates with prior Layer contents ##
     fSaveLayerFlags are used to create Layer without transparency,
-    create Layer for LCD text, and to create Layer with the
-    contents of the previous Layer.
+    and to create Layer with the ontents of the previous Layer.
 ##
 
 #Example
@@ -1458,8 +1418,8 @@
 
 #Example
     SkCanvas::SaveLayerRec rec1;
-    rec1.fSaveLayerFlags = SkCanvas::kPreserveLCDText_SaveLayerFlag;
-    SkCanvas::SaveLayerRec rec2(nullptr, nullptr, SkCanvas::kPreserveLCDText_SaveLayerFlag);
+    rec1.fSaveLayerFlags = SkCanvas::kInitWithPrevious_SaveLayerFlag;
+    SkCanvas::SaveLayerRec rec2(nullptr, nullptr, SkCanvas::kInitWithPrevious_SaveLayerFlag);
     SkDebugf("rec1 %c= rec2\n", rec1.fBounds == rec2.fBounds
             && rec1.fPaint == rec2.fPaint
             && rec1.fBackdrop == rec2.fBackdrop
@@ -1469,7 +1429,7 @@
     ##
 ##
 
-#SeeAlso save restore saveLayer saveLayerPreserveLCDTextRequests saveLayerAlpha
+#SeeAlso save restore saveLayer saveLayerAlpha
 
 ##
 
@@ -1488,7 +1448,7 @@
     ##
 ##
 
-#SeeAlso save restore saveLayer saveLayerPreserveLCDTextRequests saveLayerAlpha
+#SeeAlso save restore saveLayer saveLayerAlpha
 
 ##
 
@@ -1508,7 +1468,7 @@
     ##
 ##
 
-#SeeAlso save restore saveLayer saveLayerPreserveLCDTextRequests saveLayerAlpha
+#SeeAlso save restore saveLayer saveLayerAlpha
 
 ##
 
@@ -1544,7 +1504,7 @@
 
 #ToDo above example needs to replace GetResourceAsImage with way to select image in fiddle ##
 
-#SeeAlso save restore saveLayer saveLayerPreserveLCDTextRequests saveLayerAlpha
+#SeeAlso save restore saveLayer saveLayerAlpha
 
 ##
 
@@ -3919,8 +3879,8 @@
 #Line # draws text into Canvas ##
 ##
 
-#Method void drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                  const SkPaint& paint)
+#Method void drawSimpleText(const void* text, size_t byteLength, SkTextEncoding encoding,
+                            SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint)
 #In Draw_Text
 #In Draw
 #Line # draws text at (x, y), using font advance ##
@@ -3929,33 +3889,32 @@
 #Example
 #Height 200
 #Description
-    The same text is drawn varying Paint_Text_Size and varying
-    Matrix.
+The same text is drawn varying Paint_Text_Size and varying
+Matrix.
 ##
 void draw(SkCanvas* canvas) {
     SkPaint paint;
-    paint.setAntiAlias(true);
+    SkFont font;
     float textSizes[] = { 12, 18, 24, 36 };
     for (auto size: textSizes ) {
-        paint.setTextSize(size);
-        canvas->drawText("Aa", 2, 10, 20, paint);
+        font.setSize(size);
+        canvas->drawText("Aa", 2, kUTF8_SkTextEncoding, 10, 20, font, paint);
         canvas->translate(0, size * 2);
     }
-    paint.reset();
-    paint.setAntiAlias(true);
+    font.setSize(12);
     float yPos = 20;
     for (auto size: textSizes ) {
         float scale = size / 12.f;
         canvas->resetMatrix();
         canvas->translate(100, 0);
         canvas->scale(scale, scale);
-        canvas->drawText("Aa", 2, 10 / scale, yPos / scale, paint);
+        canvas->drawText("Aa", 2, kUTF8_SkTextEncoding, 10 / scale, yPos / scale, font, paint);
         yPos += size * 2;
     }
 }
 ##
 
-#SeeAlso drawString drawPosText drawPosTextH drawTextBlob drawTextRSXform
+#SeeAlso drawString drawTextBlob
 
 ##
 
@@ -3971,7 +3930,7 @@
    canvas->drawString("a small hello", 20, 20, paint);
 ##
 
-#SeeAlso drawText drawPosText drawPosTextH drawTextBlob drawTextRSXform
+#SeeAlso drawText drawTextBlob
 
 ##
 
@@ -3984,88 +3943,7 @@
    canvas->drawString(string, 20, 20, paint);
 ##
 
-#SeeAlso drawText drawPosText drawPosTextH drawTextBlob drawTextRSXform
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                     const SkPaint& paint)
-#In Draw_Text
-#In Draw
-#Line # draws text at array of (x, y) positions ##
-#Populate
-
-#Example
-#Height 120
-void draw(SkCanvas* canvas) {
-  const char hello[] = "HeLLo!";
-  const SkPoint pos[] = { {40, 100}, {82, 95}, {115, 110}, {130, 95}, {145, 85},
-    {172, 100} };
-  SkPaint paint;
-  paint.setTextSize(60);
-  canvas->drawPosText(hello, strlen(hello), pos, paint);
-}
-##
-
-#SeeAlso drawText drawPosTextH drawTextBlob drawTextRSXform
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], SkScalar constY,
-                      const SkPaint& paint)
-#In Draw_Text
-#In Draw
-#Line # draws text at x positions with common baseline ##
-#Populate
-
-#Example
-#Height 40
-    void draw(SkCanvas* canvas) {
-        SkScalar xpos[] = { 20, 40, 80, 160 };
-        SkPaint paint;
-        canvas->drawPosTextH("XXXX", 4, xpos, 20, paint);
-    }
-##
-
-#SeeAlso drawText drawPosText drawTextBlob drawTextRSXform
-
-##
-
-# ------------------------------------------------------------------------------
-
-#Method void drawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                         const SkRect* cullRect, const SkPaint& paint)
-#In Draw_Text
-#In Draw
-#Line # draws text with array of RSXform ##
-#Populate
-
-#Example
-void draw(SkCanvas* canvas) {
-    const int iterations = 26;
-    SkRSXform transforms[iterations];
-    char alphabet[iterations];
-    SkScalar angle = 0;
-    SkScalar scale = 1;
-    for (size_t i = 0; i < SK_ARRAY_COUNT(transforms); ++i) {
-        const SkScalar s = SkScalarSin(angle) * scale;
-        const SkScalar c = SkScalarCos(angle) * scale;
-        transforms[i] = SkRSXform::Make(-c, -s, -s * 16, c * 16);
-        angle += .45;
-        scale += .2;
-        alphabet[i] = 'A' + i;
-    }
-    SkPaint paint;
-    canvas->translate(110, 138);
-    canvas->drawTextRSXform(alphabet, sizeof(alphabet), transforms, nullptr, paint);
-}
-##
-
-#SeeAlso drawText drawPosText drawTextBlob
+#SeeAlso drawText drawTextBlob
 
 ##
 
@@ -4078,9 +3956,9 @@
 Draws Text_Blob blob at (x, y), using Clip, Matrix, and Paint paint.
 
 blob contains Glyphs, their positions, and paint attributes specific to text:
-#paint_font_metrics#.
+#font_metrics#.
 
-Paint_Text_Encoding must be set to SkPaint::kGlyphID_TextEncoding.
+Paint_Text_Encoding must be set to kGlyphID_SkTextEncoding.
 
 Elements of paint: Anti_Alias, Blend_Mode, Color including Color_Alpha,
 Color_Filter, Paint_Dither, Draw_Looper, Mask_Filter, Path_Effect, Shader, and
@@ -4096,33 +3974,31 @@
 #Example
 #Height 120
 void draw(SkCanvas* canvas) {
-    SkTextBlobBuilder textBlobBuilder;

-    const char bunny[] = "/(^x^)\\";

-    const int len = sizeof(bunny) - 1;

-    uint16_t glyphs[len];

-    SkPaint paint;

-    paint.textToGlyphs(bunny, len, glyphs);

-    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);

-    SkFont font;

-    int runs[] = { 3, 1, 3 };

-    SkPoint textPos = { 20, 100 };

-    int glyphIndex = 0;

-    for (auto runLen : runs) {

-        font.setSize(1 == runLen ? 20 : 50);

-        const SkTextBlobBuilder::RunBuffer& run =

-                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);

-        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);

-        paint.setTextSize(1 == runLen ? 20 : 50);

-        textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);

-        glyphIndex += runLen;

-    }

-    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();

-    paint.reset();

-    canvas->drawTextBlob(blob.get(), 0, 0, paint);

+    SkTextBlobBuilder textBlobBuilder;
+    const char bunny[] = "/(^x^)\\";
+    const int len = sizeof(bunny) - 1;
+    uint16_t glyphs[len];
+    SkFont font;
+    font.textToGlyphs(bunny, len, SkTextEncoding::kUTF8, glyphs, len);
+    int runs[] = { 3, 1, 3 };
+    SkPoint textPos = { 20, 100 };
+    int glyphIndex = 0;
+    for (auto runLen : runs) {
+        font.setSize(1 == runLen ? 20 : 50);
+        const SkTextBlobBuilder::RunBuffer& run =
+                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);
+        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);
+        font.setSize(1 == runLen ? 20 : 50);
+        textPos.fX += font.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen,
+                SkTextEncoding::kGlyphID);
+        glyphIndex += runLen;
+    }
+    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+    canvas->drawTextBlob(blob.get(), 0, 0, SkPaint());
 }
 ##
 
-#SeeAlso drawText drawPosText drawPosTextH
+#SeeAlso drawText
 
 ##
 
@@ -4133,9 +4009,9 @@
 Draws Text_Blob blob at (x, y), using Clip, Matrix, and Paint paint.
 
 blob contains Glyphs, their positions, and paint attributes specific to text:
-#paint_font_metrics#.
+#font_metrics#.
 
-Paint_Text_Encoding must be set to SkPaint::kGlyphID_TextEncoding.
+Paint_Text_Encoding must be set to kGlyphID_SkTextEncoding.
 
 Elements of paint: Path_Effect, Mask_Filter, Shader, Color_Filter,
 Image_Filter, and Draw_Looper; apply to blob.
@@ -4165,7 +4041,7 @@
     }
 ##
 
-#SeeAlso drawText drawPosText drawPosTextH
+#SeeAlso drawText
 
 ##
 
@@ -4677,8 +4553,8 @@
     const char text[] = "Click this link!";
     SkRect bounds;
     SkPaint paint;
-    paint.setTextSize(40);
-    (void)paint.measureText(text, strlen(text), &bounds);
+    SkFont font(nullptr, 40);
+    (void)font.measureText(text, strlen(text), SkTextEncoding::kUTF8, &bounds);
     const char url[] = "https://www.google.com/";
     sk_sp<SkData> urlData(SkData::MakeWithCString(url));
     canvas->drawAnnotation(bounds, "url_key", urlData.get());
@@ -4698,8 +4574,8 @@
     const char text[] = "Click this link!";
     SkRect bounds;
     SkPaint paint;
-    paint.setTextSize(40);
-    (void)paint.measureText(text, strlen(text), &bounds);
+    SkFont font(nullptr, 40);
+    (void)font.measureText(text, strlen(text), SkTextEncoding::kUTF8, &bounds);
     const char url[] = "https://www.google.com/";
     sk_sp<SkData> urlData(SkData::MakeWithCString(url));
     canvas->drawAnnotation(bounds, "url_key", urlData.get());
diff --git a/docs/SkFont_Reference.bmh b/docs/SkFont_Reference.bmh
index dc14d81..ef89b63 100644
--- a/docs/SkFont_Reference.bmh
+++ b/docs/SkFont_Reference.bmh
@@ -1,6 +1,17 @@
 #Topic Font
 #Alias Font_Reference ##
 
+#Code
+#Populate
+##
+
+#PhraseDef font_metrics
+Typeface, Font_Size, Font_Scale_X,
+Font_Skew_X, Font_Hinting, Paint_Anti_Alias, Font_Embolden, Font_Force_Hinting,
+Font_Embedded_Bitmaps, Font_Hinting_Spacing, Font_Anti_Alias, Font_Linear,
+and Font_Subpixel
+##
+
 #Subtopic Advance
 # incomplete, should probably be in overview, not reference
 ##
@@ -8,8 +19,111 @@
 # incomplete, should probably be in overview, not reference
 ##
 
-#Code
-#Populate
+#Subtopic Size
+#Line # overall height in points ##
+Font_Size adjusts the overall text size in points.
+Font_Size can be set to any positive value or zero.
+Font_Size defaults to 12.
+Font_Size
+##
+
+#Subtopic Scale_X
+#Line # text horizontal scale ##
+Font_Scale_X adjusts the text horizontal scale.
+Text scaling approximates condensed and expanded type faces when the actual face
+is not available.
+Font_Scale_X can be set to any value.
+Font_Scale_X defaults to 1.
+##
+
+#Subtopic Skew_X
+#Line # text horizontal slant ##
+Font_Skew_X adjusts the text horizontal slant.
+Text skewing approximates italic and oblique type faces when the actual face
+is not available.
+Font_Skew_X can be set to any value.
+Font_Skew_X defaults to 0.
+##
+
+#Subtopic Embolden
+#Line # approximate font styles ##
+
+Font_Embolden approximates the bold font style accompanying a normal font when a bold font face
+is not available. Skia does not provide font substitution; it is up to the client to find the
+bold font face using the platform Font_Manager.
+
+Use Font_Skew_X to approximate an italic font style when the italic font face
+is not available.
+
+A FreeType based port may define SK_USE_FREETYPE_EMBOLDEN at compile time to direct
+the font engine to create the bold Glyphs. Otherwise, the extra bold is computed
+by increasing the stroke width and setting the SkPaint::Style to
+SkPaint::kStrokeAndFill_Style as needed.
+
+Font_Embolden is disabled by default.
+#Subtopic Embolden ##
+
+#Subtopic Hinting_Spacing
+#Line # glyph spacing affected by hinting ##
+
+If Hinting is set to SkFontHinting::kFull, Hinting_Spacing adjusts the character
+spacing by the difference of the hinted and unhinted Left_Side_Bearing and
+Right_Side_Bearing. Hinting_Spacing only applies to platforms that use
+FreeType as their Font_Engine.
+
+Hinting_Spacing is not related to text kerning, where the space between
+a specific pair of characters is adjusted using data in the font kerning tables.
+#Subtopic Hinting_Spacing ##
+
+#Subtopic Linear
+#Line # selects text rendering as Glyph or Path ##
+Font_Linear selects whether text is rendered as a Glyph or as a Path.
+If Font_Linear is set, it has the same effect as setting Hinting to SkFontHinting::kNormal.
+If Font_Linear is clear, it is the same as setting Hinting to SkFontHinting::kNone.
+#Subtopic Linear ##
+
+#Subtopic Subpixel
+#Line # uses pixel transparency to represent fractional offset ##
+#Substitute sub-pixel
+Font_Subpixel uses the pixel transparency to represent a fractional offset.
+As the opaqueness of the color increases, the edge of the glyph appears to move
+towards the outside of the pixel.
+#Subtopic Subpixel ##
+
+#Subtopic Anti_Alias
+#Line # text relying on the order of RGB stripes ##
+When set, Anti_Alias positions glyphs within a pixel, using alpha and
+possibly RGB striping. It can take advantage of the organization of RGB stripes
+that create a color, and relies on the small size of the stripe and visual perception
+to make the color fringing imperceptible.
+
+Anti_Alias can be enabled on devices that orient stripes horizontally
+or vertically, and that order the color components as RGB or BGR. Internally, the
+glyph cache may store multiple copies of the same glyph with different sub-pixel
+positions, requiring more memory.
+#Subtopic Anti_Alias ##
+
+#Subtopic Force_Hinting
+#Line # always adjust glyph paths ##
+
+If Hinting is set to SkFontHinting::kNormal or SkFontHinting::kFull, Force_Hinting
+instructs the Font_Manager to always hint Glyphs.
+Force_Hinting has no effect if Hinting is set to SkFontHinting::kNone or
+SkFontHinting::kSlight.
+
+Force_Hinting only affects platforms that use FreeType as the Font_Manager.
+#Subtopic Force_Hinting ##
+
+#Subtopic Embedded_Bitmaps
+#Line # custom sized bitmap Glyphs ##
+Embedded_Bitmaps allows selecting custom sized bitmap Glyphs.
+Embedded_Bitmaps when set chooses an embedded bitmap glyph over an outline contained
+in a font if the platform supports this option.
+
+FreeType selects the bitmap glyph if available when Embedded_Bitmaps is set, and selects
+the outline glyph if Embedded_Bitmaps is clear.
+Windows may select the bitmap glyph but is not required to do so.
+OS_X and iOS do not support this option.
 ##
 
 # ------------------------------------------------------------------------------
@@ -89,6 +203,20 @@
 
 # ------------------------------------------------------------------------------
 
+#Method explicit SkFont(sk_sp<SkTypeface> typeface)
+#In Constructor
+#Line # incomplete ##
+#Populate
+#Example
+// incomplete
+##
+
+#SeeAlso incomplete
+
+#Method ##
+
+# ------------------------------------------------------------------------------
+
 #Method SkFont(sk_sp<SkTypeface> typeface, SkScalar size, SkScalar scaleX, SkScalar skewX)
 #In Constructor
 #Line # incomplete ##
@@ -121,6 +249,22 @@
 
 # ------------------------------------------------------------------------------
 
+#Method bool operator!=(const SkFont& font) const
+#In Operator
+#Line # compares fonts for inequality ##
+
+#Populate
+
+#Example
+// incomplete
+##
+
+#SeeAlso incomplete
+
+#Method ##
+
+# ------------------------------------------------------------------------------
+
 #Method bool isForceAutoHinting() const
 #In incomplete
 #Line # incomplete ##
@@ -507,13 +651,24 @@
 
 #Method int textToGlyphs(const void* text, size_t byteLength, SkTextEncoding encoding,
                      SkGlyphID glyphs[], int maxGlyphCount) const
-#In incomplete
-#Line # incomplete ##
-
+#In Utility
+#Line # converts text into glyph indices ##
 #Populate
 
 #Example
-// incomplete
+    #Height 64
+    void draw(SkCanvas* canvas) {
+        SkFont font;
+        const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };
+        std::vector<SkGlyphID> glyphs;
+        int count = font.textToGlyphs(utf8, sizeof(utf8), SkTextEncoding::kUTF8, nullptr, 0);
+        glyphs.resize(count);
+        (void) font.textToGlyphs(utf8, sizeof(utf8), SkTextEncoding::kUTF8, &glyphs.front(),
+                count);
+        font.setSize(32);
+        canvas->drawSimpleText(&glyphs.front(), glyphs.size() * sizeof(SkGlyphID),
+                SkTextEncoding::kGlyphID, 10, 40, font, SkPaint());
+    }
 ##
 
 #SeeAlso incomplete
@@ -539,13 +694,18 @@
 # ------------------------------------------------------------------------------
 
 #Method int countText(const void* text, size_t byteLength, SkTextEncoding encoding) const
-#In incomplete
-#Line # incomplete ##
-
+#In Utility
+#Line # returns number of Glyphs in text ##
 #Populate
 
 #Example
-// incomplete
+    SkFont font;
+    const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };
+    SkDebugf("count = %d\n", font.countText(utf8, sizeof(utf8), SkTextEncoding::kUTF8));
+
+    #StdOut
+        count = 5
+    ##
 ##
 
 #SeeAlso incomplete
@@ -555,13 +715,50 @@
 # ------------------------------------------------------------------------------
 
 #Method bool containsText(const void* text, size_t byteLength, SkTextEncoding encoding) const
-#In incomplete
-#Line # incomplete ##
-
+#In Utility
+#Line # returns if all text corresponds to Glyphs ##
 #Populate
 
+#NoExample
+    #Description
+    containsText succeeds for degree symbol, but cannot find a glyph index
+    corresponding to the Unicode surrogate code point.
+    ##
+    SkFont font;
+    const uint16_t goodChar = 0x00B0;  // degree symbol
+    const uint16_t badChar = 0xD800;   // Unicode surrogate
+    SkDebugf("0x%04x %c= has char\n", goodChar,
+            font.containsText(&goodChar, 2, SkTextEncoding::kUTF16) ? '=' : '!');
+    SkDebugf("0x%04x %c= has char\n", badChar,
+            font.containsText(&badChar, 2, SkTextEncoding::kUTF16) ? '=' : '!');
+
+    #StdOut
+        0x00b0 == has char
+        0xd800 != has char
+    ##
+##
+
 #Example
-// incomplete
+    #Description
+    containsText returns true that glyph index is greater than zero, not
+    that it corresponds to an entry in Typeface.
+    ##
+    SkFont font;
+    const uint16_t goodGlyph = 511;
+    const uint16_t zeroGlyph = 0;
+    const uint16_t badGlyph = 65535; // larger than glyph count in font
+    SkDebugf("0x%04x %c= has glyph\n", goodGlyph,
+            font.containsText(&goodGlyph, 2, SkTextEncoding::kGlyphID) ? '=' : '!');
+    SkDebugf("0x%04x %c= has glyph\n", zeroGlyph,
+            font.containsText(&zeroGlyph, 2, SkTextEncoding::kGlyphID) ? '=' : '!');
+    SkDebugf("0x%04x %c= has glyph\n", badGlyph,
+            font.containsText(&badGlyph, 2, SkTextEncoding::kGlyphID) ? '=' : '!');
+
+    #StdOut
+        0x01ff == has glyph
+        0x0000 != has glyph
+        0xffff == has glyph
+    ##
 ##
 
 #SeeAlso incomplete
@@ -577,7 +774,28 @@
 #Populate

 

 #Example
-// incomplete
+    #Description
+    Line under "Breakfast" shows desired width, shorter than available characters.
+    Line under "Bre" shows measured width after breaking text.
+    ##
+    #Height 128
+    #Width 280
+        void draw(SkCanvas* canvas) {
+            SkPaint paint;
+            paint.setAntiAlias(true);
+            paint.setTextSize(50);
+            const char str[] = "Breakfast";
+            const int count = sizeof(str) - 1;
+            canvas->drawText(str, count, 25, 50, paint);
+            SkScalar measuredWidth;
+            SkFont font;
+            font.setSize(50);
+            int partialBytes = font.breakText(str, count, kUTF8_SkTextEncoding,
+                    100, &measuredWidth);
+            canvas->drawText(str, partialBytes, 25, 100, paint);
+            canvas->drawLine(25, 60, 25 + 100, 60, paint);
+            canvas->drawLine(25, 110, 25 + measuredWidth, 110, paint);
+        }
 ##

 

 #SeeAlso incomplete

@@ -589,28 +807,172 @@
 #Method SkScalar measureText(const void* text, size_t byteLength, SkTextEncoding encoding,
                          SkRect* bounds = nullptr) const
 #In incomplete
-#Line # incomplete ##
-
+#Line # returns advance width and bounds of text ##
 #Populate
 
 #Example
-// incomplete
+    SkFont font;
+    SkDebugf("default width = %g\n", font.measureText("!", 1, SkTextEncoding::kUTF8));
+    font.setSize(font.getSize() * 2);
+    SkDebugf("double width = %g\n", font.measureText("!", 1, SkTextEncoding::kUTF8));
+
+    #StdOut
+        default width = 5
+        double width = 10
+    ##
 ##
 
 #SeeAlso incomplete
 
 #Method ##
 
+
+#Method SkScalar measureText(const void* text, size_t byteLength, SkTextEncoding encoding,
+                         SkRect* bounds, const SkPaint* paint) const
+#In incomplete
+#Populate
+
+#Example
+    #Height 64
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        SkFont font(nullptr, 50);
+        const char str[] = "ay^jZ";
+        const int count = sizeof(str) - 1;
+        canvas->drawSimpleText(str, count, SkTextEncoding::kUTF8, 25, 50, font, paint);
+        SkRect bounds;
+        font.measureText(str, count, SkTextEncoding::kUTF8, &bounds, nullptr);
+        canvas->translate(25, 50);
+        paint.setStyle(SkPaint::kStroke_Style);
+        canvas->drawRect(bounds, paint);
+    }
+##
+
+#SeeAlso incomplete
+
+#Method ##
+
+#Method void getWidths(const uint16_t glyphs[], int count, SkScalar widths[]) const
+#In incomplete
+#Line # returns advance and bounds for each glyph in text ##
+#Populate
+#Example
+// incomplete
+##
+#SeeAlso incomplete
+#Method ##
+
+#Method void getWidthsBounds(const uint16_t glyphs[], int count, SkScalar widths[], SkRect bounds[],
+                         const SkPaint* paint) const
+#In incomplete
+#Populate
+#Example
+    #Height 160
+    #Description
+    Bounds of Glyphs increase for stroked text, but text advance remains the same.
+    The underlines show the text advance, spaced to keep them distinct.
+    ##
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        SkFont font(nullptr, 50);
+        const char str[] = "abc";
+        const int bytes = sizeof(str) - 1;
+        int count = font.textToGlyphs(str, bytes, SkTextEncoding::kUTF8, nullptr, 0);
+        std::vector<SkGlyphID> glyphs;
+        std::vector<SkScalar> widths;
+        std::vector<SkRect> bounds;
+        glyphs.resize(count);
+        (void) font.textToGlyphs(str, bytes, SkTextEncoding::kUTF8, &glyphs.front(), count);
+        widths.resize(count);
+        bounds.resize(count);
+        for (int loop = 0; loop < 2; ++loop) {
+            (void) font.getWidthsBounds(&glyphs.front(), count, &widths.front(), &bounds.front(),
+                    &paint);
+            SkPoint loc = { 25, 50 };
+            canvas->drawSimpleText(str, bytes, SkTextEncoding::kUTF8, loc.fX, loc.fY, font, paint);
+            paint.setStyle(SkPaint::kStroke_Style);
+            paint.setStrokeWidth(0);
+            SkScalar advanceY = loc.fY + 10;
+            for (int index = 0; index < count; ++index) {
+                bounds[index].offset(loc.fX, loc.fY);
+                canvas->drawRect(bounds[index], paint);
+                canvas->drawLine(loc.fX, advanceY, loc.fX + widths[index], advanceY, paint);
+                loc.fX += widths[index];
+                advanceY += 5;
+            }
+            canvas->translate(0, 80);
+            paint.setStrokeWidth(3);
+        }
+    }
+##
+#SeeAlso incomplete
+#Method ##
+
+#Method void getBounds(const uint16_t glyphs[], int count, SkRect bounds[],
+                   const SkPaint* paint) const
+#In incomplete
+#Populate
+#Example
+// incomplete
+##
+#SeeAlso incomplete
+#Method ##
+
+#Method void getPos(const uint16_t glyphs[], int count, SkPoint pos[], SkPoint origin = {0, 0}) const
+#In incomplete
+#Populate
+#Example
+// incomplete
+##
+#SeeAlso incomplete
+#Method ##
+
+#Method void getXPos(const uint16_t glyphs[], int count, SkScalar xpos[], SkScalar origin = 0) const
+#In incomplete
+#Populate
+#Example
+// incomplete
+##
+#SeeAlso incomplete
+#Method ##
+
 # ------------------------------------------------------------------------------
 
 #Method bool getPath(uint16_t glyphID, SkPath* path) const
 #In incomplete
-#Line # incomplete ##
-
+#Line # returns Path equivalent to text ##
 #Populate
 
 #Example
-// incomplete
+    #Description
+    Text is added to Path, offset, and subtracted from Path, then added at
+    the offset location. The result is rendered with one draw call.
+    ##
+    #Height 128
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;

+        SkFont font(nullptr, 80);

+        SkPath onePath, path, path2;

+        const char str[] = "ABC";

+        const int bytes = sizeof(str) - 1;

+        int count = font.textToGlyphs(str, bytes, SkTextEncoding::kUTF8, nullptr, 0);

+        std::vector<SkGlyphID> glyphs;

+        glyphs.resize(count);

+        (void) font.textToGlyphs(str, bytes, SkTextEncoding::kUTF8, &glyphs.front(), count);

+        int xPos = 20;

+        for (auto oneGlyph : glyphs) {

+            font.getPath(oneGlyph, &onePath);

+            path.addPath(onePath, xPos, 60);

+            xPos += 60;

+        }

+        path.offset(20, 20, &path2);

+        Op(path, path2, SkPathOp::kDifference_SkPathOp, &path);

+        path.addPath(path2);

+        paint.setStyle(SkPaint::kStroke_Style);

+        canvas->drawPath(path, paint);
+    }
 ##
 
 #SeeAlso incomplete
@@ -639,12 +1001,18 @@
 
 #Method SkScalar getMetrics(SkFontMetrics* metrics) const
 #In incomplete
-#Line # incomplete ##
-
+#Line # returns Typeface metrics scaled by text size ##
 #Populate
 
 #Example
-// incomplete
+    #Height 128
+    void draw(SkCanvas* canvas) {
+        SkFont font(nullptr, 32);
+        SkScalar lineHeight = font.getMetrics(nullptr);
+        SkPaint paint;
+        canvas->drawSimpleText("line 1", 6, SkTextEncoding::kUTF8, 10, 40, font, paint);
+        canvas->drawSimpleText("line 2", 6, SkTextEncoding::kUTF8, 10, 40 + lineHeight, font, paint);
+    }
 ##
 
 #SeeAlso incomplete
@@ -655,12 +1023,23 @@
 
 #Method SkScalar getSpacing() const
 #In incomplete
-#Line # incomplete ##
+#Line # returns recommended spacing between lines ##
 
 #Populate
 
 #Example
-// incomplete
+        SkFont font;
+        for (SkScalar textSize : { 12, 18, 24, 32 } ) {
+            font.setSize(textSize);
+            SkDebugf("textSize: %g spacing: %g\n", textSize, font.getSpacing());
+        }
+
+        #StdOut
+            textSize: 12 spacing: 13.9688
+            textSize: 18 spacing: 20.9531
+            textSize: 24 spacing: 27.9375
+            textSize: 32 spacing: 37.25
+        ##
 ##
 
 #SeeAlso incomplete
diff --git a/docs/SkImage_Reference.bmh b/docs/SkImage_Reference.bmh
index 7a0bc33..097b7df 100644
--- a/docs/SkImage_Reference.bmh
+++ b/docs/SkImage_Reference.bmh
@@ -312,6 +312,10 @@
 #Populate
 
 #Example
+#Description
+textureReleaseProc may be called at some later point in time. In this example,
+textureReleaseProc has no effect on the drawing.
+##
 #Platform gpu
 #Image 4
 GrContext* context = canvas->getGrContext();
@@ -319,15 +323,14 @@
    return;
 }
 auto debugster = [](SkImage::ReleaseContext releaseContext) -> void {
- // broken
- //   *((int *) releaseContext) += 128;
+   *((int *) releaseContext) += 128;
 };
-int x = 0;
+int x = 0, y = 0;
 for (auto origin : { kBottomLeft_GrSurfaceOrigin, kTopLeft_GrSurfaceOrigin } ) {
     sk_sp<SkImage> image = SkImage::MakeFromTexture(context, backEndTexture,
            origin, kRGBA_8888_SkColorType, kOpaque_SkAlphaType, nullptr, debugster, &x);
-    canvas->drawImage(image, x, 0);
-    x += 128;
+    canvas->drawImage(image, x, y);
+    y += 128;
 }
 ##
 
diff --git a/docs/SkPaint_Reference.bmh b/docs/SkPaint_Reference.bmh
index 208ccab..d259582 100644
--- a/docs/SkPaint_Reference.bmh
+++ b/docs/SkPaint_Reference.bmh
@@ -1,13 +1,6 @@
 #Topic Paint
 #Alias Paint_Reference ##
 
-#PhraseDef paint_font_metrics
-Typeface, Paint_Text_Size, Paint_Text_Scale_X,
-Paint_Text_Skew_X, Paint_Hinting, Anti_Alias, Paint_Fake_Bold,
-Font_Embedded_Bitmaps, Full_Hinting_Spacing, LCD_Text, Linear_Text,
-and Subpixel_Text
-##
-
 #Class SkPaint
 
 #Code
@@ -55,29 +48,29 @@
 # Color_Filter           # nullptr                  ##
 # Dither                 # false                    ##
 # Draw_Looper            # nullptr                  ##
-# Fake_Bold              # false                    ##
 # Filter_Quality         # kNone_SkFilterQuality    ##
+# Font_Force_Hinting     # false                    ##
 # Font_Embedded_Bitmaps  # false                    ##
-# Automatic_Hinting      # false                    ##
-# Full_Hinting_Spacing   # false                    ##
-# Hinting                # SkFontHinting::kNormal   ##
+# Font_Embolden          # false                    ##
+# Font_Hinting           # SkFontHinting::kNormal   ##
+# Font_Hinting_Spacing   # false                    ##
+# Font_Anti_Alias        # false                    ##
+# Font_Linear            # false                    ##
+# Font_Scale_X           # 1                        ##
+# Font_Size              # 12                       ##
+# Font_Skew_X            # 0                        ##
+# Font_Subpixel          # false                    ##
 # Image_Filter           # nullptr                  ##
-# LCD_Text               # false                    ##
-# Linear_Text            # false                    ##
 # Miter_Limit            # 4                        ##
 # Mask_Filter            # nullptr                  ##
 # Path_Effect            # nullptr                  ##
 # Shader                 # nullptr                  ##
 # Style                  # kFill_Style              ##
-# Text_Encoding          # kUTF8_TextEncoding       ##
-# Text_Scale_X           # 1                        ##
-# Text_Size              # 12                       ##
-# Text_Skew_X            # 0                        ##
+# Text_Encoding          # kUTF8_SkTextEncoding     ##
 # Typeface               # nullptr                  ##
 # Stroke_Cap             # kButt_Cap                ##
 # Stroke_Join            # kMiter_Join              ##
 # Stroke_Width           # 0                        ##
-# Subpixel_Text          # false                    ##
 #Table ##
 
 The flags, text size, hinting, and miter limit may be overridden at compile time by defining
@@ -353,22 +346,22 @@
     #Line # mask for setting Dither ##
     ##
     #Const kFakeBoldText_Flag       0x0020
-    #Line # mask for setting Fake_Bold ##
+    #Line # mask for setting Font_Embolden ##
     ##
     #Const kLinearText_Flag         0x0040
-    #Line # mask for setting Linear_Text ##
+    #Line # mask for setting Font_Linear ##
     ##
     #Const kSubpixelText_Flag       0x0080
-    #Line # mask for setting Subpixel_Text ##
+    #Line # mask for setting Font_Subpixel ##
     ##
     #Const kLCDRenderText_Flag      0x0200
-    #Line # mask for setting LCD_Text ##
+    #Line # mask for setting Font_Anti_Alias ##
     ##
     #Const kEmbeddedBitmapText_Flag 0x0400
     #Line # mask for setting Font_Embedded_Bitmaps ##
     ##
     #Const kAutoHinting_Flag        0x0800
-    #Line # mask for setting Automatic_Hinting ##
+    #Line # mask for setting Font_Force_Hinting ##
     ##
     #Const kAllFlags                0xFFFF
     #Line # mask of all Flags ##
@@ -645,12 +638,12 @@
 #Subtopic Device_Text
 #Line # increase precision of glyph position ##
 
-LCD_Text and Subpixel_Text increase the precision of glyph position.
+Font_Anti_Alias and Font_Subpixel increase the precision of glyph position.
 
 When set, Flags kLCDRenderText_Flag takes advantage of the organization of RGB stripes that
 create a color, and relies
 on the small size of the stripe and visual perception to make the color fringing imperceptible.
-LCD_Text can be enabled on devices that orient stripes horizontally or vertically, and that order
+Font_Anti_Alias can be enabled on devices that orient stripes horizontally or vertically, and that order
 the color components as RGB or BGR.
 
 Flags kSubpixelText_Flag uses the pixel transparency to represent a fractional offset.
@@ -659,14 +652,14 @@
 
 Either or both techniques can be enabled.
 kLCDRenderText_Flag and kSubpixelText_Flag are clear by default.
-LCD_Text or Subpixel_Text can be enabled by default by setting SkPaintDefaults_Flags to
+Font_Anti_Alias or Font_Subpixel can be enabled by default by setting SkPaintDefaults_Flags to
 kLCDRenderText_Flag or kSubpixelText_Flag (or both) at compile time.
 
 #Example
     #Description
-        Four commas are drawn normally and with combinations of LCD_Text and Subpixel_Text.
-        When Subpixel_Text is disabled, the comma Glyphs are identical, but not evenly spaced.
-        When Subpixel_Text is enabled, the comma Glyphs are unique, but appear evenly spaced.
+        Four commas are drawn normally and with combinations of Font_Anti_Alias and Font_Subpixel.
+        When Font_Subpixel is disabled, the comma Glyphs are identical, but not evenly spaced.
+        When Font_Subpixel is enabled, the comma Glyphs are unique, but appear evenly spaced.
     ##
 
     SkBitmap bitmap;
@@ -691,12 +684,11 @@
 #Subtopic Device_Text ##
 
 #Subtopic Linear_Text
-#Alias Linear_Text ##
 #Line # selects text rendering as Glyph or Path ##
 
-Linear_Text selects whether text is rendered as a Glyph or as a Path.
-If kLinearText_Flag is set, it has the same effect as setting Hinting to SkFontHinting::kNormal.
-If kLinearText_Flag is clear, it is the same as setting Hinting to SkFontHinting::kNone.
+Font_Linear selects whether text is rendered as a Glyph or as a Path.
+If Font_Linear is set, it has the same effect as setting Hinting to SkFontHinting::kNormal.
+If Font_Linear is clear, it is the same as setting Hinting to SkFontHinting::kNone.
 #Subtopic Linear_Text ##
 
 #Method bool isLinearText() const
@@ -733,29 +725,31 @@
 
 #Example
     #Height 128
-      void draw(SkCanvas* canvas) {
-          SkPaint paint;
-          paint.setAntiAlias(true);
-          const char testStr[] = "abcd efgh";
-          for (int textSize : { 12, 24 } ) {
-              paint.setTextSize(textSize);
-              for (auto linearText : { false, true } ) {
-                  paint.setLinearText(linearText);
-                  SkString width;
-                  width.appendScalar(paint.measureText(testStr, SK_ARRAY_COUNT(testStr), nullptr));
-                  canvas->translate(0, textSize + 4);
-                  canvas->drawString(testStr, 10, 0, paint);
-                  canvas->drawString(width, 128, 0, paint);
-              }
-           }
-        }
+    void draw(SkCanvas* canvas) {
+        SkPaint paint;

+        paint.setAntiAlias(true);

+        const char testStr[] = "abcd efgh";

+        size_t count = 9;

+        for (int textSize : { 12, 24 } ) {

+            SkFont font(nullptr, textSize);

+            for (auto linearMetrics : { false, true } ) {

+                font.setLinearMetrics(linearMetrics);

+                SkString width;

+                width.appendScalar(font.measureText(testStr, count, SkTextEncoding::kUTF8));

+                canvas->translate(0, textSize + 4);

+                canvas->drawSimpleText(testStr, count, SkTextEncoding::kUTF8,

+                    10, 0, font, paint);

+                canvas->drawSimpleText(width.c_str(), width.size(), SkTextEncoding::kUTF8,

+                    128, 0, font, paint);

+            }

+        }

+    }
     ##
 
     #SeeAlso isLinearText Hinting
 ##
 
 #Subtopic Subpixel_Text
-#Alias Subpixel_Text ##
 #Line # uses pixel transparency to represent fractional offset ##
 
 Flags kSubpixelText_Flag uses the pixel transparency to represent a fractional offset.
@@ -765,7 +759,7 @@
 
 #Method bool isSubpixelText() const
 #In Subpixel_Text
-#Line # returns true if Subpixel_Text is set ##
+#Line # returns true if Font_Subpixel is set ##
 #Populate
 
 #Example
@@ -786,7 +780,7 @@
 
 #Method void setSubpixelText(bool subpixelText)
 #In Subpixel_Text
-#Line # sets or clears Subpixel_Text ##
+#Line # sets or clears Font_Subpixel ##
 #Populate
 
 #Example
@@ -805,21 +799,16 @@
 #Subtopic LCD_Text
 #Line # text relying on the order of RGB stripes ##
 
-# make this a top level name, since it is under subtopic Device_Text
-#Alias LCD_Text
-#Substitute LCD text
-##
-
-When set, Flags kLCDRenderText_Flag takes advantage of the organization of RGB stripes that
+When set, Font_Anti_Alias takes advantage of the organization of RGB stripes that
 create a color, and relies
 on the small size of the stripe and visual perception to make the color fringing imperceptible.
-LCD_Text can be enabled on devices that orient stripes horizontally or vertically, and that order
+Font_Anti_Alias can be enabled on devices that orient stripes horizontally or vertically, and that order
 the color components as RGB or BGR.
 #Subtopic LCD_Text ##
 
 #Method bool isLCDRenderText() const
 #In LCD_Text
-#Line # returns true if LCD_Text is set ##
+#Line # returns true if Font_Anti_Alias is set ##
 #Populate
 
 #Example
@@ -840,7 +829,7 @@
 
 #Method void setLCDRenderText(bool lcdText)
 #In LCD_Text
-#Line # sets or clears LCD_Text ##
+#Line # sets or clears Font_Anti_Alias ##
 #Populate
 
 #Example
@@ -858,9 +847,8 @@
 ##
 
 # ------------------------------------------------------------------------------
-#Subtopic Font_Embedded_Bitmaps
+#Subtopic Embedded_Bitmaps
 #Line # custom sized bitmap Glyphs ##
-#Alias Font_Embedded_Bitmaps ## # long-winded enough, alias so I don't type Paint_Font_...
 
 Font_Embedded_Bitmaps allows selecting custom sized bitmap Glyphs.
 Flags kEmbeddedBitmapText_Flag when set chooses an embedded bitmap glyph over an outline contained
@@ -903,10 +891,10 @@
     canvas->scale(10, 10);
     canvas->drawBitmap(bitmap, -2, 1);
 ##
-#Subtopic Font_Embedded_Bitmaps ##
+#Subtopic Embedded_Bitmaps ##
 
 #Method bool isEmbeddedBitmapText() const
-#In Font_Embedded_Bitmaps
+#In Embedded_Bitmaps
 #Line # returns true if Font_Embedded_Bitmaps is set ##
 #Populate
 
@@ -931,7 +919,7 @@
 ##
 
 #Method void setEmbeddedBitmapText(bool useEmbeddedBitmapText)
-#In Font_Embedded_Bitmaps
+#In Embedded_Bitmaps
 #Line # sets or clears Font_Embedded_Bitmaps ##
 #Populate
 
@@ -951,14 +939,13 @@
 # ------------------------------------------------------------------------------
 #Subtopic Automatic_Hinting
 #Line # always adjust glyph paths ##
-#Substitute auto-hinting
 
-If Hinting is set to SkFontHinting::kNormal or SkFontHinting::kFull, Automatic_Hinting
+If Hinting is set to SkFontHinting::kNormal or SkFontHinting::kFull, Font_Force_Hinting
 instructs the Font_Manager to always hint Glyphs.
-Automatic_Hinting has no effect if Hinting is set to SkFontHinting::kNone or
+Font_Force_Hinting has no effect if Hinting is set to SkFontHinting::kNone or
 SkFontHinting::kSlight.
 
-Automatic_Hinting only affects platforms that use FreeType as the Font_Manager.
+Font_Force_Hinting only affects platforms that use FreeType as the Font_Manager.
 #Subtopic Automatic_Hinting ##
 
 #Method bool isAutohinted() const
@@ -1017,7 +1004,7 @@
 #Subtopic Fake_Bold
 #Line # approximate font styles ##
 
-Fake_Bold approximates the bold font style accompanying a normal font when a bold font face
+Font_Embolden approximates the bold font style accompanying a normal font when a bold font face
 is not available. Skia does not provide font substitution; it is up to the client to find the
 bold font face using the platform Font_Manager.
 
@@ -1028,7 +1015,7 @@
 the font engine to create the bold Glyphs. Otherwise, the extra bold is computed
 by increasing the stroke width and setting the Style to kStrokeAndFill_Style as needed.
 
-Fake_Bold is disabled by default.
+Font_Embolden is disabled by default.
 
 #Example
 #Height 128
@@ -1050,7 +1037,7 @@
 
 #Method bool isFakeBoldText() const
 #In Fake_Bold
-#Line # returns true if Fake_Bold is set ##
+#Line # returns true if Font_Embolden is set ##
 #Populate
 
 #Example
@@ -1071,7 +1058,7 @@
 
 #Method void setFakeBoldText(bool fakeBoldText)
 #In Fake_Bold
-#Line # sets or clears Fake_Bold ##
+#Line # sets or clears Font_Embolden ##
 #Populate
 
 #Example
@@ -1088,20 +1075,6 @@
 ##
 
 # ------------------------------------------------------------------------------
-#Subtopic Full_Hinting_Spacing
-#Line # glyph spacing affected by hinting ##
-#Alias Full_Hinting_Spacing ## # long winded enough -- maybe things with two underscores auto-aliased?
-
-if Hinting is set to SkFontHinting::kFull, Full_Hinting_Spacing adjusts the character
-spacing by the difference of the hinted and unhinted Left_Side_Bearing and
-Right_Side_Bearing. Full_Hinting_Spacing only applies to platforms that use
-FreeType as their Font_Engine.
-
-Full_Hinting_Spacing is not related to text kerning, where the space between
-a specific pair of characters is adjusted using data in the font kerning tables.
-#Subtopic Full_Hinting_Spacing ##
-
-# ------------------------------------------------------------------------------
 #Subtopic Filter_Quality_Methods
 #Line # get and set Filter_Quality ##
 
@@ -3015,51 +2988,6 @@
 #Subtopic Text_Encoding
 #Line # text encoded as characters or Glyphs ##
 
-#Enum TextEncoding
-#Line # character or glyph encoded size ##
-
-#Code
-#Populate
-##
-
-TextEncoding determines whether text specifies character codes and their encoded
-size, or glyph indices. Characters are encoded as specified by the
-#A Unicode standard # https://unicode.org/standard/standard.html ##
-.
-
-Character codes encoded size are specified by UTF-8, UTF-16, or UTF-32.
-All character code formats are able to represent all of Unicode, differing only
-in the total storage required.
-
-#A UTF-8 (RFC 3629) # https://tools.ietf.org/html/rfc3629 ##
- encodes each character as one or more 8-bit bytes.
-
-#A UTF-16 (RFC 2781) # https://tools.ietf.org/html/rfc2781 ##
- encodes each character as one or two 16-bit words.
-
-#A UTF-32 # https://www.unicode.org/versions/Unicode5.0.0/ch03.pdf ##
- encodes each character as one 32-bit word.
-
-Font_Manager uses font data to convert character code points into glyph indices.
-A glyph index is a 16-bit word.
-
-TextEncoding is set to kUTF8_TextEncoding by default.
-
-#Const kUTF8_TextEncoding 0
-#Line # uses bytes to represent UTF-8 or ASCII ##
-##
-#Const kUTF16_TextEncoding 1
-#Line # uses two byte words to represent most of Unicode ##
-##
-#Const kUTF32_TextEncoding 2
-#Line # uses four byte words to represent all of Unicode ##
-##
-#Const kGlyphID_TextEncoding 3
-#Line # uses two byte words to represent glyph indices ##
-##
-
-#Enum ##
-
 #Example
 #Height 128
 #Description
@@ -3075,18 +3003,20 @@
     const uint32_t hello32[] = { 'H', 'e', 'l', 'l', 'o', 0x263A };
     paint.setTextSize(24);
     canvas->drawText(hello8, sizeof(hello8) - 1, 10, 30, paint);
-    paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+    paint.setTextEncoding(SkTextEncoding::kUTF16);
     canvas->drawText(hello16, sizeof(hello16), 10, 60, paint);
-    paint.setTextEncoding(SkPaint::kUTF32_TextEncoding);
+    paint.setTextEncoding(SkTextEncoding::kUTF32);
     canvas->drawText(hello32, sizeof(hello32), 10, 90, paint);
     uint16_t glyphs[SK_ARRAY_COUNT(hello32)];
-    paint.textToGlyphs(hello32, sizeof(hello32), glyphs);
-    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+    SkFont font;
+    font.textToGlyphs(hello32, sizeof(hello32), SkTextEncoding::kUTF32,
+            glyphs, SK_ARRAY_COUNT(hello32));
+    paint.setTextEncoding(kGlyphID_SkTextEncoding);
     canvas->drawText(glyphs, sizeof(glyphs), 10, 120, paint);
 }
 ##
 
-#Method TextEncoding getTextEncoding() const
+#Method SkTextEncoding getTextEncoding() const
 
 #In Text_Encoding
 #Line # returns character or glyph encoded size ##
@@ -3094,22 +3024,22 @@
 
 #Example
         SkPaint paint;
-        SkDebugf("kUTF8_TextEncoding %c= text encoding\n",
-                SkPaint::kUTF8_TextEncoding == paint.getTextEncoding() ? '=' : '!');
-        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
-        SkDebugf("kGlyphID_TextEncoding %c= text encoding\n",
-                SkPaint::kGlyphID_TextEncoding == paint.getTextEncoding() ? '=' : '!');
+        SkDebugf("kUTF8_SkTextEncoding %c= text encoding\n",
+                kUTF8_SkTextEncoding == paint.getTextEncoding() ? '=' : '!');
+        paint.setTextEncoding(kGlyphID_SkTextEncoding);
+        SkDebugf("kGlyphID_SkTextEncoding %c= text encoding\n",
+                kGlyphID_SkTextEncoding == paint.getTextEncoding() ? '=' : '!');
 
         #StdOut
-            kUTF8_TextEncoding == text encoding
-            kGlyphID_TextEncoding == text encoding
+            kUTF8_SkTextEncoding == text encoding
+            kGlyphID_SkTextEncoding == text encoding
         ##
     ##
 
 ##
 
 
-#Method void setTextEncoding(TextEncoding encoding)
+#Method void setTextEncoding(SkTextEncoding encoding)
 
 #In Text_Encoding
 #Line # sets character or glyph encoded size ##
@@ -3117,8 +3047,8 @@
 
 #Example
         SkPaint paint;
-        paint.setTextEncoding((SkPaint::TextEncoding) 4);
-        SkDebugf("4 %c= text encoding\n", (SkPaint::TextEncoding) 4 == paint.getTextEncoding() ? '=' : '!');
+        paint.setTextEncoding((SkTextEncoding) 4);
+        SkDebugf("4 %c= text encoding\n", (SkTextEncoding) 4 == paint.getTextEncoding() ? '=' : '!');
 
         #StdOut
             4 != text encoding
@@ -3127,155 +3057,7 @@
 
 ##
 
-#Typedef typedef SkFontMetrics FontMetrics
-##
-
 #Subtopic Text_Encoding ##
-# ------------------------------------------------------------------------------
-#Subtopic Font_Metrics
-
-#Method SkScalar getFontMetrics(SkFontMetrics* metrics) const
-
-#In Font_Metrics
-#Line # returns Typeface metrics scaled by text size ##
-#Populate
-
-#Example
-    #Height 128
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setTextSize(32);
-            SkScalar lineHeight = paint.getFontMetrics(nullptr);
-            canvas->drawString("line 1", 10, 40, paint);
-            canvas->drawString("line 2", 10, 40 + lineHeight, paint);
-        }
-    ##
-
-    #SeeAlso Text_Size Typeface Typeface_Methods
-
-##
-
-
-#Method SkScalar getFontSpacing() const
-
-#In Font_Metrics
-#Line # returns recommended spacing between lines ##
-#Populate
-
-#Example
-        SkPaint paint;
-        for (SkScalar textSize : { 12, 18, 24, 32 } ) {
-            paint.setTextSize(textSize);
-            SkDebugf("textSize: %g fontSpacing: %g\n", textSize, paint.getFontSpacing());
-        }
-
-        #StdOut
-            textSize: 12 fontSpacing: 13.9688
-            textSize: 18 fontSpacing: 20.9531
-            textSize: 24 fontSpacing: 27.9375
-            textSize: 32 fontSpacing: 37.25
-        ##
-    ##
-
-##
-
-#Subtopic Font_Metrics ##
-
-# ------------------------------------------------------------------------------
-
-#Method int textToGlyphs(const void* text, size_t byteLength,
-                     SkGlyphID glyphs[]) const
-#In Utility
-#Line # converts text into glyph indices ##
-#Populate
-
-#Example
-    #Height 64
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };
-            std::vector<SkGlyphID> glyphs;
-            int count = paint.textToGlyphs(utf8, sizeof(utf8), nullptr);
-            glyphs.resize(count);
-            (void) paint.textToGlyphs(utf8, sizeof(utf8), &glyphs.front());
-            paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
-            paint.setTextSize(32);
-            canvas->drawText(&glyphs.front(), glyphs.size() * sizeof(SkGlyphID), 10, 40, paint);
-        }
-    ##
-
-##
-
-#Method int countText(const void* text, size_t byteLength) const
-#In Utility
-#Line # returns number of Glyphs in text ##
-#Populate
-
-#Example
-        SkPaint paint;
-        const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };
-        SkDebugf("count = %d\n", paint.countText(utf8, sizeof(utf8)));
-
-        #StdOut
-            count = 5
-        ##
-    ##
-##
-
-# ------------------------------------------------------------------------------
-
-#Method bool containsText(const void* text, size_t byteLength) const
-#In Utility
-#Line # returns if all text corresponds to Glyphs ##
-#Populate
-
-#NoExample
-    #Description
-    containsText succeeds for degree symbol, but cannot find a glyph index
-    corresponding to the Unicode surrogate code point.
-    ##
-        SkPaint paint;
-        const uint16_t goodChar = 0x00B0;  // degree symbol
-        const uint16_t badChar = 0xD800;   // Unicode surrogate
-        paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
-        SkDebugf("0x%04x %c= has char\n", goodChar,
-                paint.containsText(&goodChar, 2) ? '=' : '!');
-        SkDebugf("0x%04x %c= has char\n", badChar,
-                paint.containsText(&badChar, 2) ? '=' : '!');
-
-        #StdOut
-            0x00b0 == has char
-            0xd800 != has char
-        ##
-    ##
-
-    #Example
-    #Description
-    containsText returns true that glyph index is greater than zero, not
-    that it corresponds to an entry in Typeface.
-    ##
-        SkPaint paint;
-        const uint16_t goodGlyph = 511;
-        const uint16_t zeroGlyph = 0;
-        const uint16_t badGlyph = 65535; // larger than glyph count in font
-        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
-        SkDebugf("0x%04x %c= has glyph\n", goodGlyph,
-                paint.containsText(&goodGlyph, 2) ? '=' : '!');
-        SkDebugf("0x%04x %c= has glyph\n", zeroGlyph,
-                paint.containsText(&zeroGlyph, 2) ? '=' : '!');
-        SkDebugf("0x%04x %c= has glyph\n", badGlyph,
-                paint.containsText(&badGlyph, 2) ? '=' : '!');
-
-        #StdOut
-            0x01ff == has glyph
-            0x0000 != has glyph
-            0xffff == has glyph
-        ##
-    ##
-
-#SeeAlso setTextEncoding Typeface
-
-##
 
 # ------------------------------------------------------------------------------
 
@@ -3285,7 +3067,9 @@
 #Line # converts Glyphs into text ##
 #Populate
 
-#Example
+# crashes fiddle, draws missing characters locally ##
+#Bug 8608
+#NoExample
     #Height 64
     #Description
     Convert UTF-8 text to glyphs; then convert glyphs to Unichar code points.
@@ -3300,7 +3084,7 @@
         }
         SkUnichar unichars[count];
         paint.glyphsToUnichars(glyphs, count, unichars);
-        paint.setTextEncoding(SkPaint::kUTF32_TextEncoding);
+        paint.setTextEncoding(kUTF32_SkTextEncoding);
         canvas->drawText(unichars, sizeof(unichars), 10, 30, paint);
     }
     ##
@@ -3308,371 +3092,6 @@
 ##
 
 # ------------------------------------------------------------------------------
-#Subtopic Measure_Text
-#Line # width, height, bounds of text ##
-
-#Method SkScalar measureText(const void* text, size_t length, SkRect* bounds) const
-
-#In Measure_Text
-#Line # returns advance width and bounds of text ##
-#Populate
-
-#Example
-    #Height 64
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setAntiAlias(true);
-            paint.setTextSize(50);
-            const char str[] = "ay^jZ";
-            const int count = sizeof(str) - 1;
-            canvas->drawText(str, count, 25, 50, paint);
-            SkRect bounds;
-            paint.measureText(str, count, &bounds);
-            canvas->translate(25, 50);
-            paint.setStyle(SkPaint::kStroke_Style);
-            canvas->drawRect(bounds, paint);
-        }
-    ##
-
-##
-
-#Method SkScalar measureText(const void* text, size_t length) const
-
-#In Measure_Text
-#Populate
-
-#Example
-        SkPaint paint;
-        SkDebugf("default width = %g\n", paint.measureText("!", 1));
-        paint.setTextSize(paint.getTextSize() * 2);
-        SkDebugf("double width = %g\n", paint.measureText("!", 1));
-
-        #StdOut
-            default width = 5
-            double width = 10
-        ##
-    ##
-
-##
-
-#Method size_t breakText(const void* text, size_t length, SkScalar maxWidth,
-                      SkScalar* measuredWidth = nullptr) const
-#In Measure_Text
-#Line # returns text that fits in a width ##
-#Populate
-
-#Example
-    #Description
-    Line under "Breakfast" shows desired width, shorter than available characters.
-    Line under "Bre" shows measured width after breaking text.
-    ##
-    #Height 128
-    #Width 280
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setAntiAlias(true);
-            paint.setTextSize(50);
-            const char str[] = "Breakfast";
-            const int count = sizeof(str) - 1;
-            canvas->drawText(str, count, 25, 50, paint);
-            SkScalar measuredWidth;
-            int partialBytes = paint.breakText(str, count, 100, &measuredWidth);
-            canvas->drawText(str, partialBytes, 25, 100, paint);
-            canvas->drawLine(25, 60, 25 + 100, 60, paint);
-            canvas->drawLine(25, 110, 25 + measuredWidth, 110, paint);
-        }
-    ##
-
-##
-
-#Method int getTextWidths(const void* text, size_t byteLength, SkScalar widths[],
-                      SkRect bounds[] = nullptr) const
-#In Measure_Text
-#Line # returns advance and bounds for each glyph in text ##
-#Populate
-
-#Example
-    #Height 160
-    #Description
-    Bounds of Glyphs increase for stroked text, but text advance remains the same.
-    The underlines show the text advance, spaced to keep them distinct.
-    ##
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setAntiAlias(true);
-            paint.setTextSize(50);
-            const char str[] = "abc";
-            const int bytes = sizeof(str) - 1;
-            int count = paint.getTextWidths(str, bytes, nullptr);
-            std::vector<SkScalar> widths;
-            std::vector<SkRect> bounds;
-            widths.resize(count);
-            bounds.resize(count);
-            for (int loop = 0; loop < 2; ++loop) {
-                (void) paint.getTextWidths(str, count, &widths.front(), &bounds.front());
-                SkPoint loc = { 25, 50 };
-                canvas->drawText(str, bytes, loc.fX, loc.fY, paint);
-                paint.setStyle(SkPaint::kStroke_Style);
-                paint.setStrokeWidth(0);
-                SkScalar advanceY = loc.fY + 10;
-                for (int index = 0; index < count; ++index) {
-                    bounds[index].offset(loc.fX, loc.fY);
-                    canvas->drawRect(bounds[index], paint);
-                    canvas->drawLine(loc.fX, advanceY, loc.fX + widths[index], advanceY, paint);
-                    loc.fX += widths[index];
-                    advanceY += 5;
-                }
-                canvas->translate(0, 80);
-                paint.setStrokeWidth(3);
-            }
-        }
-    ##
-
-##
-
-#Subtopic Measure_Text ##
-# ------------------------------------------------------------------------------
-#Subtopic Text_Path
-#Line # geometry of Glyphs ##
-
-Text_Path describes the geometry of Glyphs used to draw text.
-
-#Method void getTextPath(const void* text, size_t length, SkScalar x, SkScalar y,
-                     SkPath* path) const
-#In Text_Path
-#Line # returns Path equivalent to text ##
-#Populate
-
-#Example
-    #Description
-    Text is added to Path, offset, and subtracted from Path, then added at
-    the offset location. The result is rendered with one draw call.
-    ##
-    #Height 128
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setTextSize(80);
-            SkPath path, path2;
-            paint.getTextPath("ABC", 3, 20, 80, &path);
-            path.offset(20, 20, &path2);
-            Op(path, path2, SkPathOp::kDifference_SkPathOp, &path);
-            path.addPath(path2);
-            paint.setStyle(SkPaint::kStroke_Style);
-            canvas->drawPath(path, paint);
-        }
-    ##
-
-##
-
-#Method void getPosTextPath(const void* text, size_t length,
-                        const SkPoint pos[], SkPath* path) const
-#In Text_Path
-#Line # returns Path equivalent to positioned text ##
-#Populate
-
-#Example
-    #Height 85
-    #Description
-    Simplifies three Glyphs to eliminate overlaps, and strokes the result.
-    ##
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setTextSize(80);
-            SkPath path, path2;
-            SkPoint pos[] = {{20, 60}, {30, 70}, {40, 80}};
-            paint.getPosTextPath("ABC", 3, pos, &path);
-            Simplify(path, &path);
-            paint.setStyle(SkPaint::kStroke_Style);
-            canvas->drawPath(path, paint);
-        }
-    ##
-
-##
-
-#Subtopic Text_Path ##
-# ------------------------------------------------------------------------------
-#Subtopic Text_Intercepts
-#Line # advanced underline, strike through ##
-
-Text_Intercepts describe the intersection of drawn text Glyphs with a pair
-of lines parallel to the text advance. Text_Intercepts permits creating a
-underline that skips Descenders.
-
-#Method int getTextIntercepts(const void* text, size_t length, SkScalar x, SkScalar y,
-                          const SkScalar bounds[2], SkScalar* intervals) const
-#In Text_Intercepts
-#Line # returns where lines intersect text; underlines ##
-#Populate
-
-#NoExample
-#Height 128
-#Description
-Underline uses intercepts to draw on either side of the glyph Descender.
-##
-void draw(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setTextSize(120);
-    SkPoint textOrigin = { 20, 100 };
-    SkScalar bounds[] = { 100, 108 };
-    int count = paint.getTextIntercepts("y", 1, textOrigin.fX, textOrigin.fY, bounds, nullptr);
-    std::vector<SkScalar> intervals;
-    intervals.resize(count);
-    (void) paint.getTextIntercepts("y", 1, textOrigin.fX, textOrigin.fY, bounds,
-            &intervals.front());
-    canvas->drawString("y", textOrigin.fX, textOrigin.fY, paint);
-    paint.setColor(SK_ColorRED);
-    SkScalar x = textOrigin.fX;
-    for (int i = 0; i < count; i += 2) {
-        canvas->drawRect({x, bounds[0], intervals[i], bounds[1]}, paint);
-        x = intervals[i + 1];
-    }
-    canvas->drawRect({intervals[count - 1], bounds[0],
-        textOrigin.fX + paint.measureText("y", 1), bounds[1]}, paint);
-}
-##
-
-##
-
-#Method int getPosTextIntercepts(const void* text, size_t length, const SkPoint pos[],
-                             const SkScalar bounds[2], SkScalar* intervals) const
-#In Text_Intercepts
-#Line # returns where lines intersect positioned text; underlines ##
-#Populate
-
-#NoExample
-    #Description
-    Text intercepts draw on either side of, but not inside, Glyphs in a run.
-    ##
-    void draw(SkCanvas* canvas) {
-        SkPaint paint;
-        paint.setTextSize(120);
-        SkPoint textPos[] = {{ 60, 90 }, { 120, 90 }};
-        SkScalar bounds[] = { 40, 70 };
-        const char str[] = "A+";
-        int len = sizeof(str) - 1;
-        int count = paint.getPosTextIntercepts(str, len, textPos, bounds, nullptr);
-        std::vector<SkScalar> intervals;
-        intervals.resize(count);
-        (void) paint.getPosTextIntercepts(str, len, textPos, bounds, &intervals.front());
-        canvas->drawPosText(str, len, textPos, paint);
-        paint.setColor(SK_ColorRED);
-        SkScalar x = textPos[0].fX;
-        for (int i = 0; i < count; i+= 2) {
-            canvas->drawRect({x, bounds[0], intervals[i], bounds[1]}, paint);
-            x = intervals[i + 1];
-        }
-        if (count) {
-            canvas->drawRect({intervals[count - 1], bounds[0], 180, bounds[1]}, paint);
-        }
-    }
-    ##
-
-##
-
-#Method int getPosTextHIntercepts(const void* text, size_t length, const SkScalar xpos[],
-                                  SkScalar constY, const SkScalar bounds[2],
-                                  SkScalar* intervals) const
-#In Text_Intercepts
-#Line # returns where lines intersect horizontally positioned text; underlines ##
-#Populate
-
-#NoExample
-    #Height 128
-    #Description
-    Text intercepts do not take stroke thickness into consideration.
-    ##
-        void draw(SkCanvas* canvas) {
-            SkPaint paint;
-            paint.setTextSize(120);
-            paint.setStyle(SkPaint::kStroke_Style);
-            paint.setStrokeWidth(4);
-            SkScalar textPosH[] = { 20, 80, 140 };
-            SkScalar y = 100;
-            SkScalar bounds[] = { 56, 78 };
-            const char str[] = "\\-/";
-            int len = sizeof(str) - 1;
-            int count = paint.getPosTextHIntercepts(str, len, textPosH, y, bounds, nullptr);
-            std::vector<SkScalar> intervals;
-            intervals.resize(count);
-            (void) paint.getPosTextHIntercepts(str, len, textPosH, y, bounds, &intervals.front());
-            canvas->drawPosTextH(str, len, textPosH, y, paint);
-            paint.setColor(0xFFFF7777);
-            paint.setStyle(SkPaint::kFill_Style);
-            SkScalar x = textPosH[0];
-            for (int i = 0; i < count; i+= 2) {
-                canvas->drawRect({x, bounds[0], intervals[i], bounds[1]}, paint);
-                x = intervals[i + 1];
-            }
-            canvas->drawRect({intervals[count - 1], bounds[0], 180, bounds[1]}, paint);
-        }
-    ##
-
-##
-
-
-#Method int getTextBlobIntercepts(const SkTextBlob* blob, const SkScalar bounds[2],
-                              SkScalar* intervals) const
-#In Text_Intercepts
-#Line # returns where lines intersect Text_Blob; underlines ##
-#Populate
-
-#Example
-    #Height 143
-        void draw(SkCanvas* canvas) {
-            SkFont font;
-            font.setSize(120);
-            SkPoint textPos = { 20, 110 };
-            int len = 3;
-            SkTextBlobBuilder textBlobBuilder;
-            const SkTextBlobBuilder::RunBuffer& run =
-                    textBlobBuilder.allocRun(font, len, textPos.fX, textPos.fY);
-            run.glyphs[0] = 10;
-            run.glyphs[1] = 20;
-            run.glyphs[2] = 30;
-            sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
-            SkPaint paint;
-            SkScalar bounds[] = { 116, 134 };
-            int count = paint.getTextBlobIntercepts(blob.get(), bounds, nullptr);
-            std::vector<SkScalar> intervals;
-            intervals.resize(count);
-            (void) paint.getTextBlobIntercepts(blob.get(), bounds, &intervals.front());
-            canvas->drawTextBlob(blob.get(), 0, 0, paint);
-            paint.setColor(0xFFFF7777);
-            SkScalar x = textPos.fX;
-            for (int i = 0; i < count; i+= 2) {
-                canvas->drawRect({x, bounds[0], intervals[i], bounds[1]}, paint);
-                x = intervals[i + 1];
-            }
-            canvas->drawRect({intervals[count - 1], bounds[0], 180, bounds[1]}, paint);
-        }
-    ##
-
-##
-
-#Subtopic Text_Intercepts ##
-# ------------------------------------------------------------------------------
-
-#Method SkRect getFontBounds() const
-
-#In Font_Metrics
-#Line # returns union all glyph bounds ##
-#Populate
-
-#Example
-    SkPaint paint;
-    SkFontMetrics fm;
-    paint.getFontMetrics(&fm);
-    SkRect fb = paint.getFontBounds();
-    SkDebugf("metrics bounds = { %g, %g, %g, %g }\n", fm.fXMin, fm.fTop, fm.fXMax, fm.fBottom );
-    SkDebugf("font bounds    = { %g, %g, %g, %g }\n", fb.fLeft, fb.fTop, fb.fRight, fm.fBottom );
-
-    #StdOut
-        metrics bounds = { -12.2461, -14.7891, 21.5215, 5.55469 }
-        font bounds    = { -12.2461, -14.7891, 21.5215, 5.55469 }
-    ##
-##
-
-##
 
 #Method bool nothingToDraw() const
 #In Utility
diff --git a/docs/SkTextBlobBuilder_Reference.bmh b/docs/SkTextBlobBuilder_Reference.bmh
index 7874313..489142e 100644
--- a/docs/SkTextBlobBuilder_Reference.bmh
+++ b/docs/SkTextBlobBuilder_Reference.bmh
@@ -25,9 +25,9 @@
 
 RunBuffer supplies storage for Glyphs and positions within a run.
 
-A run is a sequence of Glyphs sharing Paint_Font_Metrics and positioning.
+A run is a sequence of Glyphs sharing Font_Metrics and positioning.
 Each run may position its Glyphs in one of three ways:
-by specifying where the first Glyph is drawn, and allowing Paint_Font_Metrics to
+by specifying where the first Glyph is drawn, and allowing Font_Metrics to
 determine the advance to subsequent Glyphs; by specifying a baseline, and
 the position on that baseline for each Glyph in run; or by providing Point
 array, one per Glyph.
@@ -105,7 +105,7 @@
     sk_sp<SkTextBlob> blob = builder.make();
     SkDebugf("blob " "%s" " nullptr\n", blob == nullptr ? "equals" : "does not equal");
     SkPaint paint;
-    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+    paint.setTextEncoding(kGlyphID_SkTextEncoding);
     SkFont font;
     paint.textToGlyphs("x", 1, builder.allocRun(font, 1, 20, 20).glyphs);
     blob = builder.make();
diff --git a/docs/SkTextBlob_Reference.bmh b/docs/SkTextBlob_Reference.bmh
index e089c23..fc01ab1 100644
--- a/docs/SkTextBlob_Reference.bmh
+++ b/docs/SkTextBlob_Reference.bmh
@@ -1,259 +1,305 @@
-#Topic Text_Blob

-#Alias Text_Blob_Reference ##

-

-#Class SkTextBlob

-

-#Code

-#Populate

-##

-

-SkTextBlob combines multiple text runs into an immutable container. Each text

-run consists of Glyphs, Paint, and position. Only parts of Paint related to

-fonts and text rendering are used by run.

-

-# ------------------------------------------------------------------------------

-

-#Method const SkRect& bounds() const

-#In Property

-#Line # returns conservative bounding box ##

-#Populate

-

-#Example

-#Height 70

-    SkTextBlobBuilder textBlobBuilder;

-    const char bunny[] = "/(^x^)\\";

-    const int len = sizeof(bunny) - 1;

-    uint16_t glyphs[len];

-    SkPaint paint;

-    paint.textToGlyphs(bunny, len, glyphs);

-    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);

-    SkFont font;

-    int runs[] = { 3, 1, 3 };

-    SkPoint textPos = { 20, 50 };

-    int glyphIndex = 0;

-    for (auto runLen : runs) {

-        font.setSize(1 == runLen ? 20 : 50);

-        paint.setTextSize(1 == runLen ? 20 : 50);

-        const SkTextBlobBuilder::RunBuffer& run =

-                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);

-        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);

-        textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);

-        glyphIndex += runLen;

-    }

-    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();

-    canvas->drawTextBlob(blob.get(), 0, 0, paint);

-    paint.setStyle(SkPaint::kStroke_Style);

-    canvas->drawRect(blob->bounds(), paint);

-##

-

-#SeeAlso SkPath::getBounds

-

-#Method ##

-

-# ------------------------------------------------------------------------------

-

-#Method uint32_t uniqueID() const

-#In Property

-#Line # returns identifier for Text_Blob ##

-#Populate

-

-#Example

-for (int index = 0; index < 2; ++index) {

-    SkTextBlobBuilder textBlobBuilder;

-    const char bunny[] = "/(^x^)\\";

-    const int len = sizeof(bunny) - 1;

-    uint16_t glyphs[len];

-    SkPaint paint;

-    paint.textToGlyphs(bunny, len, glyphs);

-    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);

-    paint.setTextScaleX(0.5);

-    SkFont font;

-    font.setScaleX(0.5);

-    int runs[] = { 3, 1, 3 };

-    SkPoint textPos = { 20, 50 };

-    int glyphIndex = 0;

-    for (auto runLen : runs) {

-        font.setSize(1 == runLen ? 20 : 50);

-        paint.setTextSize(1 == runLen ? 20 : 50);

-        const SkTextBlobBuilder::RunBuffer& run =

-                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);

-        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);

-        textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);

-        glyphIndex += runLen;

-    }

-    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();

-    paint.reset();

-    canvas->drawTextBlob(blob.get(), 0, 0, paint);

-    std::string id = "unique ID:" + std::to_string(blob->uniqueID());

-    canvas->drawString(id.c_str(), 30, blob->bounds().fBottom + 15, paint);

-    canvas->translate(blob->bounds().fRight + 10, 0);

-}

-##

-

-#SeeAlso SkRefCnt

-

-#Method ##

-

-# ------------------------------------------------------------------------------

-

-#Method static sk_sp<SkTextBlob> MakeFromText(const void* text, size_t byteLength, const SkFont& font,

-                                          SkTextEncoding encoding = kUTF8_SkTextEncoding)

-#In Constructors

-#Line # constructs Text_Blob with one run ##

-

-Creates Text_Blob with a single run. text meaning depends on Paint_Text_Encoding;

-by default, text is encoded as UTF-8.

-

-font contains attributes used to define the run text: #paint_font_metrics#.

-

-#Param text character code points or Glyphs drawn ##

-#Param  byteLength   byte length of text array ##

-#Param  font    text size, typeface, text scale, and so on, used to draw ##

-#Param  encoding  one of: kUTF8_SkTextEncoding, kUTF16_SkTextEncoding,

-                          kUTF32_SkTextEncoding, kGlyphID_SkTextEncoding

-##

-

-#Return Text_Blob constructed from one run ##

-

-#Example

-#Height 24

-    SkFont font;

-    font.setSize(24);

-    SkPaint canvasPaint;

-    canvasPaint.setColor(SK_ColorBLUE); // respected

-    canvasPaint.setTextSize(2); // ignored

-    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, font);

-    canvas->drawTextBlob(blob, 20, 20, canvasPaint);

-##

-

-#SeeAlso MakeFromString SkTextBlobBuilder

-

-##

-

-# ------------------------------------------------------------------------------

-

-#Method static sk_sp<SkTextBlob> MakeFromString(const char* string, const SkFont& font,

-             SkTextEncoding encoding = kUTF8_SkTextEncoding)

-#In Constructors

-#Line # constructs Text_Blob with one run ##

-

-Creates Text_Blob with a single run. string meaning depends on Paint_Text_Encoding;

-by default, string is encoded as UTF-8.

-

-font contains Paint_Font_Metrics used to define the run text: #paint_font_metrics#.

-

-#Param string character code points or Glyphs drawn ##

-#Param  font    text size, typeface, text scale, and so on, used to draw ##

-#Param  encoding  one of: kUTF8_SkTextEncoding, kUTF16_SkTextEncoding,

-                          kUTF32_SkTextEncoding, kGlyphID_SkTextEncoding

-##

-

-#Return Text_Blob constructed from one run ##

-

-#Example

-#Height 24

-    SkFont font;

-    font.setSize(24);

-    SkPaint canvasPaint;

-    canvasPaint.setColor(SK_ColorBLUE); // respected

-    canvasPaint.setTextSize(2); // ignored

-    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString("Hello World", font);

-    canvas->drawTextBlob(blob, 20, 20, canvasPaint);

-##

-

-#SeeAlso MakeFromText SkTextBlobBuilder

-

-##

-

-# ------------------------------------------------------------------------------

-

-#Method size_t serialize(const SkSerialProcs& procs, void* memory, size_t memory_size) const

-#In Utility

-#Line # writes Text_Blob to memory ##

-#Populate

-

-#Example

-#Height 64

-###$

-$Function

-#include "SkSerialProcs.h"

-$$

-$$$#

-    SkFont blobFont;

-    blobFont.setSize(24);

-    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, blobFont);

-    char storage[2048];

-    size_t used = blob->serialize(SkSerialProcs(), storage, sizeof(storage));

-    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(storage, used, SkDeserialProcs());

-    canvas->drawTextBlob(copy, 20, 20, SkPaint());

-    std::string usage = "size=" + std::to_string(sizeof(storage)) + " used=" + std::to_string(used);

-    canvas->drawString(usage.c_str(), 20, 40, SkPaint());

-##

-

-#SeeAlso Deserialize SkSerialProcs

-

-#Method ##

-

-# ------------------------------------------------------------------------------

-

-#Method sk_sp<SkData> serialize(const SkSerialProcs& procs) const

-#In Utility

-#Line # writes Text_Blob to Data ##

-#Populate

-

-#Example

-#Height 24

-###$

-$Function

-#include "SkSerialProcs.h"

-$$

-$$$#

-    SkFont blobFont;

-    blobFont.setSize(24);

-    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, blobFont);

-    sk_sp<SkData> data = blob->serialize(SkSerialProcs());

-    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(data->data(), data->size(), SkDeserialProcs());

-    canvas->drawTextBlob(copy, 20, 20, SkPaint());

-##

-

-#SeeAlso Deserialize SkData SkSerialProcs

-

-#Method ##

-

-# ------------------------------------------------------------------------------

-

-#Method static sk_sp<SkTextBlob> Deserialize(const void* data, size_t size, const SkDeserialProcs& procs)

-#In Constructors

-#Line # constructs Text_Blob from memory ##

-#Populate

-

-#Example

-#Height 24

-#Description

-Text "Hacker" replaces "World!", but does not update its metrics.

-When drawn, "Hacker" uses the spacing computed for "World!".

-##

-###$

-$Function

-#include "SkSerialProcs.h"

-$$

-$$$#

-    SkFont blobFont;

-    blobFont.setSize(24);

-    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World!", 12, blobFont);

-    sk_sp<SkData> data = blob->serialize(SkSerialProcs());

-    uint16_t glyphs[6];

-    SkPaint blobPaint;

-    blobPaint.textToGlyphs("Hacker", 6, glyphs);

-    memcpy((char*)data->writable_data() + 0x54, glyphs, sizeof(glyphs));

-    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(data->data(), data->size(), SkDeserialProcs());

-    canvas->drawTextBlob(copy, 20, 20, SkPaint());

-##

-

-#SeeAlso serialize SkDeserialProcs

-

-#Method ##

-

-#Class SkTextBlob ##

-

-#Topic Text_Blob ##

+#Topic Text_Blob
+#Alias Text_Blob_Reference ##
+
+#Class SkTextBlob
+
+#Code
+#Populate
+##
+
+SkTextBlob combines multiple text runs into an immutable container. Each text
+run consists of Glyphs, Paint, and position. Only parts of Paint related to
+fonts and text rendering are used by run.
+
+# ------------------------------------------------------------------------------
+
+#Method const SkRect& bounds() const
+#In Property
+#Line # returns conservative bounding box ##
+#Populate
+
+#Example
+#Height 70
+    SkTextBlobBuilder textBlobBuilder;
+    const char bunny[] = "/(^x^)\\";
+    const int len = sizeof(bunny) - 1;
+    uint16_t glyphs[len];
+    SkFont font;
+    font.textToGlyphs(bunny, len, SkTextEncoding::kUTF8, glyphs, sizeof(glyphs));
+    int runs[] = { 3, 1, 3 };
+    SkPoint textPos = { 20, 50 };
+    int glyphIndex = 0;
+    for (auto runLen : runs) {
+        font.setSize(1 == runLen ? 20 : 50);
+        const SkTextBlobBuilder::RunBuffer& run =
+                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);
+        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);
+        textPos.fX += font.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, 
+                SkTextEncoding::kGlyphID);
+        glyphIndex += runLen;
+    }
+    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+    SkPaint paint;
+    canvas->drawTextBlob(blob.get(), 0, 0, paint);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawRect(blob->bounds(), paint);
+##
+
+#SeeAlso SkPath::getBounds
+
+#Method ##
+
+# ------------------------------------------------------------------------------
+
+#Method uint32_t uniqueID() const
+#In Property
+#Line # returns identifier for Text_Blob ##
+#Populate
+
+#Example
+for (int index = 0; index < 2; ++index) {
+    SkTextBlobBuilder textBlobBuilder;
+    const char bunny[] = "/(^x^)\\";
+    const int len = sizeof(bunny) - 1;
+    uint16_t glyphs[len];
+    SkFont font;
+    font.textToGlyphs(bunny, len, SkTextEncoding::kUTF8, glyphs, sizeof(glyphs));
+    font.setScaleX(0.5);
+    int runs[] = { 3, 1, 3 };
+    SkPoint textPos = { 20, 50 };
+    int glyphIndex = 0;
+    for (auto runLen : runs) {
+        font.setSize(1 == runLen ? 20 : 50);
+        const SkTextBlobBuilder::RunBuffer& run =
+                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);
+        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);
+        textPos.fX += font.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen,
+                SkTextEncoding::kGlyphID);
+        glyphIndex += runLen;
+    }
+    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+    SkPaint paint;
+    canvas->drawTextBlob(blob.get(), 0, 0, paint);
+    std::string id = "unique ID:" + std::to_string(blob->uniqueID());
+    canvas->drawString(id.c_str(), 30, blob->bounds().fBottom + 15, paint);
+    canvas->translate(blob->bounds().fRight + 10, 0);
+}
+##
+
+#SeeAlso SkRefCnt
+
+#Method ##
+
+# ------------------------------------------------------------------------------
+
+#Subtopic Text_Intercepts
+#Line # advanced underline, strike through ##
+
+Text_Intercepts describe the intersection of drawn text Glyphs with a pair
+of lines parallel to the text advance. Text_Intercepts permits creating a
+underline that skips Descenders.
+
+#Method int getIntercepts(const SkScalar bounds[2], SkScalar intervals[],
+                      const SkPaint* paint = nullptr) const;
+#In Text_Intercepts
+#Line # returns where lines intersect Text_Blob; underlines ##
+#Populate
+
+#Example
+#Height 143
+    void draw(SkCanvas* canvas) {
+        SkFont font;
+        font.setSize(120);
+        SkPoint textPos = { 20, 110 };
+        int len = 3;
+        SkTextBlobBuilder textBlobBuilder;
+        const SkTextBlobBuilder::RunBuffer& run =
+                textBlobBuilder.allocRun(font, len, textPos.fX, textPos.fY);
+        run.glyphs[0] = 10;
+        run.glyphs[1] = 20;
+        run.glyphs[2] = 30;
+        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+        SkPaint paint;
+        SkScalar bounds[] = { 116, 134 };
+        int count = blob->getIntercepts(bounds, nullptr);
+        std::vector<SkScalar> intervals;
+        intervals.resize(count);
+        (void) paint.getTextBlobIntercepts(blob.get(), bounds, &intervals.front());
+        canvas->drawTextBlob(blob.get(), 0, 0, paint);
+        paint.setColor(0xFFFF7777);
+        SkScalar x = textPos.fX;
+        for (int i = 0; i < count; i+= 2) {
+            canvas->drawRect({x, bounds[0], intervals[i], bounds[1]}, paint);
+            x = intervals[i + 1];
+        }
+        canvas->drawRect({intervals[count - 1], bounds[0], 180, bounds[1]}, paint);
+    }
+##
+
+#Method ##
+
+#Subtopic Text_Intercepts ##
+
+# ------------------------------------------------------------------------------
+
+#Method static sk_sp<SkTextBlob> MakeFromText(const void* text, size_t byteLength, const SkFont& font,
+                                          SkTextEncoding encoding = kUTF8_SkTextEncoding)
+#In Constructors
+#Line # constructs Text_Blob with one run ##
+
+Creates Text_Blob with a single run. text meaning depends on Text_Encoding;
+by default, text is encoded as UTF-8.
+
+font contains attributes used to define the run text: #font_metrics#.
+
+#Param text character code points or Glyphs drawn ##
+#Param  byteLength   byte length of text array ##
+#Param  font    text size, typeface, text scale, and so on, used to draw ##
+#Param  encoding  one of: kUTF8_SkTextEncoding, kUTF16_SkTextEncoding,
+                          kUTF32_SkTextEncoding, kGlyphID_SkTextEncoding
+##
+
+#Return Text_Blob constructed from one run ##
+
+#Example
+#Height 24
+    SkFont font;
+    font.setSize(24);
+    SkPaint canvasPaint;
+    canvasPaint.setColor(SK_ColorBLUE); // respected
+    canvasPaint.setTextSize(2); // ignored
+    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, font);
+    canvas->drawTextBlob(blob, 20, 20, canvasPaint);
+##
+
+#SeeAlso MakeFromString SkTextBlobBuilder
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method static sk_sp<SkTextBlob> MakeFromString(const char* string, const SkFont& font,
+             SkTextEncoding encoding = kUTF8_SkTextEncoding)
+#In Constructors
+#Line # constructs Text_Blob with one run ##
+
+Creates Text_Blob with a single run. string meaning depends on Text_Encoding;
+by default, string is encoded as UTF-8.
+
+font contains Font_Metrics used to define the run text: #font_metrics#.
+
+#Param string character code points or Glyphs drawn ##
+#Param  font    text size, typeface, text scale, and so on, used to draw ##
+#Param  encoding  one of: kUTF8_SkTextEncoding, kUTF16_SkTextEncoding,
+                          kUTF32_SkTextEncoding, kGlyphID_SkTextEncoding
+##
+
+#Return Text_Blob constructed from one run ##
+
+#Example
+#Height 24
+    SkFont font;
+    font.setSize(24);
+    SkPaint canvasPaint;
+    canvasPaint.setColor(SK_ColorBLUE); // respected
+    canvasPaint.setTextSize(2); // ignored
+    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromString("Hello World", font);
+    canvas->drawTextBlob(blob, 20, 20, canvasPaint);
+##
+
+#SeeAlso MakeFromText SkTextBlobBuilder
+
+##
+
+# ------------------------------------------------------------------------------
+
+#Method size_t serialize(const SkSerialProcs& procs, void* memory, size_t memory_size) const
+#In Utility
+#Line # writes Text_Blob to memory ##
+#Populate
+
+#Example
+#Height 64
+###$
+$Function
+#include "SkSerialProcs.h"
+$$
+$$$#
+    SkFont blobFont;
+    blobFont.setSize(24);
+    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, blobFont);
+    char storage[2048];
+    size_t used = blob->serialize(SkSerialProcs(), storage, sizeof(storage));
+    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(storage, used, SkDeserialProcs());
+    canvas->drawTextBlob(copy, 20, 20, SkPaint());
+    std::string usage = "size=" + std::to_string(sizeof(storage)) + " used=" + std::to_string(used);
+    canvas->drawString(usage.c_str(), 20, 40, SkPaint());
+##
+
+#SeeAlso Deserialize SkSerialProcs
+
+#Method ##
+
+# ------------------------------------------------------------------------------
+
+#Method sk_sp<SkData> serialize(const SkSerialProcs& procs) const
+#In Utility
+#Line # writes Text_Blob to Data ##
+#Populate
+
+#Example
+#Height 24
+###$
+$Function
+#include "SkSerialProcs.h"
+$$
+$$$#
+    SkFont blobFont;
+    blobFont.setSize(24);
+    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World", 11, blobFont);
+    sk_sp<SkData> data = blob->serialize(SkSerialProcs());
+    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(data->data(), data->size(), SkDeserialProcs());
+    canvas->drawTextBlob(copy, 20, 20, SkPaint());
+##
+
+#SeeAlso Deserialize SkData SkSerialProcs
+
+#Method ##
+
+# ------------------------------------------------------------------------------
+
+#Method static sk_sp<SkTextBlob> Deserialize(const void* data, size_t size, const SkDeserialProcs& procs)
+#In Constructors
+#Line # constructs Text_Blob from memory ##
+#Populate
+
+#Example
+#Height 24
+#Description
+Text "Hacker" replaces "World!", but does not update its metrics.
+When drawn, "Hacker" uses the spacing computed for "World!".
+##
+###$
+$Function
+#include "SkSerialProcs.h"
+$$
+$$$#
+    SkFont blobFont;
+    blobFont.setSize(24);
+    sk_sp<SkTextBlob> blob = SkTextBlob::MakeFromText("Hello World!", 12, blobFont);
+    sk_sp<SkData> data = blob->serialize(SkSerialProcs());
+    uint16_t glyphs[6];
+    SkPaint blobPaint;
+    blobPaint.textToGlyphs("Hacker", 6, glyphs);
+    memcpy((char*)data->writable_data() + 0x54, glyphs, sizeof(glyphs));
+    sk_sp<SkTextBlob> copy = SkTextBlob::Deserialize(data->data(), data->size(), SkDeserialProcs());
+    canvas->drawTextBlob(copy, 20, 20, SkPaint());
+##
+
+#SeeAlso serialize SkDeserialProcs
+
+#Method ##
+
+#Class SkTextBlob ##
+
+#Topic Text_Blob ##
diff --git a/docs/illustrations.bmh b/docs/illustrations.bmh
index cac966a..d82d250 100644
--- a/docs/illustrations.bmh
+++ b/docs/illustrations.bmh
@@ -14,11 +14,11 @@
     canvas->scale(1.25f, 1.25f);
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    SkTextUtils::DrawString(canvas, "16-bit word", 5 + 20 * 8, 20, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 1.5f, 137, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 6.5f, 187, paint, SkTextUtils::kCenter_Align);
+    SkFont font(nullptr, 10);
+    SkTextUtils::DrawString(canvas, "16-bit word", 5 + 20 * 8, 20, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 1.5f, 137, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 6.5f, 187, font, paint, SkTextUtils::kCenter_Align);
     auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
         SkPaint p(paint);
         p.setColor(SK_ColorRED);
@@ -30,7 +30,7 @@
                 int a = width - e[i];
                 if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
                     char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, p, SkTextUtils::kCenter_Align);
+                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
                     break;
                 }
             }
@@ -38,7 +38,7 @@
         }
         p.setColor(SK_ColorBLACK);
         for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, p, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
         }
         p.setStyle(SkPaint::kStroke_Style);
         for (int i = 0; i <= count; ++i) {
@@ -73,9 +73,9 @@
     canvas->scale(1.25f, 1.25f);
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    SkTextUtils::DrawString(canvas, "16-bit word", 5 + 20 * 8, 20, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, paint, SkTextUtils::kCenter_Align);
+    SkFont font(nullptr, 10);
+    SkTextUtils::DrawString(canvas, "16-bit word", 5 + 20 * 8, 20, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
     auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
         SkPaint p(paint);
         p.setColor(SK_ColorRED);
@@ -87,7 +87,7 @@
                 int a = width - e[i];
                 if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
                     char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, p, SkTextUtils::kCenter_Align);
+                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
                     break;
                 }
             }
@@ -95,7 +95,7 @@
         }
         p.setColor(SK_ColorBLACK);
         for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, p, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
         }
         p.setStyle(SkPaint::kStroke_Style);
         for (int i = 0; i <= count; ++i) {
@@ -128,9 +128,9 @@
     canvas->scale(1.25f, 1.25f);
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, paint, SkTextUtils::kCenter_Align);
+    SkFont font(nullptr, 10);
+    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
     auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
         SkPaint p(paint);
         p.setColor(SK_ColorRED);
@@ -142,7 +142,7 @@
                 int a = width - e[i];
                 if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
                     char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, p, SkTextUtils::kCenter_Align);
+                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
                     break;
                 }
             }
@@ -150,7 +150,7 @@
         }
         p.setColor(SK_ColorBLACK);
         for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, p, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
         }
         p.setStyle(SkPaint::kStroke_Style);
         for (int i = 0; i <= count; ++i) {
@@ -185,9 +185,9 @@
     canvas->scale(1.25f, 1.25f);
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, paint, SkTextUtils::kCenter_Align);
+    SkFont font(nullptr, 10);
+    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
     auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
         SkPaint p(paint);
         p.setColor(SK_ColorRED);
@@ -199,7 +199,7 @@
                 int a = width - e[i];
                 if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
                     char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, p, SkTextUtils::kCenter_Align);
+                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
                     break;
                 }
             }
@@ -207,7 +207,7 @@
         }
         p.setColor(SK_ColorBLACK);
         for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, p, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
         }
         p.setStyle(SkPaint::kStroke_Style);
         for (int i = 0; i <= count; ++i) {
@@ -242,9 +242,9 @@
     canvas->scale(1.25f, 1.25f);
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, paint, SkTextUtils::kCenter_Align);
+    SkFont font(nullptr, 10);
+    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
     auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
         SkPaint p(paint);
         p.setColor(SK_ColorRED);
@@ -256,7 +256,7 @@
                 int a = width - e[i];
                 if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
                     char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, p, SkTextUtils::kCenter_Align);
+                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
                     break;
                 }
             }
@@ -264,7 +264,7 @@
         }
         p.setColor(SK_ColorBLACK);
         for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, p, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
         }
         p.setStyle(SkPaint::kStroke_Style);
         for (int i = 0; i <= count; ++i) {
@@ -299,15 +299,15 @@
     canvas->scale(1.25f, 1.25f);
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 4, 137, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 3, 187, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 7, 187, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 2, 237, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 6, 237, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 5, 287, paint, SkTextUtils::kCenter_Align);
+    SkFont font(nullptr, 10);
+    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 4, 137, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 3, 187, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 7, 187, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 2, 237, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 6, 237, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 5, 287, font, paint, SkTextUtils::kCenter_Align);
     auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
         SkPaint p(paint);
         p.setColor(SK_ColorRED);
@@ -319,7 +319,7 @@
                 int a = width - e[i];
                 if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
                     char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, p, SkTextUtils::kCenter_Align);
+                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
                     break;
                 }
             }
@@ -327,7 +327,7 @@
         }
         p.setColor(SK_ColorBLACK);
         for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, p, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
         }
         p.setStyle(SkPaint::kStroke_Style);
         for (int i = 0; i <= count; ++i) {
@@ -367,15 +367,15 @@
     canvas->scale(1.25f, 1.25f);
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 4, 137, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 3, 187, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 7, 187, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 2, 237, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 6, 237, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 5, 287, paint, SkTextUtils::kCenter_Align);
+    SkFont font(nullptr, 10);
+    SkTextUtils::DrawString(canvas, "32-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 85, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 4, 137, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 3, 187, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 7, 187, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 2, 237, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 6, 237, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 5, 287, font, paint, SkTextUtils::kCenter_Align);
     auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
         SkPaint p(paint);
         p.setColor(SK_ColorRED);
@@ -387,7 +387,7 @@
                 int a = width - e[i];
                 if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
                     char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, p, SkTextUtils::kCenter_Align);
+                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
                     break;
                 }
             }
@@ -395,7 +395,7 @@
         }
         p.setColor(SK_ColorBLACK);
         for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, p, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
         }
         p.setStyle(SkPaint::kStroke_Style);
         for (int i = 0; i <= count; ++i) {
@@ -435,12 +435,12 @@
     canvas->scale(1.25f, 1.25f);
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    SkTextUtils::DrawString(canvas, "64-bit word", 5 + 20 * 16, 20, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 135, paint, SkTextUtils::kCenter_Align);
+    SkFont font(nullptr, 10);
+    SkTextUtils::DrawString(canvas, "64-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 135, font, paint, SkTextUtils::kCenter_Align);
     for (int i = 0; i < 4; ++i) {
-        SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 4, 187 + i * 100, paint, SkTextUtils::kCenter_Align);
-        SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 4, 237 + i * 100, paint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, "(low bits)", 5 + 20 * 4, 187 + i * 100, font, paint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, "(high bits)", 5 + 20 * 4, 237 + i * 100, font, paint, SkTextUtils::kCenter_Align);
     }
     auto drawBoxText = [=](SkScalar e[], const char* s[], int count, int n, SkScalar yPos) -> void {
         SkPaint p(paint);
@@ -453,7 +453,7 @@
                 int a = width - e[i];
                 if (a == n || a == n + 1 || a == n - 32 || a == n - 31) {
                     char num[3] = {(char) ('0' + n / 10), (char) ('0' + n % 10), '\0'};
-                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, p, SkTextUtils::kCenter_Align);
+                    SkTextUtils::DrawString(canvas, n >= 10 ? num : &num[1], xPos, yPos - 5, font, p, SkTextUtils::kCenter_Align);
                     break;
                 }
             }
@@ -461,7 +461,7 @@
         }
         p.setColor(SK_ColorBLACK);
         for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, p, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 10, yPos + 10, font, p, SkTextUtils::kCenter_Align);
         }
         p.setStyle(SkPaint::kStroke_Style);
         for (int i = 0; i <= count; ++i) {
@@ -503,12 +503,12 @@
     canvas->scale(1.25f, 1.25f);
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setTextSize(10);
-    SkTextUtils::DrawString(canvas, "128-bit word", 5 + 20 * 16, 20, paint, SkTextUtils::kCenter_Align);
-    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 135, paint, SkTextUtils::kCenter_Align);
+    SkFont font(nullptr, 10);
+    SkTextUtils::DrawString(canvas, "128-bit word", 5 + 20 * 16, 20, font, paint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "little endian byte order", 5 + 20 * 4, 135, font, paint, SkTextUtils::kCenter_Align);
     for (int i = 0; i < 4; ++i) {
-        SkTextUtils::DrawString(canvas, "(low bits)", 5 + 10 * 4, 187 + i * 100, paint, SkTextUtils::kCenter_Align);
-        SkTextUtils::DrawString(canvas, "(high bits)", 105 + 10 * 4, 237 + i * 100, paint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, "(low bits)", 5 + 10 * 4, 187 + i * 100, font, paint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, "(high bits)", 105 + 10 * 4, 237 + i * 100, font, paint, SkTextUtils::kCenter_Align);
     }
     auto drawBoxText = [=](SkScalar e[], const char* s[], const char* nums[] , 
              int count, int n, SkScalar yPos) -> void {
@@ -522,9 +522,9 @@
                 if (2 == count) {
                     x += stringIndex * 12 + (stringIndex ? 8 : 0);
                 }
-                SkTextUtils::DrawString(canvas, nums[stringIndex], x, yPos - 5, p, SkTextUtils::kCenter_Align);
+                SkTextUtils::DrawString(canvas, nums[stringIndex], x, yPos - 5, font, p, SkTextUtils::kCenter_Align);
                 if (1 == count) {
-                    SkTextUtils::DrawString(canvas, nums[stringIndex], xPos + 100, yPos - 5, p, SkTextUtils::kCenter_Align);
+                    SkTextUtils::DrawString(canvas, nums[stringIndex], xPos + 100, yPos - 5, font, p, SkTextUtils::kCenter_Align);
                 }
                 ++stringIndex;
             }
@@ -532,9 +532,9 @@
         }
         p.setColor(SK_ColorBLACK);
         for (int i = 0; i < count; ++i) {
-            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 5, yPos + 10, p, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, s[i], 5 + (e[i] + e[i + 1]) * 5, yPos + 10, font, p, SkTextUtils::kCenter_Align);
             if (1 == count) {
-                SkTextUtils::DrawString(canvas, s[i], 105 + (e[i] + e[i + 1]) * 5, yPos + 10, p, SkTextUtils::kCenter_Align);
+                SkTextUtils::DrawString(canvas, s[i], 105 + (e[i] + e[i + 1]) * 5, yPos + 10, font, p, SkTextUtils::kCenter_Align);
             }
         }
         p.setStyle(SkPaint::kStroke_Style);
@@ -588,11 +588,10 @@
     SkPaint srcPaint;
     srcPaint.setAntiAlias(true);
     SkPaint labelPaint = srcPaint;
-    labelPaint.setTextSize(16);
-    SkPaint dstPaint = labelPaint;
-    dstPaint.setTextSize(80);
+    SkFont labelFont(nullptr, 16);
+    SkPaint dstPaint = srcPaint;
     dstPaint.setColor(0xFF606080);
-    dstPaint.setTypeface(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()));
+    SkFont dstFont(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()), 80);
 
     SkBitmap srcBits;
     srcBits.allocN32Pixels(80, 84);
@@ -610,10 +609,10 @@
                         SkBlendMode::kDst, SkBlendMode::kDstATop, SkBlendMode::kDstOver,
                         SkBlendMode::kDstIn, SkBlendMode::kDstOut,
                         SkBlendMode::kClear, SkBlendMode::kXor } ) {
-        SkTextUtils::DrawString(canvas, "&", 50, 80, dstPaint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, "&", 50, 80, dstFont, dstPaint, SkTextUtils::kCenter_Align);
         srcPaint.setBlendMode(blend);
         canvas->drawBitmap(srcBits, 0, 0, &srcPaint);
-        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelPaint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelFont, labelPaint, SkTextUtils::kCenter_Align);
         canvas->translate(80, 0);
         if (SkBlendMode::kSrcOut == blend || SkBlendMode::kDstOut == blend) {
             canvas->translate(-80 * 5, 100);
@@ -637,11 +636,10 @@
     SkPaint srcPaint;
     srcPaint.setAntiAlias(true);
     SkPaint labelPaint = srcPaint;
-    labelPaint.setTextSize(16);
-    SkPaint dstPaint = labelPaint;
-    dstPaint.setTextSize(80);
+    SkFont labelFont(nullptr, 16);
+    SkPaint dstPaint = srcPaint;
     dstPaint.setColor(0xFF606080);
-    dstPaint.setTypeface(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()));
+    SkFont dstFont(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()), 80);
 
     srcPaint.setColor(0xFFcc6633);
     SkPath srcPath;
@@ -657,10 +655,10 @@
                         SkBlendMode::kDst, SkBlendMode::kDstATop, SkBlendMode::kDstOver,
                         SkBlendMode::kDstIn, SkBlendMode::kDstOut,
                         SkBlendMode::kClear, SkBlendMode::kXor } ) {
-        SkTextUtils::DrawString(canvas, "&", 50, 80, dstPaint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, "&", 50, 80, dstFont, dstPaint, SkTextUtils::kCenter_Align);
         srcPaint.setBlendMode(blend);
         canvas->drawPath(srcPath, srcPaint);
-        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelPaint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelFont, labelPaint, SkTextUtils::kCenter_Align);
         canvas->translate(80, 0);
         if (SkBlendMode::kSrcOut == blend || SkBlendMode::kDstOut == blend) {
             canvas->translate(-80 * 5, 100);
@@ -684,11 +682,10 @@
     SkPaint srcPaint;
     srcPaint.setAntiAlias(true);
     SkPaint labelPaint = srcPaint;
-    labelPaint.setTextSize(16);
-    SkPaint dstPaint = labelPaint;
-    dstPaint.setTextSize(80);
+    SkFont labelFont(nullptr, 16);
+    SkPaint dstPaint = srcPaint;
     dstPaint.setColor(0xFF606080);
-    dstPaint.setTypeface(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()));
+    SkFont dstFont(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()), 80);
 
     srcPaint.setColor(0xFFcc6633);
     SkPath srcPath;
@@ -699,10 +696,11 @@
             SkBlendMode::kDarken, SkBlendMode::kLighten, SkBlendMode::kColorDodge,
             SkBlendMode::kColorBurn, SkBlendMode::kHardLight, SkBlendMode::kSoftLight,
             SkBlendMode::kDifference, SkBlendMode::kExclusion, SkBlendMode::kMultiply } ) {
-        SkTextUtils::DrawString(canvas, "&", 50, 80, dstPaint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, "&", 50, 80, dstFont, dstPaint, SkTextUtils::kCenter_Align);
         srcPaint.setBlendMode(blend);
         canvas->drawPath(srcPath, srcPaint);
-        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelPaint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelFont, labelPaint,
+                SkTextUtils::kCenter_Align);
         canvas->translate(90, 0);
         if (SkBlendMode::kLighten == blend || SkBlendMode::kDifference == blend) {
             canvas->translate(-90 * 5, 100);
@@ -723,29 +721,28 @@
 ##
 
 void draw(SkCanvas* canvas) {
-    SkPaint srcPaint;

-    srcPaint.setAntiAlias(true);

-    SkPaint labelPaint = srcPaint;

-    labelPaint.setTextSize(16);

-    SkPaint dstPaint = labelPaint;

-    dstPaint.setTextSize(80);

-    dstPaint.setColor(0xFF606080);

-    dstPaint.setTypeface(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()));

-

-    srcPaint.setColor(0xFFcc6633);

-    SkPath srcPath;

-    const SkPoint points[] = {{20, 20}, {80, 45}, {45, 80}};

-    srcPath.addPoly(points, SK_ARRAY_COUNT(points), true);

-    canvas->drawColor(0, SkBlendMode::kClear);

-    for (auto blend : { SkBlendMode::kHue, SkBlendMode::kSaturation, SkBlendMode::kColor,

-                        SkBlendMode::kLuminosity } ) {

-        SkTextUtils::DrawString(canvas, "&", 50, 80, dstPaint, SkTextUtils::kCenter_Align);

-        srcPaint.setBlendMode(blend);

-        canvas->drawPath(srcPath, srcPaint);

-        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelPaint,

-                SkTextUtils::kCenter_Align);

-        canvas->translate(90, 0);

-    }

+    SkPaint srcPaint;
+    srcPaint.setAntiAlias(true);
+    SkPaint labelPaint = srcPaint;
+    SkFont labelFont(nullptr, 16);
+    SkPaint dstPaint = labelPaint;
+    dstPaint.setColor(0xFF606080);
+    SkFont dstFont(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()), 80);
+
+    srcPaint.setColor(0xFFcc6633);
+    SkPath srcPath;
+    const SkPoint points[] = {{20, 20}, {80, 45}, {45, 80}};
+    srcPath.addPoly(points, SK_ARRAY_COUNT(points), true);
+    canvas->drawColor(0, SkBlendMode::kClear);
+    for (auto blend : { SkBlendMode::kHue, SkBlendMode::kSaturation, SkBlendMode::kColor,
+                        SkBlendMode::kLuminosity } ) {
+        SkTextUtils::DrawString(canvas, "&", 50, 80, dstFont, dstPaint, SkTextUtils::kCenter_Align);
+        srcPaint.setBlendMode(blend);
+        canvas->drawPath(srcPath, srcPaint);
+        SkTextUtils::DrawString(canvas, SkBlendMode_Name(blend), 50, 100, labelFont, labelPaint,
+                SkTextUtils::kCenter_Align);
+        canvas->translate(90, 0);
+    }
 }
 ##
 ##
@@ -764,11 +761,10 @@
     SkPaint srcPaint;
     srcPaint.setAntiAlias(true);
     SkPaint labelPaint = srcPaint;
-    labelPaint.setTextSize(16);
-    SkPaint dstPaint = labelPaint;
-    dstPaint.setTextSize(80);
+    SkFont labelFont(nullptr, 16);
+    SkPaint dstPaint = srcPaint;
     dstPaint.setColor(0xFF606080);
-    dstPaint.setTypeface(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()));
+    SkFont dstFont(SkTypeface::MakeFromName("Roboto", SkFontStyle::Bold()), 80);
 
     SkBitmap srcBits;
     srcBits.allocN32Pixels(80, 84);
@@ -783,15 +779,15 @@
     canvas->drawColor(0, SkBlendMode::kClear);
     srcPaint.setBlendMode(SkBlendMode::kModulate);
     for (auto step: { 1, 2 } ) {
-        SkTextUtils::DrawString(canvas, "&", 50, 80, dstPaint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, "&", 50, 80, dstFont, dstPaint, SkTextUtils::kCenter_Align);
         if (1 == step) {
             canvas->drawBitmap(srcBits, 0, 0, &srcPaint);
-            SkTextUtils::DrawString(canvas, "Bitmap", 50, 18, labelPaint, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, "Bitmap", 50, 18, labelFont, labelPaint, SkTextUtils::kCenter_Align);
         } else {
             canvas->drawPath(srcPath, srcPaint);
-            SkTextUtils::DrawString(canvas, "Geometry", 50, 18, labelPaint, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, "Geometry", 50, 18, labelFont, labelPaint, SkTextUtils::kCenter_Align);
         }
-        SkTextUtils::DrawString(canvas, SkBlendMode_Name(SkBlendMode::kModulate), 50, 100, labelPaint,

+        SkTextUtils::DrawString(canvas, SkBlendMode_Name(SkBlendMode::kModulate), 50, 100, labelFont, labelPaint,
                 SkTextUtils::kCenter_Align);
         canvas->translate(120, 0);
     }
@@ -844,54 +840,59 @@
 $$$#
 ##
 void draw(SkCanvas* canvas) {
-    SkPaint lp;
-    lp.setAntiAlias(true);
-    SkPaint tp(lp);
-    SkPaint sp(tp);
-    SkPaint bp(tp);
-    bp.setFakeBoldText(true);
-    sp.setTextSize(10);
-    lp.setColor(SK_ColorGRAY);
-    canvas->translate(0, 32);
-    const int tl = 115;
-    for (unsigned col = 0; col <= SK_ARRAY_COUNT(headers); ++col) {
-       canvas->drawLine(tl + col * 35, 100, tl + col * 35, 250, lp);
-       if (0 == col) {
-          continue;
-       }
-       canvas->drawLine( tl +        col * 35, 100,  tl + 100  + col * 35,   0, lp);
-       SkPoint pts[] = {{tl - 10.f + col * 35, 98}, {tl + 90.f + col * 35,  -2}};
-       SkVector v = pts[1] - pts[0];
-       v.normalize();
-       SkMatrix matrix;
-       matrix.setSinCos(v.fY, v.fX, pts[0].fX, pts[0].fY);
-       canvas->save();
-       canvas->concat(matrix);
-       canvas->drawText(headers[col -1], strlen(headers[col -1]), pts[0].fX, pts[0].fY, bp);
-       canvas->restore();
-    }
-    for (unsigned row = 0; row <= SK_ARRAY_COUNT(dataSet); ++row) {
-        if (0 == row) {
-            canvas->drawLine(tl, 100, tl + 350, 100, lp);
-        } else {
-            canvas->drawLine(5, 100 + row * 25, tl + 350, 100 + row * 25, lp);
-        }
-        if (row == SK_ARRAY_COUNT(dataSet)) {
-            break;
-        }
-        canvas->drawString(dataSet[row].name, 5, 117 + row * 25, bp);
-        if (dataSet[row].super) {
-            SkScalar width = bp.measureText(dataSet[row].name, strlen(dataSet[row].name));
-            canvas->drawText(&dataSet[row].super, 1, 8 + width, 112 + row * 25, sp);
-        }
-        for (unsigned col = 0; col < SK_ARRAY_COUNT(headers); ++col) {
-            int val = dataSet[row].yn[col];
-            canvas->drawString(yna[SkTMin(2, val + 1)], tl + 5 + col * 35, 117 + row * 25, tp);
-            if (val > 1) {
-                char supe = '0' + val - 1;
-                canvas->drawText(&supe, 1, tl + 25 + col * 35, 112 + row * 25, sp);
-            }
-        }
+    SkPaint lp;

+    lp.setAntiAlias(true);

+    SkPaint tp(lp);

+    SkPaint sp(tp);

+    SkFont bf;

+    bf.setEmbolden(true);

+    SkFont sf(nullptr, 10);

+    lp.setColor(SK_ColorGRAY);

+    canvas->translate(0, 32);

+    const int tl = 115;

+    for (unsigned col = 0; col <= SK_ARRAY_COUNT(headers); ++col) {

+       canvas->drawLine(tl + col * 35, 100, tl + col * 35, 250, lp);

+       if (0 == col) {

+          continue;

+       }

+       canvas->drawLine( tl +        col * 35, 100,  tl + 100  + col * 35,   0, lp);

+       SkPoint pts[] = {{tl - 10.f + col * 35, 98}, {tl + 90.f + col * 35,  -2}};

+       SkVector v = pts[1] - pts[0];

+       v.normalize();

+       SkMatrix matrix;

+       matrix.setSinCos(v.fY, v.fX, pts[0].fX, pts[0].fY);

+       canvas->save();

+       canvas->concat(matrix);

+       canvas->drawSimpleText(headers[col -1], strlen(headers[col -1]), SkTextEncoding::kUTF8,

+            pts[0].fX, pts[0].fY, bf, lp);

+       canvas->restore();

+    }

+    for (unsigned row = 0; row <= SK_ARRAY_COUNT(dataSet); ++row) {

+        if (0 == row) {

+            canvas->drawLine(tl, 100, tl + 350, 100, lp);

+        } else {

+            canvas->drawLine(5, 100 + row * 25, tl + 350, 100 + row * 25, lp);

+        }

+        if (row == SK_ARRAY_COUNT(dataSet)) {

+            break;

+        }

+        canvas->drawSimpleText(dataSet[row].name, strlen(dataSet[row].name),

+              SkTextEncoding::kUTF8, 5, 117 + row * 25, bf, lp);

+        if (dataSet[row].super) {

+            SkScalar width = bf.measureText(dataSet[row].name, strlen(dataSet[row].name),

+                    SkTextEncoding::kUTF8);

+            canvas->drawSimpleText(&dataSet[row].super, 1, SkTextEncoding::kUTF8,

+                    8 + width, 112 + row * 25, sf, lp);

+        }

+        for (unsigned col = 0; col < SK_ARRAY_COUNT(headers); ++col) {

+            int val = dataSet[row].yn[col];

+            canvas->drawString(yna[SkTMin(2, val + 1)], tl + 5 + col * 35, 117 + row * 25, tp);

+            if (val > 1) {

+                char supe = '0' + val - 1;

+                canvas->drawSimpleText(&supe, 1, SkTextEncoding::kUTF8,

+                     tl + 25 + col * 35, 112 + row * 25, sf, lp);

+            }

+        }

     }
 }
 #Example ##
diff --git a/docs/spelling.txt b/docs/spelling.txt
index 796637b..52f20bd 100644
--- a/docs/spelling.txt
+++ b/docs/spelling.txt
@@ -187,7 +187,8 @@
 representative represented representing represents request requested requests require 

 required requirements requires requiring resemble reserve reserved

 reset resets reside residing resolution

-resolves resource resources respect respects responsible restore restored restores restoring

+resolves resource resources respect respected respects

+responsible restore restored restores restoring

 restrict restricted restriction restrictive restricts result resulting results retain

 retained retains retrieve retrieved retrieves retroactive return returned returning returns

 reused reveals reverse reversed reverses revert rewinds right right-bottom right-top rightmost root

@@ -211,7 +212,7 @@
 standard standards stands start started starting starts state states stationary stay

 stays std step steps still stock stop stops storage store stored stores storing straight

 straight-line streams strength stretched strictly strikeout strings stripe stripes 

-striping stroke stroked strokes stroking struct studio style stylistic sub-pixel subclass

+striping stroke stroked strokes stroking struct studio style stylistic subclass

 submitting subsequent subsequently subset substitution subtle subtract subtracted subtracts

 succeed succeeded succeeds success successful successfully successive such sufficient

 suggests sum summing supplied supplies supply supplying support supported supports

@@ -249,7 +250,7 @@
 word words work works world would wrap wrapped wraps writable write writes writing 

 written wrong 

 

-x-axis x-coordinate x-radii

+x-axis x-coordinate x-position x-positions x-radii

 

 y-axis y-coordinate y-radii yellow

 

diff --git a/docs/undocumented.bmh b/docs/undocumented.bmh
index 50ee7ab..c6e3fe6 100644
--- a/docs/undocumented.bmh
+++ b/docs/undocumented.bmh
@@ -328,7 +328,7 @@
 ##
 #Topic ##
 
-#Topic Font_Types
+#Topic Text_Encoding
 #Enum SkTextEncoding
 #Const kUTF8_SkTextEncoding 0
 ##
@@ -338,8 +338,31 @@
 ##
 #Const kGlyphID_SkTextEncoding 3
 ##
-##
+TextEncoding determines whether text specifies character codes and their encoded
+size, or glyph indices. Characters are encoded as specified by the
+#A Unicode standard # https://unicode.org/standard/standard.html ##
+.
 
+Character codes encoded size are specified by UTF-8, UTF-16, or UTF-32.
+All character code formats are able to represent all of Unicode, differing only
+in the total storage required.
+
+#A UTF-8 (RFC 3629) # https://tools.ietf.org/html/rfc3629 ##
+encodes each character as one or more 8-bit bytes.
+
+#A UTF-16 (RFC 2781) # https://tools.ietf.org/html/rfc2781 ##
+encodes each character as one or two 16-bit words.
+
+#A UTF-32 # https://www.unicode.org/versions/Unicode5.0.0/ch03.pdf ##
+encodes each character as one 32-bit word.
+
+Font_Manager uses font data to convert character code points into glyph indices.
+A glyph index is a 16-bit word.
+#Enum SkTextEncoding ##
+#Topic Text_Encoding ##
+
+#Topic Font_Hinting
+#Line # glyph outline adjustment ##
 #EnumClass SkFontHinting
 #Const kNone 0
 #Line # glyph outlines unchanged ##
@@ -372,8 +395,10 @@
     horizontally decimated LCD displays; FT_LOAD_TARGET_LCD_V is a
     variant of FT_LOAD_TARGET_NORMAL optimized for vertically decimated LCD displays.
 ##
-##
+#EnumClass SkFontHinting ##
+#Topic Font_Hinting ##
 
+#Topic Font_Metrics
 #Struct SkFontMetrics
 #Line # values computed by Font_Manager using Typeface ##
 
diff --git a/example/HelloWorld.cpp b/example/HelloWorld.cpp
index b20d30a..812c631 100644
--- a/example/HelloWorld.cpp
+++ b/example/HelloWorld.cpp
@@ -9,6 +9,7 @@
 
 #include "GrContext.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkGraphics.h"
 
@@ -79,9 +80,10 @@
     }
 
     // Draw a message with a nice black paint
-    paint.setSubpixelText(true);
+    SkFont font;
+    font.setSubpixel(true);
+    font.setSize(20);
     paint.setColor(SK_ColorBLACK);
-    paint.setTextSize(20);
 
     canvas->save();
     static const char message[] = "Hello World";
@@ -95,7 +97,7 @@
     canvas->rotate(fRotationAngle);
 
     // Draw the text
-    canvas->drawText(message, strlen(message), 0, 0, paint);
+    canvas->drawSimpleText(message, strlen(message), kUTF8_SkTextEncoding, 0, 0, font, paint);
 
     canvas->restore();
 }
diff --git a/experimental/c-api-example/c.md b/experimental/c-api-example/c.md
index 19ae515..d173236 100644
--- a/experimental/c-api-example/c.md
+++ b/experimental/c-api-example/c.md
@@ -127,5 +127,4 @@
             experimental/c-api-example/skia-c-example.c \
             "$SKIA_LIB_DIR"/libskia.* -Wl,-rpath -Wl,"$SKIA_LIB_DIR"
         ./skia-c-example
-        [ $(uname) = Darwin ] && open     skia-c-example.png
-        [ $(uname) = Linux  ] && xdg-open skia-c-example.png
+        bin/sysopen skia-c-example.png
diff --git a/experimental/canvaskit/.gitignore b/experimental/canvaskit/.gitignore
index d8b83df..2c681f5 100644
--- a/experimental/canvaskit/.gitignore
+++ b/experimental/canvaskit/.gitignore
@@ -1 +1,2 @@
 package-lock.json
+fonts/*.cpp
\ No newline at end of file
diff --git a/experimental/canvaskit/CHANGELOG.md b/experimental/canvaskit/CHANGELOG.md
new file mode 100644
index 0000000..bb86db4
--- /dev/null
+++ b/experimental/canvaskit/CHANGELOG.md
@@ -0,0 +1,57 @@
+# CanvasKit Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+ - `SkPath.addRoundRect`, `SkPath.reset`, `SkPath.rewind` exposed.
+ - `SkCanvas.drawArc`, `SkCanvas.drawLine`, `SkCanvas.drawOval`, `SkCanvas.drawRoundRect` exposed.
+ - Can import/export a SkPath to an array of commands. See `CanvasKit.MakePathFromCmds` and
+   `SkPath.toCmds`.
+
+### Fixed
+ - Potential bug in `ready()` if already loaded.
+
+## [0.3.1] - 2019-01-04
+### Added
+ - `SkFont` now exposed.
+ - `MakeCanvasSurface` can now take a canvas element directly.
+ - `MakeWebGLCanvasSurface` can now take a WebGL context as an integer and use it directly.
+
+### Changed
+ - `CanvasKitInit(...).then()` is no longer the recommended way to initialize things.
+It will be removed in 0.4.0. Use `CanvasKitInit(...).ready()`, which returns a real Promise.
+
+### Removed
+- `SkPaint.measureText` - use `SkFont.measureText` instead.
+
+## [0.3.0] - 2018-12-18
+
+### Added
+- Add Canvas2D JS layer. This mirrors the HTML Canvas API. This may be omitted at compile time
+    it by adding `no_canvas` to the `compile.sh` invocation.
+- `CanvasKit.FontMgr.DefaultRef()` and `fontmgr.MakeTypefaceFromData` to load fonts.
+- Exposed `SkPath.setVolatile`. Some animations see performance improvements by setting
+their paths' volatility to true.
+
+### Fixed
+- `SkPath.addRect` now correctly draws counter-clockwise vs clockwise.
+
+### Changed
+- `CanvasKit.MakeImageShader` no longer takes encoded bytes, but an `SkImage`, created from
+    `CanvasKit.MakeImageFromEncoded`. Additionally, the optional parameters `clampIfUnpremul`
+    and `localMatrix` have been exposed.
+- `SkPath.arcTo` now takes `startAngle`, `sweepAngle`, `forceMoveTo` as additional parameters.
+- `SkPath.stroke` has a new option `precision`  It defaults to 1.0.
+- CanvasKit comes with one font (NotoMono) instead of the Skia TestTypeface. Clients are encouraged
+  to use the new `fontmgr.MakeTypefaceFromData` for more font variety.
+
+### Removed
+- `CanvasKit.initFonts()` - no longer needed.
+
+
+## [0.2.1] - 2018-11-20
+Beginning of Changelog history
\ No newline at end of file
diff --git a/experimental/canvaskit/Makefile b/experimental/canvaskit/Makefile
index 5994936..0e65549 100644
--- a/experimental/canvaskit/Makefile
+++ b/experimental/canvaskit/Makefile
@@ -23,6 +23,7 @@
 	mkdir -p ./canvaskit/bin
 	cp ../../out/canvaskit_wasm_debug/canvaskit.js   ./canvaskit/bin
 	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm.map ./canvaskit/bin
 
 debug_cpu:
 	# Does an incremental build where possible.
@@ -30,6 +31,7 @@
 	mkdir -p ./canvaskit/bin
 	cp ../../out/canvaskit_wasm_debug/canvaskit.js   ./canvaskit/bin
 	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm ./canvaskit/bin
+	cp ../../out/canvaskit_wasm_debug/canvaskit.wasm.map ./canvaskit/bin
 
 profile:
 	./compile.sh profiling
diff --git a/experimental/canvaskit/WasmAliases.h b/experimental/canvaskit/WasmAliases.h
new file mode 100644
index 0000000..eff6e43
--- /dev/null
+++ b/experimental/canvaskit/WasmAliases.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef WasmAliases_DEFINED
+#define WasmAliases_DEFINED
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+
+using namespace emscripten;
+
+// Self-documenting types
+using JSArray = emscripten::val;
+using JSColor = int32_t;
+using JSObject = emscripten::val;
+using JSString = emscripten::val;
+using SkPathOrNull = emscripten::val;
+using Uint8Array = emscripten::val;
+
+#endif
diff --git a/experimental/canvaskit/canvaskit/CODE_OF_CONDUCT.md b/experimental/canvaskit/canvaskit/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..e5358bc
--- /dev/null
+++ b/experimental/canvaskit/canvaskit/CODE_OF_CONDUCT.md
@@ -0,0 +1,93 @@
+# Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of
+experience, education, socio-economic status, nationality, personal appearance,
+race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+*   Using welcoming and inclusive language
+*   Being respectful of differing viewpoints and experiences
+*   Gracefully accepting constructive criticism
+*   Focusing on what is best for the community
+*   Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+*   The use of sexualized language or imagery and unwelcome sexual attention or
+    advances
+*   Trolling, insulting/derogatory comments, and personal or political attacks
+*   Public or private harassment
+*   Publishing others' private information, such as a physical or electronic
+    address, without explicit permission
+*   Other conduct which could reasonably be considered inappropriate in a
+    professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, or to ban temporarily or permanently any
+contributor for other behaviors that they deem inappropriate, threatening,
+offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+This Code of Conduct also applies outside the project spaces when the Project
+Steward has a reasonable belief that an individual's behavior may have a
+negative impact on the project or its community.
+
+## Conflict Resolution
+
+We do not believe that all conflict is bad; healthy debate and disagreement
+often yield positive results. However, it is never okay to be disrespectful or
+to engage in behavior that violates the project’s code of conduct.
+
+If you see someone violating the code of conduct, you are encouraged to address
+the behavior directly with those involved. Many issues can be resolved quickly
+and easily, and this gives people more control over the outcome of their
+dispute. If you are unable to resolve the matter for any reason, or if the
+behavior is threatening or harassing, report it. We are dedicated to providing
+an environment where participants feel welcome and safe.
+
+Reports should be directed to [Heather Miller](mailto:hcm@google.com), the
+Project Steward(s) for *canvaskit*. It is the Project Steward’s duty to
+receive and address reported violations of the code of conduct. They will then
+work with a committee consisting of representatives from the Open Source
+Programs Office and the Google Open Source Strategy team. If for any reason you
+are uncomfortable reaching out the Project Steward, please email
+opensource@google.com.
+
+We will investigate every complaint, but you may not receive a direct response.
+We will use our discretion in determining when and how to follow up on reported
+incidents, which may range from not taking action to permanent expulsion from
+the project and project-sponsored spaces. We will notify the accused of the
+report and provide them an opportunity to discuss it before any action is taken.
+The identity of the reporter will be omitted from the details of the report
+supplied to the accused. In potentially harmful situations, such as ongoing
+harassment or threats to anyone's safety, we may take action without notice.
+
+## Attribution
+
+This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
+available at
+https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
diff --git a/experimental/canvaskit/canvaskit/CONTRIBUTING.md b/experimental/canvaskit/canvaskit/CONTRIBUTING.md
new file mode 100644
index 0000000..5fa3c4a
--- /dev/null
+++ b/experimental/canvaskit/canvaskit/CONTRIBUTING.md
@@ -0,0 +1,21 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement. You (or your employer) retain the copyright to your contribution;
+this simply gives us permission to use and redistribute your contributions as
+part of the project. Head over to <https://cla.developers.google.com/> to see
+your current agreements on file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code reviews
+
+All submissions, including submissions by project members, require review.
+Please see the guidelines for contributing code at https://skia.org/dev/contrib/
\ No newline at end of file
diff --git a/experimental/canvaskit/canvaskit/LICENSE b/experimental/canvaskit/canvaskit/LICENSE
new file mode 100644
index 0000000..fc53e3e
--- /dev/null
+++ b/experimental/canvaskit/canvaskit/LICENSE
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 Google Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+--------------------------------------------------------------------------------
diff --git a/experimental/canvaskit/canvaskit/README.md b/experimental/canvaskit/canvaskit/README.md
new file mode 100644
index 0000000..70e4737
--- /dev/null
+++ b/experimental/canvaskit/canvaskit/README.md
@@ -0,0 +1,106 @@
+A WASM version of Skia's Canvas API.
+
+See https://skia.org/user/modules/canvaskit for more background information.
+
+# Getting Started
+
+## Browser
+To use the library, run `npm install canvaskit-wasm` and then simply include it:
+
+    <script src="/node_modules/canvaskit-wasm/bin/canvaskit.js"></script>
+    CanvasKitInit({
+        locateFile: (file) => '/node_modules/canvaskit-wasm/bin/'+file,
+    }).ready().then((CanvasKit) => {
+        // Code goes here using CanvasKit
+    });
+
+As with all npm packages, there's a freely available CDN via unpkg.com:
+
+    <script src="https://unpkg.com/canvaskit-wasm@0.3.0/bin/canvaskit.js"></script>
+    CanvasKitInit({
+         locateFile: (file) => 'https://unpkg.com/canvaskit-wasm@0.3.0/bin/'+file,
+    }).ready().then(...)
+
+## Node
+To use CanvasKit in Node, it's similar to the browser:
+
+    const CanvasKitInit = require('/node_modules/canvaskit-wasm/bin/canvaskit.js');
+    CanvasKitInit({
+        locateFile: (file) => __dirname + '/bin/'+file,
+    }).ready().then((CanvasKit) => {
+        // Code goes here using CanvasKit
+    });
+
+With node, you also need to supply the `--expose-wasm` flag.
+
+## WebPack
+
+WebPack's support for WASM is still somewhat experimental, but CanvasKit can be
+used with a few configuration changes.
+
+In the JS code, use require():
+
+    const CanvasKitInit = require('canvaskit-wasm/bin/canvaskit.js')
+    CanvasKitInit().ready().then((CanvasKit) => {
+        // Code goes here using CanvasKit
+    });
+
+Since WebPack does not expose the entire `/node_modules/` directory, but instead
+packages only the needed pieces, we have to copy canvaskit.wasm into the build directory.
+One such solution is to use [CopyWebpackPlugin](https://github.com/webpack-contrib/copy-webpack-plugin).
+For example, add the following plugin:
+
+    config.plugins.push(
+        new CopyWebpackPlugin([
+            { from: 'node_modules/canvaskit-wasm/bin/canvaskit.wasm' }
+        ])
+    );
+
+If webpack gives an error similar to:
+
+    ERROR in ./node_modules/canvaskit-wasm/bin/canvaskit.js
+    Module not found: Error: Can't resolve 'fs' in '...'
+
+Then, add the following configuration change to the node section of the config:
+
+    config.node = {
+        fs: 'empty'
+    };
+
+
+# Using the CanvasKit API
+
+See `example.html` and `node.example.js` for demos of how to use the API.
+
+More detailed docs will be coming soon.
+
+## Drop-in Canvas2D replacement
+For environments where an HTML canvas is not available (e.g. Node, headless servers),
+CanvasKit has an optional API (included by default) that mirrors the HTML canvas.
+
+    let skcanvas = CanvasKit.MakeCanvas(600, 600);
+
+    let ctx = skcanvas.getContext('2d');
+    let rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
+
+    // Add three color stops
+    rgradient.addColorStop(0, 'red');
+    rgradient.addColorStop(0.7, 'white');
+    rgradient.addColorStop(1, 'blue');
+
+    ctx.fillStyle = rgradient;
+    ctx.globalAlpha = 0.7;
+    ctx.fillRect(0, 0, 600, 600);
+
+    let imgData = skcanvas.toDataURL();
+    // imgData is now a base64 encoded image.
+
+See more examples in `example.html` and `node.example.js`.
+
+
+# Filing bugs
+
+Please file bugs at [skbug.com](skbug.com).
+It may be convenient to use [our online fiddle](jsfiddle.skia.org/canvaskit) to demonstrate any issues encountered.
+
+See CONTRIBUTING.md for more information on sending pull requests.
\ No newline at end of file
diff --git a/experimental/canvaskit/canvaskit/Roboto-Regular.ttf b/experimental/canvaskit/canvaskit/Roboto-Regular.ttf
new file mode 100644
index 0000000..0e58508
--- /dev/null
+++ b/experimental/canvaskit/canvaskit/Roboto-Regular.ttf
Binary files differ
diff --git a/experimental/canvaskit/canvaskit/Roboto-Regular.woff b/experimental/canvaskit/canvaskit/Roboto-Regular.woff
new file mode 100644
index 0000000..f823258
--- /dev/null
+++ b/experimental/canvaskit/canvaskit/Roboto-Regular.woff
Binary files differ
diff --git a/experimental/canvaskit/canvaskit/example.html b/experimental/canvaskit/canvaskit/example.html
index f388019..c018b4b 100644
--- a/experimental/canvaskit/canvaskit/example.html
+++ b/experimental/canvaskit/canvaskit/example.html
@@ -30,6 +30,10 @@
 <canvas id=api5_c width=300 height=300></canvas>
 <img id=api6 width=300 height=300>
 <canvas id=api6_c width=300 height=300></canvas>
+<img id=api7 width=300 height=300>
+<canvas id=api7_c width=300 height=300></canvas>
+<img id=api8 width=300 height=300>
+<canvas id=api8_c width=300 height=300></canvas>
 
 <h2> CanvasKit draws Paths to the browser</h2>
 <canvas id=vertex1 width=300 height=300></canvas>
@@ -45,10 +49,6 @@
 <canvas id=sk_party width=500 height=500></canvas>
 <canvas id=sk_onboarding width=500 height=500></canvas>
 
-<!-- Doesn't work yet. -->
-<button id=lego_btn>Take a picture of the legos</button>
-
-
 <script type="text/javascript" src="/node_modules/canvaskit/bin/canvaskit.js"></script>
 
 <script type="text/javascript" charset="utf-8">
@@ -60,52 +60,44 @@
   var onboardingJSON = null;
   var fullBounds = {fLeft: 0, fTop: 0, fRight: 500, fBottom: 500};
 
-  var nimaFile = null;
-  var nimaTexture = null;
+  var robotoData = null;
 
-  var bonesImage = null;
+  var bonesImageData = null;
   CanvasKitInit({
     locateFile: (file) => '/node_modules/canvaskit/bin/'+file,
-  }).then((CK) => {
-     // when debugging, it can be handy to not run directly in the then, because if there
-     // is a failure (for example, miscalling an API), the WASM loader tries to re-load
-     // the web assembly in the (much slower) ArrayBuffer version. This will also fail
-     // and thus there is a lot of extra log spew.
-     // Thus, the setTimeout to run on the next microtask avoids this second loading
-     // and the log spew.
-    setTimeout(() => {
-      CK.initFonts();
-      CanvasKit = CK;
-      DrawingExample(CanvasKit);
-      PathExample(CanvasKit);
-      InkExample(CanvasKit);
-      // Set bounds to fix the 4:3 resolution of the legos
-      addScreenshotListener(SkottieExample(CanvasKit, 'sk_legos', legoJSON,
-                              {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}));
-      // Re-size to fit
-      SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
-      SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
-      SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
+  }).ready().then((CK) => {
+    CanvasKit = CK;
+    DrawingExample(CanvasKit, robotoData);
+    PathExample(CanvasKit);
+    InkExample(CanvasKit);
+    // Set bounds to fix the 4:3 resolution of the legos
+    SkottieExample(CanvasKit, 'sk_legos', legoJSON,
+                  {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
+    // Re-size to fit
+    SkottieExample(CanvasKit, 'sk_drinks', drinksJSON, fullBounds);
+    SkottieExample(CanvasKit, 'sk_party', confettiJSON, fullBounds);
+    SkottieExample(CanvasKit, 'sk_onboarding', onboardingJSON, fullBounds);
 
-      CanvasAPI1(CanvasKit);
-      CanvasAPI2(CanvasKit);
-      CanvasAPI3(CanvasKit);
-      CanvasAPI4(CanvasKit);
-      CanvasAPI5(CanvasKit);
-      CanvasAPI6(CanvasKit);
+    CanvasAPI1(CanvasKit);
+    CanvasAPI2(CanvasKit);
+    CanvasAPI3(CanvasKit);
+    CanvasAPI4(CanvasKit);
+    CanvasAPI5(CanvasKit);
+    CanvasAPI6(CanvasKit);
+    CanvasAPI7(CanvasKit);
+    CanvasAPI8(CanvasKit)
 
-      VertexAPI1(CanvasKit);
-      VertexAPI2(CanvasKit, bonesImage);
+    VertexAPI1(CanvasKit);
+    VertexAPI2(CanvasKit, bonesImageData);
 
-      GradiantAPI1(CanvasKit);
-    }, 0);
+    GradiantAPI1(CanvasKit);
   });
 
   fetch('https://storage.googleapis.com/skia-cdn/misc/lego_loader.json').then((resp) => {
     resp.text().then((str) => {
       legoJSON = str;
-      addScreenshotListener(SkottieExample(CanvasKit, 'sk_legos', legoJSON,
-                            {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300}));
+      SkottieExample(CanvasKit, 'sk_legos', legoJSON,
+                    {fLeft: -50, fTop: 0, fRight: 350, fBottom: 300});
     });
   });
 
@@ -132,34 +124,22 @@
 
   fetch('https://storage.googleapis.com/skia-cdn/misc/bones.jpg').then((resp) => {
     resp.arrayBuffer().then((buffer) => {
-      bonesImage = buffer;
-      VertexAPI2(CanvasKit, bonesImage);
+      bonesImageData = buffer;
+      VertexAPI2(CanvasKit, bonesImageData);
     });
   });
 
-  function addScreenshotListener(surface) {
-    if (!surface) {
-      return;
-    }
-    if (CanvasKit.gpu) {
-      // Doesn't work on GPU (yet)
-      document.getElementById('lego_btn').remove();
-      return;
-    }
-    const btn = document.getElementById('lego_btn');
-    btn.addEventListener('click', () => {
-      const img = surface.makeImageSnapshot()
-      if (!img) { return }
-      const png = img.encodeToData()
-      if (!png) { return }
-      const pngBytes = CanvasKit.getSkDataBytes(png);
-      // See https://stackoverflow.com/a/12713326
-      let b64encoded = btoa(String.fromCharCode.apply(null, pngBytes));
-      console.log("base64 encoded image", b64encoded);
+  fetch('./Roboto-Regular.woff').then((resp) => {
+    resp.arrayBuffer().then((buffer) => {
+      robotoData = buffer;
+      DrawingExample(CanvasKit, robotoData)
     });
-  }
+  });
 
-  function DrawingExample(CanvasKit) {
+  function DrawingExample(CanvasKit, robotoData) {
+    if (!robotoData || !CanvasKit) {
+      return;
+    }
     const surface = CanvasKit.MakeCanvasSurface('patheffect');
     if (!surface) {
       console.error('Could not make surface');
@@ -171,10 +151,14 @@
 
     const paint = new CanvasKit.SkPaint();
 
+    const fontMgr = CanvasKit.SkFontMgr.RefDefault();
+    let roboto = fontMgr.MakeTypefaceFromData(robotoData);
+
     const textPaint = new CanvasKit.SkPaint();
     textPaint.setColor(CanvasKit.RED);
     textPaint.setTextSize(30);
     textPaint.setAntiAlias(true);
+    textPaint.setTypeface(roboto);
 
     let i = 0;
 
@@ -183,6 +167,9 @@
 
     function drawFrame() {
       const path = starPath(CanvasKit, X, Y);
+      // Some animations see performance improvements by marking their
+      // paths as volatile.
+      path.setIsVolatile(true);
       CanvasKit.setCurrentContext(context);
       const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], i/5);
       i++;
@@ -263,9 +250,16 @@
 
       canvas.drawPath(path, paint);
 
+      let rrect = new CanvasKit.SkPath()
+                               .addRoundRect(100, 10, 140, 62,
+                                             10, 4, true);
+
+      canvas.drawPath(rrect, paint);
+
       surface.flush();
 
       path.delete();
+      rrect.delete();
       paint.delete();
       // Intentionally just draw frame once
     }
@@ -431,25 +425,43 @@
                           realCanvas._img = bitmap;
                         });
 
+    let realFontLoaded = new FontFace('Bungee', 'url(/tests/assets/Bungee-Regular.ttf)', {
+      'family': 'Bungee',
+      'style': 'normal',
+      'weight': '400',
+    }).load().then((font) => {
+      document.fonts.add(font);
+    });
 
-    Promise.all([realPromise, skPromise]).then(() => {
+    let skFontLoaded = fetch('/tests/assets/Bungee-Regular.ttf').then(
+                             (response) => response.arrayBuffer()).then(
+                             (buffer) => {
+                                // loadFont is synchronous
+                                skcanvas.loadFont(buffer, {
+                                  'family': 'Bungee',
+                                  'style': 'normal',
+                                  'weight': '400',
+                                });
+                              });
+
+    Promise.all([realPromise, skPromise, realFontLoaded, skFontLoaded]).then(() => {
       for (let canvas of [skcanvas, realCanvas]) {
         let ctx = canvas.getContext('2d');
         ctx.fillStyle = '#EEE';
         ctx.fillRect(0, 0, 300, 300);
         ctx.fillStyle = 'black';
-        ctx.font = '30px Impact'
+        ctx.font = '26px Bungee';
         ctx.rotate(.1);
         let text = ctx.measureText('Awesome');
-        ctx.fillText('Awesome ', 50, 100);
-        ctx.strokeText('Groovy!', 60+text.width, 100);
+        ctx.fillText('Awesome ', 25, 100);
+        ctx.strokeText('Groovy!', 35+text.width, 100);
 
         // Draw line under Awesome
         ctx.strokeStyle = 'rgba(125,0,0,0.5)';
         ctx.beginPath();
-        ctx.lineWidth=6;
-        ctx.lineTo(50, 105);
-        ctx.lineTo(50 + text.width, 105);
+        ctx.lineWidth = 6;
+        ctx.moveTo(25, 105);
+        ctx.lineTo(25 + text.width, 105);
         ctx.stroke();
 
         // squished vertically
@@ -459,8 +471,18 @@
         ctx.rotate(-.2);
         ctx.imageSmoothingEnabled = false;
         ctx.drawImage(canvas._img, 100, 150, 400, 350, 10, 200, 150, 100);
-      }
 
+        let idata = ctx.getImageData(80, 220, 40, 45);
+        ctx.putImageData(idata, 250, 10);
+        ctx.putImageData(idata, 200, 10, 20, 10, 20, 30);
+        ctx.resetTransform();
+        ctx.strokeStyle = 'black';
+        ctx.lineWidth = 1;
+        ctx.strokeRect(200, 10, 40, 45);
+
+        idata = ctx.createImageData(10, 20);
+        ctx.putImageData(idata, 10, 10);
+      }
 
       document.getElementById('api1').src = skcanvas.toDataURL();
       skcanvas.dispose();
@@ -474,6 +496,10 @@
     realCanvas.width = 300;
     realCanvas.height = 300;
 
+    // svg data for a clock
+    skcanvas._path = skcanvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
+    realCanvas._path = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
+
     for (let canvas of [skcanvas, realCanvas]) {
       let ctx = canvas.getContext('2d');
       ctx.scale(1.5, 1.5);
@@ -506,8 +532,32 @@
 
       ctx.lineWidth = 4/3;
       ctx.stroke();
+
+      // make a clock
+      ctx.stroke(canvas._path);
+
+      // Test edgecases and draw direction
+      ctx.beginPath();
+      ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true);
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true);
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false);
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true);
+      ctx.stroke();
+      ctx.beginPath();
+      ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true);
+      ctx.stroke();
     }
     document.getElementById('api2').src = skcanvas.toDataURL();
+    skcanvas.dispose();
   }
 
   function CanvasAPI3(CanvasKit) {
@@ -538,14 +588,15 @@
       ctx.rect(90, 10, 20, 20);
       ctx.resetTransform();
 
+      ctx.save();
       ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
       ctx.rect(110, 10, 20, 20);
       ctx.lineTo(110, 0);
-      ctx.resetTransform();
+      ctx.restore();
       ctx.lineTo(220, 120);
 
       ctx.scale(3.0, 3.0);
-      ctx.font = '6pt Arial';
+      ctx.font = '6pt Noto Mono';
       ctx.fillText('This text should be huge', 10, 80);
       ctx.resetTransform();
 
@@ -565,6 +616,7 @@
 
     }
     document.getElementById('api3').src = skcanvas.toDataURL();
+    skcanvas.dispose();
   }
 
   function CanvasAPI4(CanvasKit) {
@@ -613,10 +665,23 @@
       ctx.stroke();
 
       ctx.fillStyle = 'green';
-      ctx.font = '16pt Arial';
+      ctx.font = '16pt Noto Mono';
       ctx.fillText('This should be shadowed', 20, 80);
+
+      ctx.beginPath();
+      ctx.lineWidth = 6;
+      ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2);
+      ctx.scale(2, 1);
+      ctx.moveTo(10, 290)
+      ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2);
+      ctx.resetTransform();
+      ctx.scale(3, 1);
+      ctx.moveTo(10, 290)
+      ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2);
+      ctx.stroke();
     }
     document.getElementById('api4').src = skcanvas.toDataURL();
+    skcanvas.dispose();
   }
 
   function CanvasAPI5(CanvasKit) {
@@ -667,12 +732,13 @@
       ctx.lineTo(300, 400);
       ctx.stroke();
 
-      ctx.font = '36pt Arial';
+      ctx.font = '36pt Noto Mono';
       ctx.strokeText('Dashed', 20, 350);
       ctx.fillText('Not Dashed', 20, 400);
 
     }
     document.getElementById('api5').src = skcanvas.toDataURL();
+    skcanvas.dispose();
   }
 
   function CanvasAPI6(CanvasKit) {
@@ -684,7 +750,7 @@
     for (let canvas of [skcanvas, realCanvas]) {
       let ctx = canvas.getContext('2d');
 
-      var rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
+      let rgradient = ctx.createRadialGradient(200, 300, 10, 100, 100, 300);
 
       // Add three color stops
       rgradient.addColorStop(0, 'red');
@@ -705,7 +771,7 @@
       ctx.save();
       ctx.clip();
 
-      var lgradient = ctx.createLinearGradient(200, 20, 420, 40);
+      let lgradient = ctx.createLinearGradient(200, 20, 420, 40);
 
       // Add three color stops
       lgradient.addColorStop(0, 'green');
@@ -721,6 +787,152 @@
 
     }
     document.getElementById('api6').src = skcanvas.toDataURL();
+    skcanvas.dispose();
+  }
+
+  function CanvasAPI7(CanvasKit) {
+    let skcanvas = CanvasKit.MakeCanvas(300, 300);
+    let realCanvas = document.getElementById('api7_c');
+
+    let skPromise   = fetch('./test.png')
+                        // if clients want to use a Blob, they are responsible
+                        // for reading it themselves.
+                        .then((response) => response.arrayBuffer())
+                        .then((buffer) => {
+                          skcanvas._img = skcanvas.decodeImage(buffer);
+                        });
+    let realPromise = fetch('./test.png')
+                        .then((response) => response.blob())
+                        .then((blob) => createImageBitmap(blob))
+                        .then((bitmap) => {
+                          realCanvas._img = bitmap;
+                        });
+
+
+    Promise.all([realPromise, skPromise]).then(() => {
+      for (let canvas of [skcanvas, realCanvas]) {
+        let ctx = canvas.getContext('2d');
+        ctx.fillStyle = '#EEE';
+        ctx.fillRect(0, 0, 300, 300);
+        ctx.lineWidth = 20;
+        ctx.scale(0.1, 0.2);
+
+        let pattern = ctx.createPattern(canvas._img, 'repeat');
+        ctx.fillStyle = pattern;
+        ctx.fillRect(0, 0, 1500, 750);
+
+        pattern = ctx.createPattern(canvas._img, 'repeat-x');
+        ctx.fillStyle = pattern;
+        ctx.fillRect(1500, 0, 3000, 750);
+
+        ctx.globalAlpha = 0.7
+        pattern = ctx.createPattern(canvas._img, 'repeat-y');
+        ctx.fillStyle = pattern;
+        ctx.fillRect(0, 750, 1500, 1500);
+        ctx.strokeRect(0, 750, 1500, 1500);
+
+        pattern = ctx.createPattern(canvas._img, 'no-repeat');
+        ctx.fillStyle = pattern;
+        pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800});
+        ctx.fillRect(0, 0, 3000, 1500);
+      }
+
+      document.getElementById('api7').src = skcanvas.toDataURL();
+      skcanvas.dispose();
+    });
+  }
+
+  function CanvasAPI8(CanvasKit) {
+    let skcanvas = CanvasKit.MakeCanvas(300, 300);
+    let realCanvas = document.getElementById('api8_c');
+
+    function drawPoint(ctx, x, y, color) {
+      ctx.fillStyle = color;
+      ctx.fillRect(x, y, 1, 1);
+    }
+    const IN = 'purple';
+    const OUT = 'orange';
+    const SCALE = 4;
+
+    const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10],
+                 [6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24],
+                 [25, 25], [26, 26], [27, 27]];
+
+    const tests = [
+      {
+        xOffset: 0,
+        yOffset: 0,
+        fillType: 'nonzero',
+        strokeWidth: 0,
+        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'),
+      },
+      {
+        xOffset: 30,
+        yOffset: 0,
+        fillType: 'evenodd',
+        strokeWidth: 0,
+        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'),
+      },
+      {
+        xOffset: 0,
+        yOffset: 30,
+        fillType: null,
+        strokeWidth: 1,
+        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
+      },
+      {
+        xOffset: 30,
+        yOffset: 30,
+        fillType: null,
+        strokeWidth: 2,
+        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
+      },
+    ];
+
+    for (let canvas of [skcanvas, realCanvas]) {
+      let ctx = canvas.getContext('2d');
+      ctx.font = '11px Noto Mono';
+      // Draw some visual aids
+      ctx.fillText('path-nonzero', 30, 15);
+      ctx.fillText('path-evenodd', 150, 15);
+      ctx.fillText('stroke-1px-wide', 30, 130);
+      ctx.fillText('stroke-2px-wide', 150, 130);
+      ctx.fillText('purple is IN, orange is OUT', 10, 280);
+
+      // Scale up to make single pixels easier to see
+      ctx.scale(SCALE, SCALE);
+      for (let test of tests) {
+        ctx.beginPath();
+        let xOffset = test.xOffset;
+        let yOffset = test.yOffset;
+
+        ctx.fillStyle = '#AAA';
+        ctx.lineWidth = test.strokeWidth;
+        ctx.rect(5+xOffset, 5+yOffset, 20, 20);
+        ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false);
+        if (test.fillType) {
+          ctx.fill(test.fillType);
+        } else {
+          ctx.stroke();
+        }
+
+        for (let pt of pts) {
+          let [x, y] = pt;
+          x += xOffset;
+          y += yOffset;
+          // naively apply transform when querying because the points queried
+          // ignore the CTM.
+          if (test.testFn(ctx, x, y)) {
+            drawPoint(ctx, x, y, IN);
+          } else {
+            drawPoint(ctx, x, y, OUT);
+          }
+        }
+      }
+    }
+
+    document.getElementById('api8').src = skcanvas.toDataURL();
+    skcanvas.dispose();
   }
 
   function VertexAPI1(CanvasKit) {
@@ -765,8 +977,9 @@
     surface.delete();
   }
 
-  function VertexAPI2(CanvasKit, bonesImage) {
-    if (!CanvasKit || !bonesImage) {
+  // bonesImageDatae is passed in as raw, encoded bytes.
+  function VertexAPI2(CanvasKit, bonesImageData) {
+    if (!CanvasKit || !bonesImageData) {
       return;
     }
     const surface = CanvasKit.MakeCanvasSurface('vertex2');
@@ -777,6 +990,7 @@
     const context = CanvasKit.currentContext();
     const canvas = surface.getCanvas();
     let paint = new CanvasKit.SkPaint();
+    let bonesImage = CanvasKit.MakeImageFromEncoded(bonesImageData);
 
     let shader = CanvasKit.MakeImageShader(bonesImage,
                     CanvasKit.TileMode.Clamp, CanvasKit.TileMode.Clamp);
@@ -824,6 +1038,7 @@
     //tVerts.delete();
     //vertices.delete();
 
+    // bonesImage && bonesImage.delete();
     //shader && shader.delete();
     //paint.delete();
     //surface.delete();
@@ -852,8 +1067,8 @@
                               pos, CanvasKit.TileMode.Mirror, transform);
 
     paint.setShader(shader);
-    paint.setTextSize(100);
-    canvas.drawText("Radial", 10, 200, paint);
+    paint.setTextSize(75);
+    canvas.drawText('Radial', 10, 200, paint);
     surface.flush();
   }
 </script>
diff --git a/experimental/canvaskit/canvaskit/node.example.js b/experimental/canvaskit/canvaskit/node.example.js
index b9afb7f..f9ae3f6 100644
--- a/experimental/canvaskit/canvaskit/node.example.js
+++ b/experimental/canvaskit/canvaskit/node.example.js
@@ -1,21 +1,24 @@
-console.log('hello world');
-
 const CanvasKitInit = require('./bin/canvaskit.js');
 const fs = require('fs');
 const path = require('path');
 
-
 CanvasKitInit({
   locateFile: (file) => __dirname + '/bin/'+file,
-}).then((CK) => {
-  CanvasKit = CK;
+}).ready().then((CanvasKit) => {
   let canvas = CanvasKit.MakeCanvas(300, 300);
 
   let img = fs.readFileSync(path.join(__dirname, 'test.png'));
   img = canvas.decodeImage(img);
 
+  let fontData = fs.readFileSync(path.join(__dirname, './Roboto-Regular.woff'));
+  canvas.loadFont(fontData, {
+                                'family': 'Roboto',
+                                'style': 'normal',
+                                'weight': '400',
+                              });
+
   let ctx = canvas.getContext('2d');
-  ctx.font = '30px Impact'
+  ctx.font = '30px Roboto';
   ctx.rotate(.1);
   let text = ctx.measureText('Awesome');
   ctx.fillText('Awesome ', 50, 100);
@@ -24,7 +27,7 @@
   // Draw line under Awesome
   ctx.strokeStyle = 'rgba(125,0,0,0.5)';
   ctx.beginPath();
-  ctx.lineWidth=6;
+  ctx.lineWidth = 6;
   ctx.lineTo(50, 102);
   ctx.lineTo(50 + text.width, 102);
   ctx.stroke();
@@ -37,36 +40,38 @@
   ctx.imageSmoothingEnabled = false;
   ctx.drawImage(img, 100, 150, 400, 350, 10, 200, 150, 100);
 
-  // TODO load an image from file
   console.log('<img src="' + canvas.toDataURL() + '" />');
 });
 
-function fancyAPI() {
-  CanvasKit.initFonts();
-  console.log('loaded');
-
+// Not currently called
+function fancyAPI(CanvasKit) {
   let surface = CanvasKit.MakeSurface(300, 300);
   const canvas = surface.getCanvas();
 
   const paint = new CanvasKit.SkPaint();
 
+  const fontMgr = CanvasKit.SkFontMgr.RefDefault();
+  let robotoData = fs.readFileSync(path.join(__dirname, './Roboto-Regular.woff'));
+  const roboto = fontMgr.MakeTypefaceFromData(robotoData);
+
   const textPaint = new CanvasKit.SkPaint();
   textPaint.setColor(CanvasKit.Color(40, 0, 0));
   textPaint.setTextSize(30);
   textPaint.setAntiAlias(true);
+  textPaint.setTypeface(roboto);
 
-  const path = starPath(CanvasKit);
+  const skpath = starPath(CanvasKit);
   const dpe = CanvasKit.MakeSkDashPathEffect([15, 5, 5, 10], 1);
 
   paint.setPathEffect(dpe);
-  paint.setStyle(CanvasKit.PaintStyle.STROKE);
+  paint.setStyle(CanvasKit.PaintStyle.Stroke);
   paint.setStrokeWidth(5.0);
   paint.setAntiAlias(true);
   paint.setColor(CanvasKit.Color(66, 129, 164, 1.0));
 
   canvas.clear(CanvasKit.Color(255, 255, 255, 1.0));
 
-  canvas.drawPath(path, paint);
+  canvas.drawPath(skpath, paint);
   canvas.drawText('Try Clicking!', 10, 280, textPaint);
 
   surface.flush();
@@ -86,11 +91,12 @@
   let b64encoded = Buffer.from(pngBytes).toString('base64');
   console.log(`<img src="data:image/png;base64,${b64encoded}" />`);
 
+  // These delete calls free up memeory in the C++ WASM memory block.
   dpe.delete();
-  path.delete();
-  canvas.delete();
+  skpath.delete();
   textPaint.delete();
   paint.delete();
+  roboto.delete();
 
   surface.dispose();
 }
diff --git a/experimental/canvaskit/canvaskit/package.json b/experimental/canvaskit/canvaskit/package.json
index 87c5dc1..91c17dd 100644
--- a/experimental/canvaskit/canvaskit/package.json
+++ b/experimental/canvaskit/canvaskit/package.json
@@ -1,11 +1,11 @@
 {
   "name": "canvaskit-wasm",
-  "version": "0.2.1",
+  "version": "0.3.1",
   "description": "A WASM version of Skia's Canvas API",
   "main": "bin/canvaskit.js",
   "homepage": "https://github.com/google/skia/tree/master/experimental/canvaskit",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1"
   },
-  "license": "Apache-2.0"
+  "license": "BSD-3-Clause"
 }
diff --git a/experimental/canvaskit/canvaskit_bindings.cpp b/experimental/canvaskit/canvaskit_bindings.cpp
index 2a71d2e..df90f14 100644
--- a/experimental/canvaskit/canvaskit_bindings.cpp
+++ b/experimental/canvaskit/canvaskit_bindings.cpp
@@ -22,8 +22,10 @@
 #include "SkDiscretePathEffect.h"
 #include "SkEncodedImageFormat.h"
 #include "SkFilterQuality.h"
+#include "SkFont.h"
 #include "SkFontMgr.h"
 #include "SkFontMgrPriv.h"
+#include "SkFontTypes.h"
 #include "SkGradientShader.h"
 #include "SkImage.h"
 #include "SkImageInfo.h"
@@ -42,68 +44,36 @@
 #include "SkStrokeRec.h"
 #include "SkSurface.h"
 #include "SkSurfaceProps.h"
-#include "SkTestFontMgr.h"
 #include "SkTrimPathEffect.h"
+#include "SkTypeface.h"
+#include "SkTypes.h"
 #include "SkVertices.h"
 
-#if SK_INCLUDE_SKOTTIE
-#include "Skottie.h"
-#if SK_INCLUDE_MANAGED_SKOTTIE
-#include "SkottieProperty.h"
-#include "SkottieUtils.h"
-#endif // SK_INCLUDE_MANAGED_SKOTTIE
-#endif // SK_INCLUDE_SKOTTIE
-
 #include <iostream>
 #include <string>
 
+#include "WasmAliases.h"
 #include <emscripten.h>
 #include <emscripten/bind.h>
+
 #if SK_SUPPORT_GPU
 #include <GL/gl.h>
 #include <emscripten/html5.h>
 #endif
 
-using namespace emscripten;
-
-// Self-documenting types
-using JSArray = emscripten::val;
-using JSColor = int32_t;
-using JSObject = emscripten::val;
-using JSString = emscripten::val;
-using SkPathOrNull = emscripten::val;
-using Uint8Array = emscripten::val;
-
 // Aliases for less typing
 using BoneIndices = SkVertices::BoneIndices;
 using BoneWeights = SkVertices::BoneWeights;
 using Bone        = SkVertices::Bone;
 
-void EMSCRIPTEN_KEEPALIVE initFonts() {
-    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
-}
-
 #if SK_SUPPORT_GPU
 // Wraps the WebGL context in an SkSurface and returns it.
 // This function based on the work of
 // https://github.com/Zubnix/skia-wasm-port/, used under the terms of the MIT license.
-sk_sp<SkSurface> getWebGLSurface(std::string id, int width, int height) {
-    // Context configurations
-    EmscriptenWebGLContextAttributes attrs;
-    emscripten_webgl_init_context_attributes(&attrs);
-    attrs.alpha = true;
-    attrs.premultipliedAlpha = true;
-    attrs.majorVersion = 1;
-    attrs.enableExtensionsByDefault = true;
-
-    EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(id.c_str(), &attrs);
-    if (context < 0) {
-        printf("failed to create webgl context %d\n", context);
-        return nullptr;
-    }
+sk_sp<SkSurface> getWebGLSurface(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context, int width, int height) {
     EMSCRIPTEN_RESULT r = emscripten_webgl_make_context_current(context);
     if (r < 0) {
-        printf("failed to make webgl current %d\n", r);
+        printf("failed to make webgl context current %d\n", r);
         return nullptr;
     }
 
@@ -149,6 +119,13 @@
                              sm.pers0 , sm.pers1 , sm.pers2);
 }
 
+SimpleMatrix toSimpleSkMatrix(const SkMatrix& sm) {
+    SimpleMatrix m {sm[0], sm[1], sm[2],
+                    sm[3], sm[4], sm[5],
+                    sm[6], sm[7], sm[8]};
+    return m;
+}
+
 struct SimpleImageInfo {
     int width;
     int height;
@@ -161,91 +138,6 @@
     return SkImageInfo::Make(sii.width, sii.height, sii.colorType, sii.alphaType);
 }
 
-#if SK_INCLUDE_SKOTTIE && SK_INCLUDE_MANAGED_SKOTTIE
-namespace {
-
-class ManagedAnimation final : public SkRefCnt {
-public:
-    static sk_sp<ManagedAnimation> Make(const std::string& json) {
-        auto mgr = skstd::make_unique<skottie_utils::CustomPropertyManager>();
-        auto animation = skottie::Animation::Builder()
-                            .setMarkerObserver(mgr->getMarkerObserver())
-                            .setPropertyObserver(mgr->getPropertyObserver())
-                            .make(json.c_str(), json.size());
-
-        return animation
-            ? sk_sp<ManagedAnimation>(new ManagedAnimation(std::move(animation), std::move(mgr)))
-            : nullptr;
-    }
-
-    // skottie::Animation API
-    void render(SkCanvas* canvas) const { fAnimation->render(canvas, nullptr); }
-    void render(SkCanvas* canvas, const SkRect& dst) const { fAnimation->render(canvas, &dst); }
-    void seek(SkScalar t) { fAnimation->seek(t); }
-    SkScalar duration() const { return fAnimation->duration(); }
-    const SkSize&      size() const { return fAnimation->size(); }
-    std::string version() const { return std::string(fAnimation->version().c_str()); }
-
-    // CustomPropertyManager API
-    JSArray getColorProps() const {
-        JSArray props = emscripten::val::array();
-
-        for (const auto& cp : fPropMgr->getColorProps()) {
-            JSObject prop = emscripten::val::object();
-            prop.set("key", cp);
-            prop.set("value", fPropMgr->getColor(cp));
-            props.call<void>("push", prop);
-        }
-
-        return props;
-    }
-
-    JSArray getOpacityProps() const {
-        JSArray props = emscripten::val::array();
-
-        for (const auto& op : fPropMgr->getOpacityProps()) {
-            JSObject prop = emscripten::val::object();
-            prop.set("key", op);
-            prop.set("value", fPropMgr->getOpacity(op));
-            props.call<void>("push", prop);
-        }
-
-        return props;
-    }
-
-    bool setColor(const std::string& key, JSColor c) {
-        return fPropMgr->setColor(key, static_cast<SkColor>(c));
-    }
-
-    bool setOpacity(const std::string& key, float o) {
-        return fPropMgr->setOpacity(key, o);
-    }
-
-    JSArray getMarkers() const {
-        JSArray markers = emscripten::val::array();
-        for (const auto& m : fPropMgr->markers()) {
-            JSObject marker = emscripten::val::object();
-            marker.set("name", m.name);
-            marker.set("t0"  , m.t0);
-            marker.set("t1"  , m.t1);
-            markers.call<void>("push", marker);
-        }
-        return markers;
-    }
-
-private:
-    ManagedAnimation(sk_sp<skottie::Animation> animation,
-                     std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)
-        : fAnimation(std::move(animation))
-        , fPropMgr(std::move(propMgr)) {}
-
-    sk_sp<skottie::Animation>                             fAnimation;
-    std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr;
-};
-
-} // anonymous ns
-#endif // SK_INCLUDE_SKOTTIE
-
 //========================================================================================
 // Path things
 //========================================================================================
@@ -275,15 +167,29 @@
 void ApplyAddRect(SkPath& path, SkScalar left, SkScalar top,
                   SkScalar right, SkScalar bottom, bool ccw) {
     path.addRect(left, top, right, bottom,
-                 ccw ? SkPath::Direction::kCW_Direction :
-                 SkPath::Direction::kCCW_Direction);
+                 ccw ? SkPath::Direction::kCCW_Direction :
+                 SkPath::Direction::kCW_Direction);
 }
 
+void ApplyAddRoundRect(SkPath& path, SkScalar left, SkScalar top,
+                  SkScalar right, SkScalar bottom, uintptr_t /* SkScalar*  */ rPtr,
+                  bool ccw) {
+    // See comment below for uintptr_t explanation
+    const SkScalar* radii = reinterpret_cast<const SkScalar*>(rPtr);
+    path.addRoundRect(SkRect::MakeLTRB(left, top, right, bottom), radii,
+                      ccw ? SkPath::Direction::kCCW_Direction : SkPath::Direction::kCW_Direction);
+}
+
+
 void ApplyArcTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
                 SkScalar radius) {
     p.arcTo(x1, y1, x2, y2, radius);
 }
 
+void ApplyArcToAngle(SkPath& p, SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo) {
+    p.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
+}
+
 void ApplyClose(SkPath& p) {
     p.close();
 }
@@ -306,6 +212,14 @@
     p.moveTo(x, y);
 }
 
+void ApplyReset(SkPath& p) {
+    p.reset();
+}
+
+void ApplyRewind(SkPath& p) {
+    p.rewind();
+}
+
 void ApplyQuadTo(SkPath& p, SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) {
     p.quadTo(x1, y1, x2, y2);
 }
@@ -334,6 +248,14 @@
     return emscripten::val(s.c_str());
 }
 
+SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromSVGString(std::string str) {
+    SkPath path;
+    if (SkParsePath::FromSVGString(str.c_str(), &path)) {
+        return emscripten::val(path);
+    }
+    return emscripten::val::null();
+}
+
 SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromOp(const SkPath& pathOne, const SkPath& pathTwo, SkPathOp op) {
     SkPath out;
     if (Op(pathOne, pathTwo, op, &out)) {
@@ -351,6 +273,133 @@
     return a == b;
 }
 
+// =================================================================================
+// Creating/Exporting Paths with cmd arrays
+// =================================================================================
+
+static const int MOVE = 0;
+static const int LINE = 1;
+static const int QUAD = 2;
+static const int CONIC = 3;
+static const int CUBIC = 4;
+static const int CLOSE = 5;
+
+template <typename VisitFunc>
+void VisitPath(const SkPath& p, VisitFunc&& f) {
+    SkPath::RawIter iter(p);
+    SkPoint pts[4];
+    SkPath::Verb verb;
+    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+        f(verb, pts, iter);
+    }
+}
+
+JSArray EMSCRIPTEN_KEEPALIVE ToCmds(const SkPath& path) {
+    JSArray cmds = emscripten::val::array();
+
+    VisitPath(path, [&cmds](SkPath::Verb verb, const SkPoint pts[4], SkPath::RawIter iter) {
+        JSArray cmd = emscripten::val::array();
+        switch (verb) {
+        case SkPath::kMove_Verb:
+            cmd.call<void>("push", MOVE, pts[0].x(), pts[0].y());
+            break;
+        case SkPath::kLine_Verb:
+            cmd.call<void>("push", LINE, pts[1].x(), pts[1].y());
+            break;
+        case SkPath::kQuad_Verb:
+            cmd.call<void>("push", QUAD, pts[1].x(), pts[1].y(), pts[2].x(), pts[2].y());
+            break;
+        case SkPath::kConic_Verb:
+            cmd.call<void>("push", CONIC,
+                           pts[1].x(), pts[1].y(),
+                           pts[2].x(), pts[2].y(), iter.conicWeight());
+            break;
+        case SkPath::kCubic_Verb:
+            cmd.call<void>("push", CUBIC,
+                           pts[1].x(), pts[1].y(),
+                           pts[2].x(), pts[2].y(),
+                           pts[3].x(), pts[3].y());
+            break;
+        case SkPath::kClose_Verb:
+            cmd.call<void>("push", CLOSE);
+            break;
+        case SkPath::kDone_Verb:
+            SkASSERT(false);
+            break;
+        }
+        cmds.call<void>("push", cmd);
+    });
+    return cmds;
+}
+
+// This type signature is a mess, but it's necessary. See, we can't use "bind" (EMSCRIPTEN_BINDINGS)
+// and pointers to primitive types (Only bound types like SkPoint). We could if we used
+// cwrap (see https://becominghuman.ai/passing-and-returning-webassembly-array-parameters-a0f572c65d97)
+// but that requires us to stick to C code and, AFAIK, doesn't allow us to return nice things like
+// SkPath or SkOpBuilder.
+//
+// So, basically, if we are using C++ and EMSCRIPTEN_BINDINGS, we can't have primative pointers
+// in our function type signatures. (this gives an error message like "Cannot call foo due to unbound
+// types Pi, Pf").  But, we can just pretend they are numbers and cast them to be pointers and
+// the compiler is happy.
+SkPathOrNull EMSCRIPTEN_KEEPALIVE MakePathFromCmds(uintptr_t /* float* */ cptr, int numCmds) {
+    const auto* cmds = reinterpret_cast<const float*>(cptr);
+    SkPath path;
+    float x1, y1, x2, y2, x3, y3;
+
+    // if there are not enough arguments, bail with the path we've constructed so far.
+    #define CHECK_NUM_ARGS(n) \
+        if ((i + n) > numCmds) { \
+            SkDebugf("Not enough args to match the verbs. Saw %d commands\n", numCmds); \
+            return emscripten::val::null(); \
+        }
+
+    for(int i = 0; i < numCmds;){
+         switch (sk_float_floor2int(cmds[i++])) {
+            case MOVE:
+                CHECK_NUM_ARGS(2);
+                x1 = cmds[i++], y1 = cmds[i++];
+                path.moveTo(x1, y1);
+                break;
+            case LINE:
+                CHECK_NUM_ARGS(2);
+                x1 = cmds[i++], y1 = cmds[i++];
+                path.lineTo(x1, y1);
+                break;
+            case QUAD:
+                CHECK_NUM_ARGS(4);
+                x1 = cmds[i++], y1 = cmds[i++];
+                x2 = cmds[i++], y2 = cmds[i++];
+                path.quadTo(x1, y1, x2, y2);
+                break;
+            case CONIC:
+                CHECK_NUM_ARGS(5);
+                x1 = cmds[i++], y1 = cmds[i++];
+                x2 = cmds[i++], y2 = cmds[i++];
+                x3 = cmds[i++]; // weight
+                path.conicTo(x1, y1, x2, y2, x3);
+                break;
+            case CUBIC:
+                CHECK_NUM_ARGS(6);
+                x1 = cmds[i++], y1 = cmds[i++];
+                x2 = cmds[i++], y2 = cmds[i++];
+                x3 = cmds[i++], y3 = cmds[i++];
+                path.cubicTo(x1, y1, x2, y2, x3, y3);
+                break;
+            case CLOSE:
+                path.close();
+                break;
+            default:
+                SkDebugf("  path: UNKNOWN command %f, aborting dump...\n", cmds[i-1]);
+                return emscripten::val::null();
+        }
+    }
+
+    #undef CHECK_NUM_ARGS
+
+    return emscripten::val(path);
+}
+
 //========================================================================================
 // Path Effects
 //========================================================================================
@@ -393,6 +442,7 @@
     SkScalar miter_limit;
     SkPaint::Join join;
     SkPaint::Cap cap;
+    float precision;
 };
 
 bool ApplyStroke(SkPath& path, StrokeOpts opts) {
@@ -403,7 +453,7 @@
     p.setStrokeWidth(opts.width);
     p.setStrokeMiter(opts.miter_limit);
 
-    return p.getFillPath(path, &path);
+    return p.getFillPath(path, &path, nullptr, opts.precision);
 }
 
 // to map from raw memory to a uint8array
@@ -423,6 +473,10 @@
         }
 
         template<>
+        void raw_destructor<SkTypeface>(SkTypeface *ptr) {
+        }
+
+        template<>
         void raw_destructor<SkVertices>(SkVertices *ptr) {
         }
     }
@@ -440,7 +494,6 @@
 // types Pi, Pf").  But, we can just pretend they are numbers and cast them to be pointers and
 // the compiler is happy.
 EMSCRIPTEN_BINDINGS(Skia) {
-    function("initFonts", &initFonts);
 #if SK_SUPPORT_GPU
     function("_getWebGLSurface", &getWebGLSurface, allow_raw_pointers());
     function("currentContext", &emscripten_webgl_get_current_context);
@@ -454,9 +507,9 @@
         return SkImage::MakeFromEncoded(bytes);
     }), allow_raw_pointers());
     function("_getRasterDirectSurface", optional_override([](const SimpleImageInfo ii,
-                                                             uintptr_t /* uint8_t*  */ pptr,
+                                                             uintptr_t /* uint8_t*  */ pPtr,
                                                              size_t rowBytes)->sk_sp<SkSurface> {
-        uint8_t* pixels = reinterpret_cast<uint8_t*>(pptr);
+        uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
         SkImageInfo imageInfo = toSkImageInfo(ii);
         return SkSurface::MakeRasterDirect(imageInfo, pixels, rowBytes, nullptr);
     }), allow_raw_pointers());
@@ -471,7 +524,9 @@
         // Adds a little helper because emscripten doesn't expose default params.
         return SkMaskFilter::MakeBlur(style, sigma, respectCTM);
     }), allow_raw_pointers());
+    function("_MakePathFromCmds", &MakePathFromCmds);
     function("MakePathFromOp", &MakePathFromOp);
+    function("MakePathFromSVGString", &MakePathFromSVGString);
 
     // These won't be called directly, there's a JS helper to deal with typed arrays.
     function("_MakeSkDashPathEffect", optional_override([](uintptr_t /* float* */ cptr, int count, SkScalar phase)->sk_sp<SkPathEffect> {
@@ -479,21 +534,29 @@
         const float* intervals = reinterpret_cast<const float*>(cptr);
         return SkDashPathEffect::Make(intervals, count, phase);
     }), allow_raw_pointers());
-    function("_MakeImageShader", optional_override([](uintptr_t /* uint8_t*  */ iPtr, int ilen,
-                                SkShader::TileMode tx, SkShader::TileMode ty)->sk_sp<SkShader> {
+    function("_MakeImage", optional_override([](SimpleImageInfo ii,
+                                                uintptr_t /* uint8_t*  */ pPtr, int plen,
+                                                size_t rowBytes)->sk_sp<SkImage> {
         // See comment above for uintptr_t explanation
-        const uint8_t* imgBytes = reinterpret_cast<const uint8_t*>(iPtr);
+        uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
+        SkImageInfo info = toSkImageInfo(ii);
+        sk_sp<SkData> pixelData = SkData::MakeFromMalloc(pixels, plen);
 
-        auto imgData = SkData::MakeFromMalloc(imgBytes, ilen);
-        auto img = SkImage::MakeFromEncoded(imgData);
-        if (!img) {
-            SkDebugf("Could not decode image\n");
-            return nullptr;
-        }
-
-        return SkImageShader::Make(img, tx, ty, nullptr);
+        return SkImage::MakeRasterData(info, pixelData, rowBytes);
     }), allow_raw_pointers());
-    // Allow localMatrix to be optional, so we have 2 declarations of these gradients
+    // Allow localMatrix to be optional, so we have 2 declarations of these shaders
+    function("_MakeImageShader", optional_override([](sk_sp<SkImage> img,
+                                SkShader::TileMode tx, SkShader::TileMode ty,
+                                bool clampAsIfUnpremul)->sk_sp<SkShader> {
+        return SkImageShader::Make(img, tx, ty, nullptr, clampAsIfUnpremul);
+    }), allow_raw_pointers());
+    function("_MakeImageShader", optional_override([](sk_sp<SkImage> img,
+                                SkShader::TileMode tx, SkShader::TileMode ty,
+                                bool clampAsIfUnpremul, const SimpleMatrix& lm)->sk_sp<SkShader> {
+        SkMatrix localMatrix = toSkMatrix(lm);
+
+        return SkImageShader::Make(img, tx, ty, &localMatrix, clampAsIfUnpremul);
+    }), allow_raw_pointers());
     function("_MakeLinearGradientShader", optional_override([](SkPoint start, SkPoint end,
                                 uintptr_t /* SkColor*  */ cPtr, uintptr_t /* SkScalar*  */ pPtr,
                                 int count, SkShader::TileMode mode, uint32_t flags)->sk_sp<SkShader> {
@@ -595,6 +658,11 @@
             self.clear(SkColor(color));
         }))
         .function("clipPath", select_overload<void (const SkPath&, SkClipOp, bool)>(&SkCanvas::clipPath))
+        .function("clipRect", select_overload<void (const SkRect&, SkClipOp, bool)>(&SkCanvas::clipRect))
+        .function("concat", optional_override([](SkCanvas& self, const SimpleMatrix& m) {
+            self.concat(toSkMatrix(m));
+        }))
+        .function("drawArc", &SkCanvas::drawArc)
         .function("drawImage", select_overload<void (const sk_sp<SkImage>&, SkScalar, SkScalar, const SkPaint*)>(&SkCanvas::drawImage), allow_raw_pointers())
         .function("drawImageRect", optional_override([](SkCanvas& self, const sk_sp<SkImage>& image,
                                                         SkRect src, SkRect dst,
@@ -603,9 +671,12 @@
                                fastSample ? SkCanvas::kFast_SrcRectConstraint :
                                             SkCanvas::kStrict_SrcRectConstraint);
         }), allow_raw_pointers())
+        .function("drawLine", select_overload<void (SkScalar, SkScalar, SkScalar, SkScalar, const SkPaint&)>(&SkCanvas::drawLine))
+        .function("drawOval", &SkCanvas::drawOval)
         .function("drawPaint", &SkCanvas::drawPaint)
         .function("drawPath", &SkCanvas::drawPath)
         .function("drawRect", &SkCanvas::drawRect)
+        .function("drawRoundRect", &SkCanvas::drawRoundRect)
         .function("drawShadow", optional_override([](SkCanvas& self, const SkPath& path,
                                                      const SkPoint3& zPlaneParams,
                                                      const SkPoint3& lightPos, SkScalar lightRadius,
@@ -623,20 +694,83 @@
         }))
         .function("drawVertices", select_overload<void (const sk_sp<SkVertices>&, SkBlendMode, const SkPaint&)>(&SkCanvas::drawVertices))
         .function("flush", &SkCanvas::flush)
+        .function("getTotalMatrix", optional_override([](const SkCanvas& self)->SimpleMatrix {
+            SkMatrix m = self.getTotalMatrix();
+            return toSimpleSkMatrix(m);
+        }))
+        .function("_readPixels", optional_override([](SkCanvas& self, SimpleImageInfo di,
+                                                      uintptr_t /* uint8_t* */ pPtr,
+                                                      size_t dstRowBytes, int srcX, int srcY) {
+            uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
+            SkImageInfo dstInfo = toSkImageInfo(di);
+
+            return self.readPixels(dstInfo, pixels, dstRowBytes, srcX, srcY);
+        }))
         .function("restore", &SkCanvas::restore)
         .function("rotate", select_overload<void (SkScalar, SkScalar, SkScalar)>(&SkCanvas::rotate))
         .function("save", &SkCanvas::save)
         .function("scale", &SkCanvas::scale)
-        .function("setMatrix", optional_override([](SkCanvas& self, const SimpleMatrix& m) {
-            self.setMatrix(toSkMatrix(m));
-        }))
         .function("skew", &SkCanvas::skew)
-        .function("translate", &SkCanvas::translate);
+        .function("translate", &SkCanvas::translate)
+        .function("_writePixels", optional_override([](SkCanvas& self, SimpleImageInfo di,
+                                                       uintptr_t /* uint8_t* */ pPtr,
+                                                       size_t srcRowBytes, int dstX, int dstY) {
+            uint8_t* pixels = reinterpret_cast<uint8_t*>(pPtr);
+            SkImageInfo dstInfo = toSkImageInfo(di);
+
+            return self.writePixels(dstInfo, pixels, srcRowBytes, dstX, dstY);
+        }))
+        ;
 
     class_<SkData>("SkData")
         .smart_ptr<sk_sp<SkData>>("sk_sp<SkData>>")
         .function("size", &SkData::size);
 
+    class_<SkFont>("SkFont")
+        .constructor<>()
+        .constructor<sk_sp<SkTypeface>>()
+        .constructor<sk_sp<SkTypeface>, SkScalar>()
+        .constructor<sk_sp<SkTypeface>, SkScalar, SkScalar, SkScalar>()
+        .function("getScaleX", &SkFont::getScaleX)
+        .function("getSize", &SkFont::getSize)
+        .function("getSkewX", &SkFont::getSkewX)
+        .function("getTypeface", &SkFont::getTypeface, allow_raw_pointers())
+        .function("measureText", optional_override([](SkFont& self, std::string text) {
+            // TODO(kjlubick): This does not work well for non-ascii
+            // Need to maybe add a helper in interface.js that supports UTF-8
+            // Otherwise, go with std::wstring and set UTF-32 encoding.
+            return self.measureText(text.c_str(), text.length(), SkTextEncoding::kUTF8);
+        }))
+        .function("setScaleX", &SkFont::setScaleX)
+        .function("setSize", &SkFont::setSize)
+        .function("setSkewX", &SkFont::setSkewX)
+        .function("setTypeface", &SkFont::setTypeface, allow_raw_pointers());
+
+    class_<SkFontMgr>("SkFontMgr")
+        .smart_ptr<sk_sp<SkFontMgr>>("sk_sp<SkFontMgr>")
+        .class_function("RefDefault", &SkFontMgr::RefDefault)
+#ifdef SK_DEBUG
+        .function("dumpFamilies", optional_override([](SkFontMgr& self) {
+            int numFam = self.countFamilies();
+            SkDebugf("There are %d font families\n");
+            for (int i = 0 ; i< numFam; i++) {
+                SkString s;
+                self.getFamilyName(i, &s);
+                SkDebugf("\t%s", s.c_str());
+            }
+        }))
+#endif
+        .function("countFamilies", &SkFontMgr::countFamilies)
+        .function("_makeTypefaceFromData", optional_override([](SkFontMgr& self,
+                                                uintptr_t /* uint8_t*  */ fPtr,
+                                                int flen)->sk_sp<SkTypeface> {
+        // See comment above for uintptr_t explanation
+        uint8_t* font = reinterpret_cast<uint8_t*>(fPtr);
+        sk_sp<SkData> fontData = SkData::MakeFromMalloc(font, flen);
+
+        return self.makeFromData(fontData);
+    }), allow_raw_pointers());
+
     class_<SkImage>("SkImage")
         .smart_ptr<sk_sp<SkImage>>("sk_sp<SkImage>")
         .function("height", &SkImage::height)
@@ -665,12 +799,6 @@
         .function("getStrokeMiter", &SkPaint::getStrokeMiter)
         .function("getStrokeWidth", &SkPaint::getStrokeWidth)
         .function("getTextSize", &SkPaint::getTextSize)
-        .function("measureText", optional_override([](SkPaint& self, std::string text) {
-            // TODO(kjlubick): This does not work well for non-ascii
-            // Need to maybe add a helper in interface.js that supports UTF-8
-            // Otherwise, go with std::wstring and set UTF-32 encoding.
-            return self.measureText(text.c_str(), text.length());
-        }))
         .function("setAntiAlias", &SkPaint::setAntiAlias)
         .function("setBlendMode", &SkPaint::setBlendMode)
         .function("setColor", optional_override([](SkPaint& self, JSColor color)->void {
@@ -687,6 +815,7 @@
         .function("setStrokeMiter", &SkPaint::setStrokeMiter)
         .function("setStrokeWidth", &SkPaint::setStrokeWidth)
         .function("setStyle", &SkPaint::setStyle)
+        .function("setTypeface", &SkPaint::setTypeface)
         .function("setTextSize", &SkPaint::setTextSize);
 
     class_<SkPathEffect>("SkPathEffect")
@@ -700,15 +829,24 @@
         .function("_addPath", &ApplyAddPath)
         // interface.js has 4 overloads of addRect
         .function("_addRect", &ApplyAddRect)
+        // interface.js has 4 overloads of addRoundRect
+        .function("_addRoundRect", &ApplyAddRoundRect)
         .function("_arcTo", &ApplyArcTo)
+        .function("_arcTo", &ApplyArcToAngle)
         .function("_close", &ApplyClose)
         .function("_conicTo", &ApplyConicTo)
         .function("countPoints", &SkPath::countPoints)
+        .function("contains", &SkPath::contains)
         .function("_cubicTo", &ApplyCubicTo)
         .function("getPoint", &SkPath::getPoint)
+        .function("isEmpty",  &SkPath::isEmpty)
+        .function("isVolatile", &SkPath::isVolatile)
         .function("_lineTo", &ApplyLineTo)
         .function("_moveTo", &ApplyMoveTo)
+        .function("reset", &ApplyReset)
+        .function("rewind", &ApplyRewind)
         .function("_quadTo", &ApplyQuadTo)
+        .function("setIsVolatile", &SkPath::setIsVolatile)
         .function("_transform", select_overload<void(SkPath&, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar, SkScalar)>(&ApplyTransform))
 
         // PathEffects
@@ -722,6 +860,7 @@
 
         // Exporting
         .function("toSVGString", &ToSVGString)
+        .function("toCmds", &ToCmds)
 
         .function("setFillType", &SkPath::setFillType)
         .function("getFillType", &SkPath::getFillType)
@@ -745,13 +884,11 @@
         .function("_flush", &SkSurface::flush)
         .function("makeImageSnapshot", select_overload<sk_sp<SkImage>()>(&SkSurface::makeImageSnapshot))
         .function("makeImageSnapshot", select_overload<sk_sp<SkImage>(const SkIRect& bounds)>(&SkSurface::makeImageSnapshot))
-        .function("_readPixels", optional_override([](SkSurface& self, int width, int height, uintptr_t /* uint8_t* */ cptr)->bool {
-            uint8_t* dst = reinterpret_cast<uint8_t*>(cptr);
-            auto dstInfo = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType);
-            return self.readPixels(dstInfo, dst, width*4, 0, 0);
-        }))
         .function("getCanvas", &SkSurface::getCanvas, allow_raw_pointers());
 
+    class_<SkTypeface>("SkTypeface")
+        .smart_ptr<sk_sp<SkTypeface>>("sk_sp<SkTypeface>");
+
     class_<SkVertices>("SkVertices")
         .smart_ptr<sk_sp<SkVertices>>("sk_sp<SkVertices>")
         .function("_applyBones", optional_override([](SkVertices& self, uintptr_t /* Bone* */ bptr, int boneCount)->sk_sp<SkVertices> {
@@ -873,12 +1010,15 @@
         .field("width",       &StrokeOpts::width)
         .field("miter_limit", &StrokeOpts::miter_limit)
         .field("join",        &StrokeOpts::join)
-        .field("cap",         &StrokeOpts::cap);
+        .field("cap",         &StrokeOpts::cap)
+        .field("precision",   &StrokeOpts::precision);
 
     enum_<SkShader::TileMode>("TileMode")
         .value("Clamp",    SkShader::TileMode::kClamp_TileMode)
         .value("Repeat",   SkShader::TileMode::kRepeat_TileMode)
-        .value("Mirror",   SkShader::TileMode::kMirror_TileMode);
+        .value("Mirror",   SkShader::TileMode::kMirror_TileMode)
+        // Decal mode only works in the SW backend, not WebGl (yet).
+        .value("Decal",    SkShader::TileMode::kDecal_TileMode);
 
     enum_<SkVertices::VertexMode>("VertexMode")
         .value("Triangles",       SkVertices::VertexMode::kTriangles_VertexMode)
@@ -954,49 +1094,10 @@
     constant("WHITE",       (JSColor) SK_ColorWHITE);
     // TODO(?)
 
-#if SK_INCLUDE_SKOTTIE
-    // Animation things (may eventually go in own library)
-    class_<skottie::Animation>("Animation")
-        .smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
-        .function("version", optional_override([](skottie::Animation& self)->std::string {
-            return std::string(self.version().c_str());
-        }))
-        .function("size", &skottie::Animation::size)
-        .function("duration", &skottie::Animation::duration)
-        .function("seek", &skottie::Animation::seek)
-        .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas)->void {
-            self.render(canvas, nullptr);
-        }), allow_raw_pointers())
-        .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas,
-                                                 const SkRect r)->void {
-            self.render(canvas, &r);
-        }), allow_raw_pointers());
-
-    function("MakeAnimation", optional_override([](std::string json)->sk_sp<skottie::Animation> {
-        return skottie::Animation::Make(json.c_str(), json.length());
-    }));
-    constant("skottie", true);
-
-#if SK_INCLUDE_MANAGED_SKOTTIE
-    class_<ManagedAnimation>("ManagedAnimation")
-        .smart_ptr<sk_sp<ManagedAnimation>>("sk_sp<ManagedAnimation>")
-        .function("version"   , &ManagedAnimation::version)
-        .function("size"      , &ManagedAnimation::size)
-        .function("duration"  , &ManagedAnimation::duration)
-        .function("seek"      , &ManagedAnimation::seek)
-        .function("render"    , select_overload<void(SkCanvas*) const>
-                                    (&ManagedAnimation::render), allow_raw_pointers())
-        .function("render"    , select_overload<void(SkCanvas*, const SkRect&) const>
-                                    (&ManagedAnimation::render), allow_raw_pointers())
-        .function("setColor"  , &ManagedAnimation::setColor)
-        .function("setOpacity", &ManagedAnimation::setOpacity)
-        .function("getMarkers", &ManagedAnimation::getMarkers)
-        .function("getColorProps"  , &ManagedAnimation::getColorProps)
-        .function("getOpacityProps", &ManagedAnimation::getOpacityProps);
-
-    function("MakeManagedAnimation", &ManagedAnimation::Make);
-    constant("managed_skottie", true);
-#endif // SK_INCLUDE_MANAGED_SKOTTIE
-#endif // SK_INCLUDE_SKOTTIE
-
+    constant("MOVE_VERB",  MOVE);
+    constant("LINE_VERB",  LINE);
+    constant("QUAD_VERB",  QUAD);
+    constant("CONIC_VERB", CONIC);
+    constant("CUBIC_VERB", CUBIC);
+    constant("CLOSE_VERB", CLOSE);
 }
diff --git a/experimental/canvaskit/compile.sh b/experimental/canvaskit/compile.sh
index 4167cb7..780d5ea 100755
--- a/experimental/canvaskit/compile.sh
+++ b/experimental/canvaskit/compile.sh
@@ -20,17 +20,19 @@
 EMCC=`which emcc`
 EMCXX=`which em++`
 
-RELEASE_CONF="-Oz --closure 1 --llvm-lto 3 -DSK_RELEASE --pre-js $BASE_DIR/release.js"
-EXTRA_CFLAGS="\"-DSK_RELEASE\""
+RELEASE_CONF="-Oz --closure 1 --llvm-lto 3 -DSK_RELEASE --pre-js $BASE_DIR/release.js \
+              -DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0"
+EXTRA_CFLAGS="\"-DSK_RELEASE\", \"-DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0\","
 if [[ $@ == *debug* ]]; then
   echo "Building a Debug build"
   EXTRA_CFLAGS="\"-DSK_DEBUG\""
-  RELEASE_CONF="-O0 --js-opts 0 -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=1 -s GL_ASSERTIONS=1 -g3 \
-                -DPATHKIT_TESTING -DSK_DEBUG --pre-js $BASE_DIR/debug.js"
+  RELEASE_CONF="-O0 --js-opts 0 -s DEMANGLE_SUPPORT=1 -s ASSERTIONS=1 -s GL_ASSERTIONS=1 -g4 \
+                --source-map-base /node_modules/canvaskit/bin/ -DSK_DEBUG --pre-js $BASE_DIR/debug.js"
   BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm_debug"}
 elif [[ $@ == *profiling* ]]; then
   echo "Building a build for profiling"
-  RELEASE_CONF="-O3 --source-map-base /node_modules/canvaskit/bin/ --profiling -g4 -DSK_RELEASE --pre-js $BASE_DIR/release.js"
+  RELEASE_CONF="-O3 --source-map-base /node_modules/canvaskit/bin/ --profiling -g4 -DSK_RELEASE \
+                --pre-js $BASE_DIR/release.js -DGR_GL_CHECK_ALLOC_WITH_GET_ERROR=0"
   BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm_profile"}
 else
   BUILD_DIR=${BUILD_DIR:="out/canvaskit_wasm"}
@@ -49,7 +51,8 @@
   WASM_GPU="-DSK_SUPPORT_GPU=0 --pre-js $BASE_DIR/cpu.js"
 fi
 
-WASM_SKOTTIE="-DSK_INCLUDE_SKOTTIE=1 \
+WASM_SKOTTIE=" \
+  $BASE_DIR/skottie_bindings.cpp \
   modules/skottie/src/Skottie.cpp \
   modules/skottie/src/SkottieAdapter.cpp \
   modules/skottie/src/SkottieAnimator.cpp \
@@ -69,7 +72,7 @@
   src/utils/SkParse.cpp "
 if [[ $@ == *no_skottie* ]]; then
   echo "Omitting Skottie"
-  WASM_SKOTTIE="-DSK_INCLUDE_SKOTTIE=0"
+  WASM_SKOTTIE=""
 fi
 
 WASM_MANAGED_SKOTTIE="\
@@ -80,12 +83,36 @@
   WASM_MANAGED_SKOTTIE="-DSK_INCLUDE_MANAGED_SKOTTIE=0"
 fi
 
-HTML_CANVAS_API="--pre-js $BASE_DIR/htmlcanvas/canvas2d.js"
+HTML_CANVAS_API="--pre-js $BASE_DIR/htmlcanvas/preamble.js \
+--pre-js $BASE_DIR/htmlcanvas/util.js \
+--pre-js $BASE_DIR/htmlcanvas/color.js \
+--pre-js $BASE_DIR/htmlcanvas/font.js \
+--pre-js $BASE_DIR/htmlcanvas/canvas2dcontext.js \
+--pre-js $BASE_DIR/htmlcanvas/htmlcanvas.js \
+--pre-js $BASE_DIR/htmlcanvas/imagedata.js \
+--pre-js $BASE_DIR/htmlcanvas/lineargradient.js \
+--pre-js $BASE_DIR/htmlcanvas/path2d.js \
+--pre-js $BASE_DIR/htmlcanvas/pattern.js \
+--pre-js $BASE_DIR/htmlcanvas/radialgradient.js \
+--pre-js $BASE_DIR/htmlcanvas/postamble.js "
 if [[ $@ == *no_canvas* ]]; then
   echo "Omitting bindings for HTML Canvas API"
   HTML_CANVAS_API=""
 fi
 
+BUILTIN_FONT="$BASE_DIR/fonts/NotoMono-Regular.ttf.cpp"
+if [[ $@ == *no_font* ]]; then
+  echo "Omitting the built-in font(s)"
+  BUILTIN_FONT=""
+else
+  # Generate the font's binary file (which is covered by .gitignore)
+  python tools/embed_resources.py \
+      --name SK_EMBEDDED_FONTS \
+      --input $BASE_DIR/fonts/NotoMono-Regular.ttf \
+      --output $BASE_DIR/fonts/NotoMono-Regular.ttf.cpp \
+      --align 4
+fi
+
 # Turn off exiting while we check for ninja (which may not be on PATH)
 set +e
 NINJA=`which ninja`
@@ -180,10 +207,10 @@
     --bind \
     --pre-js $BASE_DIR/helper.js \
     --pre-js $BASE_DIR/interface.js \
+    --post-js $BASE_DIR/ready.js \
     $HTML_CANVAS_API \
+    $BUILTIN_FONT \
     $BASE_DIR/canvaskit_bindings.cpp \
-    tools/fonts/SkTestFontMgr.cpp \
-    tools/fonts/SkTestTypeface.cpp \
     $WASM_SKOTTIE \
     $WASM_MANAGED_SKOTTIE \
     $BUILD_DIR/libskia.a \
diff --git a/experimental/canvaskit/cpu.js b/experimental/canvaskit/cpu.js
index 8951be7..5a20c98 100644
--- a/experimental/canvaskit/cpu.js
+++ b/experimental/canvaskit/cpu.js
@@ -3,11 +3,15 @@
 (function(CanvasKit){
   CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
   CanvasKit._extraInitializations.push(function() {
-    CanvasKit.MakeSWCanvasSurface = function(htmlID) {
-      var canvas = document.getElementById(htmlID);
-      if (!canvas) {
-        throw 'Canvas with id ' + htmlID + ' was not found';
-      }
+    // Takes in an html id or a canvas element
+    CanvasKit.MakeSWCanvasSurface = function(idOrElement) {
+        var canvas = idOrElement;
+        if (canvas.tagName !== 'CANVAS') {
+          canvas = document.getElementById(idOrElement);
+          if (!canvas) {
+            throw 'Canvas with id ' + idOrElement + ' was not found';
+          }
+        }
       // Maybe better to use clientWidth/height.  See:
       // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
       var surface = CanvasKit.MakeSurface(canvas.width, canvas.height);
diff --git a/experimental/canvaskit/externs.js b/experimental/canvaskit/externs.js
index fec7b88..e445798 100644
--- a/experimental/canvaskit/externs.js
+++ b/experimental/canvaskit/externs.js
@@ -29,6 +29,8 @@
 	LTRBRect: function() {},
 	/** @return {CanvasKit.SkRect} */
 	XYWHRect: function() {},
+	/** @return {ImageData} */
+	ImageData: function() {},
 	MakeBlurMaskFilter: function() {},
 	MakeCanvas: function() {},
 	MakeCanvasSurface: function() {},
@@ -37,6 +39,9 @@
 	MakeImageFromEncoded: function() {},
 	/** @return {LinearCanvasGradient} */
 	MakeLinearGradientShader: function() {},
+	MakePathFromCmds: function() {},
+	MakePathFromOp: function() {},
+	MakePathFromSVGString: function() {},
 	MakeRadialGradientShader: function() {},
 	MakeSWCanvasSurface: function() {},
 	MakeSkDashPathEffect: function() {},
@@ -48,14 +53,15 @@
 	currentContext: function() {},
 	getColorComponents: function() {},
 	getSkDataBytes: function() {},
-	initFonts: function() {},
 	multiplyByAlpha: function() {},
 	setCurrentContext: function() {},
 
 	// private API (i.e. things declared in the bindings that we use
 	// in the pre-js file)
+	_MakeImage: function() {},
 	_MakeImageShader: function() {},
 	_MakeLinearGradientShader: function() {},
+	_MakePathFromCmds: function() {},
 	_MakeRadialGradientShader: function() {},
 	_MakeSkDashPathEffect: function() {},
 	_MakeSkVertices: function() {},
@@ -75,27 +81,57 @@
 		// public API (from C++ bindings)
 		clear: function() {},
 		clipPath: function() {},
+		clipRect: function() {},
+		concat: function() {},
+		drawArc: function() {},
 		drawImage: function() {},
 		drawImageRect: function() {},
+		drawLine: function() {},
+		drawOval: function() {},
 		drawPaint: function() {},
 		drawPath: function() {},
 		drawRect: function() {},
+		drawRoundRect: function() {},
 		drawShadow: function() {},
 		drawText: function() {},
 		drawVertices: function() {},
 		flush: function() {},
+		getTotalMatrix: function() {},
 		restore: function() {},
 		rotate: function() {},
 		save: function() {},
 		scale: function() {},
-		setMatrix: function() {},
 		skew: function() {},
 		translate: function() {},
 
 		// private API
+		_readPixels: function() {},
+		_writePixels: function() {},
 		delete: function() {},
 	},
 
+	SkFont: {
+		// public API (from C++ bindings)
+		getScaleX: function() {},
+		getSize: function() {},
+		getSkewX: function() {},
+		getTypeface: function() {},
+		measureText: function() {},
+		setScaleX: function() {},
+		setSize: function() {},
+		setSkewX: function() {},
+		setTypeface: function() {},
+	},
+
+	SkFontMgr: {
+		// public API (from C++ bindings)
+		RefDefault: function() {},
+		countFamilies: function() {},
+
+		// private API
+		_makeTypefaceFromData: function() {},
+	},
+
 	SkImage: {
 		// public API (from C++ bindings)
 		height: function() {},
@@ -127,7 +163,6 @@
 		getStrokeMiter: function() {},
 		getStrokeWidth: function() {},
 		getTextSize: function() {},
-		measureText: function() {},
 		setAntiAlias: function() {},
 		setBlendMode: function() {},
 		setColor: function() {},
@@ -141,6 +176,7 @@
 		setStrokeWidth: function() {},
 		setStyle: function() {},
 		setTextSize: function() {},
+		setTypeface: function() {},
 
 		//private API
 		delete: function() {},
@@ -149,6 +185,7 @@
 	SkPath: {
 		// public API (from C++ bindings)
 		computeTightBounds: function() {},
+		contains: function() {},
 		/** @return {CanvasKit.SkPath} */
 		copy: function() {},
 		countPoints: function() {},
@@ -156,13 +193,19 @@
 		getBounds: function() {},
 		getFillType: function() {},
 		getPoint: function() {},
+		isEmpty: function() {},
+		isVolatile: function() {},
+		reset: function() {},
+		rewind: function() {},
 		setFillType: function() {},
+		setIsVolatile: function() {},
 		toSVGString: function() {},
 
 		// private API
 		_addArc: function() {},
 		_addPath: function() {},
 		_addRect: function() {},
+		_addRoundRect: function() {},
 		_arc: function() {},
 		_arcTo: function() {},
 		_close: function() {},
@@ -200,7 +243,6 @@
 		// private API
 		_flush: function() {},
 		_getRasterN32PremulSurface: function() {},
-		_readPixels: function() {},
 		delete: function() {},
 	},
 
@@ -228,6 +270,13 @@
 	BLACK: {},
 	WHITE: {},
 
+	MOVE_VERB: {},
+	LINE_VERB: {},
+	QUAD_VERB: {},
+	CONIC_VERB: {},
+	CUBIC_VERB: {},
+	CLOSE_VERB: {},
+
 	AlphaType: {
 		Opaque: {},
 		Premul: {},
@@ -341,6 +390,7 @@
 		Clamp: {},
 		Repeat: {},
 		Mirror: {},
+		Decal: {},
 	},
 
 	VertexMode: {
@@ -383,6 +433,7 @@
 CanvasKit.SkPath.prototype.addArc = function() {};
 CanvasKit.SkPath.prototype.addPath = function() {};
 CanvasKit.SkPath.prototype.addRect = function() {};
+CanvasKit.SkPath.prototype.addRoundRect = function() {};
 CanvasKit.SkPath.prototype.arc = function() {};
 CanvasKit.SkPath.prototype.arcTo = function() {};
 CanvasKit.SkPath.prototype.close = function() {};
@@ -407,18 +458,27 @@
 
 CanvasKit.SkImage.prototype.encodeToData = function() {};
 
+/** @return {Uint8Array} */
+CanvasKit.SkCanvas.prototype.readPixels = function() {};
+CanvasKit.SkCanvas.prototype.writePixels = function() {};
+
+CanvasKit.SkFontMgr.prototype.MakeTypefaceFromData = function() {};
+
 // Define StrokeOpts object
 var StrokeOpts = {};
 StrokeOpts.prototype.width;
 StrokeOpts.prototype.miter_limit;
 StrokeOpts.prototype.cap;
 StrokeOpts.prototype.join;
+StrokeOpts.prototype.precision;
 
 // Define everything created in the canvas2d spec here
 var HTMLCanvas = {};
 HTMLCanvas.prototype.decodeImage = function() {};
 HTMLCanvas.prototype.dispose = function() {};
 HTMLCanvas.prototype.getContext = function() {};
+HTMLCanvas.prototype.loadFont = function() {};
+HTMLCanvas.prototype.makePath2D = function() {};
 HTMLCanvas.prototype.toDataURL = function() {};
 
 var CanvasRenderingContext2D = {};
@@ -431,7 +491,9 @@
 CanvasRenderingContext2D.prototype.clearRect = function() {};
 CanvasRenderingContext2D.prototype.clip = function() {};
 CanvasRenderingContext2D.prototype.closePath = function() {};
+CanvasRenderingContext2D.prototype.createImageData = function() {};
 CanvasRenderingContext2D.prototype.createLinearGradient = function() {};
+CanvasRenderingContext2D.prototype.createPattern = function() {};
 CanvasRenderingContext2D.prototype.createRadialGradient = function() {};
 CanvasRenderingContext2D.prototype.drawFocusIfNeeded = function() {};
 CanvasRenderingContext2D.prototype.drawImage = function() {};
@@ -439,10 +501,14 @@
 CanvasRenderingContext2D.prototype.fill = function() {};
 CanvasRenderingContext2D.prototype.fillRect = function() {};
 CanvasRenderingContext2D.prototype.fillText = function() {};
+CanvasRenderingContext2D.prototype.getImageData = function() {};
 CanvasRenderingContext2D.prototype.getLineDash = function() {};
+CanvasRenderingContext2D.prototype.isPointInPath = function() {};
+CanvasRenderingContext2D.prototype.isPointInStroke = function() {};
 CanvasRenderingContext2D.prototype.lineTo = function() {};
 CanvasRenderingContext2D.prototype.measureText = function() {};
 CanvasRenderingContext2D.prototype.moveTo = function() {};
+CanvasRenderingContext2D.prototype.putImageData = function() {};
 CanvasRenderingContext2D.prototype.quadraticCurveTo = function() {};
 CanvasRenderingContext2D.prototype.rect = function() {};
 CanvasRenderingContext2D.prototype.removeHitRegion = function() {};
@@ -460,10 +526,42 @@
 CanvasRenderingContext2D.prototype.transform = function() {};
 CanvasRenderingContext2D.prototype.translate = function() {};
 
+var Path2D = {};
+Path2D.prototype.addPath = function() {};
+Path2D.prototype.arc = function() {};
+Path2D.prototype.arcTo = function() {};
+Path2D.prototype.bezierCurveTo = function() {};
+Path2D.prototype.closePath = function() {};
+Path2D.prototype.ellipse = function() {};
+Path2D.prototype.lineTo = function() {};
+Path2D.prototype.moveTo = function() {};
+Path2D.prototype.quadraticCurveTo = function() {};
+Path2D.prototype.rect = function() {};
+
 var LinearCanvasGradient = {};
 LinearCanvasGradient.prototype.addColorStop = function() {};
 var RadialCanvasGradient = {};
 RadialCanvasGradient.prototype.addColorStop = function() {};
+var CanvasPattern = {};
+CanvasPattern.prototype.setTransform = function() {};
+
+var ImageData = {
+	/**
+	 * @type {Uint8ClampedArray}
+	 */
+	data: {},
+	height: {},
+	width: {},
+};
+
+var DOMMatrix = {
+	a: {},
+	b: {},
+	c: {},
+	d: {},
+	e: {},
+	f: {},
+};
 
 // Not sure why this is needed - might be a bug in emsdk that this isn't properly declared.
 function loadWebAssemblyModule() {};
diff --git a/experimental/canvaskit/fonts/NotoMono-Regular.ttf b/experimental/canvaskit/fonts/NotoMono-Regular.ttf
new file mode 100644
index 0000000..3560a3a
--- /dev/null
+++ b/experimental/canvaskit/fonts/NotoMono-Regular.ttf
Binary files differ
diff --git a/experimental/canvaskit/fonts/README.md b/experimental/canvaskit/fonts/README.md
new file mode 100644
index 0000000..6e2addb
--- /dev/null
+++ b/experimental/canvaskit/fonts/README.md
@@ -0,0 +1,3 @@
+To generate the files in here:
+
+    python tools/embed_resources.py --name SK_EMBEDDED_FONTS --input ~/Downloads/NotoMono-Regular.ttf --output experimental/canvaskit/NotoMono-Regular.ttf.cpp --align 4
\ No newline at end of file
diff --git a/experimental/canvaskit/gpu.js b/experimental/canvaskit/gpu.js
index 41cbb65..6dca353 100644
--- a/experimental/canvaskit/gpu.js
+++ b/experimental/canvaskit/gpu.js
@@ -3,17 +3,81 @@
 (function(CanvasKit){
     CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
     CanvasKit._extraInitializations.push(function() {
-      CanvasKit.MakeWebGLCanvasSurface = function(htmlID) {
-        var canvas = document.getElementById(htmlID);
-        if (!canvas) {
-          throw 'Canvas with id ' + htmlID + ' was not found';
+      function get(obj, attr, defaultValue) {
+        if (obj && obj.hasOwnProperty(attr)) {
+          return obj[attr];
         }
+        return defaultValue;
+      }
+
+      function makeWebGLContext(canvas, attrs) {
+        // These defaults come from the emscripten _emscripten_webgl_create_context
+        var contextAttributes = {
+          'alpha': get(attrs, 'alpha', 1),
+          'depth': get(attrs, 'depth', 1),
+          'stencil': get(attrs, 'stencil', 0),
+          'antialias': get(attrs, 'antialias', 1),
+          'premultipliedAlpha': get(attrs, 'premultipliedAlpha', 1),
+          'preserveDrawingBuffer': get(attrs, 'preserveDrawingBuffer', 0),
+          'preferLowPowerToHighPerformance': get(attrs, 'preferLowPowerToHighPerformance', 0),
+          'failIfMajorPerformanceCaveat': get(attrs, 'failIfMajorPerformanceCaveat', 0),
+          'majorVersion': get(attrs, 'majorVersion', 1),
+          'minorVersion': get(attrs, 'minorVersion', 0),
+          'enableExtensionsByDefault': get(attrs, 'enableExtensionsByDefault', 1),
+          'explicitSwapControl': get(attrs, 'explicitSwapControl', 0),
+          'renderViaOffscreenBackBuffer': get(attrs, 'renderViaOffscreenBackBuffer', 0),
+        };
+        if (!canvas) {
+          SkDebug('null canvas passed into makeWebGLContext');
+          return 0;
+        }
+        // This check is from the emscripten version
+        if (contextAttributes['explicitSwapControl']) {
+          SkDebug('explicitSwapControl is not supported');
+          return 0;
+        }
+        return GL.createContext(canvas, contextAttributes);
+      }
+
+      // arg can be of types:
+      //  - String - in which case it is interpreted as an id of a
+      //          canvas element.
+      //  - HTMLCanvasElement - in which the provided canvas element will
+      //          be used directly.
+      //  - int - in which case it will be used as a WebGLContext. Only 1.0
+      //          contexts are known to work for now.
+      // Width and height can be provided to override those on the canvas
+      // element, or specify a height for when a context is provided.
+      CanvasKit.MakeWebGLCanvasSurface = function(arg, width, height) {
+        var ctx = arg;
+        // ctx is only > 0 if it's an int, and thus a valid context
+        if (!(ctx > 0)) {
+          var canvas = arg;
+          if (canvas.tagName !== 'CANVAS') {
+            canvas = document.getElementById(arg);
+            if (!canvas) {
+              throw 'Canvas with id ' + arg + ' was not found';
+            }
+          }
+          // we are ok with all the defaults
+          ctx = makeWebGLContext(canvas);
+        }
+
+        if (!ctx || ctx < 0) {
+          throw 'failed to create webgl context: err ' + ctx;
+        }
+
+        if (!canvas && (!width || !height)) {
+          throw 'height and width must be provided with context';
+        }
+
         // Maybe better to use clientWidth/height.  See:
         // https://webglfundamentals.org/webgl/lessons/webgl-anti-patterns.html
-        var surface = this._getWebGLSurface(htmlID, canvas.width, canvas.height);
+        var surface = this._getWebGLSurface(ctx, width || canvas.width,
+                                            height || canvas.height);
         if (!surface) {
           SkDebug('falling back from GPU implementation to a SW based one');
-          return CanvasKit.MakeSWCanvasSurface(htmlID);
+          return CanvasKit.MakeSWCanvasSurface(arg);
         }
         return surface;
       };
diff --git a/experimental/canvaskit/helper.js b/experimental/canvaskit/helper.js
index e90ec9f1..e49d2b7 100644
--- a/experimental/canvaskit/helper.js
+++ b/experimental/canvaskit/helper.js
@@ -48,3 +48,7 @@
 // if btoa is defined *and* prevents runtime "btoa" or "window" is not defined.
 // Defined outside any scopes to make it available in all files.
 var isNode = !(new Function("try {return this===window;}catch(e){ return false;}")());
+
+function almostEqual(floata, floatb) {
+  return Math.abs(floata - floatb) < 0.00001;
+}
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/_namedcolors.js b/experimental/canvaskit/htmlcanvas/_namedcolors.js
index f7c9ac8..16dec34 100644
--- a/experimental/canvaskit/htmlcanvas/_namedcolors.js
+++ b/experimental/canvaskit/htmlcanvas/_namedcolors.js
@@ -1,13 +1,13 @@
 // This node script is meant to pre-compute the named colors.
 // node ./htmlcanvas/_namedcolors.js --expose-wasm
-// Put the result in canvas2d.js
+// Put the result in color.js
 // This should likely never need to be re-run.
 
 const CanvasKitInit = require('../canvaskit/bin/canvaskit.js');
 
 CanvasKitInit({
   locateFile: (file) => __dirname + '/../canvaskit/bin/'+file,
-}).then((CanvasKit) => {
+}).ready().then((CanvasKit) => {
   let colorMap = {
     // From https://drafts.csswg.org/css-color/#named-colors
     'aliceblue': CanvasKit.Color(240, 248, 255),
diff --git a/experimental/canvaskit/htmlcanvas/canvas2d.js b/experimental/canvaskit/htmlcanvas/canvas2d.js
deleted file mode 100644
index e025fb0..0000000
--- a/experimental/canvaskit/htmlcanvas/canvas2d.js
+++ /dev/null
@@ -1,1455 +0,0 @@
-// Adds compile-time JS functions to augment the CanvasKit interface.
-// Specifically, the code that emulates the HTML Canvas interface
-// (which may be called HTMLCanvas or similar to avoid confusion with
-// SkCanvas).
-(function(CanvasKit) {
-
-  function allAreFinite(args) {
-    for (var i = 0; i < args.length; i++) {
-      if (args[i] !== undefined && !Number.isFinite(args[i])) {
-        return false;
-      }
-    }
-    return true;
-  }
-
-  function toBase64String(bytes) {
-    if (isNode) {
-      return Buffer.from(bytes).toString('base64');
-    } else {
-      // From https://stackoverflow.com/a/25644409
-      // because the naive solution of
-      //     btoa(String.fromCharCode.apply(null, bytes));
-      // would occasionally throw "Maximum call stack size exceeded"
-      var CHUNK_SIZE = 0x8000; //arbitrary number
-      var index = 0;
-      var length = bytes.length;
-      var result = '';
-      var slice;
-      while (index < length) {
-        slice = bytes.slice(index, Math.min(index + CHUNK_SIZE, length));
-        result += String.fromCharCode.apply(null, slice);
-        index += CHUNK_SIZE;
-      }
-      return btoa(result);
-    }
-  }
-
-  CanvasKit._testing = {};
-
-  function HTMLCanvas(skSurface) {
-    this._surface = skSurface;
-    this._context = new CanvasRenderingContext2D(skSurface.getCanvas());
-    this._imgs = [];
-
-    // Data is either an ArrayBuffer, a TypedArray, or a Node Buffer
-    this.decodeImage = function(data) {
-      var img = CanvasKit.MakeImageFromEncoded(data);
-      if (!img) {
-        throw 'Invalid input';
-      }
-      this._imgs.push(img);
-      return img;
-    }
-
-    // A normal <canvas> requires that clients call getContext
-    this.getContext = function(type) {
-      if (type === '2d') {
-        return this._context;
-      }
-      return null;
-    }
-
-    this.toDataURL = function(codec, quality) {
-      // TODO(kjlubick): maybe support other codecs (webp?)
-      // For now, just to png and jpeg
-      this._surface.flush();
-
-      var img = this._surface.makeImageSnapshot();
-      if (!img) {
-        SkDebug('no snapshot');
-        return;
-      }
-      var codec = codec || 'image/png';
-      var format = CanvasKit.ImageFormat.PNG;
-      if (codec === 'image/jpeg') {
-        format = CanvasKit.ImageFormat.JPEG;
-      }
-      var quality = quality || 0.92;
-      var skimg = img.encodeToData(format, quality);
-      if (!skimg) {
-        SkDebug('encoding failure');
-        return
-      }
-      var imgBytes = CanvasKit.getSkDataBytes(skimg);
-      return 'data:' + codec + ';base64,' + toBase64String(imgBytes);
-    }
-
-    this.dispose = function() {
-      this._context._dispose();
-      this._imgs.forEach(function(i) {
-        i.delete();
-      });
-      this._surface.dispose();
-    }
-  }
-
-  function LinearCanvasGradient(x1, y1, x2, y2) {
-    this._shader = null;
-    this._colors = [];
-    this._pos = [];
-
-    this.addColorStop = function(offset, color) {
-      if (offset < 0 || offset > 1 || !isFinite(offset)) {
-        throw 'offset must be between 0 and 1 inclusively';
-      }
-
-      color = parseColor(color);
-      // From the spec: If multiple stops are added at the same offset on a
-      // gradient, then they must be placed in the order added, with the first
-      // one closest to the start of the gradient, and each subsequent one
-      // infinitesimally further along towards the end point (in effect
-      // causing all but the first and last stop added at each point to be
-      // ignored).
-      // To implement that, if an offset is already in the list,
-      // we just overwrite its color (since the user can't remove Color stops
-      // after the fact).
-      var idx = this._pos.indexOf(offset);
-      if (idx !== -1) {
-        this._colors[idx] = color;
-      } else {
-        // insert it in sorted order
-        for (idx = 0; idx < this._pos.length; idx++) {
-          if (this._pos[idx] > offset) {
-            break;
-          }
-        }
-        this._pos   .splice(idx, 0, offset);
-        this._colors.splice(idx, 0, color);
-      }
-    }
-
-    this._copy = function() {
-      var lcg = new LinearCanvasGradient(x1, y1, x2, y2);
-      lcg._colors = this._colors.slice();
-      lcg._pos    = this._pos.slice();
-      return lcg;
-    }
-
-    this._dispose = function() {
-      if (this._shader) {
-        this._shader.delete();
-        this._shader = null;
-      }
-    }
-
-    this._getShader = function(currentTransform, globalAlpha) {
-      // From the spec: "The points in the linear gradient must be transformed
-      // as described by the current transformation matrix when rendering."
-      var pts = [x1, y1, x2, y2];
-      CanvasKit.SkMatrix.mapPoints(currentTransform, pts);
-      var sx1 = pts[0];
-      var sy1 = pts[1];
-      var sx2 = pts[2];
-      var sy2 = pts[3];
-
-      this._dispose();
-      var colors = this._colors.map(function(c) {
-        return CanvasKit.multiplyByAlpha(c, globalAlpha);
-      });
-      this._shader = CanvasKit.MakeLinearGradientShader([sx1, sy1], [sx2, sy2],
-        colors, this._pos, CanvasKit.TileMode.Clamp);
-      return this._shader;
-    }
-  }
-
-  // Note, Skia has a different notion of a "radial" gradient.
-  // Skia has a twoPointConical gradient that is the same as the
-  // canvas's RadialGradient.
-  function RadialCanvasGradient(x1, y1, r1, x2, y2, r2) {
-    this._shader = null;
-    this._colors = [];
-    this._pos = [];
-
-    this.addColorStop = function(offset, color) {
-      if (offset < 0 || offset > 1 || !isFinite(offset)) {
-        throw 'offset must be between 0 and 1 inclusively';
-      }
-
-      color = parseColor(color);
-      // From the spec: If multiple stops are added at the same offset on a
-      // gradient, then they must be placed in the order added, with the first
-      // one closest to the start of the gradient, and each subsequent one
-      // infinitesimally further along towards the end point (in effect
-      // causing all but the first and last stop added at each point to be
-      // ignored).
-      // To implement that, if an offset is already in the list,
-      // we just overwrite its color (since the user can't remove Color stops
-      // after the fact).
-      var idx = this._pos.indexOf(offset);
-      if (idx !== -1) {
-        this._colors[idx] = color;
-      } else {
-        // insert it in sorted order
-        for (idx = 0; idx < this._pos.length; idx++) {
-          if (this._pos[idx] > offset) {
-            break;
-          }
-        }
-        this._pos   .splice(idx, 0, offset);
-        this._colors.splice(idx, 0, color);
-      }
-    }
-
-    this._copy = function() {
-      var rcg = new RadialCanvasGradient(x1, y1, r1, x2, y2, r2);
-      rcg._colors = this._colors.slice();
-      rcg._pos    = this._pos.slice();
-      return rcg;
-    }
-
-    this._dispose = function() {
-      if (this._shader) {
-        this._shader.delete();
-        this._shader = null;
-      }
-    }
-
-    this._getShader = function(currentTransform, globalAlpha) {
-      // From the spec: "The points in the linear gradient must be transformed
-      // as described by the current transformation matrix when rendering."
-      var pts = [x1, y1, x2, y2];
-      CanvasKit.SkMatrix.mapPoints(currentTransform, pts);
-      var sx1 = pts[0];
-      var sy1 = pts[1];
-      var sx2 = pts[2];
-      var sy2 = pts[3];
-
-      // Maybe refactor _scalefactor() on which this is taken?
-      var sx = currentTransform[0];
-      var sy = currentTransform[4];
-      var scaleFactor = (Math.abs(sx) + Math.abs(sy))/2;
-
-      var sr1 = r1 * scaleFactor;
-      var sr2 = r2 * scaleFactor;
-
-      this._dispose();
-      var colors = this._colors.map(function(c) {
-        return CanvasKit.multiplyByAlpha(c, globalAlpha);
-      });
-      this._shader = CanvasKit.MakeTwoPointConicalGradientShader(
-          [sx1, sy1], sr1, [sx2, sy2], sr2, colors, this._pos,
-          CanvasKit.TileMode.Clamp);
-      return this._shader;
-    }
-  }
-
-  function CanvasRenderingContext2D(skcanvas) {
-    this._canvas = skcanvas;
-    this._paint = new CanvasKit.SkPaint();
-    this._paint.setAntiAlias(true);
-
-    this._paint.setStrokeMiter(10);
-    this._paint.setStrokeCap(CanvasKit.StrokeCap.Butt);
-    this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Miter);
-
-    this._strokeStyle    = CanvasKit.BLACK;
-    this._fillStyle      = CanvasKit.BLACK;
-    this._shadowBlur     = 0;
-    this._shadowColor    = CanvasKit.TRANSPARENT;
-    this._shadowOffsetX  = 0;
-    this._shadowOffsetY  = 0;
-    this._globalAlpha    = 1;
-    this._strokeWidth    = 1;
-    this._lineDashOffset = 0;
-    this._lineDashList   = [];
-    // aka SkBlendMode
-    this._globalCompositeOperation = CanvasKit.BlendMode.SrcOver;
-    this._imageFilterQuality = CanvasKit.FilterQuality.Low;
-    this._imageSmoothingEnabled = true;
-
-    this._paint.setStrokeWidth(this._strokeWidth);
-    this._paint.setBlendMode(this._globalCompositeOperation);
-
-    this._currentPath = new CanvasKit.SkPath();
-    this._currentSubpath = null;
-    this._currentTransform = CanvasKit.SkMatrix.identity();
-
-    // Use this for save/restore
-    this._canvasStateStack = [];
-    // Keep a reference to all the gradients that were allocated
-    // for cleanup in _dispose;
-    this._gradients = [];
-
-    this._dispose = function() {
-      this._currentPath.delete();
-      this._currentSubpath && this._currentSubpath.delete();
-      this._paint.delete();
-      this._gradients.forEach(function(gradient) {
-        gradient._dispose();
-      });
-      // Don't delete this._canvas as it will be disposed
-      // by the surface of which it is based.
-    }
-
-    // This always accepts DOMMatrix/SVGMatrix or any other
-    // object that has properties a,b,c,d,e,f defined.
-    // It will return DOMMatrix if the constructor is defined
-    // (e.g. we are in a browser), otherwise it will return a
-    // flattened 9-element matrix. That 9 element matrix
-    // can also be accepted on all platforms (somewhat
-    //  contrary to the specification, but at least keeps it usable
-    // on Node)
-    Object.defineProperty(this, 'currentTransform', {
-      enumerable: true,
-      get: function() {
-        if (isNode) {
-          return this._currentTransform.slice();
-        } else {
-          // a-f aren't in the order as we have them. a-f are in "SVG order".
-          var m = new DOMMatrix();
-          m.a = this._currentTransform[0];
-          m.c = this._currentTransform[1];
-          m.e = this._currentTransform[2];
-          m.b = this._currentTransform[3];
-          m.d = this._currentTransform[4];
-          m.f = this._currentTransform[5];
-          return m;
-        }
-      },
-      set: function(matrix) {
-        if (matrix.a) {
-          // if we see a property named 'a', guess that b-f will
-          // also be there.
-          this._currentTransform = [matrix.a, matrix.c, matrix.e,
-                                    matrix.b, matrix.d, matrix.f,
-                                           0,        0,        1];
-        } else if (matrix.length === 9) {
-          this._currentTransform = matrix;
-        }
-      }
-    });
-
-    Object.defineProperty(this, 'fillStyle', {
-      enumerable: true,
-      get: function() {
-        if (Number.isInteger(this._fillStyle)) {
-          return colorToString(this._fillStyle);
-        }
-        return this._fillStyle;
-      },
-      set: function(newStyle) {
-        if (typeof newStyle === 'string') {
-          this._fillStyle = parseColor(newStyle);
-        } else if (newStyle.addColorStop) {
-          // It's probably a gradient.
-          this._fillStyle = newStyle
-        }
-      }
-    });
-
-    Object.defineProperty(this, 'font', {
-      enumerable: true,
-      get: function(newStyle) {
-        // TODO generate this
-        return '10px sans-serif';
-      },
-      set: function(newStyle) {
-        var size = parseFontSize(newStyle);
-        // TODO(kjlubick) styles, font name
-        this._paint.setTextSize(size);
-      }
-    });
-
-    Object.defineProperty(this, 'globalAlpha', {
-      enumerable: true,
-      get: function() {
-        return this._globalAlpha;
-      },
-      set: function(newAlpha) {
-        // ignore invalid values, as per the spec
-        if (!isFinite(newAlpha) || newAlpha < 0 || newAlpha > 1) {
-          return;
-        }
-        this._globalAlpha = newAlpha;
-      }
-    });
-
-    Object.defineProperty(this, 'globalCompositeOperation', {
-      enumerable: true,
-      get: function() {
-        switch (this._globalCompositeOperation) {
-          // composite-mode
-          case CanvasKit.BlendMode.SrcOver:
-            return 'source-over';
-          case CanvasKit.BlendMode.DstOver:
-            return 'destination-over';
-          case CanvasKit.BlendMode.Src:
-            return 'copy';
-          case CanvasKit.BlendMode.Dst:
-            return 'destination';
-          case CanvasKit.BlendMode.Clear:
-            return 'clear';
-          case CanvasKit.BlendMode.SrcIn:
-            return 'source-in';
-          case CanvasKit.BlendMode.DstIn:
-            return 'destination-in';
-          case CanvasKit.BlendMode.SrcOut:
-            return 'source-out';
-          case CanvasKit.BlendMode.DstOut:
-            return 'destination-out';
-          case CanvasKit.BlendMode.SrcATop:
-            return 'source-atop';
-          case CanvasKit.BlendMode.DstATop:
-            return 'destination-atop';
-          case CanvasKit.BlendMode.Xor:
-            return 'xor';
-          case CanvasKit.BlendMode.Plus:
-            return 'lighter';
-
-          case CanvasKit.BlendMode.Multiply:
-            return 'multiply';
-          case CanvasKit.BlendMode.Screen:
-            return 'screen';
-          case CanvasKit.BlendMode.Overlay:
-            return 'overlay';
-          case CanvasKit.BlendMode.Darken:
-            return 'darken';
-          case CanvasKit.BlendMode.Lighten:
-            return 'lighten';
-          case CanvasKit.BlendMode.ColorDodge:
-            return 'color-dodge';
-          case CanvasKit.BlendMode.ColorBurn:
-            return 'color-burn';
-          case CanvasKit.BlendMode.HardLight:
-            return 'hard-light';
-          case CanvasKit.BlendMode.SoftLight:
-            return 'soft-light';
-          case CanvasKit.BlendMode.Difference:
-            return 'difference';
-          case CanvasKit.BlendMode.Exclusion:
-            return 'exclusion';
-          case CanvasKit.BlendMode.Hue:
-            return 'hue';
-          case CanvasKit.BlendMode.Saturation:
-            return 'saturation';
-          case CanvasKit.BlendMode.Color:
-            return 'color';
-          case CanvasKit.BlendMode.Luminosity:
-            return 'luminosity';
-        }
-      },
-      set: function(newMode) {
-        switch (newMode) {
-          // composite-mode
-          case 'source-over':
-            this._globalCompositeOperation = CanvasKit.BlendMode.SrcOver;
-            break;
-          case 'destination-over':
-            this._globalCompositeOperation = CanvasKit.BlendMode.DstOver;
-            break;
-          case 'copy':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Src;
-            break;
-          case 'destination':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Dst;
-            break;
-          case 'clear':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Clear;
-            break;
-          case 'source-in':
-            this._globalCompositeOperation = CanvasKit.BlendMode.SrcIn;
-            break;
-          case 'destination-in':
-            this._globalCompositeOperation = CanvasKit.BlendMode.DstIn;
-            break;
-          case 'source-out':
-            this._globalCompositeOperation = CanvasKit.BlendMode.SrcOut;
-            break;
-          case 'destination-out':
-            this._globalCompositeOperation = CanvasKit.BlendMode.DstOut;
-            break;
-          case 'source-atop':
-            this._globalCompositeOperation = CanvasKit.BlendMode.SrcATop;
-            break;
-          case 'destination-atop':
-            this._globalCompositeOperation = CanvasKit.BlendMode.DstATop;
-            break;
-          case 'xor':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Xor;
-            break;
-          case 'lighter':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Plus;
-            break;
-          case 'plus-lighter':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Plus;
-            break;
-          case 'plus-darker':
-            throw 'plus-darker is not supported';
-
-          // blend-mode
-          case 'multiply':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Multiply;
-            break;
-          case 'screen':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Screen;
-            break;
-          case 'overlay':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Overlay;
-            break;
-          case 'darken':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Darken;
-            break;
-          case 'lighten':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Lighten;
-            break;
-          case 'color-dodge':
-            this._globalCompositeOperation = CanvasKit.BlendMode.ColorDodge;
-            break;
-          case 'color-burn':
-            this._globalCompositeOperation = CanvasKit.BlendMode.ColorBurn;
-            break;
-          case 'hard-light':
-            this._globalCompositeOperation = CanvasKit.BlendMode.HardLight;
-            break;
-          case 'soft-light':
-            this._globalCompositeOperation = CanvasKit.BlendMode.SoftLight;
-            break;
-          case 'difference':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Difference;
-            break;
-          case 'exclusion':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Exclusion;
-            break;
-          case 'hue':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Hue;
-            break;
-          case 'saturation':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Saturation;
-            break;
-          case 'color':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Color;
-            break;
-          case 'luminosity':
-            this._globalCompositeOperation = CanvasKit.BlendMode.Luminosity;
-            break;
-          default:
-            return;
-        }
-        this._paint.setBlendMode(this._globalCompositeOperation);
-      }
-    });
-
-    Object.defineProperty(this, 'imageSmoothingEnabled', {
-      enumerable: true,
-      get: function() {
-        return this._imageSmoothingEnabled;
-      },
-      set: function(newVal) {
-        this._imageSmoothingEnabled = !!newVal;
-      }
-    });
-
-    Object.defineProperty(this, 'imageSmoothingQuality', {
-      enumerable: true,
-      get: function() {
-        switch (this._imageFilterQuality) {
-          case CanvasKit.FilterQuality.Low:
-            return 'low';
-          case CanvasKit.FilterQuality.Medium:
-            return 'medium';
-          case CanvasKit.FilterQuality.High:
-            return 'high';
-        }
-      },
-      set: function(newQuality) {
-        switch (newQuality) {
-          case 'low':
-            this._imageFilterQuality = CanvasKit.FilterQuality.Low;
-            return;
-          case 'medium':
-            this._imageFilterQuality = CanvasKit.FilterQuality.Medium;
-            return;
-          case 'high':
-            this._imageFilterQuality = CanvasKit.FilterQuality.High;
-            return;
-        }
-      }
-    });
-
-    Object.defineProperty(this, 'lineCap', {
-      enumerable: true,
-      get: function() {
-        switch (this._paint.getStrokeCap()) {
-          case CanvasKit.StrokeCap.Butt:
-            return 'butt';
-          case CanvasKit.StrokeCap.Round:
-            return 'round';
-          case CanvasKit.StrokeCap.Square:
-            return 'square';
-        }
-      },
-      set: function(newCap) {
-        switch (newCap) {
-          case 'butt':
-            this._paint.setStrokeCap(CanvasKit.StrokeCap.Butt);
-            return;
-          case 'round':
-            this._paint.setStrokeCap(CanvasKit.StrokeCap.Round);
-            return;
-          case 'square':
-            this._paint.setStrokeCap(CanvasKit.StrokeCap.Square);
-            return;
-        }
-      }
-    });
-
-    Object.defineProperty(this, 'lineDashOffset', {
-      enumerable: true,
-      get: function() {
-        return this._lineDashOffset;
-      },
-      set: function(newOffset) {
-        if (!isFinite(newOffset)) {
-          return;
-        }
-        this._lineDashOffset = newOffset;
-      }
-    });
-
-    Object.defineProperty(this, 'lineJoin', {
-      enumerable: true,
-      get: function() {
-        switch (this._paint.getStrokeJoin()) {
-          case CanvasKit.StrokeJoin.Miter:
-            return 'miter';
-          case CanvasKit.StrokeJoin.Round:
-            return 'round';
-          case CanvasKit.StrokeJoin.Bevel:
-            return 'bevel';
-        }
-      },
-      set: function(newJoin) {
-        switch (newJoin) {
-          case 'miter':
-            this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Miter);
-            return;
-          case 'round':
-            this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Round);
-            return;
-          case 'bevel':
-            this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Bevel);
-            return;
-        }
-      }
-    });
-
-    Object.defineProperty(this, 'lineWidth', {
-      enumerable: true,
-      get: function() {
-        return this._paint.getStrokeWidth();
-      },
-      set: function(newWidth) {
-        if (newWidth <= 0 || !newWidth) {
-          // Spec says to ignore NaN/Inf/0/negative values
-          return;
-        }
-        this._strokeWidth = newWidth;
-        this._paint.setStrokeWidth(newWidth);
-      }
-    });
-
-    Object.defineProperty(this, 'miterLimit', {
-      enumerable: true,
-      get: function() {
-        return this._paint.getStrokeMiter();
-      },
-      set: function(newLimit) {
-        if (newLimit <= 0 || !newLimit) {
-          // Spec says to ignore NaN/Inf/0/negative values
-          return;
-        }
-        this._paint.setStrokeMiter(newLimit);
-      }
-    });
-
-    Object.defineProperty(this, 'shadowBlur', {
-      enumerable: true,
-      get: function() {
-        return this._shadowBlur;
-      },
-      set: function(newBlur) {
-        // ignore negative, inf and NAN (but not 0) as per the spec.
-        if (newBlur < 0 || !isFinite(newBlur)) {
-          return;
-        }
-        this._shadowBlur = newBlur;
-      }
-    });
-
-    Object.defineProperty(this, 'shadowColor', {
-      enumerable: true,
-      get: function() {
-        return colorToString(this._shadowColor);
-      },
-      set: function(newColor) {
-        this._shadowColor = parseColor(newColor);
-      }
-    });
-
-    Object.defineProperty(this, 'shadowOffsetX', {
-      enumerable: true,
-      get: function() {
-        return this._shadowOffsetX;
-      },
-      set: function(newOffset) {
-        if (!isFinite(newOffset)) {
-          return;
-        }
-        this._shadowOffsetX = newOffset;
-      }
-    });
-
-    Object.defineProperty(this, 'shadowOffsetY', {
-      enumerable: true,
-      get: function() {
-        return this._shadowOffsetY;
-      },
-      set: function(newOffset) {
-        if (!isFinite(newOffset)) {
-          return;
-        }
-        this._shadowOffsetY = newOffset;
-      }
-    });
-
-    Object.defineProperty(this, 'strokeStyle', {
-      enumerable: true,
-      get: function() {
-        return colorToString(this._strokeStyle);
-      },
-      set: function(newStyle) {
-        if (typeof newStyle === 'string') {
-          this._strokeStyle = parseColor(newStyle);
-        } else if (newStyle.addColorStop) {
-          // It's probably a gradient.
-          this._strokeStyle = newStyle
-        }
-
-      }
-    });
-
-    this.arc = function(x, y, radius, startAngle, endAngle, ccw) {
-      // As per  https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-arc
-      // arc is essentially a simpler version of ellipse.
-      this.ellipse(x, y, radius, radius, 0, startAngle, endAngle, ccw);
-    }
-
-    this.arcTo = function(x1, y1, x2, y2, radius) {
-      if (!allAreFinite(arguments)) {
-        return;
-      }
-      if (radius < 0) {
-        throw 'radii cannot be negative';
-      }
-      var pts = [x1, y1, x2, y2];
-      CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
-      x1 = pts[0];
-      y1 = pts[1];
-      x2 = pts[2];
-      y2 = pts[3];
-      if (!this._currentSubpath) {
-        this._newSubpath(x1, y1);
-      }
-      this._currentSubpath.arcTo(x1, y1, x2, y2, radius * this._scalefactor());
-    }
-
-    // As per the spec this doesn't begin any paths, it only
-    // clears out any previous subpaths.
-    this.beginPath = function() {
-      this._currentPath.delete();
-      this._currentPath = new CanvasKit.SkPath();
-      this._currentSubpath && this._currentSubpath.delete();
-      this._currentSubpath = null;
-    }
-
-    this.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
-      if (!allAreFinite(arguments)) {
-        return;
-      }
-      var pts = [cp1x, cp1y, cp2x, cp2y, x, y];
-      CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
-      cp1x = pts[0];
-      cp1y = pts[1];
-      cp2x = pts[2];
-      cp2y = pts[3];
-      x    = pts[4];
-      y    = pts[5];
-      if (!this._currentSubpath) {
-        this._newSubpath(cp1x, cp1y);
-      }
-      this._currentSubpath.cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
-    }
-
-    this.clearRect = function(x, y, width, height) {
-      this._canvas.setMatrix(this._currentTransform);
-      this._paint.setStyle(CanvasKit.PaintStyle.Fill);
-      this._paint.setBlendMode(CanvasKit.BlendMode.Clear);
-      this._canvas.drawRect(CanvasKit.XYWHRect(x, y, width, height), this._paint);
-      this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
-      this._paint.setBlendMode(this._globalCompositeOperation);
-    }
-
-    this.clip = function(fillRule) {
-      this._commitSubpath();
-      var clip = this._currentPath.copy();
-      if (fillRule && fillRule.toLowerCase() === 'evenodd') {
-        clip.setFillType(CanvasKit.FillType.EvenOdd);
-      } else {
-        clip.setFillType(CanvasKit.FillType.Winding);
-      }
-      this._canvas.clipPath(clip, CanvasKit.ClipOp.Intersect, true);
-    }
-
-    this.closePath = function() {
-      if (this._currentSubpath) {
-        this._currentSubpath.close();
-        var lastPt = this._currentSubpath.getPoint(0);
-        this._newSubpath(lastPt[0], lastPt[1]);
-      }
-    }
-
-    this.createLinearGradient = function(x1, y1, x2, y2) {
-      if (!allAreFinite(arguments)) {
-        return;
-      }
-      var lcg = new LinearCanvasGradient(x1, y1, x2, y2);
-      this._gradients.push(lcg);
-      return lcg;
-    }
-
-    this.createRadialGradient = function(x1, y1, r1, x2, y2, r2) {
-      if (!allAreFinite(arguments)) {
-        return;
-      }
-      var rcg = new RadialCanvasGradient(x1, y1, r1, x2, y2, r2);
-      this._gradients.push(rcg);
-      return rcg;
-    }
-
-    this._commitSubpath = function() {
-      if (this._currentSubpath) {
-        this._currentPath.addPath(this._currentSubpath, false);
-        this._currentSubpath.delete();
-        this._currentSubpath = null;
-      }
-    }
-
-    this._imagePaint = function() {
-      var iPaint = this._fillPaint();
-      if (!this._imageSmoothingEnabled) {
-        iPaint.setFilterQuality(CanvasKit.FilterQuality.None);
-      } else {
-        iPaint.setFilterQuality(this._imageFilterQuality);
-      }
-      return iPaint;
-    }
-
-    this.drawImage = function(img) {
-      // 3 potential sets of arguments
-      // - image, dx, dy
-      // - image, dx, dy, dWidth, dHeight
-      // - image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight
-      this._canvas.setMatrix(this._currentTransform);
-      // use the fillPaint, which has the globalAlpha in it
-      // which drawImageRect will use.
-      var iPaint = this._imagePaint();
-      if (arguments.length === 3 || arguments.length === 5) {
-        var destRect = CanvasKit.XYWHRect(arguments[1], arguments[2],
-                          arguments[3] || img.width(), arguments[4] || img.height());
-        var srcRect = CanvasKit.XYWHRect(0, 0, img.width(), img.height());
-      } else if (arguments.length === 9){
-        var destRect = CanvasKit.XYWHRect(arguments[5], arguments[6],
-                                          arguments[7], arguments[8]);
-        var srcRect = CanvasKit.XYWHRect(arguments[1], arguments[2],
-                                         arguments[3], arguments[4]);
-      } else {
-        throw 'invalid number of args for drawImage, need 3, 5, or 9; got '+ arguments.length;
-      }
-      this._canvas.drawImageRect(img, srcRect, destRect, iPaint, false);
-
-      this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
-      iPaint.dispose();
-    }
-
-    this.ellipse = function(x, y, radiusX, radiusY, rotation,
-                            startAngle, endAngle, ccw) {
-      if (!allAreFinite(arguments)) {
-        return;
-      }
-      if (radiusX < 0 || radiusY < 0) {
-        throw 'radii cannot be negative';
-      }
-
-      if (!this._currentSubpath) {
-        // Don't use newSubpath here because calculating the starting
-        // point in the arc is non-trivial. Just make a new, empty
-        // subpath to append to.
-        this._currentSubpath = new CanvasKit.SkPath();
-      }
-      var bounds = CanvasKit.LTRBRect(x-radiusX, y-radiusY, x+radiusX, y+radiusY);
-      var sweep = radiansToDegrees(endAngle - startAngle) - (360 * !!ccw);
-      var temp = new CanvasKit.SkPath();
-      // Skia takes degrees. JS tends to be radians.
-      temp.addArc(bounds, radiansToDegrees(startAngle), sweep);
-      var m = CanvasKit.SkMatrix.multiply(
-                  this._currentTransform,
-                  CanvasKit.SkMatrix.rotated(rotation, x, y));
-
-      this._currentSubpath.addPath(temp, m, true);
-      temp.delete();
-    }
-
-    // A helper to copy the current paint, ready for filling
-    // This applies the global alpha.
-    // Call dispose() after to clean up.
-    this._fillPaint = function() {
-      var paint = this._paint.copy();
-      paint.setStyle(CanvasKit.PaintStyle.Fill);
-      if (Number.isInteger(this._fillStyle)) {
-        var alphaColor = CanvasKit.multiplyByAlpha(this._fillStyle, this._globalAlpha);
-        paint.setColor(alphaColor);
-      } else {
-        var gradient = this._fillStyle._getShader(this._currentTransform, this._globalAlpha);
-        paint.setShader(gradient);
-      }
-
-      paint.dispose = function() {
-        // If there are some helper effects in the future, clean them up
-        // here. In any case, we have .dispose() to make _fillPaint behave
-        // like _strokePaint and _shadowPaint.
-        this.delete();
-      }
-      return paint;
-    }
-
-    this.fill = function() {
-      this._commitSubpath();
-      var fillPaint = this._fillPaint();
-
-      var shadowPaint = this._shadowPaint(fillPaint);
-      if (shadowPaint) {
-        var offsetMatrix = CanvasKit.SkMatrix.multiply(
-          this._currentTransform,
-          CanvasKit.SkMatrix.translated(this._shadowOffsetX, this._shadowOffsetY)
-        );
-        this._canvas.setMatrix(offsetMatrix);
-        this._canvas.drawPath(this._currentPath, shadowPaint);
-        this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
-        shadowPaint.dispose();
-      }
-
-      this._canvas.drawPath(this._currentPath, fillPaint);
-      fillPaint.dispose();
-    }
-
-    this.fillRect = function(x, y, width, height) {
-      var fillPaint = this._fillPaint();
-      this._canvas.setMatrix(this._currentTransform);
-      this._canvas.drawRect(CanvasKit.XYWHRect(x, y, width, height), fillPaint);
-      this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
-      fillPaint.dispose();
-    }
-
-    this.fillText = function(text, x, y, maxWidth) {
-      // TODO do something with maxWidth, probably involving measure
-      var fillPaint = this._fillPaint()
-      var shadowPaint = this._shadowPaint(fillPaint);
-      if (shadowPaint) {
-        var offsetMatrix = CanvasKit.SkMatrix.multiply(
-          this._currentTransform,
-          CanvasKit.SkMatrix.translated(this._shadowOffsetX, this._shadowOffsetY)
-        );
-        this._canvas.setMatrix(offsetMatrix);
-        this._canvas.drawText(text, x, y, shadowPaint);
-        shadowPaint.dispose();
-        // Don't need to setMatrix back, it will be handled by the next few lines.
-      }
-      this._canvas.setMatrix(this._currentTransform);
-      this._canvas.drawText(text, x, y, fillPaint);
-      this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
-      fillPaint.dispose();
-    }
-
-    this.getLineDash = function() {
-      return this._lineDashList.slice();
-    }
-
-    this.lineTo = function(x, y) {
-      if (!allAreFinite(arguments)) {
-        return;
-      }
-      var pts = [x, y];
-      CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
-      x = pts[0];
-      y = pts[1];
-      // A lineTo without a previous subpath is turned into a moveTo
-      if (!this._currentSubpath) {
-        this._newSubpath(x, y);
-      } else {
-        this._currentSubpath.lineTo(x, y);
-      }
-    }
-
-    this.measureText = function(text) {
-      return {
-        width: this._paint.measureText(text),
-        // TODO other measurements?
-      }
-    }
-
-    this.moveTo = function(x, y) {
-      if (!allAreFinite(arguments)) {
-        return;
-      }
-      var pts = [x, y];
-      CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
-      x = pts[0];
-      y = pts[1];
-      this._newSubpath(x, y);
-    }
-
-    this._newSubpath = function(x, y) {
-      this._commitSubpath();
-      this._currentSubpath = new CanvasKit.SkPath();
-      this._currentSubpath.moveTo(x, y);
-    }
-
-    this.quadraticCurveTo = function(cpx, cpy, x, y) {
-      if (!allAreFinite(arguments)) {
-        return;
-      }
-      var pts = [cpx, cpy, x, y];
-      CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
-      cpx = pts[0];
-      cpy = pts[1];
-      x   = pts[2];
-      y   = pts[3];
-      if (!this._currentSubpath) {
-        this._newSubpath(cpx, cpy);
-      }
-      this._currentSubpath.quadTo(cpx, cpy, x, y);
-    }
-
-    this.rect = function(x, y, width, height) {
-      if (!allAreFinite(arguments)) {
-        return;
-      }
-      var pts = [x, y];
-      CanvasKit.SkMatrix.mapPoints(this._currentTransform, pts);
-      // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rect
-      this._newSubpath(x, y);
-      var scale = this._scalefactor();
-      this._currentSubpath.addRect(x, y, x+width, y+height);
-      this._currentSubpath.transform(this._currentTransform);
-      this._newSubpath(pts[0], pts[1]);
-    }
-
-    this.resetTransform = function() {
-      this._currentTransform = CanvasKit.SkMatrix.identity();
-    }
-
-    this.restore = function() {
-      var newState = this._canvasStateStack.pop();
-      if (!newState) {
-        return;
-      }
-      this._currentTransform = newState.ctm;
-      this._lineDashList = newState.ldl;
-      this._strokeWidth = newState.sw;
-      this._paint.setStrokeWidth(this._strokeWidth);
-      this._strokeStyle = newState.ss;
-      this._fillStyle = newState.fs;
-      this._paint.setStrokeCap(newState.cap);
-      this._paint.setStrokeJoin(newState.jn);
-      this._paint.setStrokeMiter(newState.mtr);
-      this._shadowOffsetX = newState.sox;
-      this._shadowOffsetY = newState.soy;
-      this._shadowBlur = newState.sb;
-      this._shadowColor = newState.shc;
-      this._globalAlpha = newState.ga;
-      this._globalCompositeOperation = newState.gco;
-      this._paint.setBlendMode(this._globalCompositeOperation);
-      this._lineDashOffset = newState.ldo;
-      this._imageSmoothingEnabled = newState.ise;
-      this._imageFilterQuality = newState.isq;
-      //TODO: font, textAlign, textBaseline, direction
-
-      // restores the clip
-      this._canvas.restore();
-    }
-
-    this.rotate = function(radians, px, py) {
-      this._currentTransform = CanvasKit.SkMatrix.multiply(
-                                  this._currentTransform,
-                                  CanvasKit.SkMatrix.rotated(radians, px, py));
-    }
-
-    this.save = function() {
-      if (this._fillStyle._copy) {
-        var fs = this._fillStyle._copy();
-        this._gradients.push(fs);
-      } else {
-        var fs = this._fillStyle;
-      }
-
-      if (this._strokeStyle._copy) {
-        var ss = this._strokeStyle._copy();
-        this._gradients.push(ss);
-      } else {
-        var ss = this._strokeStyle;
-      }
-
-      this._canvasStateStack.push({
-        ctm:  this._currentTransform.slice(),
-        ldl: this._lineDashList.slice(),
-        sw:  this._strokeWidth,
-        ss:  ss,
-        fs:  fs,
-        cap: this._paint.getStrokeCap(),
-        jn:  this._paint.getStrokeJoin(),
-        mtr: this._paint.getStrokeMiter(),
-        sox: this._shadowOffsetX,
-        soy: this._shadowOffsetY,
-        sb:  this._shadowBlur,
-        shc: this._shadowColor,
-        ga:  this._globalAlpha,
-        ldo: this._lineDashOffset,
-        gco: this._globalCompositeOperation,
-        ise: this._imageSmoothingEnabled,
-        isq: this._imageFilterQuality,
-        //TODO: font, textAlign, textBaseline, direction
-      });
-      // Saves the clip
-      this._canvas.save();
-    }
-
-    this.scale = function(sx, sy) {
-      this._currentTransform = CanvasKit.SkMatrix.multiply(
-                                  this._currentTransform,
-                                  CanvasKit.SkMatrix.scaled(sx, sy));
-    }
-
-    this._scalefactor = function() {
-      // This is an approximation of what Chrome does when scaling up
-      // line width.
-      var m = this._currentTransform;
-      var sx = m[0];
-      var sy = m[4];
-      return (Math.abs(sx) + Math.abs(sy))/2;
-    }
-
-    this.setLineDash = function(dashes) {
-      for (var i = 0; i < dashes.length; i++) {
-        if (!Number.isFinite(dashes[i]) || dashes[i] < 0) {
-          SkDebug('dash list must have positive, finite values');
-          return;
-        }
-      }
-      if (dashes.length % 2 === 1) {
-        // as per the spec, concatenate 2 copies of dashes
-        // to give it an even number of elements.
-        Array.prototype.push.apply(dashes, dashes);
-      }
-      this._lineDashList = dashes;
-    }
-
-    this.setTransform = function(a, b, c, d, e, f) {
-      this._currentTransform = [a, c, e,
-                                b, d, f,
-                                0, 0, 1];
-    }
-
-    // Returns the shadow paint for the current settings or null if there
-    // should be no shadow. This ends up being a copy of the given
-    // paint with a blur maskfilter and the correct color.
-    this._shadowPaint = function(basePaint) {
-      // multiply first to see if the alpha channel goes to 0 after multiplication.
-      var alphaColor = CanvasKit.multiplyByAlpha(this._shadowColor, this._globalAlpha);
-      // if alpha is zero, no shadows
-      if (!CanvasKit.getColorComponents(alphaColor)[3]) {
-        return null;
-      }
-      // one of these must also be non-zero (otherwise the shadow is
-      // completely hidden.  And the spec says so).
-      if (!(this._shadowBlur || this._shadowOffsetY || this._shadowOffsetX)) {
-        return null;
-      }
-      var shadowPaint = basePaint.copy();
-      shadowPaint.setColor(alphaColor);
-      var blurEffect = CanvasKit.MakeBlurMaskFilter(CanvasKit.BlurStyle.Normal,
-        Math.max(1, this._shadowBlur/2), // very little blur when < 1
-        false);
-      shadowPaint.setMaskFilter(blurEffect);
-
-      // hack up a "destructor" which also cleans up the blurEffect. Otherwise,
-      // we leak the blurEffect (since smart pointers don't help us in JS land).
-      shadowPaint.dispose = function() {
-        blurEffect.delete();
-        this.delete();
-      };
-      return shadowPaint;
-    }
-
-    // A helper to get a copy of the current paint, ready for stroking.
-    // This applies the global alpha and the dashedness.
-    // Call dispose() after to clean up.
-    this._strokePaint = function() {
-      var paint = this._paint.copy();
-      paint.setStyle(CanvasKit.PaintStyle.Stroke);
-      if (Number.isInteger(this._strokeStyle)) {
-        var alphaColor = CanvasKit.multiplyByAlpha(this._strokeStyle, this._globalAlpha);
-        paint.setColor(alphaColor);
-      } else {
-          var gradient = this._strokeStyle._getShader(this._currentTransform, this._globalAlpha);
-          paint.setShader(gradient);
-      }
-
-      // This is not in the spec, but it appears Chrome scales up
-      // the line width by some amount when stroking (and filling?).
-      var scaledWidth = this._strokeWidth * this._scalefactor();
-      paint.setStrokeWidth(scaledWidth);
-
-      if (this._lineDashList.length) {
-        var dashedEffect = CanvasKit.MakeSkDashPathEffect(this._lineDashList, this._lineDashOffset);
-        paint.setPathEffect(dashedEffect);
-      }
-
-      paint.dispose = function() {
-        dashedEffect && dashedEffect.delete();
-        this.delete();
-      }
-      return paint;
-    }
-
-    this.stroke = function() {
-      this._commitSubpath();
-      var strokePaint = this._strokePaint();
-
-      var shadowPaint = this._shadowPaint(strokePaint);
-      if (shadowPaint) {
-        var offsetMatrix = CanvasKit.SkMatrix.multiply(
-          this._currentTransform,
-          CanvasKit.SkMatrix.translated(this._shadowOffsetX, this._shadowOffsetY)
-        );
-        this._canvas.setMatrix(offsetMatrix);
-        this._canvas.drawPath(this._currentPath, shadowPaint);
-        this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
-        shadowPaint.dispose();
-      }
-
-      this._canvas.drawPath(this._currentPath, strokePaint);
-      strokePaint.dispose();
-    }
-
-    this.strokeRect = function(x, y, width, height) {
-      var strokePaint = this._strokePaint();
-      this._canvas.setMatrix(this._currentTransform);
-      this._canvas.drawRect(CanvasKit.XYWHRect(x, y, width, height), strokePaint);
-      this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
-      strokePaint.dispose();
-    }
-
-    this.strokeText = function(text, x, y, maxWidth) {
-      // TODO do something with maxWidth, probably involving measure
-      var strokePaint = this._strokePaint();
-
-      var shadowPaint = this._shadowPaint(strokePaint);
-      if (shadowPaint) {
-        var offsetMatrix = CanvasKit.SkMatrix.multiply(
-          this._currentTransform,
-          CanvasKit.SkMatrix.translated(this._shadowOffsetX, this._shadowOffsetY)
-        );
-        this._canvas.setMatrix(offsetMatrix);
-        this._canvas.drawText(text, x, y, shadowPaint);
-        shadowPaint.dispose();
-        // Don't need to setMatrix back, it will be handled by the next few lines.
-      }
-      this._canvas.setMatrix(this._currentTransform);
-      this._canvas.drawText(text, x, y, strokePaint);
-      this._canvas.setMatrix(CanvasKit.SkMatrix.identity());
-      strokePaint.dispose();
-    }
-
-    this.translate = function(dx, dy) {
-      this._currentTransform = CanvasKit.SkMatrix.multiply(
-                                  this._currentTransform,
-                                  CanvasKit.SkMatrix.translated(dx, dy));
-    }
-
-    this.transform = function(a, b, c, d, e, f) {
-      this._currentTransform = CanvasKit.SkMatrix.multiply(
-                                  this._currentTransform,
-                                  [a, c, e,
-                                   b, d, f,
-                                   0, 0, 1]);
-    }
-
-    // Not supported operations (e.g. for Web only)
-    this.addHitRegion = function() {};
-    this.clearHitRegions = function() {};
-    this.drawFocusIfNeeded = function() {};
-    this.removeHitRegion = function() {};
-    this.scrollPathIntoView = function() {};
-
-    Object.defineProperty(this, 'canvas', {
-      value: null,
-      writable: false
-    });
-  }
-
-  CanvasKit.MakeCanvas = function(width, height) {
-    // TODO(kjlubick) do fonts the "correct" way
-    CanvasKit.initFonts();
-    var surf = CanvasKit.MakeSurface(width, height);
-    if (surf) {
-      return new HTMLCanvas(surf);
-    }
-    return null;
-  }
-
-  var units = 'px|pt|pc|in|cm|mm|%|em|ex|ch|rem|q';
-  var fontSizeRegex = new RegExp('([\\d\\.]+)(' + units + ')');
-  var defaultHeight = 12;
-  // Based off of node-canvas's parseFont
-  // returns font size in *points* (original impl was in px);
-  function parseFontSize(fontStr) {
-    // This is naive and doesn't account for line-height yet
-    // (but neither does node-canvas's?)
-    var fontSize = fontSizeRegex.exec(fontStr);
-    if (!fontSize) {
-      SkDebug('Could not parse font size' + fontStr);
-      return 16;
-    }
-    var size = parseFloat(fontSize[1]);
-    var unit = fontSize[2];
-    switch (unit) {
-      case 'pt':
-        return size;
-      case 'px':
-        return size * 3/4;
-      case 'pc':
-        return size * 12;
-      case 'in':
-        return size * 72;
-      case 'cm':
-        return size * 72.0 / 2.54;
-      case 'mm':
-        return size * (72.0 / 25.4);
-      case '%':
-        return size * (defaultHeight / 100);
-      case 'em':
-      case 'rem':
-        return size * defaultHeight;
-      case 'q':
-        return size * (96 / 25.4 / 3);
-    }
-  }
-
-  function colorToString(skcolor) {
-    // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
-    var components = CanvasKit.getColorComponents(skcolor);
-    var r = components[0];
-    var g = components[1];
-    var b = components[2];
-    var a = components[3];
-    if (a === 1.0) {
-      // hex
-      r = r.toString(16).toLowerCase();
-      g = g.toString(16).toLowerCase();
-      b = b.toString(16).toLowerCase();
-      r = (r.length === 1 ? '0'+r: r);
-      g = (g.length === 1 ? '0'+g: g);
-      b = (b.length === 1 ? '0'+b: b);
-      return '#'+r+g+b;
-    } else {
-      a = (a === 0 || a === 1) ? a : a.toFixed(8);
-      return 'rgba('+r+', '+g+', '+b+', '+a+')';
-    }
-  }
-
-  function valueOrPercent(aStr) {
-    var a = parseFloat(aStr) || 1;
-    if (aStr && aStr.indexOf('%') !== -1) {
-      return a / 100;
-    }
-    return a;
-  }
-
-  function parseColor(colorStr) {
-    colorStr = colorStr.toLowerCase();
-    // See https://drafts.csswg.org/css-color/#typedef-hex-color
-    if (colorStr.startsWith('#')) {
-      var r, g, b, a = 255;
-      switch (colorStr.length) {
-        case 9: // 8 hex chars #RRGGBBAA
-          a = parseInt(colorStr.slice(7, 9), 16);
-        case 7: // 6 hex chars #RRGGBB
-          r = parseInt(colorStr.slice(1, 3), 16);
-          g = parseInt(colorStr.slice(3, 5), 16);
-          b = parseInt(colorStr.slice(5, 7), 16);
-          break;
-        case 5: // 4 hex chars #RGBA
-          // multiplying by 17 is the same effect as
-          // appending another character of the same value
-          // e.g. e => ee == 14 => 238
-          a = parseInt(colorStr.slice(4, 5), 16) * 17;
-        case 4: // 6 hex chars #RGB
-          r = parseInt(colorStr.slice(1, 2), 16) * 17;
-          g = parseInt(colorStr.slice(2, 3), 16) * 17;
-          b = parseInt(colorStr.slice(3, 4), 16) * 17;
-          break;
-      }
-      return CanvasKit.Color(r, g, b, a/255);
-
-    } else if (colorStr.startsWith('rgba')) {
-      // Trim off rgba( and the closing )
-      colorStr = colorStr.slice(5, -1);
-      var nums = colorStr.split(',');
-      return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
-                             valueOrPercent(nums[3]));
-    } else if (colorStr.startsWith('rgb')) {
-      // Trim off rgba( and the closing )
-      colorStr = colorStr.slice(4, -1);
-      var nums = colorStr.split(',');
-      // rgb can take 3 or 4 arguments
-      return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
-                             valueOrPercent(nums[3]));
-    } else if (colorStr.startsWith('gray(')) {
-      // TODO
-    } else if (colorStr.startsWith('hsl')) {
-      // TODO
-    } else {
-      // Try for named color
-      var nc = colorMap[colorStr];
-      if (nc !== undefined) {
-        return nc;
-      }
-    }
-    SkDebug('unrecognized color ' + colorStr);
-    return CanvasKit.BLACK;
-  }
-
-  CanvasKit._testing['parseColor'] = parseColor;
-  CanvasKit._testing['colorToString'] = colorToString;
-
-  // Create the following with
-  // node ./htmlcanvas/_namedcolors.js --expose-wasm
-  // JS/closure doesn't have a constexpr like thing which
-  // would really help here. Since we don't, we pre-compute
-  // the map, which saves (a tiny amount of) startup time
-  // and (a small amount of) code size.
-  /* @dict */
-  var colorMap = {"aliceblue":-984833,"antiquewhite":-332841,"aqua":-16711681,"aquamarine":-8388652,"azure":-983041,"beige":-657956,"bisque":-6972,"black":-16777216,"blanchedalmond":-5171,"blue":-16776961,"blueviolet":-7722014,"brown":-5952982,"burlywood":-2180985,"cadetblue":-10510688,"chartreuse":-8388864,"chocolate":-2987746,"coral":-32944,"cornflowerblue":-10185235,"cornsilk":-1828,"crimson":-2354116,"cyan":-16711681,"darkblue":-16777077,"darkcyan":-16741493,"darkgoldenrod":-4684277,"darkgray":-5658199,"darkgreen":-16751616,"darkgrey":-5658199,"darkkhaki":-4343957,"darkmagenta":-7667573,"darkolivegreen":-11179217,"darkorange":-29696,"darkorchid":-6737204,"darkred":-7667712,"darksalmon":-1468806,"darkseagreen":-7357297,"darkslateblue":-12042869,"darkslategray":-13676721,"darkslategrey":-13676721,"darkturquoise":-16724271,"darkviolet":-7077677,"deeppink":-60269,"deepskyblue":-16728065,"dimgray":-9868951,"dimgrey":-9868951,"dodgerblue":-14774017,"firebrick":-5103070,"floralwhite":-1296,"forestgreen":-14513374,"fuchsia":-65281,"gainsboro":-2302756,"ghostwhite":-460545,"gold":-10496,"goldenrod":-2448096,"gray":-8355712,"green":-16744448,"greenyellow":-5374161,"grey":-8355712,"honeydew":-983056,"hotpink":-38476,"indianred":-3318692,"indigo":-11861886,"ivory":-16,"khaki":-989556,"lavender":-1644806,"lavenderblush":-3851,"lawngreen":-8586240,"lemonchiffon":-1331,"lightblue":-5383962,"lightcoral":-1015680,"lightcyan":-2031617,"lightgoldenrodyellow":-329006,"lightgray":-2894893,"lightgreen":-7278960,"lightgrey":-2894893,"lightpink":-18751,"lightsalmon":-24454,"lightseagreen":-14634326,"lightskyblue":-7876870,"lightslategray":-8943463,"lightslategrey":-8943463,"lightsteelblue":-5192482,"lightyellow":-32,"lime":-16711936,"limegreen":-13447886,"linen":-331546,"magenta":-65281,"maroon":-8388608,"mediumaquamarine":-10039894,"mediumblue":-16777011,"mediumorchid":-4565549,"mediumpurple":-7114533,"mediumseagreen":-12799119,"mediumslateblue":-8689426,"mediumspringgreen":-16713062,"mediumturquoise":-12004916,"mediumvioletred":-3730043,"midnightblue":-15132304,"mintcream":-655366,"mistyrose":-6943,"moccasin":-6987,"navajowhite":-8531,"navy":-16777088,"oldlace":-133658,"olive":-8355840,"olivedrab":-9728477,"orange":-23296,"orangered":-47872,"orchid":-2461482,"palegoldenrod":-1120086,"palegreen":-6751336,"paleturquoise":-5247250,"palevioletred":-2396013,"papayawhip":-4139,"peachpuff":-9543,"peru":-3308225,"pink":-16181,"plum":-2252579,"powderblue":-5185306,"purple":-8388480,"rebeccapurple":-10079335,"red":-65536,"rosybrown":-4419697,"royalblue":-12490271,"saddlebrown":-7650029,"salmon":-360334,"sandybrown":-744352,"seagreen":-13726889,"seashell":-2578,"sienna":-6270419,"silver":-4144960,"skyblue":-7876885,"slateblue":-9807155,"slategray":-9404272,"slategrey":-9404272,"snow":-1286,"springgreen":-16711809,"steelblue":-12156236,"tan":-2968436,"teal":-16744320,"thistle":-2572328,"transparent":0,"tomato":-40121,"turquoise":-12525360,"violet":-1146130,"wheat":-663885,"white":-1,"whitesmoke":-657931,"yellow":-256,"yellowgreen":-6632142};
-
-}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/canvaskit/htmlcanvas/canvas2dcontext.js b/experimental/canvaskit/htmlcanvas/canvas2dcontext.js
new file mode 100644
index 0000000..3d79f19
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/canvas2dcontext.js
@@ -0,0 +1,1150 @@
+function CanvasRenderingContext2D(skcanvas) {
+  this._canvas = skcanvas;
+  this._paint = new CanvasKit.SkPaint();
+  this._paint.setAntiAlias(true);
+
+  this._paint.setStrokeMiter(10);
+  this._paint.setStrokeCap(CanvasKit.StrokeCap.Butt);
+  this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Miter);
+  this._paint.setTextSize(10);
+  this._paint.setTypeface(null);
+  this._fontString = '10px monospace';
+
+  this._font = new CanvasKit.SkFont();
+
+  this._strokeStyle    = CanvasKit.BLACK;
+  this._fillStyle      = CanvasKit.BLACK;
+  this._shadowBlur     = 0;
+  this._shadowColor    = CanvasKit.TRANSPARENT;
+  this._shadowOffsetX  = 0;
+  this._shadowOffsetY  = 0;
+  this._globalAlpha    = 1;
+  this._strokeWidth    = 1;
+  this._lineDashOffset = 0;
+  this._lineDashList   = [];
+  // aka SkBlendMode
+  this._globalCompositeOperation = CanvasKit.BlendMode.SrcOver;
+  this._imageFilterQuality = CanvasKit.FilterQuality.Low;
+  this._imageSmoothingEnabled = true;
+
+
+  this._paint.setStrokeWidth(this._strokeWidth);
+  this._paint.setBlendMode(this._globalCompositeOperation);
+
+  this._currentPath = new CanvasKit.SkPath();
+  this._currentTransform = CanvasKit.SkMatrix.identity();
+
+  // Use this for save/restore
+  this._canvasStateStack = [];
+  // Keep a reference to all the effects (e.g. gradients, patterns)
+  // that were allocated for cleanup in _dispose.
+  this._toCleanUp = [];
+
+  this._dispose = function() {
+    this._currentPath.delete();
+    this._paint.delete();
+    this._font.delete();
+    this._toCleanUp.forEach(function(c) {
+      c._dispose();
+    });
+    // Don't delete this._canvas as it will be disposed
+    // by the surface of which it is based.
+  }
+
+  // This always accepts DOMMatrix/SVGMatrix or any other
+  // object that has properties a,b,c,d,e,f defined.
+  // Returns a DOM-Matrix like dictionary
+  Object.defineProperty(this, 'currentTransform', {
+    enumerable: true,
+    get: function() {
+      return {
+        'a' : this._currentTransform[0],
+        'c' : this._currentTransform[1],
+        'e' : this._currentTransform[2],
+        'b' : this._currentTransform[3],
+        'd' : this._currentTransform[4],
+        'f' : this._currentTransform[5],
+      };
+    },
+    // @param {DOMMatrix} matrix
+    set: function(matrix) {
+      if (matrix.a) {
+        // if we see a property named 'a', guess that b-f will
+        // also be there.
+        this.setTransform(matrix.a, matrix.b, matrix.c,
+                          matrix.d, matrix.e, matrix.f);
+      }
+    }
+  });
+
+  Object.defineProperty(this, 'fillStyle', {
+    enumerable: true,
+    get: function() {
+      if (Number.isInteger(this._fillStyle)) {
+        return colorToString(this._fillStyle);
+      }
+      return this._fillStyle;
+    },
+    set: function(newStyle) {
+      if (typeof newStyle === 'string') {
+        this._fillStyle = parseColor(newStyle);
+      } else if (newStyle._getShader) {
+        // It's an effect that has a shader.
+        this._fillStyle = newStyle
+      }
+    }
+  });
+
+  Object.defineProperty(this, 'font', {
+    enumerable: true,
+    get: function() {
+      return this._fontString;
+    },
+    set: function(newFont) {
+      var tf = getTypeface(newFont);
+      if (tf) {
+        // tf is a "dict" according to closure, that is, the field
+        // names are not minified. Thus, we need to access it via
+        // bracket notation to tell closure not to minify these names.
+        this._paint.setTextSize(tf['sizePx']);
+        this._paint.setTypeface(tf['typeface']);
+        this._font.setSize(tf['sizePx']);
+        this._font.setTypeface(tf['typeface']);
+        this._fontString = newFont;
+      }
+    }
+  });
+
+  Object.defineProperty(this, 'globalAlpha', {
+    enumerable: true,
+    get: function() {
+      return this._globalAlpha;
+    },
+    set: function(newAlpha) {
+      // ignore invalid values, as per the spec
+      if (!isFinite(newAlpha) || newAlpha < 0 || newAlpha > 1) {
+        return;
+      }
+      this._globalAlpha = newAlpha;
+    }
+  });
+
+  Object.defineProperty(this, 'globalCompositeOperation', {
+    enumerable: true,
+    get: function() {
+      switch (this._globalCompositeOperation) {
+        // composite-mode
+        case CanvasKit.BlendMode.SrcOver:
+          return 'source-over';
+        case CanvasKit.BlendMode.DstOver:
+          return 'destination-over';
+        case CanvasKit.BlendMode.Src:
+          return 'copy';
+        case CanvasKit.BlendMode.Dst:
+          return 'destination';
+        case CanvasKit.BlendMode.Clear:
+          return 'clear';
+        case CanvasKit.BlendMode.SrcIn:
+          return 'source-in';
+        case CanvasKit.BlendMode.DstIn:
+          return 'destination-in';
+        case CanvasKit.BlendMode.SrcOut:
+          return 'source-out';
+        case CanvasKit.BlendMode.DstOut:
+          return 'destination-out';
+        case CanvasKit.BlendMode.SrcATop:
+          return 'source-atop';
+        case CanvasKit.BlendMode.DstATop:
+          return 'destination-atop';
+        case CanvasKit.BlendMode.Xor:
+          return 'xor';
+        case CanvasKit.BlendMode.Plus:
+          return 'lighter';
+
+        case CanvasKit.BlendMode.Multiply:
+          return 'multiply';
+        case CanvasKit.BlendMode.Screen:
+          return 'screen';
+        case CanvasKit.BlendMode.Overlay:
+          return 'overlay';
+        case CanvasKit.BlendMode.Darken:
+          return 'darken';
+        case CanvasKit.BlendMode.Lighten:
+          return 'lighten';
+        case CanvasKit.BlendMode.ColorDodge:
+          return 'color-dodge';
+        case CanvasKit.BlendMode.ColorBurn:
+          return 'color-burn';
+        case CanvasKit.BlendMode.HardLight:
+          return 'hard-light';
+        case CanvasKit.BlendMode.SoftLight:
+          return 'soft-light';
+        case CanvasKit.BlendMode.Difference:
+          return 'difference';
+        case CanvasKit.BlendMode.Exclusion:
+          return 'exclusion';
+        case CanvasKit.BlendMode.Hue:
+          return 'hue';
+        case CanvasKit.BlendMode.Saturation:
+          return 'saturation';
+        case CanvasKit.BlendMode.Color:
+          return 'color';
+        case CanvasKit.BlendMode.Luminosity:
+          return 'luminosity';
+      }
+    },
+    set: function(newMode) {
+      switch (newMode) {
+        // composite-mode
+        case 'source-over':
+          this._globalCompositeOperation = CanvasKit.BlendMode.SrcOver;
+          break;
+        case 'destination-over':
+          this._globalCompositeOperation = CanvasKit.BlendMode.DstOver;
+          break;
+        case 'copy':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Src;
+          break;
+        case 'destination':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Dst;
+          break;
+        case 'clear':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Clear;
+          break;
+        case 'source-in':
+          this._globalCompositeOperation = CanvasKit.BlendMode.SrcIn;
+          break;
+        case 'destination-in':
+          this._globalCompositeOperation = CanvasKit.BlendMode.DstIn;
+          break;
+        case 'source-out':
+          this._globalCompositeOperation = CanvasKit.BlendMode.SrcOut;
+          break;
+        case 'destination-out':
+          this._globalCompositeOperation = CanvasKit.BlendMode.DstOut;
+          break;
+        case 'source-atop':
+          this._globalCompositeOperation = CanvasKit.BlendMode.SrcATop;
+          break;
+        case 'destination-atop':
+          this._globalCompositeOperation = CanvasKit.BlendMode.DstATop;
+          break;
+        case 'xor':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Xor;
+          break;
+        case 'lighter':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Plus;
+          break;
+        case 'plus-lighter':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Plus;
+          break;
+        case 'plus-darker':
+          throw 'plus-darker is not supported';
+
+        // blend-mode
+        case 'multiply':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Multiply;
+          break;
+        case 'screen':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Screen;
+          break;
+        case 'overlay':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Overlay;
+          break;
+        case 'darken':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Darken;
+          break;
+        case 'lighten':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Lighten;
+          break;
+        case 'color-dodge':
+          this._globalCompositeOperation = CanvasKit.BlendMode.ColorDodge;
+          break;
+        case 'color-burn':
+          this._globalCompositeOperation = CanvasKit.BlendMode.ColorBurn;
+          break;
+        case 'hard-light':
+          this._globalCompositeOperation = CanvasKit.BlendMode.HardLight;
+          break;
+        case 'soft-light':
+          this._globalCompositeOperation = CanvasKit.BlendMode.SoftLight;
+          break;
+        case 'difference':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Difference;
+          break;
+        case 'exclusion':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Exclusion;
+          break;
+        case 'hue':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Hue;
+          break;
+        case 'saturation':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Saturation;
+          break;
+        case 'color':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Color;
+          break;
+        case 'luminosity':
+          this._globalCompositeOperation = CanvasKit.BlendMode.Luminosity;
+          break;
+        default:
+          return;
+      }
+      this._paint.setBlendMode(this._globalCompositeOperation);
+    }
+  });
+
+  Object.defineProperty(this, 'imageSmoothingEnabled', {
+    enumerable: true,
+    get: function() {
+      return this._imageSmoothingEnabled;
+    },
+    set: function(newVal) {
+      this._imageSmoothingEnabled = !!newVal;
+    }
+  });
+
+  Object.defineProperty(this, 'imageSmoothingQuality', {
+    enumerable: true,
+    get: function() {
+      switch (this._imageFilterQuality) {
+        case CanvasKit.FilterQuality.Low:
+          return 'low';
+        case CanvasKit.FilterQuality.Medium:
+          return 'medium';
+        case CanvasKit.FilterQuality.High:
+          return 'high';
+      }
+    },
+    set: function(newQuality) {
+      switch (newQuality) {
+        case 'low':
+          this._imageFilterQuality = CanvasKit.FilterQuality.Low;
+          return;
+        case 'medium':
+          this._imageFilterQuality = CanvasKit.FilterQuality.Medium;
+          return;
+        case 'high':
+          this._imageFilterQuality = CanvasKit.FilterQuality.High;
+          return;
+      }
+    }
+  });
+
+  Object.defineProperty(this, 'lineCap', {
+    enumerable: true,
+    get: function() {
+      switch (this._paint.getStrokeCap()) {
+        case CanvasKit.StrokeCap.Butt:
+          return 'butt';
+        case CanvasKit.StrokeCap.Round:
+          return 'round';
+        case CanvasKit.StrokeCap.Square:
+          return 'square';
+      }
+    },
+    set: function(newCap) {
+      switch (newCap) {
+        case 'butt':
+          this._paint.setStrokeCap(CanvasKit.StrokeCap.Butt);
+          return;
+        case 'round':
+          this._paint.setStrokeCap(CanvasKit.StrokeCap.Round);
+          return;
+        case 'square':
+          this._paint.setStrokeCap(CanvasKit.StrokeCap.Square);
+          return;
+      }
+    }
+  });
+
+  Object.defineProperty(this, 'lineDashOffset', {
+    enumerable: true,
+    get: function() {
+      return this._lineDashOffset;
+    },
+    set: function(newOffset) {
+      if (!isFinite(newOffset)) {
+        return;
+      }
+      this._lineDashOffset = newOffset;
+    }
+  });
+
+  Object.defineProperty(this, 'lineJoin', {
+    enumerable: true,
+    get: function() {
+      switch (this._paint.getStrokeJoin()) {
+        case CanvasKit.StrokeJoin.Miter:
+          return 'miter';
+        case CanvasKit.StrokeJoin.Round:
+          return 'round';
+        case CanvasKit.StrokeJoin.Bevel:
+          return 'bevel';
+      }
+    },
+    set: function(newJoin) {
+      switch (newJoin) {
+        case 'miter':
+          this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Miter);
+          return;
+        case 'round':
+          this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Round);
+          return;
+        case 'bevel':
+          this._paint.setStrokeJoin(CanvasKit.StrokeJoin.Bevel);
+          return;
+      }
+    }
+  });
+
+  Object.defineProperty(this, 'lineWidth', {
+    enumerable: true,
+    get: function() {
+      return this._paint.getStrokeWidth();
+    },
+    set: function(newWidth) {
+      if (newWidth <= 0 || !newWidth) {
+        // Spec says to ignore NaN/Inf/0/negative values
+        return;
+      }
+      this._strokeWidth = newWidth;
+      this._paint.setStrokeWidth(newWidth);
+    }
+  });
+
+  Object.defineProperty(this, 'miterLimit', {
+    enumerable: true,
+    get: function() {
+      return this._paint.getStrokeMiter();
+    },
+    set: function(newLimit) {
+      if (newLimit <= 0 || !newLimit) {
+        // Spec says to ignore NaN/Inf/0/negative values
+        return;
+      }
+      this._paint.setStrokeMiter(newLimit);
+    }
+  });
+
+  Object.defineProperty(this, 'shadowBlur', {
+    enumerable: true,
+    get: function() {
+      return this._shadowBlur;
+    },
+    set: function(newBlur) {
+      // ignore negative, inf and NAN (but not 0) as per the spec.
+      if (newBlur < 0 || !isFinite(newBlur)) {
+        return;
+      }
+      this._shadowBlur = newBlur;
+    }
+  });
+
+  Object.defineProperty(this, 'shadowColor', {
+    enumerable: true,
+    get: function() {
+      return colorToString(this._shadowColor);
+    },
+    set: function(newColor) {
+      this._shadowColor = parseColor(newColor);
+    }
+  });
+
+  Object.defineProperty(this, 'shadowOffsetX', {
+    enumerable: true,
+    get: function() {
+      return this._shadowOffsetX;
+    },
+    set: function(newOffset) {
+      if (!isFinite(newOffset)) {
+        return;
+      }
+      this._shadowOffsetX = newOffset;
+    }
+  });
+
+  Object.defineProperty(this, 'shadowOffsetY', {
+    enumerable: true,
+    get: function() {
+      return this._shadowOffsetY;
+    },
+    set: function(newOffset) {
+      if (!isFinite(newOffset)) {
+        return;
+      }
+      this._shadowOffsetY = newOffset;
+    }
+  });
+
+  Object.defineProperty(this, 'strokeStyle', {
+    enumerable: true,
+    get: function() {
+      return colorToString(this._strokeStyle);
+    },
+    set: function(newStyle) {
+      if (typeof newStyle === 'string') {
+        this._strokeStyle = parseColor(newStyle);
+      } else if (newStyle._getShader) {
+        // It's probably an effect.
+        this._strokeStyle = newStyle
+      }
+    }
+  });
+
+  this.arc = function(x, y, radius, startAngle, endAngle, ccw) {
+    arc(this._currentPath, x, y, radius, startAngle, endAngle, ccw);
+  }
+
+  this.arcTo = function(x1, y1, x2, y2, radius) {
+    arcTo(this._currentPath, x1, y1, x2, y2, radius);
+  }
+
+  // As per the spec this doesn't begin any paths, it only
+  // clears out any previous paths.
+  this.beginPath = function() {
+    this._currentPath.delete();
+    this._currentPath = new CanvasKit.SkPath();
+  }
+
+  this.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
+    bezierCurveTo(this._currentPath, cp1x, cp1y, cp2x, cp2y, x, y);
+  }
+
+  this.clearRect = function(x, y, width, height) {
+    this._paint.setStyle(CanvasKit.PaintStyle.Fill);
+    this._paint.setBlendMode(CanvasKit.BlendMode.Clear);
+    this._canvas.drawRect(CanvasKit.XYWHRect(x, y, width, height), this._paint);
+    this._paint.setBlendMode(this._globalCompositeOperation);
+  }
+
+  this.clip = function(path, fillRule) {
+    if (typeof path === 'string') {
+      // shift the args if a Path2D is supplied
+      fillRule = path;
+      path = this._currentPath;
+    } else if (path && path._getPath) {
+      path = path._getPath();
+    }
+    if (!path) {
+      path = this._currentPath;
+    }
+
+    var clip = path.copy();
+    if (fillRule && fillRule.toLowerCase() === 'evenodd') {
+      clip.setFillType(CanvasKit.FillType.EvenOdd);
+    } else {
+      clip.setFillType(CanvasKit.FillType.Winding);
+    }
+    this._canvas.clipPath(clip, CanvasKit.ClipOp.Intersect, true);
+    clip.delete();
+  }
+
+  this.closePath = function() {
+    closePath(this._currentPath);
+  }
+
+  this.createImageData = function() {
+    // either takes in 1 or 2 arguments:
+    //  - imagedata on which to copy *width* and *height* only
+    //  - width, height
+    if (arguments.length === 1) {
+      var oldData = arguments[0];
+      var byteLength = 4 * oldData.width * oldData.height;
+      return new ImageData(new Uint8ClampedArray(byteLength),
+                           oldData.width, oldData.height);
+    } else if (arguments.length === 2) {
+      var width = arguments[0];
+      var height = arguments[1];
+      var byteLength = 4 * width * height;
+      return new ImageData(new Uint8ClampedArray(byteLength),
+                           width, height);
+    } else {
+      throw 'createImageData expects 1 or 2 arguments, got '+arguments.length;
+    }
+  }
+
+  this.createLinearGradient = function(x1, y1, x2, y2) {
+    if (!allAreFinite(arguments)) {
+      return;
+    }
+    var lcg = new LinearCanvasGradient(x1, y1, x2, y2);
+    this._toCleanUp.push(lcg);
+    return lcg;
+  }
+
+  this.createPattern = function(image, repetition) {
+    var cp = new CanvasPattern(image, repetition);
+    this._toCleanUp.push(cp);
+    return cp;
+  }
+
+  this.createRadialGradient = function(x1, y1, r1, x2, y2, r2) {
+    if (!allAreFinite(arguments)) {
+      return;
+    }
+    var rcg = new RadialCanvasGradient(x1, y1, r1, x2, y2, r2);
+    this._toCleanUp.push(rcg);
+    return rcg;
+  }
+
+  this._imagePaint = function() {
+    var iPaint = this._fillPaint();
+    if (!this._imageSmoothingEnabled) {
+      iPaint.setFilterQuality(CanvasKit.FilterQuality.None);
+    } else {
+      iPaint.setFilterQuality(this._imageFilterQuality);
+    }
+    return iPaint;
+  }
+
+  this.drawImage = function(img) {
+    // 3 potential sets of arguments
+    // - image, dx, dy
+    // - image, dx, dy, dWidth, dHeight
+    // - image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight
+    // use the fillPaint, which has the globalAlpha in it
+    // which drawImageRect will use.
+    var iPaint = this._imagePaint();
+    if (arguments.length === 3 || arguments.length === 5) {
+      var destRect = CanvasKit.XYWHRect(arguments[1], arguments[2],
+                        arguments[3] || img.width(), arguments[4] || img.height());
+      var srcRect = CanvasKit.XYWHRect(0, 0, img.width(), img.height());
+    } else if (arguments.length === 9){
+      var destRect = CanvasKit.XYWHRect(arguments[5], arguments[6],
+                                        arguments[7], arguments[8]);
+      var srcRect = CanvasKit.XYWHRect(arguments[1], arguments[2],
+                                       arguments[3], arguments[4]);
+    } else {
+      throw 'invalid number of args for drawImage, need 3, 5, or 9; got '+ arguments.length;
+    }
+    this._canvas.drawImageRect(img, srcRect, destRect, iPaint, false);
+
+    iPaint.dispose();
+  }
+
+  this.ellipse = function(x, y, radiusX, radiusY, rotation,
+                          startAngle, endAngle, ccw) {
+    ellipse(this._currentPath, x, y, radiusX, radiusY, rotation,
+            startAngle, endAngle, ccw);
+  }
+
+  // A helper to copy the current paint, ready for filling
+  // This applies the global alpha.
+  // Call dispose() after to clean up.
+  this._fillPaint = function() {
+    var paint = this._paint.copy();
+    paint.setStyle(CanvasKit.PaintStyle.Fill);
+    if (Number.isInteger(this._fillStyle)) {
+      var alphaColor = CanvasKit.multiplyByAlpha(this._fillStyle, this._globalAlpha);
+      paint.setColor(alphaColor);
+    } else {
+      var shader = this._fillStyle._getShader(this._currentTransform);
+      paint.setColor(CanvasKit.Color(0,0,0, this._globalAlpha));
+      paint.setShader(shader);
+    }
+
+    paint.dispose = function() {
+      // If there are some helper effects in the future, clean them up
+      // here. In any case, we have .dispose() to make _fillPaint behave
+      // like _strokePaint and _shadowPaint.
+      this.delete();
+    }
+    return paint;
+  }
+
+  this.fill = function(path, fillRule) {
+    if (typeof path === 'string') {
+      // shift the args if a Path2D is supplied
+      fillRule = path;
+      path = this._currentPath;
+    } else if (path && path._getPath) {
+      path = path._getPath();
+    }
+    if (fillRule === 'evenodd') {
+      this._currentPath.setFillType(CanvasKit.FillType.EvenOdd);
+    } else if (fillRule === 'nonzero' || !fillRule) {
+      this._currentPath.setFillType(CanvasKit.FillType.Winding);
+    } else {
+      throw 'invalid fill rule';
+    }
+    if (!path) {
+      path = this._currentPath;
+    }
+
+    var fillPaint = this._fillPaint();
+
+    var shadowPaint = this._shadowPaint(fillPaint);
+    if (shadowPaint) {
+      this._canvas.save();
+      this._canvas.concat(this._shadowOffsetMatrix());
+      this._canvas.drawPath(path, shadowPaint);
+      this._canvas.restore();
+      shadowPaint.dispose();
+    }
+    this._canvas.drawPath(path, fillPaint);
+    fillPaint.dispose();
+  }
+
+  this.fillRect = function(x, y, width, height) {
+    var fillPaint = this._fillPaint();
+    this._canvas.drawRect(CanvasKit.XYWHRect(x, y, width, height), fillPaint);
+    fillPaint.dispose();
+  }
+
+  this.fillText = function(text, x, y, maxWidth) {
+    // TODO do something with maxWidth, probably involving measure
+    var fillPaint = this._fillPaint()
+    var shadowPaint = this._shadowPaint(fillPaint);
+    if (shadowPaint) {
+      this._canvas.save();
+      this._canvas.concat(this._shadowOffsetMatrix());
+      this._canvas.drawText(text, x, y, shadowPaint);
+      this._canvas.restore();
+      shadowPaint.dispose();
+    }
+    this._canvas.drawText(text, x, y, fillPaint);
+    fillPaint.dispose();
+  }
+
+  this.getImageData = function(x, y, w, h) {
+    var pixels = this._canvas.readPixels(x, y, w, h);
+    if (!pixels) {
+      return null;
+    }
+    // This essentially re-wraps the pixels from a Uint8Array to
+    // a Uint8ClampedArray (without making a copy of pixels).
+    return new ImageData(
+      new Uint8ClampedArray(pixels.buffer),
+      w, h);
+  }
+
+  this.getLineDash = function() {
+    return this._lineDashList.slice();
+  }
+
+  this._mapToLocalCoordinates = function(pts) {
+    var inverted = CanvasKit.SkMatrix.invert(this._currentTransform);
+    CanvasKit.SkMatrix.mapPoints(inverted, pts);
+    return pts;
+  }
+
+  this.isPointInPath = function(x, y, fillmode) {
+    var args = arguments;
+    if (args.length === 3) {
+      var path = this._currentPath;
+    } else if (args.length === 4) {
+      var path = args[0];
+      x = args[1];
+      y = args[2];
+      fillmode = args[3];
+    } else {
+      throw 'invalid arg count, need 3 or 4, got ' + args.length;
+    }
+    if (!isFinite(x) || !isFinite(y)) {
+      return false;
+    }
+    fillmode = fillmode || 'nonzero';
+    if (!(fillmode === 'nonzero' || fillmode === 'evenodd')) {
+      return false;
+    }
+    // x and y are in canvas coordinates (i.e. unaffected by CTM)
+    var pts = this._mapToLocalCoordinates([x, y]);
+    x = pts[0];
+    y = pts[1];
+    path.setFillType(fillmode === 'nonzero' ?
+                                  CanvasKit.FillType.Winding :
+                                  CanvasKit.FillType.EvenOdd);
+    return path.contains(x, y);
+  }
+
+  this.isPointInStroke = function(x, y) {
+    var args = arguments;
+    if (args.length === 2) {
+      var path = this._currentPath;
+    } else if (args.length === 3) {
+      var path = args[0];
+      x = args[1];
+      y = args[2];
+    } else {
+      throw 'invalid arg count, need 2 or 3, got ' + args.length;
+    }
+    if (!isFinite(x) || !isFinite(y)) {
+      return false;
+    }
+    var pts = this._mapToLocalCoordinates([x, y]);
+    x = pts[0];
+    y = pts[1];
+    var temp = path.copy();
+    // fillmode is always nonzero
+    temp.setFillType(CanvasKit.FillType.Winding);
+    temp.stroke({'width': this.lineWidth, 'miter_limit': this.miterLimit,
+                 'cap': this._paint.getStrokeCap(), 'join': this._paint.getStrokeJoin(),
+                 'precision': 0.3, // this is what Chrome uses to compute this
+                });
+    var retVal = temp.contains(x, y);
+    temp.delete();
+    return retVal;
+  }
+
+  this.lineTo = function(x, y) {
+    lineTo(this._currentPath, x, y);
+  }
+
+  this.measureText = function(text) {
+    return {
+      width: this._font.measureText(text),
+      // TODO other measurements?
+    }
+  }
+
+  this.moveTo = function(x, y) {
+    moveTo(this._currentPath, x, y);
+  }
+
+  this.putImageData = function(imageData, x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight) {
+    if (!allAreFinite([x, y, dirtyX, dirtyY, dirtyWidth, dirtyHeight])) {
+      return;
+    }
+    if (dirtyX === undefined) {
+      // fast, simple path for basic call
+      this._canvas.writePixels(imageData.data, imageData.width, imageData.height, x, y);
+      return;
+    }
+    dirtyX = dirtyX || 0;
+    dirtyY = dirtyY || 0;
+    dirtyWidth = dirtyWidth || imageData.width;
+    dirtyHeight = dirtyHeight || imageData.height;
+
+    // as per https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-putimagedata
+    if (dirtyWidth < 0) {
+      dirtyX = dirtyX+dirtyWidth;
+      dirtyWidth = Math.abs(dirtyWidth);
+    }
+    if (dirtyHeight < 0) {
+      dirtyY = dirtyY+dirtyHeight;
+      dirtyHeight = Math.abs(dirtyHeight);
+    }
+    if (dirtyX < 0) {
+      dirtyWidth = dirtyWidth + dirtyX;
+      dirtyX = 0;
+    }
+    if (dirtyY < 0) {
+      dirtyHeight = dirtyHeight + dirtyY;
+      dirtyY = 0;
+    }
+    if (dirtyWidth <= 0 || dirtyHeight <= 0) {
+      return;
+    }
+    var img = CanvasKit.MakeImage(imageData.data, imageData.width, imageData.height,
+                                  CanvasKit.AlphaType.Unpremul,
+                                  CanvasKit.ColorType.RGBA_8888);
+    var src = CanvasKit.XYWHRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight);
+    var dst = CanvasKit.XYWHRect(x+dirtyX, y+dirtyY, dirtyWidth, dirtyHeight);
+    var inverted = CanvasKit.SkMatrix.invert(this._currentTransform);
+    this._canvas.save();
+    // putImageData() operates in device space.
+    this._canvas.concat(inverted);
+    this._canvas.drawImageRect(img, src, dst, null, false);
+    this._canvas.restore();
+    img.delete();
+  }
+
+  this.quadraticCurveTo = function(cpx, cpy, x, y) {
+    quadraticCurveTo(this._currentPath, cpx, cpy, x, y);
+  }
+
+  this.rect = function(x, y, width, height) {
+    rect(this._currentPath, x, y, width, height);
+  }
+
+  this.resetTransform = function() {
+    // Apply the current transform to the path and then reset
+    // to the identity. Essentially "commit" the transform.
+    this._currentPath.transform(this._currentTransform);
+    var inverted = CanvasKit.SkMatrix.invert(this._currentTransform);
+    this._canvas.concat(inverted);
+    // This should be identity, modulo floating point drift.
+    this._currentTransform = this._canvas.getTotalMatrix();
+  }
+
+  this.restore = function() {
+    var newState = this._canvasStateStack.pop();
+    if (!newState) {
+      return;
+    }
+    // "commit" the current transform. We pop, then apply the inverse of the
+    // popped state, which has the effect of applying just the delta of
+    // transforms between old and new.
+    var combined = CanvasKit.SkMatrix.multiply(
+      this._currentTransform,
+      CanvasKit.SkMatrix.invert(newState.ctm)
+    );
+    this._currentPath.transform(combined);
+    this._paint.delete();
+    this._paint = newState.paint;
+
+    this._lineDashList = newState.ldl;
+    this._strokeWidth = newState.sw;
+    this._strokeStyle = newState.ss;
+    this._fillStyle = newState.fs;
+    this._shadowOffsetX = newState.sox;
+    this._shadowOffsetY = newState.soy;
+    this._shadowBlur = newState.sb;
+    this._shadowColor = newState.shc;
+    this._globalAlpha = newState.ga;
+    this._globalCompositeOperation = newState.gco;
+    this._lineDashOffset = newState.ldo;
+    this._imageSmoothingEnabled = newState.ise;
+    this._imageFilterQuality = newState.isq;
+    this._fontString = newState.fontstr;
+
+    //TODO: textAlign, textBaseline
+
+    // restores the clip and ctm
+    this._canvas.restore();
+    this._currentTransform = this._canvas.getTotalMatrix();
+  }
+
+  this.rotate = function(radians) {
+    if (!isFinite(radians)) {
+      return;
+    }
+    // retroactively apply the inverse of this transform to the previous
+    // path so it cancels out when we apply the transform at draw time.
+    var inverted = CanvasKit.SkMatrix.rotated(-radians);
+    this._currentPath.transform(inverted);
+    this._canvas.rotate(radiansToDegrees(radians), 0, 0);
+    this._currentTransform = this._canvas.getTotalMatrix();
+  }
+
+  this.save = function() {
+    if (this._fillStyle._copy) {
+      var fs = this._fillStyle._copy();
+      this._toCleanUp.push(fs);
+    } else {
+      var fs = this._fillStyle;
+    }
+
+    if (this._strokeStyle._copy) {
+      var ss = this._strokeStyle._copy();
+      this._toCleanUp.push(ss);
+    } else {
+      var ss = this._strokeStyle;
+    }
+
+    this._canvasStateStack.push({
+      ctm:     this._currentTransform.slice(),
+      ldl:     this._lineDashList.slice(),
+      sw:      this._strokeWidth,
+      ss:      ss,
+      fs:      fs,
+      sox:     this._shadowOffsetX,
+      soy:     this._shadowOffsetY,
+      sb:      this._shadowBlur,
+      shc:     this._shadowColor,
+      ga:      this._globalAlpha,
+      ldo:     this._lineDashOffset,
+      gco:     this._globalCompositeOperation,
+      ise:     this._imageSmoothingEnabled,
+      isq:     this._imageFilterQuality,
+      paint:   this._paint.copy(),
+      fontstr: this._fontString,
+      //TODO: textAlign, textBaseline
+    });
+    // Saves the clip
+    this._canvas.save();
+  }
+
+  this.scale = function(sx, sy) {
+    if (!allAreFinite(arguments)) {
+      return;
+    }
+    // retroactively apply the inverse of this transform to the previous
+    // path so it cancels out when we apply the transform at draw time.
+    var inverted = CanvasKit.SkMatrix.scaled(1/sx, 1/sy);
+    this._currentPath.transform(inverted);
+    this._canvas.scale(sx, sy);
+    this._currentTransform = this._canvas.getTotalMatrix();
+  }
+
+  this.setLineDash = function(dashes) {
+    for (var i = 0; i < dashes.length; i++) {
+      if (!isFinite(dashes[i]) || dashes[i] < 0) {
+        SkDebug('dash list must have positive, finite values');
+        return;
+      }
+    }
+    if (dashes.length % 2 === 1) {
+      // as per the spec, concatenate 2 copies of dashes
+      // to give it an even number of elements.
+      Array.prototype.push.apply(dashes, dashes);
+    }
+    this._lineDashList = dashes;
+  }
+
+  this.setTransform = function(a, b, c, d, e, f) {
+    if (!(allAreFinite(arguments))) {
+      return;
+    }
+    this.resetTransform();
+    this.transform(a, b, c, d, e, f);
+  }
+
+  // Returns the matrix representing the offset of the shadows. This unapplies
+  // the effects of the scale, which should not affect the shadow offsets.
+  this._shadowOffsetMatrix = function() {
+    var sx = this._currentTransform[0];
+    var sy = this._currentTransform[4];
+    return CanvasKit.SkMatrix.translated(this._shadowOffsetX/sx, this._shadowOffsetY/sy);
+  }
+
+  // Returns the shadow paint for the current settings or null if there
+  // should be no shadow. This ends up being a copy of the given
+  // paint with a blur maskfilter and the correct color.
+  this._shadowPaint = function(basePaint) {
+    // multiply first to see if the alpha channel goes to 0 after multiplication.
+    var alphaColor = CanvasKit.multiplyByAlpha(this._shadowColor, this._globalAlpha);
+    // if alpha is zero, no shadows
+    if (!CanvasKit.getColorComponents(alphaColor)[3]) {
+      return null;
+    }
+    // one of these must also be non-zero (otherwise the shadow is
+    // completely hidden.  And the spec says so).
+    if (!(this._shadowBlur || this._shadowOffsetY || this._shadowOffsetX)) {
+      return null;
+    }
+    var shadowPaint = basePaint.copy();
+    shadowPaint.setColor(alphaColor);
+    var blurEffect = CanvasKit.MakeBlurMaskFilter(CanvasKit.BlurStyle.Normal,
+      SkBlurRadiusToSigma(this._shadowBlur),
+      false);
+    shadowPaint.setMaskFilter(blurEffect);
+
+    // hack up a "destructor" which also cleans up the blurEffect. Otherwise,
+    // we leak the blurEffect (since smart pointers don't help us in JS land).
+    shadowPaint.dispose = function() {
+      blurEffect.delete();
+      this.delete();
+    };
+    return shadowPaint;
+  }
+
+  // A helper to get a copy of the current paint, ready for stroking.
+  // This applies the global alpha and the dashedness.
+  // Call dispose() after to clean up.
+  this._strokePaint = function() {
+    var paint = this._paint.copy();
+    paint.setStyle(CanvasKit.PaintStyle.Stroke);
+    if (Number.isInteger(this._strokeStyle)) {
+      var alphaColor = CanvasKit.multiplyByAlpha(this._strokeStyle, this._globalAlpha);
+      paint.setColor(alphaColor);
+    } else {
+      var shader = this._strokeStyle._getShader(this._currentTransform);
+      paint.setColor(CanvasKit.Color(0,0,0, this._globalAlpha));
+      paint.setShader(shader);
+    }
+
+    paint.setStrokeWidth(this._strokeWidth);
+
+    if (this._lineDashList.length) {
+      var dashedEffect = CanvasKit.MakeSkDashPathEffect(this._lineDashList, this._lineDashOffset);
+      paint.setPathEffect(dashedEffect);
+    }
+
+    paint.dispose = function() {
+      dashedEffect && dashedEffect.delete();
+      this.delete();
+    }
+    return paint;
+  }
+
+  this.stroke = function(path) {
+    path = path ? path._getPath() : this._currentPath;
+    var strokePaint = this._strokePaint();
+
+    var shadowPaint = this._shadowPaint(strokePaint);
+    if (shadowPaint) {
+      this._canvas.save();
+      this._canvas.concat(this._shadowOffsetMatrix());
+      this._canvas.drawPath(path, shadowPaint);
+      this._canvas.restore();
+      shadowPaint.dispose();
+    }
+
+    this._canvas.drawPath(path, strokePaint);
+    strokePaint.dispose();
+  }
+
+  this.strokeRect = function(x, y, width, height) {
+    var strokePaint = this._strokePaint();
+    this._canvas.drawRect(CanvasKit.XYWHRect(x, y, width, height), strokePaint);
+    strokePaint.dispose();
+  }
+
+  this.strokeText = function(text, x, y, maxWidth) {
+    // TODO do something with maxWidth, probably involving measure
+    var strokePaint = this._strokePaint();
+
+    var shadowPaint = this._shadowPaint(strokePaint);
+    if (shadowPaint) {
+      this._canvas.save();
+      this._canvas.concat(this._shadowOffsetMatrix());
+      this._canvas.drawText(text, x, y, shadowPaint);
+      this._canvas.restore();
+      shadowPaint.dispose();
+    }
+    this._canvas.drawText(text, x, y, strokePaint);
+    strokePaint.dispose();
+  }
+
+  this.translate = function(dx, dy) {
+    if (!allAreFinite(arguments)) {
+      return;
+    }
+    // retroactively apply the inverse of this transform to the previous
+    // path so it cancels out when we apply the transform at draw time.
+    var inverted = CanvasKit.SkMatrix.translated(-dx, -dy);
+    this._currentPath.transform(inverted);
+    this._canvas.translate(dx, dy);
+    this._currentTransform = this._canvas.getTotalMatrix();
+  }
+
+  this.transform = function(a, b, c, d, e, f) {
+    var newTransform = [a, c, e,
+                        b, d, f,
+                        0, 0, 1];
+    // retroactively apply the inverse of this transform to the previous
+    // path so it cancels out when we apply the transform at draw time.
+    var inverted = CanvasKit.SkMatrix.invert(newTransform);
+    this._currentPath.transform(inverted);
+    this._canvas.concat(newTransform);
+    this._currentTransform = this._canvas.getTotalMatrix();
+  }
+
+  // Not supported operations (e.g. for Web only)
+  this.addHitRegion = function() {};
+  this.clearHitRegions = function() {};
+  this.drawFocusIfNeeded = function() {};
+  this.removeHitRegion = function() {};
+  this.scrollPathIntoView = function() {};
+
+  Object.defineProperty(this, 'canvas', {
+    value: null,
+    writable: false
+  });
+}
+
+function SkBlurRadiusToSigma(radius) {
+  // Blink (Chrome) does the following, for legacy reasons, even though it
+  // is against the spec. https://bugs.chromium.org/p/chromium/issues/detail?id=179006
+  // This may change in future releases.
+  // This code is staying here in case any clients are interested in using it
+  // to match Blink "exactly".
+  // if (radius <= 0)
+  //   return 0;
+  // return 0.288675 * radius + 0.5;
+  //
+  // This is what the spec says, which is how Firefox and others operate.
+  return radius/2;
+}
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/color.js b/experimental/canvaskit/htmlcanvas/color.js
new file mode 100644
index 0000000..afbb8a1
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/color.js
@@ -0,0 +1,97 @@
+// Functions dealing with parsing/stringifying color go here.
+
+// Create the following with
+// node ./htmlcanvas/_namedcolors.js --expose-wasm
+// JS/closure doesn't have a constexpr like thing which
+// would really help here. Since we don't, we pre-compute
+// the map, which saves (a tiny amount of) startup time
+// and (a small amount of) code size.
+/* @dict */
+var colorMap = {'aliceblue':-984833,'antiquewhite':-332841,'aqua':-16711681,'aquamarine':-8388652,'azure':-983041,'beige':-657956,'bisque':-6972,'black':-16777216,'blanchedalmond':-5171,'blue':-16776961,'blueviolet':-7722014,'brown':-5952982,'burlywood':-2180985,'cadetblue':-10510688,'chartreuse':-8388864,'chocolate':-2987746,'coral':-32944,'cornflowerblue':-10185235,'cornsilk':-1828,'crimson':-2354116,'cyan':-16711681,'darkblue':-16777077,'darkcyan':-16741493,'darkgoldenrod':-4684277,'darkgray':-5658199,'darkgreen':-16751616,'darkgrey':-5658199,'darkkhaki':-4343957,'darkmagenta':-7667573,'darkolivegreen':-11179217,'darkorange':-29696,'darkorchid':-6737204,'darkred':-7667712,'darksalmon':-1468806,'darkseagreen':-7357297,'darkslateblue':-12042869,'darkslategray':-13676721,'darkslategrey':-13676721,'darkturquoise':-16724271,'darkviolet':-7077677,'deeppink':-60269,'deepskyblue':-16728065,'dimgray':-9868951,'dimgrey':-9868951,'dodgerblue':-14774017,'firebrick':-5103070,'floralwhite':-1296,'forestgreen':-14513374,'fuchsia':-65281,'gainsboro':-2302756,'ghostwhite':-460545,'gold':-10496,'goldenrod':-2448096,'gray':-8355712,'green':-16744448,'greenyellow':-5374161,'grey':-8355712,'honeydew':-983056,'hotpink':-38476,'indianred':-3318692,'indigo':-11861886,'ivory':-16,'khaki':-989556,'lavender':-1644806,'lavenderblush':-3851,'lawngreen':-8586240,'lemonchiffon':-1331,'lightblue':-5383962,'lightcoral':-1015680,'lightcyan':-2031617,'lightgoldenrodyellow':-329006,'lightgray':-2894893,'lightgreen':-7278960,'lightgrey':-2894893,'lightpink':-18751,'lightsalmon':-24454,'lightseagreen':-14634326,'lightskyblue':-7876870,'lightslategray':-8943463,'lightslategrey':-8943463,'lightsteelblue':-5192482,'lightyellow':-32,'lime':-16711936,'limegreen':-13447886,'linen':-331546,'magenta':-65281,'maroon':-8388608,'mediumaquamarine':-10039894,'mediumblue':-16777011,'mediumorchid':-4565549,'mediumpurple':-7114533,'mediumseagreen':-12799119,'mediumslateblue':-8689426,'mediumspringgreen':-16713062,'mediumturquoise':-12004916,'mediumvioletred':-3730043,'midnightblue':-15132304,'mintcream':-655366,'mistyrose':-6943,'moccasin':-6987,'navajowhite':-8531,'navy':-16777088,'oldlace':-133658,'olive':-8355840,'olivedrab':-9728477,'orange':-23296,'orangered':-47872,'orchid':-2461482,'palegoldenrod':-1120086,'palegreen':-6751336,'paleturquoise':-5247250,'palevioletred':-2396013,'papayawhip':-4139,'peachpuff':-9543,'peru':-3308225,'pink':-16181,'plum':-2252579,'powderblue':-5185306,'purple':-8388480,'rebeccapurple':-10079335,'red':-65536,'rosybrown':-4419697,'royalblue':-12490271,'saddlebrown':-7650029,'salmon':-360334,'sandybrown':-744352,'seagreen':-13726889,'seashell':-2578,'sienna':-6270419,'silver':-4144960,'skyblue':-7876885,'slateblue':-9807155,'slategray':-9404272,'slategrey':-9404272,'snow':-1286,'springgreen':-16711809,'steelblue':-12156236,'tan':-2968436,'teal':-16744320,'thistle':-2572328,'transparent':0,'tomato':-40121,'turquoise':-12525360,'violet':-1146130,'wheat':-663885,'white':-1,'whitesmoke':-657931,'yellow':-256,'yellowgreen':-6632142};
+
+function colorToString(skcolor) {
+  // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
+  var components = CanvasKit.getColorComponents(skcolor);
+  var r = components[0];
+  var g = components[1];
+  var b = components[2];
+  var a = components[3];
+  if (a === 1.0) {
+    // hex
+    r = r.toString(16).toLowerCase();
+    g = g.toString(16).toLowerCase();
+    b = b.toString(16).toLowerCase();
+    r = (r.length === 1 ? '0'+r: r);
+    g = (g.length === 1 ? '0'+g: g);
+    b = (b.length === 1 ? '0'+b: b);
+    return '#'+r+g+b;
+  } else {
+    a = (a === 0 || a === 1) ? a : a.toFixed(8);
+    return 'rgba('+r+', '+g+', '+b+', '+a+')';
+  }
+}
+
+function valueOrPercent(aStr) {
+  var a = parseFloat(aStr) || 1;
+  if (aStr && aStr.indexOf('%') !== -1) {
+    return a / 100;
+  }
+  return a;
+}
+
+function parseColor(colorStr) {
+  colorStr = colorStr.toLowerCase();
+  // See https://drafts.csswg.org/css-color/#typedef-hex-color
+  if (colorStr.startsWith('#')) {
+    var r, g, b, a = 255;
+    switch (colorStr.length) {
+      case 9: // 8 hex chars #RRGGBBAA
+        a = parseInt(colorStr.slice(7, 9), 16);
+      case 7: // 6 hex chars #RRGGBB
+        r = parseInt(colorStr.slice(1, 3), 16);
+        g = parseInt(colorStr.slice(3, 5), 16);
+        b = parseInt(colorStr.slice(5, 7), 16);
+        break;
+      case 5: // 4 hex chars #RGBA
+        // multiplying by 17 is the same effect as
+        // appending another character of the same value
+        // e.g. e => ee == 14 => 238
+        a = parseInt(colorStr.slice(4, 5), 16) * 17;
+      case 4: // 6 hex chars #RGB
+        r = parseInt(colorStr.slice(1, 2), 16) * 17;
+        g = parseInt(colorStr.slice(2, 3), 16) * 17;
+        b = parseInt(colorStr.slice(3, 4), 16) * 17;
+        break;
+    }
+    return CanvasKit.Color(r, g, b, a/255);
+
+  } else if (colorStr.startsWith('rgba')) {
+    // Trim off rgba( and the closing )
+    colorStr = colorStr.slice(5, -1);
+    var nums = colorStr.split(',');
+    return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
+                           valueOrPercent(nums[3]));
+  } else if (colorStr.startsWith('rgb')) {
+    // Trim off rgba( and the closing )
+    colorStr = colorStr.slice(4, -1);
+    var nums = colorStr.split(',');
+    // rgb can take 3 or 4 arguments
+    return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
+                           valueOrPercent(nums[3]));
+  } else if (colorStr.startsWith('gray(')) {
+    // TODO
+  } else if (colorStr.startsWith('hsl')) {
+    // TODO
+  } else {
+    // Try for named color
+    var nc = colorMap[colorStr];
+    if (nc !== undefined) {
+      return nc;
+    }
+  }
+  SkDebug('unrecognized color ' + colorStr);
+  return CanvasKit.BLACK;
+}
+
+CanvasKit._testing['parseColor'] = parseColor;
+CanvasKit._testing['colorToString'] = colorToString;
diff --git a/experimental/canvaskit/htmlcanvas/font.js b/experimental/canvaskit/htmlcanvas/font.js
new file mode 100644
index 0000000..340bd00
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/font.js
@@ -0,0 +1,113 @@
+// Functions dealing with parsing/stringifying fonts go here.
+var fontStringRegex = new RegExp(
+  '(italic|oblique|normal|)\\s*' +              // style
+  '(small-caps|normal|)\\s*' +                  // variant
+  '(bold|bolder|lighter|[1-9]00|normal|)\\s*' + // weight
+  '([\\d\\.]+)' +                               // size
+  '(px|pt|pc|in|cm|mm|%|em|ex|ch|rem|q)' +      // unit
+  // line-height is ignored here, as per the spec
+  '(.+)'                                        // family
+  );
+
+function stripWhitespace(str) {
+  return str.replace(/^\s+|\s+$/, '');
+}
+
+var defaultHeight = 16;
+// Based off of node-canvas's parseFont
+// returns font size in px, which represents the em width.
+function parseFontString(fontStr) {
+
+  var font = fontStringRegex.exec(fontStr);
+  if (!font) {
+    SkDebug('Invalid font string ' + fontStr);
+    return null;
+  }
+
+  var size = parseFloat(font[4]);
+  var sizePx = defaultHeight;
+  var unit = font[5];
+  switch (unit) {
+    case 'em':
+    case 'rem':
+      sizePx = size * defaultHeight;
+      break;
+    case 'pt':
+      sizePx = size * 4/3;
+      break;
+    case 'px':
+      sizePx = size;
+      break;
+    case 'pc':
+      sizePx = size * defaultHeight;
+      break;
+    case 'in':
+      sizePx = size * 96;
+      break;
+    case 'cm':
+      sizePx = size * 96.0 / 2.54;
+      break;
+    case 'mm':
+      sizePx = size * (96.0 / 25.4);
+      break;
+    case 'q': // quarter millimeters
+      sizePx = size * (96.0 / 25.4 / 4);
+      break;
+    case '%':
+      sizePx = size * (defaultHeight / 75);
+      break;
+  }
+  return {
+    'style':   font[1],
+    'variant': font[2],
+    'weight':  font[3],
+    'sizePx':  sizePx,
+    'family':  font[6].trim()
+  };
+}
+
+function getTypeface(fontstr) {
+  var descriptors = parseFontString(fontstr);
+  var typeface = getFromFontCache(descriptors);
+  descriptors['typeface'] = typeface;
+  return descriptors;
+}
+
+// null means use the default typeface (which is currently NotoMono)
+var fontCache = {
+  'Noto Mono': {
+    '*': null, // is used if we have this font family, but not the right style/variant/weight
+  },
+  'monospace': {
+    '*': null,
+  }
+};
+
+// descriptors is like https://developer.mozilla.org/en-US/docs/Web/API/FontFace/FontFace
+// The ones currently supported are family, style, variant, weight.
+function addToFontCache(typeface, descriptors) {
+  var key = (descriptors['style']   || 'normal') + '|' +
+            (descriptors['variant'] || 'normal') + '|' +
+            (descriptors['weight']  || 'normal');
+  var fam = descriptors['family'];
+  if (!fontCache[fam]) {
+    // preload with a fallback to this typeface
+    fontCache[fam] = {
+      '*': typeface,
+    };
+  }
+  fontCache[fam][key] = typeface;
+}
+
+function getFromFontCache(descriptors) {
+  var key = (descriptors['style']   || 'normal') + '|' +
+            (descriptors['variant'] || 'normal') + '|' +
+            (descriptors['weight']  || 'normal');
+  var fam = descriptors['family'];
+  if (!fontCache[fam]) {
+    return null;
+  }
+  return fontCache[fam][key] || fontCache[fam]['*'];
+}
+
+CanvasKit._testing['parseFontString'] = parseFontString;
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/htmlcanvas.js b/experimental/canvaskit/htmlcanvas/htmlcanvas.js
new file mode 100644
index 0000000..de9794f
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/htmlcanvas.js
@@ -0,0 +1,81 @@
+CanvasKit.MakeCanvas = function(width, height) {
+  var surf = CanvasKit.MakeSurface(width, height);
+  if (surf) {
+    return new HTMLCanvas(surf);
+  }
+  return null;
+}
+
+function HTMLCanvas(skSurface) {
+  this._surface = skSurface;
+  this._context = new CanvasRenderingContext2D(skSurface.getCanvas());
+  this._toCleanup = [];
+  this._fontmgr = CanvasKit.SkFontMgr.RefDefault();
+
+  // Data is either an ArrayBuffer, a TypedArray, or a Node Buffer
+  this.decodeImage = function(data) {
+    var img = CanvasKit.MakeImageFromEncoded(data);
+    if (!img) {
+      throw 'Invalid input';
+    }
+    this._toCleanup.push(img);
+    return img;
+  }
+
+  this.loadFont = function(buffer, descriptors) {
+    var newFont = this._fontmgr.MakeTypefaceFromData(buffer);
+    if (!newFont) {
+      SkDebug('font could not be processed', descriptors);
+      return null;
+    }
+    this._toCleanup.push(newFont);
+    addToFontCache(newFont, descriptors);
+  }
+
+  this.makePath2D = function(path) {
+    var p2d = new Path2D(path);
+    this._toCleanup.push(p2d._getPath());
+    return p2d;
+  }
+
+  // A normal <canvas> requires that clients call getContext
+  this.getContext = function(type) {
+    if (type === '2d') {
+      return this._context;
+    }
+    return null;
+  }
+
+  this.toDataURL = function(codec, quality) {
+    // TODO(kjlubick): maybe support other codecs (webp?)
+    // For now, just to png and jpeg
+    this._surface.flush();
+
+    var img = this._surface.makeImageSnapshot();
+    if (!img) {
+      SkDebug('no snapshot');
+      return;
+    }
+    var codec = codec || 'image/png';
+    var format = CanvasKit.ImageFormat.PNG;
+    if (codec === 'image/jpeg') {
+      format = CanvasKit.ImageFormat.JPEG;
+    }
+    var quality = quality || 0.92;
+    var skimg = img.encodeToData(format, quality);
+    if (!skimg) {
+      SkDebug('encoding failure');
+      return
+    }
+    var imgBytes = CanvasKit.getSkDataBytes(skimg);
+    return 'data:' + codec + ';base64,' + toBase64String(imgBytes);
+  }
+
+  this.dispose = function() {
+    this._context._dispose();
+    this._toCleanup.forEach(function(i) {
+      i.delete();
+    });
+    this._surface.dispose();
+  }
+}
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/imagedata.js b/experimental/canvaskit/htmlcanvas/imagedata.js
new file mode 100644
index 0000000..cfc4da8
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/imagedata.js
@@ -0,0 +1,52 @@
+function ImageData(arr, width, height) {
+  if (!width || height === 0) {
+    throw 'invalid dimensions, width and height must be non-zero';
+  }
+  if (arr.length % 4) {
+    throw 'arr must be a multiple of 4';
+  }
+  height = height || arr.length/(4*width);
+
+  Object.defineProperty(this, 'data', {
+    value: arr,
+    writable: false
+  });
+  Object.defineProperty(this, 'height', {
+    value: height,
+    writable: false
+  });
+  Object.defineProperty(this, 'width', {
+    value: width,
+    writable: false
+  });
+}
+
+CanvasKit.ImageData = function() {
+  if (arguments.length === 2) {
+    var width = arguments[0];
+    var height = arguments[1];
+    var byteLength = 4 * width * height;
+    return new ImageData(new Uint8ClampedArray(byteLength),
+                         width, height);
+  } else if (arguments.length === 3) {
+    var arr = arguments[0];
+    if (arr.prototype.constructor !== Uint8ClampedArray ) {
+      throw 'bytes must be given as a Uint8ClampedArray';
+    }
+    var width = arguments[1];
+    var height = arguments[2];
+    if (arr % 4) {
+      throw 'bytes must be given in a multiple of 4';
+    }
+    if (arr % width) {
+      throw 'bytes must divide evenly by width';
+    }
+    if (height && (height !== (arr / (width * 4)))) {
+      throw 'invalid height given';
+    }
+    height = arr / (width * 4);
+    return new ImageData(arr, width, height);
+  } else {
+    throw 'invalid number of arguments - takes 2 or 3, saw ' + arguments.length;
+  }
+}
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/lineargradient.js b/experimental/canvaskit/htmlcanvas/lineargradient.js
new file mode 100644
index 0000000..fc75fb3
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/lineargradient.js
@@ -0,0 +1,66 @@
+
+function LinearCanvasGradient(x1, y1, x2, y2) {
+  this._shader = null;
+  this._colors = [];
+  this._pos = [];
+
+  this.addColorStop = function(offset, color) {
+    if (offset < 0 || offset > 1 || !isFinite(offset)) {
+      throw 'offset must be between 0 and 1 inclusively';
+    }
+
+    color = parseColor(color);
+    // From the spec: If multiple stops are added at the same offset on a
+    // gradient, then they must be placed in the order added, with the first
+    // one closest to the start of the gradient, and each subsequent one
+    // infinitesimally further along towards the end point (in effect
+    // causing all but the first and last stop added at each point to be
+    // ignored).
+    // To implement that, if an offset is already in the list,
+    // we just overwrite its color (since the user can't remove Color stops
+    // after the fact).
+    var idx = this._pos.indexOf(offset);
+    if (idx !== -1) {
+      this._colors[idx] = color;
+    } else {
+      // insert it in sorted order
+      for (idx = 0; idx < this._pos.length; idx++) {
+        if (this._pos[idx] > offset) {
+          break;
+        }
+      }
+      this._pos   .splice(idx, 0, offset);
+      this._colors.splice(idx, 0, color);
+    }
+  }
+
+  this._copy = function() {
+    var lcg = new LinearCanvasGradient(x1, y1, x2, y2);
+    lcg._colors = this._colors.slice();
+    lcg._pos    = this._pos.slice();
+    return lcg;
+  }
+
+  this._dispose = function() {
+    if (this._shader) {
+      this._shader.delete();
+      this._shader = null;
+    }
+  }
+
+  this._getShader = function(currentTransform) {
+    // From the spec: "The points in the linear gradient must be transformed
+    // as described by the current transformation matrix when rendering."
+    var pts = [x1, y1, x2, y2];
+    CanvasKit.SkMatrix.mapPoints(currentTransform, pts);
+    var sx1 = pts[0];
+    var sy1 = pts[1];
+    var sx2 = pts[2];
+    var sy2 = pts[3];
+
+    this._dispose();
+    this._shader = CanvasKit.MakeLinearGradientShader([sx1, sy1], [sx2, sy2],
+      this._colors, this._pos, CanvasKit.TileMode.Clamp);
+    return this._shader;
+  }
+}
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/path2d.js b/experimental/canvaskit/htmlcanvas/path2d.js
new file mode 100644
index 0000000..dd9c682
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/path2d.js
@@ -0,0 +1,206 @@
+// CanvasPath methods, which all take an SkPath object as the first param
+
+function arc(skpath, x, y, radius, startAngle, endAngle, ccw) {
+  // As per  https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-arc
+  // arc is essentially a simpler version of ellipse.
+  ellipse(skpath, x, y, radius, radius, 0, startAngle, endAngle, ccw);
+}
+
+function arcTo(skpath, x1, y1, x2, y2, radius) {
+  if (!allAreFinite([x1, y1, x2, y2, radius])) {
+    return;
+  }
+  if (radius < 0) {
+    throw 'radii cannot be negative';
+  }
+  if (skpath.isEmpty()) {
+    skpath.moveTo(x1, y1);
+  }
+  skpath.arcTo(x1, y1, x2, y2, radius);
+}
+
+function bezierCurveTo(skpath, cp1x, cp1y, cp2x, cp2y, x, y) {
+  if (!allAreFinite([cp1x, cp1y, cp2x, cp2y, x, y])) {
+    return;
+  }
+  if (skpath.isEmpty()) {
+    skpath.moveTo(cp1x, cp1y);
+  }
+  skpath.cubicTo(cp1x, cp1y, cp2x, cp2y, x, y);
+}
+
+function closePath(skpath) {
+  if (skpath.isEmpty()) {
+    return;
+  }
+  // Check to see if we are not just a single point
+  var bounds = skpath.getBounds();
+  if ((bounds.fBottom - bounds.fTop) || (bounds.fRight - bounds.fLeft)) {
+    skpath.close();
+  }
+}
+
+function _ellipseHelper(skpath, x, y, radiusX, radiusY, startAngle, endAngle) {
+  var sweepDegrees = radiansToDegrees(endAngle - startAngle);
+  var startDegrees = radiansToDegrees(startAngle);
+
+  var oval = CanvasKit.LTRBRect(x - radiusX, y - radiusY, x + radiusX, y + radiusY);
+
+  // draw in 2 180 degree segments because trying to draw all 360 degrees at once
+  // draws nothing.
+  if (almostEqual(Math.abs(sweepDegrees), 360)) {
+    var halfSweep = sweepDegrees/2;
+    skpath.arcTo(oval, startDegrees, halfSweep, false);
+    skpath.arcTo(oval, startDegrees + halfSweep, halfSweep, false);
+    return;
+  }
+  skpath.arcTo(oval, startDegrees, sweepDegrees, false);
+}
+
+function ellipse(skpath, x, y, radiusX, radiusY, rotation,
+                 startAngle, endAngle, ccw) {
+  if (!allAreFinite([x, y, radiusX, radiusY, rotation, startAngle, endAngle])) {
+    return;
+  }
+  if (radiusX < 0 || radiusY < 0) {
+    throw 'radii cannot be negative';
+  }
+
+  // based off of CanonicalizeAngle in Chrome
+  var tao = 2 * Math.PI;
+  var newStartAngle = startAngle % tao;
+  if (newStartAngle < 0) {
+    newStartAngle += tao;
+  }
+  var delta = newStartAngle - startAngle;
+  startAngle = newStartAngle;
+  endAngle += delta;
+
+  // Based off of AdjustEndAngle in Chrome.
+  if (!ccw && (endAngle - startAngle) >= tao) {
+    // Draw complete ellipse
+    endAngle = startAngle + tao;
+  } else if (ccw && (startAngle - endAngle) >= tao) {
+    // Draw complete ellipse
+    endAngle = startAngle - tao;
+  } else if (!ccw && startAngle > endAngle) {
+    endAngle = startAngle + (tao - (startAngle - endAngle) % tao);
+  } else if (ccw && startAngle < endAngle) {
+    endAngle = startAngle - (tao - (endAngle - startAngle) % tao);
+  }
+
+  // Based off of Chrome's implementation in
+  // https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/graphics/path.cc
+  // of note, can't use addArc or addOval because they close the arc, which
+  // the spec says not to do (unless the user explicitly calls closePath).
+  // This throws off points being in/out of the arc.
+  if (!rotation) {
+    _ellipseHelper(skpath, x, y, radiusX, radiusY, startAngle, endAngle);
+    return;
+  }
+  var rotated = CanvasKit.SkMatrix.rotated(rotation, x, y);
+  var rotatedInvert = CanvasKit.SkMatrix.rotated(-rotation, x, y);
+  skpath.transform(rotatedInvert);
+  _ellipseHelper(skpath, x, y, radiusX, radiusY, startAngle, endAngle);
+  skpath.transform(rotated);
+}
+
+function lineTo(skpath, x, y) {
+  if (!allAreFinite([x, y])) {
+    return;
+  }
+  // A lineTo without a previous point has a moveTo inserted before it
+  if (skpath.isEmpty()) {
+    skpath.moveTo(x, y);
+  }
+  skpath.lineTo(x, y);
+}
+
+function moveTo(skpath, x, y) {
+  if (!allAreFinite([x, y])) {
+    return;
+  }
+  skpath.moveTo(x, y);
+}
+
+function quadraticCurveTo(skpath, cpx, cpy, x, y) {
+  if (!allAreFinite([cpx, cpy, x, y])) {
+    return;
+  }
+  if (skpath.isEmpty()) {
+    skpath.moveTo(cpx, cpy);
+  }
+  skpath.quadTo(cpx, cpy, x, y);
+}
+
+function rect(skpath, x, y, width, height) {
+  if (!allAreFinite([x, y, width, height])) {
+    return;
+  }
+  // https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-rect
+  skpath.addRect(x, y, x+width, y+height);
+}
+
+function Path2D(path) {
+  this._path = null;
+  if (typeof path === 'string') {
+      this._path = CanvasKit.MakePathFromSVGString(path);
+  } else if (path && path._getPath) {
+      this._path = path._getPath().copy();
+  } else {
+    this._path = new CanvasKit.SkPath();
+  }
+
+  this._getPath = function() {
+      return this._path;
+  }
+
+  this.addPath = function(path2d, transform) {
+    if (!transform) {
+      transform = {
+        'a': 1, 'c': 0, 'e': 0,
+        'b': 0, 'd': 1, 'f': 0,
+      };
+    }
+    this._path.addPath(path2d._getPath(), [transform.a, transform.c, transform.e,
+                                           transform.b, transform.d, transform.f]);
+  }
+
+  this.arc = function(x, y, radius, startAngle, endAngle, ccw) {
+    arc(this._path, x, y, radius, startAngle, endAngle, ccw);
+  }
+
+  this.arcTo = function(x1, y1, x2, y2, radius) {
+    arcTo(this._path, x1, y1, x2, y2, radius);
+  }
+
+  this.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
+    bezierCurveTo(this._path, cp1x, cp1y, cp2x, cp2y, x, y);
+  }
+
+  this.closePath = function() {
+    closePath(this._path);
+  }
+
+  this.ellipse = function(x, y, radiusX, radiusY, rotation,
+                          startAngle, endAngle, ccw) {
+    ellipse(this._path, x, y, radiusX, radiusY, rotation,
+            startAngle, endAngle, ccw);
+  }
+
+  this.lineTo = function(x, y) {
+    lineTo(this._path, x, y);
+  }
+
+  this.moveTo = function(x, y) {
+    moveTo(this._path, x, y);
+  }
+
+  this.quadraticCurveTo = function(cpx, cpy, x, y) {
+    quadraticCurveTo(this._path, cpx, cpy, x, y);
+  }
+
+  this.rect = function(x, y, width, height) {
+    rect(this._path, x, y, width, height);
+  }
+}
diff --git a/experimental/canvaskit/htmlcanvas/pattern.js b/experimental/canvaskit/htmlcanvas/pattern.js
new file mode 100644
index 0000000..66919871
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/pattern.js
@@ -0,0 +1,69 @@
+function CanvasPattern(image, repetition) {
+  this._shader = null;
+  // image should be an SkImage returned from HTMLCanvas.decodeImage()
+  this._image = image;
+  this._transform = CanvasKit.SkMatrix.identity();
+
+  if (repetition === '') {
+    repetition = 'repeat';
+  }
+  switch(repetition) {
+    case 'repeat-x':
+      this._tileX = CanvasKit.TileMode.Repeat;
+      // Skia's 'clamp' mode repeats the last row/column
+      // which looks very very strange.
+      // Decal mode does just transparent copying, which
+      // is exactly what the spec wants.
+      this._tileY = CanvasKit.TileMode.Decal;
+      break;
+    case 'repeat-y':
+      this._tileX = CanvasKit.TileMode.Decal;
+      this._tileY = CanvasKit.TileMode.Repeat;
+      break;
+    case 'repeat':
+      this._tileX = CanvasKit.TileMode.Repeat;
+      this._tileY = CanvasKit.TileMode.Repeat;
+      break;
+    case 'no-repeat':
+      this._tileX = CanvasKit.TileMode.Decal;
+      this._tileY = CanvasKit.TileMode.Decal;
+      break;
+    default:
+      throw 'invalid repetition mode ' + repetition;
+  }
+
+  // Takes a DOMMatrix like object. e.g. the identity would be:
+  // {a:1, b: 0, c: 0, d: 1, e: 0, f: 0}
+  // @param {DOMMatrix} m
+  this.setTransform = function(m) {
+    var t = [m.a, m.c, m.e,
+             m.b, m.d, m.f,
+               0,   0,   1];
+    if (allAreFinite(t)) {
+      this._transform = t;
+    }
+  }
+
+  this._copy = function() {
+    var cp = new CanvasPattern()
+    cp._tileX = this._tileX;
+    cp._tileY = this._tileY;
+    return cp;
+  }
+
+  this._dispose = function() {
+    if (this._shader) {
+      this._shader.delete();
+      this._shader = null;
+    }
+  }
+
+  this._getShader = function(currentTransform) {
+    // Ignore currentTransform since it will be applied later
+    this._dispose();
+    this._shader = CanvasKit.MakeImageShader(this._image, this._tileX, this._tileY,
+                                             false, this._transform);
+    return this._shader;
+  }
+
+}
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/postamble.js b/experimental/canvaskit/htmlcanvas/postamble.js
new file mode 100644
index 0000000..592e6c5
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/postamble.js
@@ -0,0 +1,2 @@
+// This closes the scope started in preamble.js
+}(Module)); // When this file is loaded in, the high level object is "Module";
diff --git a/experimental/canvaskit/htmlcanvas/preamble.js b/experimental/canvaskit/htmlcanvas/preamble.js
new file mode 100644
index 0000000..4ee1fe1
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/preamble.js
@@ -0,0 +1,17 @@
+// Adds compile-time JS functions to augment the CanvasKit interface.
+// Specifically, the code that emulates the HTML Canvas interface
+// (which is called HTMLCanvas or similar to avoid confusion with
+// SkCanvas).
+(function(CanvasKit) {
+
+  // This allows us to expose internal functions (e.g. color
+  // parsing) for unit-testing, even in the minified version.
+  // Our tests are not minified like CanvasKit is, so the names
+  // would get lost otherwise.
+  CanvasKit._testing = {};
+
+// This intentionally dangles because we want all the htmlcanvas
+// JS code to be in the same scope, but JS doesn't support
+// namespaces like C++ does. Thus, we simply include this
+// preamble.js file, all the source .js files and then postamble.js
+// to bundle everything in the same scope.
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/radialgradient.js b/experimental/canvaskit/htmlcanvas/radialgradient.js
new file mode 100644
index 0000000..431bbc5
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/radialgradient.js
@@ -0,0 +1,77 @@
+// Note, Skia has a different notion of a "radial" gradient.
+// Skia has a twoPointConical gradient that is the same as the
+// canvas's RadialGradient.
+
+function RadialCanvasGradient(x1, y1, r1, x2, y2, r2) {
+  this._shader = null;
+  this._colors = [];
+  this._pos = [];
+
+  this.addColorStop = function(offset, color) {
+    if (offset < 0 || offset > 1 || !isFinite(offset)) {
+      throw 'offset must be between 0 and 1 inclusively';
+    }
+
+    color = parseColor(color);
+    // From the spec: If multiple stops are added at the same offset on a
+    // gradient, then they must be placed in the order added, with the first
+    // one closest to the start of the gradient, and each subsequent one
+    // infinitesimally further along towards the end point (in effect
+    // causing all but the first and last stop added at each point to be
+    // ignored).
+    // To implement that, if an offset is already in the list,
+    // we just overwrite its color (since the user can't remove Color stops
+    // after the fact).
+    var idx = this._pos.indexOf(offset);
+    if (idx !== -1) {
+      this._colors[idx] = color;
+    } else {
+      // insert it in sorted order
+      for (idx = 0; idx < this._pos.length; idx++) {
+        if (this._pos[idx] > offset) {
+          break;
+        }
+      }
+      this._pos   .splice(idx, 0, offset);
+      this._colors.splice(idx, 0, color);
+    }
+  }
+
+  this._copy = function() {
+    var rcg = new RadialCanvasGradient(x1, y1, r1, x2, y2, r2);
+    rcg._colors = this._colors.slice();
+    rcg._pos    = this._pos.slice();
+    return rcg;
+  }
+
+  this._dispose = function() {
+    if (this._shader) {
+      this._shader.delete();
+      this._shader = null;
+    }
+  }
+
+  this._getShader = function(currentTransform) {
+    // From the spec: "The points in the linear gradient must be transformed
+    // as described by the current transformation matrix when rendering."
+    var pts = [x1, y1, x2, y2];
+    CanvasKit.SkMatrix.mapPoints(currentTransform, pts);
+    var sx1 = pts[0];
+    var sy1 = pts[1];
+    var sx2 = pts[2];
+    var sy2 = pts[3];
+
+    var sx = currentTransform[0];
+    var sy = currentTransform[4];
+    var scaleFactor = (Math.abs(sx) + Math.abs(sy))/2;
+
+    var sr1 = r1 * scaleFactor;
+    var sr2 = r2 * scaleFactor;
+
+    this._dispose();
+    this._shader = CanvasKit.MakeTwoPointConicalGradientShader(
+        [sx1, sy1], sr1, [sx2, sy2], sr2, this._colors, this._pos,
+        CanvasKit.TileMode.Clamp);
+    return this._shader;
+  }
+}
\ No newline at end of file
diff --git a/experimental/canvaskit/htmlcanvas/util.js b/experimental/canvaskit/htmlcanvas/util.js
new file mode 100644
index 0000000..4ea121d
--- /dev/null
+++ b/experimental/canvaskit/htmlcanvas/util.js
@@ -0,0 +1,35 @@
+
+// General purpose utility functions go in this file.
+
+
+function allAreFinite(args) {
+  for (var i = 0; i < args.length; i++) {
+    if (args[i] !== undefined && !Number.isFinite(args[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+function toBase64String(bytes) {
+  if (isNode) {
+    return Buffer.from(bytes).toString('base64');
+  } else {
+    // From https://stackoverflow.com/a/25644409
+    // because the naive solution of
+    //     btoa(String.fromCharCode.apply(null, bytes));
+    // would occasionally throw "Maximum call stack size exceeded"
+    var CHUNK_SIZE = 0x8000; //arbitrary number
+    var index = 0;
+    var length = bytes.length;
+    var result = '';
+    var slice;
+    while (index < length) {
+      slice = bytes.slice(index, Math.min(index + CHUNK_SIZE, length));
+      result += String.fromCharCode.apply(null, slice);
+      index += CHUNK_SIZE;
+    }
+    return btoa(result);
+  }
+}
+
diff --git a/experimental/canvaskit/interface.js b/experimental/canvaskit/interface.js
index e3d6b3a..fbee8f4 100644
--- a/experimental/canvaskit/interface.js
+++ b/experimental/canvaskit/interface.js
@@ -12,6 +12,10 @@
     // Add some helpers for matrices. This is ported from SkMatrix.cpp
     // to save complexity and overhead of going back and forth between
     // C++ and JS layers.
+    // I would have liked to use something like DOMMatrix, except it
+    // isn't widely supported (would need polyfills) and it doesn't
+    // have a mapPoints() function (which could maybe be tacked on here).
+    // If DOMMatrix catches on, it would be worth re-considering this usage.
     CanvasKit.SkMatrix = {};
     function sdot(a, b, c, d, e, f) {
       e = e || 0;
@@ -27,6 +31,22 @@
       ];
     };
 
+    // Return the inverse (if it exists) of this matrix.
+    // Otherwise, return the identity.
+    CanvasKit.SkMatrix.invert = function(m) {
+      var det = m[0]*m[4]*m[8] + m[1]*m[5]*m[6] + m[2]*m[3]*m[7]
+              - m[2]*m[4]*m[6] - m[1]*m[3]*m[8] - m[0]*m[5]*m[7];
+      if (!det) {
+        SkDebug('Warning, uninvertible matrix');
+        return CanvasKit.SkMatrix.identity();
+      }
+      return [
+        (m[4]*m[8] - m[5]*m[7])/det, (m[2]*m[7] - m[1]*m[8])/det, (m[1]*m[5] - m[2]*m[4])/det,
+        (m[5]*m[6] - m[3]*m[8])/det, (m[0]*m[8] - m[2]*m[6])/det, (m[2]*m[3] - m[0]*m[5])/det,
+        (m[3]*m[7] - m[4]*m[6])/det, (m[1]*m[6] - m[0]*m[7])/det, (m[0]*m[4] - m[1]*m[3])/det,
+      ];
+    };
+
     // Maps the given points according to the passed in matrix.
     // Results are done in place.
     // See SkMatrix.h::mapPoints for the docs on the math.
@@ -173,6 +193,41 @@
       return this;
     };
 
+    CanvasKit.SkPath.prototype.addRoundRect = function() {
+      // Takes 3, 4, 6 or 7 args
+      //  - SkRect, radii, ccw
+      //  - SkRect, rx, ry, ccw
+      //  - left, top, right, bottom, radii, ccw
+      //  - left, top, right, bottom, rx, ry, ccw
+      var args = arguments;
+      if (args.length === 3 || args.length === 6) {
+        var radii = args[args.length-2];
+      } else if (args.length === 6 || args.length === 7){
+        // duplicate the given (rx, ry) pairs for each corner.
+        var rx = args[args.length-3];
+        var ry = args[args.length-2];
+        var radii = [rx, ry, rx, ry, rx, ry, rx, ry];
+      } else {
+        SkDebug('addRoundRect expected to take 3, 4, 6, or 7 args. Got ' + args.length);
+        return null;
+      }
+      if (radii.length !== 8) {
+        SkDebug('addRoundRect needs 8 radii provided. Got ' + radii.length);
+        return null;
+      }
+      var rptr = copy1dArray(radii, CanvasKit.HEAPF32);
+      if (args.length === 3 || args.length === 4) {
+        var r = args[0];
+        var ccw = args[args.length - 1];
+        this._addRoundRect(r.fLeft, r.fTop, r.fRight, r.fBottom, rptr, ccw);
+      } else if (args.length === 6 || args.length === 7) {
+        var a = args;
+        this._addRoundRect(a[0], a[1], a[2], a[3], rptr, ccw);
+      }
+      CanvasKit._free(rptr);
+      return this;
+    };
+
     CanvasKit.SkPath.prototype.arc = function(x, y, radius, startAngle, endAngle, ccw) {
       // emulates the HTMLCanvas behavior.  See addArc() for the SkPath version.
       // Note input angles are radians.
@@ -185,8 +240,23 @@
       return this;
     };
 
-    CanvasKit.SkPath.prototype.arcTo = function(x1, y1, x2, y2, radius) {
-      this._arcTo(x1, y1, x2, y2, radius);
+    CanvasKit.SkPath.prototype.arcTo = function() {
+      // takes 4, 5 or 7 args
+      // - 5 x1, y1, x2, y2, radius
+      // - 4 oval (as Rect), startAngle, sweepAngle, forceMoveTo
+      // - 7 x1, y1, x2, y2, startAngle, sweepAngle, forceMoveTo
+      var args = arguments;
+      if (args.length === 5) {
+        this._arcTo(args[0], args[1], args[2], args[3], args[4]);
+      } else if (args.length === 4) {
+        this._arcTo(args[0], args[1], args[2], args[3]);
+      } else if (args.length === 7) {
+        this._arcTo(CanvasKit.LTRBRect(args[0], args[1], args[2], args[3]),
+                    args[4], args[5], args[6]);
+      } else {
+        throw 'Invalid args for arcTo. Expected 4, 5, or 7, got '+ args.length;
+      }
+
       return this;
     };
 
@@ -252,6 +322,7 @@
       opts.miter_limit = opts.miter_limit || 4;
       opts.cap = opts.cap || CanvasKit.StrokeCap.Butt;
       opts.join = opts.join || CanvasKit.StrokeJoin.Miter;
+      opts.precision = opts.precision || 1;
       if (this._stroke(opts)) {
         return this;
       }
@@ -299,7 +370,7 @@
     }
 
     CanvasKit.SkImage.prototype.encodeToData = function() {
-      if (arguments.length === 0) {
+      if (!arguments.length) {
         return this._encodeToData();
       }
 
@@ -311,6 +382,77 @@
       throw 'encodeToData expected to take 0 or 2 arguments. Got ' + arguments.length;
     }
 
+    // returns Uint8Array
+    CanvasKit.SkCanvas.prototype.readPixels = function(x, y, w, h, alphaType,
+                                                       colorType, dstRowBytes) {
+      // supply defaults (which are compatible with HTMLCanvas's getImageData)
+      alphaType = alphaType || CanvasKit.AlphaType.Unpremul;
+      colorType = colorType || CanvasKit.ColorType.RGBA_8888;
+      dstRowBytes = dstRowBytes || (4 * w);
+
+      var len = h * dstRowBytes
+      var pptr = CanvasKit._malloc(len);
+      var ok = this._readPixels({
+        'width': w,
+        'height': h,
+        'colorType': colorType,
+        'alphaType': alphaType,
+      }, pptr, dstRowBytes, x, y);
+      if (!ok) {
+        CanvasKit._free(pptr);
+        return null;
+      }
+
+      // The first typed array is just a view into memory. Because we will
+      // be free-ing that, we call slice to make a persistent copy.
+      var pixels = new Uint8Array(CanvasKit.HEAPU8.buffer, pptr, len).slice();
+      CanvasKit._free(pptr);
+      return pixels;
+    }
+
+    // pixels is a TypedArray. No matter the input size, it will be treated as
+    // a Uint8Array (essentially, a byte array).
+    CanvasKit.SkCanvas.prototype.writePixels = function(pixels, srcWidth, srcHeight,
+                                                        destX, destY, alphaType, colorType) {
+      if (pixels.byteLength % (srcWidth * srcHeight)) {
+        throw 'pixels length must be a multiple of the srcWidth * srcHeight';
+      }
+      var bytesPerPixel = pixels.byteLength / (srcWidth * srcHeight);
+      // supply defaults (which are compatible with HTMLCanvas's putImageData)
+      alphaType = alphaType || CanvasKit.AlphaType.Unpremul;
+      colorType = colorType || CanvasKit.ColorType.RGBA_8888;
+      var srcRowBytes = bytesPerPixel * srcWidth;
+
+      var pptr = CanvasKit._malloc(pixels.byteLength);
+      CanvasKit.HEAPU8.set(pixels, pptr);
+
+      var ok = this._writePixels({
+        'width': srcWidth,
+        'height': srcHeight,
+        'colorType': colorType,
+        'alphaType': alphaType,
+      }, pptr, srcRowBytes, destX, destY);
+
+      CanvasKit._free(pptr);
+      return ok;
+    }
+
+    // fontData should be an arrayBuffer
+    CanvasKit.SkFontMgr.prototype.MakeTypefaceFromData = function(fontData) {
+      var data = new Uint8Array(fontData);
+
+      var fptr = CanvasKit._malloc(data.byteLength);
+      CanvasKit.HEAPU8.set(data, fptr);
+      var font = this._makeTypefaceFromData(fptr, data.byteLength);
+      if (!font) {
+        SkDebug('Could not decode font data');
+        // We do not need to free the data since the C++ will do that for us
+        // when the font is deleted (or fails to decode);
+        return null;
+      }
+      return font;
+    }
+
     // Run through the JS files that are added at compile time.
     if (CanvasKit._extraInitializations) {
       CanvasKit._extraInitializations.forEach(function(init) {
@@ -399,6 +541,56 @@
     return ptr;
   }
 
+  // Caching the Float32Arrays can save having to reallocate them
+  // over and over again.
+  var Float32ArrayCache = {};
+
+  // Takes a 2D array of commands and puts them into the WASM heap
+  // as a 1D array. This allows them to referenced from the C++ code.
+  // Returns a 2 element array, with the first item being essentially a
+  // pointer to the array and the second item being the length of
+  // the new 1D array.
+  //
+  // Example usage:
+  // let cmds = [
+  //   [CanvasKit.MOVE_VERB, 0, 10],
+  //   [CanvasKit.LINE_VERB, 30, 40],
+  //   [CanvasKit.QUAD_VERB, 20, 50, 45, 60],
+  // ];
+  function loadCmdsTypedArray(arr) {
+    var len = 0;
+    for (var r = 0; r < arr.length; r++) {
+      len += arr[r].length;
+    }
+
+    var ta;
+    if (Float32ArrayCache[len]) {
+      ta = Float32ArrayCache[len];
+    } else {
+      ta = new Float32Array(len);
+      Float32ArrayCache[len] = ta;
+    }
+    // Flatten into a 1d array
+    var i = 0;
+    for (var r = 0; r < arr.length; r++) {
+      for (var c = 0; c < arr[r].length; c++) {
+        var item = arr[r][c];
+        ta[i] = item;
+        i++;
+      }
+    }
+
+    var ptr = copy1dArray(ta, CanvasKit.HEAPF32);
+    return [ptr, len];
+  }
+
+  CanvasKit.MakePathFromCmds = function(cmds) {
+    var ptrLen = loadCmdsTypedArray(cmds);
+    var path = CanvasKit._MakePathFromCmds(ptrLen[0], ptrLen[1]);
+    CanvasKit._free(ptrLen[0]);
+    return path;
+  }
+
   CanvasKit.MakeSkDashPathEffect = function(intervals, phase) {
     if (!phase) {
       phase = 0;
@@ -412,7 +604,7 @@
     return dpe;
   }
 
-  // data is a TypedArray or ArrayBuffer
+  // data is a TypedArray or ArrayBuffer e.g. from fetch().then(resp.arrayBuffer())
   CanvasKit.MakeImageFromEncoded = function(data) {
     data = new Uint8Array(data);
 
@@ -432,12 +624,37 @@
     return img;
   }
 
-  CanvasKit.MakeImageShader = function(imgData, xTileMode, yTileMode) {
-    var iptr = CanvasKit._malloc(imgData.byteLength);
-    CanvasKit.HEAPU8.set(new Uint8Array(imgData), iptr);
-    // No need to _free iptr, ImageShader takes it with SkData::MakeFromMalloc
+  // imgData is an Encoded SkImage, e.g. from MakeImageFromEncoded
+  CanvasKit.MakeImageShader = function(img, xTileMode, yTileMode, clampUnpremul, localMatrix) {
+    if (!img) {
+      return null;
+    }
+    clampUnpremul = clampUnpremul || false;
+    if (localMatrix) {
+      // Add perspective args if not provided.
+      if (localMatrix.length === 6) {
+        localMatrix.push(0, 0, 1);
+      }
+      return CanvasKit._MakeImageShader(img, xTileMode, yTileMode, clampUnpremul, localMatrix);
+    } else {
+      return CanvasKit._MakeImageShader(img, xTileMode, yTileMode, clampUnpremul);
+    }
+  }
 
-    return CanvasKit._MakeImageShader(iptr, imgData.byteLength, xTileMode, yTileMode);
+  // pixels is a Uint8Array
+  CanvasKit.MakeImage = function(pixels, width, height, alphaType, colorType) {
+    var bytesPerPixel = pixels.byteLength / (width * height);
+    var info = {
+      'width': width,
+      'height': height,
+      'alphaType': alphaType,
+      'colorType': colorType,
+    };
+    var pptr = CanvasKit._malloc(pixels.byteLength);
+    CanvasKit.HEAPU8.set(pixels, pptr);
+    // No need to _free iptr, Image takes it with SkData::MakeFromMalloc
+
+    return CanvasKit._MakeImage(info, pptr, pixels.byteLength, width * bytesPerPixel);
   }
 
   CanvasKit.MakeLinearGradientShader = function(start, end, colors, pos, mode, localMatrix, flags) {
@@ -537,16 +754,6 @@
     return vertices;
   }
 
-  CanvasKit.MakeNimaActor = function(nimaFile, nimaTexture) {
-    var nptr = CanvasKit._malloc(nimaFile.byteLength);
-    CanvasKit.HEAPU8.set(new Uint8Array(nimaFile), nptr);
-    var tptr = CanvasKit._malloc(nimaTexture.byteLength);
-    CanvasKit.HEAPU8.set(new Uint8Array(nimaTexture), tptr);
-    // No need to _free these ptrs, NimaActor takes them with SkData::MakeFromMalloc
-
-    return CanvasKit._MakeNimaActor(nptr, nimaFile.byteLength, tptr, nimaTexture.byteLength);
-  }
-
 }(Module)); // When this file is loaded in, the high level object is "Module";
 
 // Intentionally added outside the scope to allow usage in canvas2d.js and other
diff --git a/experimental/canvaskit/package.json b/experimental/canvaskit/package.json
index 3a0a862..33a4a86 100644
--- a/experimental/canvaskit/package.json
+++ b/experimental/canvaskit/package.json
@@ -14,8 +14,8 @@
     "requirejs": "~2.3.5"
   },
   "scripts": {
-    "test": "echo \"Error: no test specified\" && exit 1"
+    "test": "make test-continuous"
   },
   "author": "",
-  "license": "ISC"
+  "license": "BSD-3-Clause"
 }
diff --git a/experimental/canvaskit/perf/animation.bench.js b/experimental/canvaskit/perf/animation.bench.js
index 7c3a530..f3f463e 100644
--- a/experimental/canvaskit/perf/animation.bench.js
+++ b/experimental/canvaskit/perf/animation.bench.js
@@ -11,9 +11,8 @@
         } else {
             CanvasKitInit({
                 locateFile: (file) => '/canvaskit/'+file,
-            }).then((_CanvasKit) => {
+            }).ready().then((_CanvasKit) => {
                 CanvasKit = _CanvasKit;
-                CanvasKit.initFonts();
                 resolve();
             });
         }
diff --git a/experimental/canvaskit/ready.js b/experimental/canvaskit/ready.js
new file mode 100644
index 0000000..66f2a0e
--- /dev/null
+++ b/experimental/canvaskit/ready.js
@@ -0,0 +1,18 @@
+// See https://github.com/kripken/emscripten/issues/5820#issuecomment-385722568
+// for context on why the .then() that comes with Module breaks things (e.g. infinite loops)
+// and why the below fixes it.
+Module['ready'] = function() {
+  return new Promise(function (resolve, reject) {
+    delete Module['then'];
+    Module['onAbort'] = reject;
+    if (runtimeInitialized) {
+      resolve(Module);
+    } else {
+      addOnPostRun(function() {
+        resolve(Module);
+      });
+    }
+  });
+}
+// TODO(kjlubick): Shut .then() entirely off in 0.4.0 by uncommenting below.
+// delete Module['then'];
\ No newline at end of file
diff --git a/experimental/canvaskit/skottie_bindings.cpp b/experimental/canvaskit/skottie_bindings.cpp
new file mode 100644
index 0000000..66be7e0
--- /dev/null
+++ b/experimental/canvaskit/skottie_bindings.cpp
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCanvas.h"
+#include "SkMakeUnique.h"
+#include "SkTypes.h"
+#include "Skottie.h"
+
+#include <string>
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include "WasmAliases.h"
+
+#if SK_INCLUDE_MANAGED_SKOTTIE
+#include "SkottieProperty.h"
+#include "SkottieUtils.h"
+#endif // SK_INCLUDE_MANAGED_SKOTTIE
+
+using namespace emscripten;
+
+#if SK_INCLUDE_MANAGED_SKOTTIE
+namespace {
+
+class ManagedAnimation final : public SkRefCnt {
+public:
+    static sk_sp<ManagedAnimation> Make(const std::string& json) {
+        auto mgr = skstd::make_unique<skottie_utils::CustomPropertyManager>();
+        auto animation = skottie::Animation::Builder()
+                            .setMarkerObserver(mgr->getMarkerObserver())
+                            .setPropertyObserver(mgr->getPropertyObserver())
+                            .make(json.c_str(), json.size());
+
+        return animation
+            ? sk_sp<ManagedAnimation>(new ManagedAnimation(std::move(animation), std::move(mgr)))
+            : nullptr;
+    }
+
+    // skottie::Animation API
+    void render(SkCanvas* canvas) const { fAnimation->render(canvas, nullptr); }
+    void render(SkCanvas* canvas, const SkRect& dst) const { fAnimation->render(canvas, &dst); }
+    void seek(SkScalar t) { fAnimation->seek(t); }
+    SkScalar duration() const { return fAnimation->duration(); }
+    const SkSize&      size() const { return fAnimation->size(); }
+    std::string version() const { return std::string(fAnimation->version().c_str()); }
+
+    // CustomPropertyManager API
+    JSArray getColorProps() const {
+        JSArray props = emscripten::val::array();
+
+        for (const auto& cp : fPropMgr->getColorProps()) {
+            JSObject prop = emscripten::val::object();
+            prop.set("key", cp);
+            prop.set("value", fPropMgr->getColor(cp));
+            props.call<void>("push", prop);
+        }
+
+        return props;
+    }
+
+    JSArray getOpacityProps() const {
+        JSArray props = emscripten::val::array();
+
+        for (const auto& op : fPropMgr->getOpacityProps()) {
+            JSObject prop = emscripten::val::object();
+            prop.set("key", op);
+            prop.set("value", fPropMgr->getOpacity(op));
+            props.call<void>("push", prop);
+        }
+
+        return props;
+    }
+
+    bool setColor(const std::string& key, JSColor c) {
+        return fPropMgr->setColor(key, static_cast<SkColor>(c));
+    }
+
+    bool setOpacity(const std::string& key, float o) {
+        return fPropMgr->setOpacity(key, o);
+    }
+
+    JSArray getMarkers() const {
+        JSArray markers = emscripten::val::array();
+        for (const auto& m : fPropMgr->markers()) {
+            JSObject marker = emscripten::val::object();
+            marker.set("name", m.name);
+            marker.set("t0"  , m.t0);
+            marker.set("t1"  , m.t1);
+            markers.call<void>("push", marker);
+        }
+        return markers;
+    }
+
+private:
+    ManagedAnimation(sk_sp<skottie::Animation> animation,
+                     std::unique_ptr<skottie_utils::CustomPropertyManager> propMgr)
+        : fAnimation(std::move(animation))
+        , fPropMgr(std::move(propMgr)) {}
+
+    sk_sp<skottie::Animation>                             fAnimation;
+    std::unique_ptr<skottie_utils::CustomPropertyManager> fPropMgr;
+};
+
+} // anonymous ns
+#endif // SK_INCLUDE_MANAGED_SKOTTIE
+
+EMSCRIPTEN_BINDINGS(Skottie) {
+    // Animation things (may eventually go in own library)
+    class_<skottie::Animation>("Animation")
+        .smart_ptr<sk_sp<skottie::Animation>>("sk_sp<Animation>")
+        .function("version", optional_override([](skottie::Animation& self)->std::string {
+            return std::string(self.version().c_str());
+        }))
+        .function("size", &skottie::Animation::size)
+        .function("duration", &skottie::Animation::duration)
+        .function("seek", &skottie::Animation::seek)
+        .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas)->void {
+            self.render(canvas, nullptr);
+        }), allow_raw_pointers())
+        .function("render", optional_override([](skottie::Animation& self, SkCanvas* canvas,
+                                                 const SkRect r)->void {
+            self.render(canvas, &r);
+        }), allow_raw_pointers());
+
+    function("MakeAnimation", optional_override([](std::string json)->sk_sp<skottie::Animation> {
+        return skottie::Animation::Make(json.c_str(), json.length());
+    }));
+    constant("skottie", true);
+
+#if SK_INCLUDE_MANAGED_SKOTTIE
+    class_<ManagedAnimation>("ManagedAnimation")
+        .smart_ptr<sk_sp<ManagedAnimation>>("sk_sp<ManagedAnimation>")
+        .function("version"   , &ManagedAnimation::version)
+        .function("size"      , &ManagedAnimation::size)
+        .function("duration"  , &ManagedAnimation::duration)
+        .function("seek"      , &ManagedAnimation::seek)
+        .function("render"    , select_overload<void(SkCanvas*) const>(&ManagedAnimation::render), allow_raw_pointers())
+        .function("render"    , select_overload<void(SkCanvas*, const SkRect&) const>
+                                    (&ManagedAnimation::render), allow_raw_pointers())
+        .function("setColor"  , &ManagedAnimation::setColor)
+        .function("setOpacity", &ManagedAnimation::setOpacity)
+        .function("getMarkers", &ManagedAnimation::getMarkers)
+        .function("getColorProps"  , &ManagedAnimation::getColorProps)
+        .function("getOpacityProps", &ManagedAnimation::getOpacityProps);
+
+    function("MakeManagedAnimation", &ManagedAnimation::Make);
+    constant("managed_skottie", true);
+#endif // SK_INCLUDE_MANAGED_SKOTTIE
+}
diff --git a/experimental/canvaskit/tests/assets/Bungee-Regular.ttf b/experimental/canvaskit/tests/assets/Bungee-Regular.ttf
new file mode 100644
index 0000000..3229ee2
--- /dev/null
+++ b/experimental/canvaskit/tests/assets/Bungee-Regular.ttf
Binary files differ
diff --git a/experimental/canvaskit/tests/canvas2d.spec.js b/experimental/canvaskit/tests/canvas2d.spec.js
index 07a0ad8..7e6e43a 100644
--- a/experimental/canvaskit/tests/canvas2d.spec.js
+++ b/experimental/canvaskit/tests/canvas2d.spec.js
@@ -11,9 +11,8 @@
         } else {
             CanvasKitInit({
                 locateFile: (file) => '/canvaskit/'+file,
-            }).then((_CanvasKit) => {
+            }).ready().then((_CanvasKit) => {
                 CanvasKit = _CanvasKit;
-                CanvasKit.initFonts();
                 resolve();
             });
         }
@@ -130,6 +129,146 @@
         });
     }); // end describe('color string parsing')
 
+    describe('fonts', function() {
+        it('can parse font sizes', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                const parseFontString = CanvasKit._testing.parseFontString;
+
+                const tests = [{
+                        'input': '10px monospace',
+                        'output': {
+                            'style': '',
+                            'variant': '',
+                            'weight': '',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': '15pt Arial',
+                        'output': {
+                            'style': '',
+                            'variant': '',
+                            'weight': '',
+                            'sizePx': 20,
+                            'family': 'Arial',
+                        }
+                    },
+                    {
+                        'input': '1.5in Arial, san-serif ',
+                        'output': {
+                            'style': '',
+                            'variant': '',
+                            'weight': '',
+                            'sizePx': 144,
+                            'family': 'Arial, san-serif',
+                        }
+                    },
+                    {
+                        'input': '1.5em SuperFont',
+                        'output': {
+                            'style': '',
+                            'variant': '',
+                            'weight': '',
+                            'sizePx': 24,
+                            'family': 'SuperFont',
+                        }
+                    },
+                ];
+
+                for (let i = 0; i < tests.length; i++) {
+                    expect(parseFontString(tests[i].input)).toEqual(tests[i].output);
+                }
+
+                done();
+            }));
+        });
+
+        it('can parse font attributes', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                const parseFontString = CanvasKit._testing.parseFontString;
+
+                const tests = [{
+                        'input': 'bold 10px monospace',
+                        'output': {
+                            'style': '',
+                            'variant': '',
+                            'weight': 'bold',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'italic bold 10px monospace',
+                        'output': {
+                            'style': 'italic',
+                            'variant': '',
+                            'weight': 'bold',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'italic small-caps bold 10px monospace',
+                        'output': {
+                            'style': 'italic',
+                            'variant': 'small-caps',
+                            'weight': 'bold',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'small-caps bold 10px monospace',
+                        'output': {
+                            'style': '',
+                            'variant': 'small-caps',
+                            'weight': 'bold',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'italic 10px monospace',
+                        'output': {
+                            'style': 'italic',
+                            'variant': '',
+                            'weight': '',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'small-caps 10px monospace',
+                        'output': {
+                            'style': '',
+                            'variant': 'small-caps',
+                            'weight': '',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                    {
+                        'input': 'normal bold 10px monospace',
+                        'output': {
+                            'style': 'normal',
+                            'variant': '',
+                            'weight': 'bold',
+                            'sizePx': 10,
+                            'family': 'monospace',
+                        }
+                    },
+                ];
+
+                for (let i = 0; i < tests.length; i++) {
+                    expect(parseFontString(tests[i].input)).toEqual(tests[i].output);
+                }
+
+                done();
+            }));
+        });
+    });
+
     function multipleCanvasTest(testname, done, test) {
         const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
         skcanvas._config = 'software_canvas';
@@ -152,7 +291,7 @@
         }).catch(reportError(done));
     }
 
-    describe('Path drawing API', function() {
+    describe('CanvasContext2D API', function() {
         it('supports all the line types', function(done) {
             LoadCanvasKit.then(catchException(done, () => {
                 multipleCanvasTest('all_line_drawing_operations', done, (canvas) => {
@@ -187,6 +326,26 @@
 
                     ctx.lineWidth = 2;
                     ctx.stroke();
+
+                    // Test edgecases and draw direction
+                    ctx.beginPath();
+                    ctx.arc(50, 100, 10, Math.PI, -Math.PI/2);
+                    ctx.stroke();
+                    ctx.beginPath();
+                    ctx.arc(75, 100, 10, Math.PI, -Math.PI/2, true);
+                    ctx.stroke();
+                    ctx.beginPath();
+                    ctx.arc(100, 100, 10, Math.PI, 100.1 * Math.PI, true);
+                    ctx.stroke();
+                    ctx.beginPath();
+                    ctx.arc(125, 100, 10, Math.PI, 100.1 * Math.PI, false);
+                    ctx.stroke();
+                    ctx.beginPath();
+                    ctx.ellipse(155, 100, 10, 15, Math.PI/8, 100.1 * Math.PI, Math.PI, true);
+                    ctx.stroke();
+                    ctx.beginPath();
+                    ctx.ellipse(180, 100, 10, 15, Math.PI/8, Math.PI, 100.1 * Math.PI, true);
+                    ctx.stroke();
                 });
             }));
         });
@@ -215,14 +374,15 @@
                     ctx.rect(90, 10, 20, 20);
                     ctx.resetTransform();
 
+                    ctx.save();
                     ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
                     ctx.rect(110, 10, 20, 20);
                     ctx.lineTo(110, 0);
-                    ctx.resetTransform();
+                    ctx.restore();
                     ctx.lineTo(220, 120);
 
                     ctx.scale(3.0, 3.0);
-                    ctx.font = '6pt Arial';
+                    ctx.font = '6pt Noto Mono';
                     ctx.fillText('This text should be huge', 10, 80);
                     ctx.resetTransform();
 
@@ -246,46 +406,59 @@
         it('properly saves and restores states, even when drawing shadows', function(done) {
             LoadCanvasKit.then(catchException(done, () => {
                 multipleCanvasTest('shadows_and_save_restore', done, (canvas) => {
-                  let ctx = canvas.getContext('2d');
-                  ctx.strokeStyle = '#000';
-                  ctx.fillStyle = '#CCC';
-                  ctx.shadowColor = 'rebeccapurple';
-                  ctx.shadowBlur = 1;
-                  ctx.shadowOffsetX = 3;
-                  ctx.shadowOffsetY = -8;
-                  ctx.rect(10, 10, 30, 30);
+                    let ctx = canvas.getContext('2d');
+                    ctx.strokeStyle = '#000';
+                    ctx.fillStyle = '#CCC';
+                    ctx.shadowColor = 'rebeccapurple';
+                    ctx.shadowBlur = 1;
+                    ctx.shadowOffsetX = 3;
+                    ctx.shadowOffsetY = -8;
+                    ctx.rect(10, 10, 30, 30);
 
-                  ctx.save();
-                  ctx.strokeStyle = '#C00';
-                  ctx.fillStyle = '#00C';
-                  ctx.shadowBlur = 0;
-                  ctx.shadowColor = 'transparent';
+                    ctx.save();
+                    ctx.strokeStyle = '#C00';
+                    ctx.fillStyle = '#00C';
+                    ctx.shadowBlur = 0;
+                    ctx.shadowColor = 'transparent';
 
-                  ctx.stroke();
+                    ctx.stroke();
 
-                  ctx.restore();
-                  ctx.fill();
+                    ctx.restore();
+                    ctx.fill();
 
-                  ctx.beginPath();
-                  ctx.moveTo(36, 148);
-                  ctx.quadraticCurveTo(66, 188, 120, 136);
-                  ctx.closePath();
-                  ctx.stroke();
+                    ctx.beginPath();
+                    ctx.moveTo(36, 148);
+                    ctx.quadraticCurveTo(66, 188, 120, 136);
+                    ctx.closePath();
+                    ctx.stroke();
 
-                  ctx.beginPath();
-                  ctx.shadowColor = '#993366AA';
-                  ctx.shadowOffsetX = 8;
-                  ctx.shadowBlur = 5;
-                  ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
-                  ctx.rect(110, 10, 20, 20);
-                  ctx.lineTo(110, 0);
-                  ctx.resetTransform();
-                  ctx.lineTo(220, 120);
-                  ctx.stroke();
+                    ctx.beginPath();
+                    ctx.shadowColor = '#993366AA';
+                    ctx.shadowOffsetX = 8;
+                    ctx.shadowBlur = 5;
+                    ctx.setTransform(2, 0, -.5, 2.5, -40, 120);
+                    ctx.rect(110, 10, 20, 20);
+                    ctx.lineTo(110, 0);
+                    ctx.resetTransform();
+                    ctx.lineTo(220, 120);
+                    ctx.stroke();
 
-                  ctx.fillStyle = 'green';
-                  ctx.font = '16pt Arial';
-                  ctx.fillText('This should be shadowed', 20, 80);
+                    ctx.fillStyle = 'green';
+                    ctx.font = '16pt Noto Mono';
+                    ctx.fillText('This should be shadowed', 20, 80);
+
+                    ctx.beginPath();
+                    ctx.lineWidth = 6;
+                    ctx.ellipse(10, 290, 30, 30, 0, 0, Math.PI * 2);
+                    ctx.scale(2, 1);
+                    ctx.moveTo(10, 290)
+                    ctx.ellipse(10, 290, 30, 60, 0, 0, Math.PI * 2);
+                    ctx.resetTransform();
+                    ctx.shadowColor = '#993366AA';
+                    ctx.scale(3, 1);
+                    ctx.moveTo(10, 290)
+                    ctx.ellipse(10, 290, 30, 90, 0, 0, Math.PI * 2);
+                    ctx.stroke();
                 });
             }));
         });
@@ -334,7 +507,7 @@
                     ctx.lineTo(300, 400);
                     ctx.stroke();
 
-                    ctx.font = '36pt Arial';
+                    ctx.font = '36pt Noto Mono';
                     ctx.strokeText('Dashed', 20, 350);
                     ctx.fillText('Not Dashed', 20, 400);
                 });
@@ -419,6 +592,221 @@
             }));
         });
 
+        it('can get and put pixels', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                multipleCanvasTest('get_put_imagedata', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    // Make a gradient so we see if the pixels copying worked
+                    let grad = ctx.createLinearGradient(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
+                    grad.addColorStop(0, 'yellow');
+                    grad.addColorStop(1, 'red');
+                    ctx.fillStyle = grad;
+                    ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
+
+                    let iData = ctx.getImageData(400, 100, 200, 150);
+                    expect(iData.width).toBe(200);
+                    expect(iData.height).toBe(150);
+                    expect(iData.data.byteLength).toBe(200*150*4);
+                    ctx.putImageData(iData, 10, 10);
+                    ctx.putImageData(iData, 350, 350, 100, 75, 45, 40);
+                    ctx.strokeRect(350, 350, 200, 150);
+
+                    let box = ctx.createImageData(20, 40);
+                    ctx.putImageData(box, 10, 300);
+                    let biggerBox = ctx.createImageData(iData);
+                    ctx.putImageData(biggerBox, 10, 350);
+                    expect(biggerBox.width).toBe(iData.width);
+                    expect(biggerBox.height).toBe(iData.height);
+                });
+            }));
+        });
+
+        it('can make patterns', function(done) {
+            let skImageData = null;
+            let htmlImage = null;
+            let skPromise = fetch('/assets/mandrill_512.png')
+                .then((response) => response.arrayBuffer())
+                .then((buffer) => {
+                    skImageData = buffer;
+
+                });
+            let realPromise = fetch('/assets/mandrill_512.png')
+                .then((response) => response.blob())
+                .then((blob) => createImageBitmap(blob))
+                .then((bitmap) => {
+                    htmlImage = bitmap;
+                });
+            LoadCanvasKit.then(catchException(done, () => {
+                Promise.all([realPromise, skPromise]).then(() => {
+                    multipleCanvasTest('draw_patterns', done, (canvas) => {
+                        let ctx = canvas.getContext('2d');
+                        let img = htmlImage;
+                        if (canvas._config == 'software_canvas') {
+                            img = canvas.decodeImage(skImageData);
+                        }
+                        ctx.fillStyle = '#EEE';
+                        ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
+                        ctx.lineWidth = 20;
+                        ctx.scale(0.2, 0.4);
+
+                        let pattern = ctx.createPattern(img, 'repeat');
+                        ctx.fillStyle = pattern;
+                        ctx.fillRect(0, 0, 1500, 750);
+
+                        pattern = ctx.createPattern(img, 'repeat-x');
+                        ctx.fillStyle = pattern;
+                        ctx.fillRect(1500, 0, 3000, 750);
+
+                        ctx.globalAlpha = 0.7
+                        pattern = ctx.createPattern(img, 'repeat-y');
+                        ctx.fillStyle = pattern;
+                        ctx.fillRect(0, 750, 1500, 1500);
+                        ctx.strokeRect(0, 750, 1500, 1500);
+
+                        pattern = ctx.createPattern(img, 'no-repeat');
+                        ctx.fillStyle = pattern;
+                        pattern.setTransform({a: 1, b: -.1, c:.1, d: 0.5, e: 1800, f:800});
+                        ctx.fillRect(0, 0, 3000, 1500);
+                    });
+                });
+            }));
+        });
+
+        it('can get and put pixels', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                function drawPoint(ctx, x, y, color) {
+                    ctx.fillStyle = color;
+                    ctx.fillRect(x, y, 1, 1);
+                }
+                const IN = 'purple';
+                const OUT = 'orange';
+                const SCALE = 8;
+
+                // Check to see if these points are in or out on each of the
+                // test configurations.
+                const pts = [[3, 3], [4, 4], [5, 5], [10, 10], [8, 10], [6, 10],
+                             [6.5, 9], [15, 10], [17, 10], [17, 11], [24, 24],
+                             [25, 25], [26, 26], [27, 27]];
+                const tests = [
+                    {
+                        xOffset: 0,
+                        yOffset: 0,
+                        fillType: 'nonzero',
+                        strokeWidth: 0,
+                        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'nonzero'),
+                    },
+                    {
+                        xOffset: 30,
+                        yOffset: 0,
+                        fillType: 'evenodd',
+                        strokeWidth: 0,
+                        testFn: (ctx, x, y) => ctx.isPointInPath(x * SCALE, y * SCALE, 'evenodd'),
+                    },
+                    {
+                        xOffset: 0,
+                        yOffset: 30,
+                        fillType: null,
+                        strokeWidth: 1,
+                        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
+                    },
+                    {
+                        xOffset: 30,
+                        yOffset: 30,
+                        fillType: null,
+                        strokeWidth: 2,
+                        testFn: (ctx, x, y) => ctx.isPointInStroke(x * SCALE, y * SCALE),
+                    },
+                ];
+                multipleCanvasTest('points_in_path_stroke', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    ctx.font = '20px Noto Mono';
+                    // Draw some visual aids
+                    ctx.fillText('path-nonzero', 60, 30);
+                    ctx.fillText('path-evenodd', 300, 30);
+                    ctx.fillText('stroke-1px-wide', 60, 260);
+                    ctx.fillText('stroke-2px-wide', 300, 260);
+                    ctx.fillText('purple is IN, orange is OUT', 20, 560);
+
+                    // Scale up to make single pixels easier to see
+                    ctx.scale(SCALE, SCALE);
+                    for (let test of tests) {
+                        ctx.beginPath();
+                        let xOffset = test.xOffset;
+                        let yOffset = test.yOffset;
+
+                        ctx.fillStyle = '#AAA';
+                        ctx.lineWidth = test.strokeWidth;
+                        ctx.rect(5+xOffset, 5+yOffset, 20, 20);
+                        ctx.arc(15+xOffset, 15+yOffset, 8, 0, Math.PI*2, false);
+                        if (test.fillType) {
+                            ctx.fill(test.fillType);
+                        } else {
+                            ctx.stroke();
+                        }
+
+                        for (let pt of pts) {
+                            let [x, y] = pt;
+                            x += xOffset;
+                            y += yOffset;
+                            // naively apply transform when querying because the points queried
+                            // ignore the CTM.
+                            if (test.testFn(ctx, x, y)) {
+                              drawPoint(ctx, x, y, IN);
+                            } else {
+                              drawPoint(ctx, x, y, OUT);
+                            }
+                        }
+                    }
+                });
+            }));
+        });
+
+        it('can load custom fonts', function(done) {
+            let realFontLoaded = new FontFace('BungeeNonSystem', 'url(/assets/Bungee-Regular.ttf)', {
+                'family': 'BungeeNonSystem', //Make sure the canvas does not use the system font
+                'style': 'normal',
+                'weight': '400',
+            }).load().then((font) => {
+                document.fonts.add(font);
+            });
+
+            let fontBuffer = null;
+
+            let skFontLoaded = fetch('/assets/Bungee-Regular.ttf').then(
+                (response) => response.arrayBuffer()).then(
+                (buffer) => {
+                    fontBuffer = buffer;
+                });
+
+            LoadCanvasKit.then(catchException(done, () => {
+                Promise.all([realFontLoaded, skFontLoaded]).then(() => {
+                    multipleCanvasTest('custom_font', done, (canvas) => {
+                        if (canvas.loadFont) {
+                            canvas.loadFont(fontBuffer, {
+                                'family': 'BungeeNonSystem',
+                                'style': 'normal',
+                                'weight': '400',
+                            });
+                        }
+                        let ctx = canvas.getContext('2d');
+
+                        ctx.font = '20px monospace';
+                        ctx.fillText('20 px monospace', 10, 30);
+
+                        ctx.font = '2.0em BungeeNonSystem';
+                        ctx.fillText('2.0em Bungee filled', 10, 80);
+                        ctx.strokeText('2.0em Bungee stroked', 10, 130);
+
+                        ctx.font = '40pt monospace';
+                        ctx.strokeText('40pt monospace', 10, 200);
+
+                        // bold wasn't defined, so should fallback to just the 400 weight
+                        ctx.font = 'bold 45px BungeeNonSystem';
+                        ctx.fillText('45px Bungee filled', 10, 260);
+                    });
+                });
+            }));
+        });
         it('can read default properties', function(done) {
             LoadCanvasKit.then(catchException(done, () => {
                 const skcanvas = CanvasKit.MakeCanvas(CANVAS_WIDTH, CANVAS_HEIGHT);
@@ -428,6 +816,9 @@
 
                 const skcontext = skcanvas.getContext('2d');
                 const realContext = realCanvas.getContext('2d');
+                // The skia canvas only comes with a monospace font by default
+                // Set the html canvas to be monospace too.
+                realContext.font = '10px monospace';
 
                 const toTest = ['font', 'lineWidth', 'strokeStyle', 'lineCap',
                                 'lineJoin', 'miterLimit', 'shadowOffsetY',
@@ -446,7 +837,57 @@
                 done();
             }));
         });
-    }); // end describe('Path drawing API')
+    }); // end describe('CanvasContext2D API')
+
+    describe('Path2D API', function() {
+        it('supports all the line types', function(done) {
+            LoadCanvasKit.then(catchException(done, () => {
+                multipleCanvasTest('path2d_line_drawing_operations', done, (canvas) => {
+                    let ctx = canvas.getContext('2d');
+                    var clock;
+                    var path;
+                    if (canvas.makePath2D) {
+                        clock = canvas.makePath2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z');
+                        path = canvas.makePath2D();
+                    } else {
+                        clock = new Path2D('M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z')
+                        path = new Path2D();
+                    }
+                    path.moveTo(20, 5);
+                    path.lineTo(30, 20);
+                    path.lineTo(40, 10);
+                    path.lineTo(50, 20);
+                    path.lineTo(60, 0);
+                    path.lineTo(20, 5);
+
+                    path.moveTo(20, 80);
+                    path.bezierCurveTo(90, 10, 160, 150, 190, 10);
+
+                    path.moveTo(36, 148);
+                    path.quadraticCurveTo(66, 188, 120, 136);
+                    path.lineTo(36, 148);
+
+                    path.rect(5, 170, 20, 25);
+
+                    path.moveTo(150, 180);
+                    path.arcTo(150, 100, 50, 200, 20);
+                    path.lineTo(160, 160);
+
+                    path.moveTo(20, 120);
+                    path.arc(20, 120, 18, 0, 1.75 * Math.PI);
+                    path.lineTo(20, 120);
+
+                    path.moveTo(150, 5);
+                    path.ellipse(130, 25, 30, 10, -1*Math.PI/8, Math.PI/6, 1.5*Math.PI)
+
+                    ctx.lineWidth = 2;
+                    ctx.scale(3.0, 3.0);
+                    ctx.stroke(path);
+                    ctx.stroke(clock);
+                });
+            }));
+        });
+    }); // end describe('Path2D API')
 
 
 });
diff --git a/experimental/canvaskit/tests/path.spec.js b/experimental/canvaskit/tests/path.spec.js
index b056608..b073670 100644
--- a/experimental/canvaskit/tests/path.spec.js
+++ b/experimental/canvaskit/tests/path.spec.js
@@ -11,9 +11,8 @@
         } else {
             CanvasKitInit({
                 locateFile: (file) => '/canvaskit/'+file,
-            }).then((_CanvasKit) => {
+            }).ready().then((_CanvasKit) => {
                 CanvasKit = _CanvasKit;
-                CanvasKit.initFonts();
                 resolve();
             });
         }
@@ -39,20 +38,12 @@
         // data. So, we copy it out and draw it to a normal canvas to take a picture.
         // To be consistent across CPU and GPU, we just do it for all configurations
         // (even though the CPU canvas shows up after flush just fine).
-        let pixelLen = CANVAS_WIDTH * CANVAS_HEIGHT * 4; // 4 bytes for r,g,b,a
-        let pixelPtr = CanvasKit._malloc(pixelLen);
-        let success = surface._readPixels(CANVAS_WIDTH, CANVAS_HEIGHT, pixelPtr);
-        if (!success) {
-            done();
-            expect(success).toBeFalsy('could not read pixels');
-            return;
-        }
-        let pixels = new Uint8ClampedArray(CanvasKit.buffer, pixelPtr, pixelLen);
+        let pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
+        pixels = new Uint8ClampedArray(pixels.buffer);
         var imageData = new ImageData(pixels, CANVAS_WIDTH, CANVAS_HEIGHT);
 
         let reportingCanvas =  document.getElementById('report');
         reportingCanvas.getContext('2d').putImageData(imageData, 0, 0);
-        CanvasKit._free(pixelPtr);
         reportCanvas(reportingCanvas, testname).then(() => {
             done();
         }).catch(reportError(done));
@@ -101,6 +92,14 @@
                             0, 0, 1 ])
 
             canvas.drawPath(path, paint);
+
+            let rrect = new CanvasKit.SkPath()
+                               .addRoundRect(100, 10, 140, 62,
+                                             10, 4, true);
+
+            canvas.drawPath(rrect, paint);
+            rrect.delete();
+
             surface.flush();
 
             path.delete();
@@ -108,7 +107,44 @@
 
             reportSurface(surface, 'path_api_example', done);
         }));
-        // See CanvasKit for more tests, since they share implementation
+        // See PathKit for more tests, since they share implementation
+    });
+
+    it('can draw directly to a canvas', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            // This is taken from example.html
+            const surface = CanvasKit.MakeCanvasSurface('test');
+            expect(surface).toBeTruthy('Could not make surface')
+            if (!surface) {
+                done();
+                return;
+            }
+            const canvas = surface.getCanvas();
+            const paint = new CanvasKit.SkPaint();
+            paint.setStrokeWidth(2.0);
+            paint.setAntiAlias(true);
+            paint.setColor(CanvasKit.Color(0, 0, 0, 1.0));
+            paint.setStyle(CanvasKit.PaintStyle.Stroke);
+
+            canvas.drawLine(3, 10, 30, 15, paint);
+            canvas.drawRoundRect(CanvasKit.LTRBRect(5, 35, 45, 80), 15, 10, paint);
+
+            canvas.drawOval(CanvasKit.LTRBRect(5, 35, 45, 80), paint);
+
+            canvas.drawArc(CanvasKit.LTRBRect(55, 35, 95, 80), 15, 270, true, paint);
+
+            paint.setTextSize(20);
+            canvas.drawText('this is ascii text', 5, 100, paint);
+
+            canvas.drawText('Unicode chars 💩 é É Øµ', 5, 130, paint);
+
+            surface.flush();
+
+            paint.delete();
+
+            reportSurface(surface, 'canvas_api_example', done);
+        }));
+        // See canvas2d for more API tests
     });
 
     function starPath(CanvasKit, X=128, Y=128, R=116) {
@@ -158,4 +194,44 @@
             reportSurface(surface, 'effect_and_text_example', done);
         }));
     });
+
+    it('can create a path from an SVG string', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            //.This is a parallelagram from
+            // https://upload.wikimedia.org/wikipedia/commons/e/e7/Simple_parallelogram.svg
+            let path = CanvasKit.MakePathFromSVGString('M 205,5 L 795,5 L 595,295 L 5,295 L 205,5 z');
+
+            let cmds = path.toCmds();
+            expect(cmds).toBeTruthy();
+            // 1 move, 4 lines, 1 close
+            // each element in cmds is an array, with index 0 being the verb, and the rest being args
+            expect(cmds.length).toBe(6);
+            expect(cmds).toEqual([[CanvasKit.MOVE_VERB, 205, 5],
+                                  [CanvasKit.LINE_VERB, 795, 5],
+                                  [CanvasKit.LINE_VERB, 595, 295],
+                                  [CanvasKit.LINE_VERB, 5, 295],
+                                  [CanvasKit.LINE_VERB, 205, 5],
+                                  [CanvasKit.CLOSE_VERB]]);
+            path.delete();
+            done();
+        }));
+    });
+
+     it('can create an SVG string from a path', function(done) {
+        LoadCanvasKit.then(catchException(done, () => {
+            let cmds = [[CanvasKit.MOVE_VERB, 205, 5],
+                       [CanvasKit.LINE_VERB, 795, 5],
+                       [CanvasKit.LINE_VERB, 595, 295],
+                       [CanvasKit.LINE_VERB, 5, 295],
+                       [CanvasKit.LINE_VERB, 205, 5],
+                       [CanvasKit.CLOSE_VERB]];
+            let path = CanvasKit.MakePathFromCmds(cmds);
+
+            let svgStr = path.toSVGString();
+            // We output it in terse form, which is different than Wikipedia's version
+            expect(svgStr).toEqual('M205 5L795 5L595 295L5 295L205 5Z');
+            path.delete();
+            done();
+        }));
+    });
 });
diff --git a/experimental/documentation/gerrit.md b/experimental/documentation/gerrit.md
index 24cf0b6..10b79eb 100644
--- a/experimental/documentation/gerrit.md
+++ b/experimental/documentation/gerrit.md
@@ -67,6 +67,9 @@
 
     [Gerrit Upload Documentation](https://gerrit-review.googlesource.com/Documentation/user-upload.html)
 
+5.  Open in web browser:
+
+        bin/sysopen https://skia-review.googlesource.com/c/skia/+/$(bin/gerrit-number @)
 
 Updating a Change
 -----------------
@@ -99,7 +102,7 @@
 
 On your current branch, after uploading to gerrit:
 
-    git cl issue $(experimental/tools/gerrit-change-id-to-number @)
+    git cl issue $(bin/gerrit-number @)
 
 Now `git cl try` and `bin/try` will work correctly.
 
@@ -117,7 +120,7 @@
 
 Set the CL issue numnber:
 
-    git config alias.setcl '!git-cl issue $(experimental/tools/gerrit-change-id-to-number @)'
+    git config alias.setcl '!git-cl issue $(bin/gerrit-number @)'
 
 The following shell script will squash all commits on the current branch,
 assuming that the branch has an upstream topic branch.
diff --git a/experimental/tools/gerrit-change-id-to-number b/experimental/tools/gerrit-change-id-to-number
deleted file mode 100755
index f854757..0000000
--- a/experimental/tools/gerrit-change-id-to-number
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/usr/bin/env python2
-# Copyright 2017 Google Inc.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import httplib
-import json
-import re
-import subprocess
-import sys
-
-def retrieve_changeid(commit_or_branch):
-  b = subprocess.check_output(['git', 'log', '-1', '--format=%B', commit_or_branch])
-  r = re.compile(r'^Change-Id: (.*)$')
-  for l in b.split('\n'):
-    m = r.match(l)
-    if m:
-      return m.group(1)
-  return None
-
-def gerrit_change_id_to_number(cid):
-    conn = httplib.HTTPSConnection('skia-review.googlesource.com')
-    conn.request('GET', '/changes/?q=change:%s' % cid)
-    r = conn.getresponse()
-    assert(r.status == 200)
-    x = r.read()
-    i = 0
-    while i < len(x) and x[i] != '[':
-      i += 1
-    return json.loads(x[i:])[0]['_number']
-
-if __name__ == '__main__':
-  try:
-    if len(sys.argv) == 2 and len(sys.argv[1]) == 41 and sys.argv[1][0] == 'I':
-      gerrit_change_id_to_number(sys.argv[1])
-    else:
-      changeid = retrieve_changeid(sys.argv[1] if len(sys.argv) == 2 else 'HEAD')
-      if changeid is None:
-        exit(2)
-      sys.stdout.write('%d\n' % gerrit_change_id_to_number(changeid))
-  except:
-    exit(1)
diff --git a/fuzz/FuzzCanvas.cpp b/fuzz/FuzzCanvas.cpp
index 7ba1961..21918d9 100644
--- a/fuzz/FuzzCanvas.cpp
+++ b/fuzz/FuzzCanvas.cpp
@@ -974,11 +974,6 @@
     return array;
 }
 
-static SkTDArray<uint8_t> make_fuzz_text(Fuzz* fuzz, const SkPaint& paint) {
-    return make_fuzz_text(fuzz, SkFont::LEGACY_ExtractFromPaint(paint),
-                          (SkTextEncoding)paint.getTextEncoding());
-}
-
 static sk_sp<SkTextBlob> make_fuzz_textblob(Fuzz* fuzz) {
     SkTextBlobBuilder textBlobBuilder;
     int8_t runCount;
@@ -994,24 +989,26 @@
         const SkTextBlobBuilder::RunBuffer* buffer;
         uint8_t runType;
         fuzz->nextRange(&runType, (uint8_t)0, (uint8_t)2);
+        const void* textPtr = text.begin();
+        size_t textLen =  SkToSizeT(text.count());
         switch (runType) {
             case 0:
                 fuzz->next(&x, &y);
                 // TODO: Test other variations of this.
                 buffer = &textBlobBuilder.allocRun(font, glyphCount, x, y);
-                memcpy(buffer->glyphs, text.begin(), SkToSizeT(text.count()));
+                (void)font.textToGlyphs(textPtr, textLen, encoding, buffer->glyphs, glyphCount);
                 break;
             case 1:
                 fuzz->next(&y);
                 // TODO: Test other variations of this.
                 buffer = &textBlobBuilder.allocRunPosH(font, glyphCount, y);
-                memcpy(buffer->glyphs, text.begin(), SkToSizeT(text.count()));
+                (void)font.textToGlyphs(textPtr, textLen, encoding, buffer->glyphs, glyphCount);
                 fuzz->nextN(buffer->pos, glyphCount);
                 break;
             case 2:
                 // TODO: Test other variations of this.
                 buffer = &textBlobBuilder.allocRunPos(font, glyphCount);
-                memcpy(buffer->glyphs, text.begin(), SkToSizeT(text.count()));
+                (void)font.textToGlyphs(textPtr, textLen, encoding, buffer->glyphs, glyphCount);
                 fuzz->nextN(buffer->pos, glyphCount * 2);
                 break;
             default:
@@ -1475,59 +1472,14 @@
                                        font, paint);
                 break;
             }
-#ifdef SK_SUPPORT_LEGACY_FONTMETRICS_IN_PAINT
             case 46: {
-                fuzz_paint(fuzz, &paint, depth - 1);
-                font = fuzz_font(fuzz);
-                SkTextEncoding encoding = fuzz_paint_text_encoding(fuzz);
-                SkTDArray<uint8_t> text = make_fuzz_text(fuzz, font, encoding);
-                int glyphCount = font.countText(text.begin(), SkToSizeT(text.count()), encoding);
-                if (glyphCount < 1) {
-                    break;
-                }
-                SkAutoTMalloc<SkPoint> pos(glyphCount);
-                SkAutoTMalloc<SkScalar> widths(glyphCount);
-                font.LEGACY_applyToPaint(&paint);
-                paint.setTextEncoding(encoding);
-                paint.getTextWidths(text.begin(), SkToSizeT(text.count()), widths.get());
-                pos[0] = {0, 0};
-                for (int i = 1; i < glyphCount; ++i) {
-                    float y;
-                    fuzz->nextRange(&y, -0.5f * paint.getTextSize(), 0.5f * paint.getTextSize());
-                    pos[i] = {pos[i - 1].x() + widths[i - 1], y};
-                }
-                canvas->drawPosText(text.begin(), SkToSizeT(text.count()), pos.get(), paint);
+                // was drawPosText
                 break;
             }
             case 47: {
-                fuzz_paint(fuzz, &paint, depth - 1);
-                font = fuzz_font(fuzz);
-                SkTextEncoding encoding = fuzz_paint_text_encoding(fuzz);
-                SkTDArray<uint8_t> text = make_fuzz_text(fuzz, font, encoding);
-                int glyphCount = font.countText(text.begin(), SkToSizeT(text.count()), encoding);
-                SkAutoTMalloc<SkScalar> widths(glyphCount);
-                if (glyphCount < 1) {
-                    break;
-                }
-                font.LEGACY_applyToPaint(&paint);
-                paint.setTextEncoding(encoding);
-                paint.getTextWidths(text.begin(), SkToSizeT(text.count()), widths.get());
-                SkScalar x = widths[0];
-                for (int i = 0; i < glyphCount; ++i) {
-                    using std::swap;
-                    swap(x, widths[i]);
-                    x += widths[i];
-                    SkScalar offset;
-                    fuzz->nextRange(&offset, -0.125f * paint.getTextSize(),
-                                    0.125f * paint.getTextSize());
-                    widths[i] += offset;
-                }
-                SkScalar y;
-                fuzz->next(&y);
-                canvas->drawPosTextH(text.begin(), SkToSizeT(text.count()), widths.get(), y, paint);
+                // was drawPosTextH
                 break;
             }
-#endif
             case 48: {
                 // was drawtextonpath
                 break;
@@ -1536,29 +1488,10 @@
                 // was drawtextonpath
                 break;
             }
-#ifdef SK_SUPPORT_LEGACY_FONTMETRICS_IN_PAINT
             case 50: {
-                fuzz_paint(fuzz, &paint, depth - 1);
-                font = fuzz_font(fuzz);
-                SkTextEncoding encoding = fuzz_paint_text_encoding(fuzz);
-                SkTDArray<uint8_t> text = make_fuzz_text(fuzz, paint);
-                SkRSXform rSXform[kMaxGlyphCount];
-                int glyphCount = font.countText(text.begin(), SkToSizeT(text.count()), encoding);
-                SkASSERT(glyphCount <= kMaxGlyphCount);
-                fuzz->nextN(rSXform, glyphCount);
-                SkRect cullRect;
-                bool useCullRect;
-                fuzz->next(&useCullRect);
-                if (useCullRect) {
-                    fuzz->next(&cullRect);
-                }
-                font.LEGACY_applyToPaint(&paint);
-                paint.setTextEncoding(encoding);
-                canvas->drawTextRSXform(text.begin(), SkToSizeT(text.count()), rSXform,
-                                        useCullRect ? &cullRect : nullptr, paint);
+                // was drawTextRSXform
                 break;
             }
-#endif
             case 51: {
                 sk_sp<SkTextBlob> blob = make_fuzz_textblob(fuzz);
                 fuzz_paint(fuzz, &paint, depth - 1);
diff --git a/fuzz/FuzzCommon.cpp b/fuzz/FuzzCommon.cpp
index 695ef74..79ffdee 100644
--- a/fuzz/FuzzCommon.cpp
+++ b/fuzz/FuzzCommon.cpp
@@ -29,7 +29,7 @@
 
 // allows some float values for path points
 void FuzzNicePath(Fuzz* fuzz, SkPath* path, int maxOps) {
-    if (maxOps <= 0) {
+    if (maxOps <= 0 || fuzz->exhausted() || path->countPoints() > 100000) {
         return;
     }
     uint8_t fillType;
@@ -38,8 +38,15 @@
     uint8_t numOps;
     fuzz->nextRange(&numOps, 0, maxOps);
     for (uint8_t i = 0; i < numOps; ++i) {
+        // When we start adding the path to itself, the fuzzer can make an
+        // exponentially long path, which causes timeouts.
+        if (path->countPoints() > 100000) {
+            return;
+        }
+        // How many items in the switch statement below.
+        constexpr uint8_t PATH_OPERATIONS = 32;
         uint8_t op;
-        fuzz->nextRange(&op, 0, 32);
+        fuzz->nextRange(&op, 0, PATH_OPERATIONS);
         bool test;
         SkPath p;
         SkMatrix m;
@@ -205,7 +212,7 @@
                 fuzz_nice_float(fuzz, &a, &b);
                 path->setLastPt(a, b);
                 break;
-            case 32:
+            case PATH_OPERATIONS:
                 path->shrinkToFit();
                 break;
 
@@ -214,7 +221,6 @@
                 break;
         }
         SkASSERTF(       path->isValid(),        "path->isValid() failed at op %d, case %d", i, op);
-        SkASSERTF(path->pathRefIsValid(), "path->pathRefIsValid() failed at op %d, case %d", i, op);
     }
 }
 
diff --git a/fuzz/FuzzDrawFunctions.cpp b/fuzz/FuzzDrawFunctions.cpp
index f9a2f2e..083c45a 100644
--- a/fuzz/FuzzDrawFunctions.cpp
+++ b/fuzz/FuzzDrawFunctions.cpp
@@ -8,9 +8,11 @@
 #include "Fuzz.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkImage.h"
 #include "SkPath.h"
 #include "SkSurface.h"
+#include "SkTextBlob.h"
 #include "SkTypeface.h"
 #include "SkClipOpPriv.h"
 
@@ -47,9 +49,6 @@
     fuzz->nextRange(&tmp_u8, 0, (int)kHigh_SkFilterQuality);
     p->setFilterQuality(static_cast<SkFilterQuality>(tmp_u8));
 
-    fuzz->nextRange(&tmp_u8, 0, (int)SkFontHinting::kFull);
-    p->setHinting(static_cast<SkFontHinting>(tmp_u8));
-
     fuzz->nextRange(&tmp_u8, 0, (int)SkPaint::kLast_Cap);
     p->setStrokeCap(static_cast<SkPaint::Cap>(tmp_u8));
 
@@ -111,7 +110,8 @@
 }
 
 
-static void fuzz_drawText(Fuzz* fuzz, sk_sp<SkTypeface> font) {
+static void fuzz_drawText(Fuzz* fuzz, sk_sp<SkTypeface> typeface) {
+    SkFont font(typeface);
     SkPaint p;
     init_paint(fuzz, &p);
     sk_sp<SkSurface> surface;
@@ -126,37 +126,33 @@
     SkPoint pts[kPtsLen];
     for (uint8_t i = 0; i < kPtsLen; ++i) {
         pts[i].set(x, y);
-        x += p.getTextSize();
+        x += font.getSize();
     }
 
-    p.setTypeface(font);
-    // set text related attributes
     bool b;
     fuzz->next(&b);
-    p.setAutohinted(b);
+    font.setForceAutoHinting(b);
     fuzz->next(&b);
-    p.setEmbeddedBitmapText(b);
+    font.setEmbeddedBitmaps(b);
     fuzz->next(&b);
-    p.setFakeBoldText(b);
+    font.setEmbolden(b);
     fuzz->next(&b);
-    p.setLCDRenderText(b);
+    font.setEdging(b ? SkFont::Edging::kAntiAlias : SkFont::Edging::kSubpixelAntiAlias);
     fuzz->next(&b);
-    p.setLinearText(b);
+    font.setLinearMetrics(b);
     fuzz->next(&b);
-    p.setSubpixelText(b);
+    font.setSubpixel(b);
     fuzz->next(&x);
-    p.setTextScaleX(x);
+    font.setScaleX(x);
     fuzz->next(&x);
-    p.setTextSkewX(x);
+    font.setSkewX(x);
     fuzz->next(&x);
-    p.setTextSize(x);
+    font.setSize(x);
 
     SkCanvas* cnv = surface->getCanvas();
-    cnv->drawPosText(text, (kTxtLen-1), pts, p);
-
     fuzz->next(&x);
     fuzz->next(&y);
-    cnv->drawText(text, (kTxtLen-1), x, y, p);
+    cnv->drawTextBlob(SkTextBlob::MakeFromPosText(text, kTxtLen-1, pts, font), x, y, p);
 }
 
 static void fuzz_drawCircle(Fuzz* fuzz) {
diff --git a/fuzz/oss_fuzz/FuzzImageFilterDeserialize.cpp b/fuzz/oss_fuzz/FuzzImageFilterDeserialize.cpp
index f9d9598..7ae7870 100644
--- a/fuzz/oss_fuzz/FuzzImageFilterDeserialize.cpp
+++ b/fuzz/oss_fuzz/FuzzImageFilterDeserialize.cpp
@@ -9,8 +9,10 @@
 #include "SkBitmap.h"
 #include "SkCanvas.h"
 #include "SkData.h"
+#include "SkFontMgrPriv.h"
 #include "SkImageFilter.h"
 #include "SkPaint.h"
+#include "SkTestFontMgr.h"
 
 void FuzzImageFilterDeserialize(sk_sp<SkData> bytes) {
     const int BitmapSize = 24;
@@ -39,6 +41,7 @@
 
 #if defined(IS_FUZZING_WITH_LIBFUZZER)
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
     auto bytes = SkData::MakeWithoutCopy(data, size);
     FuzzImageFilterDeserialize(bytes);
     return 0;
diff --git a/fuzz/oss_fuzz/FuzzMockGPUCanvas.cpp b/fuzz/oss_fuzz/FuzzMockGPUCanvas.cpp
index 539e989..5a6f389 100644
--- a/fuzz/oss_fuzz/FuzzMockGPUCanvas.cpp
+++ b/fuzz/oss_fuzz/FuzzMockGPUCanvas.cpp
@@ -6,6 +6,8 @@
  */
 
 #include "../Fuzz.h"
+#include "SkTestFontMgr.h"
+#include "SkFontMgrPriv.h"
 
 void fuzz_MockGPUCanvas(Fuzz* f);
 
@@ -18,6 +20,7 @@
     }
 
     int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+        gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
         auto fuzz = Fuzz(SkData::MakeWithoutCopy(data, size));
         fuzz_MockGPUCanvas(&fuzz);
         return 0;
diff --git a/fuzz/oss_fuzz/FuzzNullCanvas.cpp b/fuzz/oss_fuzz/FuzzNullCanvas.cpp
index 37f3288..2363af8 100644
--- a/fuzz/oss_fuzz/FuzzNullCanvas.cpp
+++ b/fuzz/oss_fuzz/FuzzNullCanvas.cpp
@@ -6,10 +6,13 @@
  */
 
 #include "../Fuzz.h"
+#include "SkTestFontMgr.h"
+#include "SkFontMgrPriv.h"
 
 void fuzz_NullCanvas(Fuzz* f);
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
     auto fuzz = Fuzz(SkData::MakeWithoutCopy(data, size));
     fuzz_NullCanvas(&fuzz);
     return 0;
diff --git a/fuzz/oss_fuzz/FuzzRasterN32Canvas.cpp b/fuzz/oss_fuzz/FuzzRasterN32Canvas.cpp
index 24d74a5..8038cf5 100644
--- a/fuzz/oss_fuzz/FuzzRasterN32Canvas.cpp
+++ b/fuzz/oss_fuzz/FuzzRasterN32Canvas.cpp
@@ -6,10 +6,13 @@
  */
 
 #include "../Fuzz.h"
+#include "SkTestFontMgr.h"
+#include "SkFontMgrPriv.h"
 
 void fuzz_RasterN32Canvas(Fuzz* f);
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
     auto fuzz = Fuzz(SkData::MakeWithoutCopy(data, size));
     fuzz_RasterN32Canvas(&fuzz);
     return 0;
diff --git a/fuzz/oss_fuzz/FuzzTextBlobDeserialize.cpp b/fuzz/oss_fuzz/FuzzTextBlobDeserialize.cpp
index de68bcc..6b82c79 100644
--- a/fuzz/oss_fuzz/FuzzTextBlobDeserialize.cpp
+++ b/fuzz/oss_fuzz/FuzzTextBlobDeserialize.cpp
@@ -6,9 +6,11 @@
  */
 
 #include "SkCanvas.h"
+#include "SkFontMgrPriv.h"
 #include "SkPaint.h"
 #include "SkReadBuffer.h"
 #include "SkSurface.h"
+#include "SkTestFontMgr.h"
 #include "SkTextBlobPriv.h"
 
 void FuzzTextBlobDeserialize(SkReadBuffer& buf) {
@@ -27,6 +29,7 @@
 
 #if defined(IS_FUZZING_WITH_LIBFUZZER)
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
     SkReadBuffer buf(data, size);
     FuzzTextBlobDeserialize(buf);
     return 0;
diff --git a/gm/aaxfermodes.cpp b/gm/aaxfermodes.cpp
index 1db4c1c..cbf8db4 100644
--- a/gm/aaxfermodes.cpp
+++ b/gm/aaxfermodes.cpp
@@ -68,10 +68,9 @@
     }
 
     void onOnceBeforeDraw() override {
-        fLabelPaint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&fLabelPaint);
-        fLabelPaint.setTextSize(5 * kShapeSize/8);
-        fLabelPaint.setSubpixelText(true);
+        fLabelFont.setTypeface(sk_tool_utils::create_portable_typeface());
+        fLabelFont.setSize(5 * kShapeSize/8);
+        fLabelFont.setSubpixel(true);
 
         constexpr SkScalar radius = -1.4f * kShapeSize/2;
         SkPoint pts[4] = {
@@ -109,12 +108,12 @@
             if (kShape_Pass == drawingPass) {
                 SkTextUtils::DrawString(canvas, "Src Unknown",
                         kLabelSpacing + kShapeTypeSpacing * 1.5f + kShapeSpacing / 2,
-                        kSubtitleSpacing / 2 + fLabelPaint.getTextSize() / 3, fLabelPaint,
+                        kSubtitleSpacing / 2 + fLabelFont.getSize() / 3, fLabelFont, SkPaint(),
                                         SkTextUtils::kCenter_Align);
                 SkTextUtils::DrawString(canvas, "Src Opaque",
                         kLabelSpacing + kShapeTypeSpacing * 1.5f + kShapeSpacing / 2 +
-                        kPaintSpacing, kSubtitleSpacing / 2 + fLabelPaint.getTextSize() / 3,
-                        fLabelPaint, SkTextUtils::kCenter_Align);
+                        kPaintSpacing, kSubtitleSpacing / 2 + fLabelFont.getSize() / 3,
+                                        fLabelFont, SkPaint(), SkTextUtils::kCenter_Align);
             }
 
             canvas->translate(0, kSubtitleSpacing + kShapeSpacing/2);
@@ -177,16 +176,16 @@
         canvas->translate(kMargin, kMargin);
         draw_pass(canvas, kBackground_Pass);
 
-        SkPaint titlePaint(fLabelPaint);
-        titlePaint.setTextSize(9 * titlePaint.getTextSize() / 8);
-        titlePaint.setFakeBoldText(true);
+        SkFont titleFont(fLabelFont);
+        titleFont.setSize(9 * titleFont.getSize() / 8);
+        titleFont.setEmbolden(true);
         SkTextUtils::DrawString(canvas, "Porter Duff",
                                 kLabelSpacing + 4 * kShapeTypeSpacing,
-                                kTitleSpacing / 2 + titlePaint.getTextSize() / 3, titlePaint,
+                                kTitleSpacing / 2 + titleFont.getSize() / 3, titleFont, SkPaint(),
                                 SkTextUtils::kCenter_Align);
         SkTextUtils::DrawString(canvas, "Advanced",
                                 kXfermodeTypeSpacing + kLabelSpacing + 4 * kShapeTypeSpacing,
-                                kTitleSpacing / 2 + titlePaint.getTextSize() / 3, titlePaint,
+                                kTitleSpacing / 2 + titleFont.getSize() / 3, titleFont, SkPaint(),
                                 SkTextUtils::kCenter_Align);
 
         draw_pass(canvas, kShape_Pass);
@@ -196,7 +195,7 @@
     void drawModeName(SkCanvas* canvas, SkBlendMode mode) {
         const char* modeName = SkBlendMode_Name(mode);
         SkTextUtils::DrawString(canvas, modeName, kLabelSpacing - kShapeSize / 4,
-                                fLabelPaint.getTextSize() / 4, fLabelPaint,
+                                fLabelFont.getSize() / 4, fLabelFont, SkPaint(),
                                 SkTextUtils::kRight_Align);
     }
 
@@ -266,7 +265,7 @@
     }
 
 private:
-    SkPaint   fLabelPaint;
+    SkFont    fLabelFont;
     SkPath    fOval;
     SkPath    fConcave;
 
diff --git a/gm/all_bitmap_configs.cpp b/gm/all_bitmap_configs.cpp
index 33d39cb..db3297b 100644
--- a/gm/all_bitmap_configs.cpp
+++ b/gm/all_bitmap_configs.cpp
@@ -106,12 +106,13 @@
 
 static void draw(SkCanvas* canvas,
                  const SkPaint& p,
+                 const SkFont& font,
                  const SkBitmap& src,
                  SkColorType colorType,
                  const char text[]) {
     SkASSERT(src.colorType() == colorType);
     canvas->drawBitmap(src, 0.0f, 0.0f);
-    canvas->drawString(text, 0.0f, 12.0f, p);
+    canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 0.0f, 12.0f, font, p);
 }
 
 DEF_SIMPLE_GM(all_bitmap_configs, canvas, SCALE, 6 * SCALE) {
@@ -119,28 +120,29 @@
     SkPaint p;
     p.setColor(SK_ColorBLACK);
     p.setAntiAlias(true);
-    sk_tool_utils::set_portable_typeface(&p, nullptr);
+
+    SkFont font(sk_tool_utils::create_portable_typeface());
 
     sk_tool_utils::draw_checkerboard(canvas, SK_ColorLTGRAY, SK_ColorWHITE, 8);
 
     SkBitmap bitmap;
     if (GetResourceAsBitmap("images/color_wheel.png", &bitmap)) {
         bitmap.setImmutable();
-        draw(canvas, p, bitmap, kN32_SkColorType, "Native 32");
+        draw(canvas, p, font, bitmap, kN32_SkColorType, "Native 32");
 
         canvas->translate(0.0f, SkIntToScalar(SCALE));
         SkBitmap copy565 = copy_bitmap(bitmap, kRGB_565_SkColorType);
         p.setColor(SK_ColorRED);
-        draw(canvas, p, copy565, kRGB_565_SkColorType, "RGB 565");
+        draw(canvas, p, font, copy565, kRGB_565_SkColorType, "RGB 565");
         p.setColor(SK_ColorBLACK);
 
         canvas->translate(0.0f, SkIntToScalar(SCALE));
         SkBitmap copy4444 = copy_bitmap(bitmap, kARGB_4444_SkColorType);
-        draw(canvas, p, copy4444, kARGB_4444_SkColorType, "ARGB 4444");
+        draw(canvas, p, font, copy4444, kARGB_4444_SkColorType, "ARGB 4444");
 
         canvas->translate(0.0f, SkIntToScalar(SCALE));
         SkBitmap copyF16 = copy_bitmap(bitmap, kRGBA_F16_SkColorType);
-        draw(canvas, p, copyF16, kRGBA_F16_SkColorType, "RGBA F16");
+        draw(canvas, p, font, copyF16, kRGBA_F16_SkColorType, "RGBA F16");
 
     } else {
         canvas->translate(0.0f, SkIntToScalar(3 * SCALE));
@@ -148,12 +150,12 @@
 
     canvas->translate(0.0f, SkIntToScalar(SCALE));
     SkBitmap bitmapA8 = make_bitmap(kAlpha_8_SkColorType);
-    draw(canvas, p, bitmapA8, kAlpha_8_SkColorType, "Alpha 8");
+    draw(canvas, p, font, bitmapA8, kAlpha_8_SkColorType, "Alpha 8");
 
     p.setColor(SK_ColorRED);
     canvas->translate(0.0f, SkIntToScalar(SCALE));
     SkBitmap bitmapG8 = make_bitmap(kGray_8_SkColorType);
-    draw(canvas, p, bitmapG8, kGray_8_SkColorType, "Gray 8");
+    draw(canvas, p, font, bitmapG8, kGray_8_SkColorType, "Gray 8");
 }
 
 sk_sp<SkImage> make_not_native32_color_wheel() {
diff --git a/gm/androidblendmodes.cpp b/gm/androidblendmodes.cpp
index 359b78e..1f1500f 100644
--- a/gm/androidblendmodes.cpp
+++ b/gm/androidblendmodes.cpp
@@ -65,9 +65,7 @@
     }
 
     void onDraw(SkCanvas* canvas) override {
-        SkPaint textPaint;
-        textPaint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&textPaint);
+        SkFont font(sk_tool_utils::create_portable_typeface());
 
         sk_tool_utils::draw_checkerboard(canvas,
                                          kWhite,
@@ -97,7 +95,7 @@
             SkTextUtils::DrawString(canvas, SkBlendMode_Name(mode),
                                xOffset + kBitmapSize/2.0f,
                                yOffset + kBitmapSize,
-                               textPaint, SkTextUtils::kCenter_Align);
+                               font, SkPaint(), SkTextUtils::kCenter_Align);
 
             xOffset += 256;
             if (xOffset >= 1024) {
diff --git a/gm/animatedGif.cpp b/gm/animatedGif.cpp
index f2fb09d..271f8f4 100644
--- a/gm/animatedGif.cpp
+++ b/gm/animatedGif.cpp
@@ -12,6 +12,7 @@
 #include "SkCodec.h"
 #include "SkColor.h"
 #include "SkCommandLineFlags.h"
+#include "SkFont.h"
 #include "SkPaint.h"
 #include "SkString.h"
 #include "Resources.h"
@@ -25,10 +26,10 @@
         constexpr SkScalar kOffset = 5.0f;
         canvas->drawColor(SK_ColorRED);
         SkPaint paint;
+        SkFont font;
         SkRect bounds;
-        paint.measureText(errorText.c_str(), errorText.size(), &bounds);
-        canvas->drawString(errorText, kOffset, bounds.height() + kOffset,
-                         paint);
+        font.measureText(errorText.c_str(), errorText.size(), kUTF8_SkTextEncoding, &bounds);
+        canvas->drawString(errorText, kOffset, bounds.height() + kOffset, font, paint);
     }
 }
 
diff --git a/gm/annotated_text.cpp b/gm/annotated_text.cpp
index 70ee9f3..c501464 100644
--- a/gm/annotated_text.cpp
+++ b/gm/annotated_text.cpp
@@ -7,21 +7,22 @@
 
 #include "SkAnnotation.h"
 #include "SkData.h"
+#include "SkFont.h"
 #include "gm.h"
 
 static void draw_url_annotated_text_with_box(
         SkCanvas* canvas, const void* text,
-        SkScalar x, SkScalar y, const SkPaint& paint, const char* url) {
+        SkScalar x, SkScalar y, const SkFont& font, const char* url) {
     size_t byteLength = strlen(static_cast<const char*>(text));
     SkRect bounds;
-    (void)paint.measureText(text, byteLength, &bounds);
+    (void)font.measureText(text, byteLength, kUTF8_SkTextEncoding, &bounds);
     bounds.offset(x, y);
     sk_sp<SkData> urlData(SkData::MakeWithCString(url));
     SkAnnotateRectWithURL(canvas, bounds, urlData.get());
     SkPaint shade;
     shade.setColor(0x80346180);
     canvas->drawRect(bounds, shade);
-    canvas->drawText(text, byteLength, x, y, paint);
+    canvas->drawSimpleText(text, byteLength, kUTF8_SkTextEncoding, x, y, font, SkPaint());
 }
 
 DEF_SIMPLE_GM(annotated_text, canvas, 512, 512) {
@@ -29,13 +30,14 @@
     canvas->clear(SK_ColorWHITE);
     canvas->clipRect(SkRect::MakeXYWH(64, 64, 256, 256));
     canvas->clear(0xFFEEEEEE);
-    SkPaint p;
-    p.setTextSize(40);
+    SkFont font;
+    font.setEdging(SkFont::Edging::kAlias);
+    font.setSize(40);
     const char text[] = "Click this link!";
     const char url[] = "https://www.google.com/";
-    draw_url_annotated_text_with_box(canvas, text, 200.0f, 80.0f, p, url);
+    draw_url_annotated_text_with_box(canvas, text, 200.0f, 80.0f, font, url);
     canvas->saveLayer(nullptr, nullptr);
     canvas->rotate(90);
-    draw_url_annotated_text_with_box(canvas, text, 150.0f, -55.0f, p, url);
+    draw_url_annotated_text_with_box(canvas, text, 150.0f, -55.0f, font, url);
     canvas->restore();
 }
diff --git a/gm/arithmode.cpp b/gm/arithmode.cpp
index 7ebce7e..adf71ce 100644
--- a/gm/arithmode.cpp
+++ b/gm/arithmode.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include <SkFont.h>
 #include "gm.h"
 #include "sk_tool_utils.h"
 #include "SkArithmeticImageFilter.h"
@@ -52,15 +53,15 @@
 }
 
 static void show_k_text(SkCanvas* canvas, SkScalar x, SkScalar y, const SkScalar k[]) {
+    SkFont font(sk_tool_utils::create_portable_typeface(), 24);
+    font.setEdging(SkFont::Edging::kAntiAlias);
     SkPaint paint;
-    paint.setTextSize(SkIntToScalar(24));
     paint.setAntiAlias(true);
-    sk_tool_utils::set_portable_typeface(&paint);
     for (int i = 0; i < 4; ++i) {
         SkString str;
         str.appendScalar(k[i]);
-        SkScalar width = paint.measureText(str.c_str(), str.size());
-        canvas->drawString(str, x, y + paint.getTextSize(), paint);
+        SkScalar width = font.measureText(str.c_str(), str.size(), kUTF8_SkTextEncoding);
+        canvas->drawString(str, x, y + font.getSize(), font, paint);
         x += width + SkIntToScalar(10);
     }
 }
@@ -148,12 +149,9 @@
                 canvas->translate(gap, 0);
 
                 // Label
-                SkPaint paint;
-                paint.setTextSize(SkIntToScalar(24));
-                paint.setAntiAlias(true);
-                sk_tool_utils::set_portable_typeface(&paint);
+                SkFont font(sk_tool_utils::create_portable_typeface(), 24);
                 SkString str(enforcePMColor ? "enforcePM" : "no enforcePM");
-                canvas->drawString(str, 0, paint.getTextSize(), paint);
+                canvas->drawString(str, 0, font.getSize(), font, SkPaint());
             }
             canvas->translate(0, HH + 12);
         }
diff --git a/gm/b_119394958.cpp b/gm/b_119394958.cpp
new file mode 100644
index 0000000..9a3f129
--- /dev/null
+++ b/gm/b_119394958.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+
+DEF_SIMPLE_GM(b_119394958, canvas, 100, 100) {
+    // The root cause of this bug was that a stroked arc with round caps was batched with a filled
+    // circle. The circle op code would choose a GeometryProcessor configuration that expected round
+    // cap centers as vertex attributes. However, the tessellation code for the filled circle would
+    // not put in dummy round cap centers and then didn't advance the pointer into which vertex data
+    // was being written by the expected vertex stride.
+    SkPaint paint;
+    paint.setColor(SK_ColorBLUE);
+    paint.setAntiAlias(true);
+    canvas->drawCircle(50, 50, 45, paint);
+    paint.setColor(SK_ColorGREEN);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(5);
+    canvas->drawCircle(50, 50, 35, paint);
+    paint.setColor(SK_ColorRED);
+    paint.setStrokeCap(SkPaint::kRound_Cap);
+    canvas->drawArc(SkRect::MakeLTRB(30, 30, 70, 70), 0, 110, false, paint);
+}
diff --git a/gm/beziereffects.cpp b/gm/beziereffects.cpp
index 41c24c3..ff4ea00 100644
--- a/gm/beziereffects.cpp
+++ b/gm/beziereffects.cpp
@@ -330,7 +330,7 @@
         }
         SkRect rect = this->rect();
         SkPointPriv::SetRectTriStrip(&verts[0].fPosition, rect, sizeof(Vertex));
-        fDevToUV.apply<4, sizeof(Vertex), sizeof(SkPoint)>(verts);
+        fDevToUV.apply(verts, 4, sizeof(Vertex), sizeof(SkPoint));
         auto pipe = this->makePipeline(target);
         helper.recordDraw(target, this->gp(), pipe.fPipeline, pipe.fFixedDynamicState);
     }
diff --git a/gm/bigrrectaaeffect.cpp b/gm/bigrrectaaeffect.cpp
index a45337c..6d55c9b 100644
--- a/gm/bigrrectaaeffect.cpp
+++ b/gm/bigrrectaaeffect.cpp
@@ -13,7 +13,7 @@
 #include "SkRRect.h"
 #include "effects/GrRRectEffect.h"
 #include "ops/GrDrawOp.h"
-#include "ops/GrRectOpFactory.h"
+#include "ops/GrFillRectOp.h"
 
 namespace skiagm {
 
@@ -95,9 +95,8 @@
                     bounds.offset(SkIntToScalar(x), SkIntToScalar(y));
 
                     renderTargetContext->priv().testingOnly_addDrawOp(
-                            GrRectOpFactory::MakeNonAAFill(context, std::move(grPaint),
-                                                           SkMatrix::I(),
-                                                           bounds, GrAAType::kNone));
+                            GrFillRectOp::Make(context, std::move(grPaint), GrAAType::kNone,
+                                               SkMatrix::I(), bounds));
                 }
             canvas->restore();
             x = x + fTestOffsetX;
diff --git a/gm/bigtext.cpp b/gm/bigtext.cpp
index 6b3c9b1..839609e 100644
--- a/gm/bigtext.cpp
+++ b/gm/bigtext.cpp
@@ -8,6 +8,7 @@
 #include "gm.h"
 #include "sk_tool_utils.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkPath.h"
 
 /**
@@ -32,21 +33,20 @@
     void onDraw(SkCanvas* canvas) override {
         SkPaint paint;
         paint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(1500);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 1500);
 
         SkRect r;
-        (void)paint.measureText("/", 1, &r);
+        (void)font.measureText("/", 1, kUTF8_SkTextEncoding, &r);
         SkPoint pos = {
             this->width()/2 - r.centerX(),
             this->height()/2 - r.centerY()
         };
 
         paint.setColor(SK_ColorRED);
-        canvas->drawString("/", pos.fX, pos.fY, paint);
+        canvas->drawSimpleText("/", 1, kUTF8_SkTextEncoding, pos.fX, pos.fY, font, paint);
 
         paint.setColor(SK_ColorBLUE);
-        canvas->drawPosText("\\", 1, &pos, paint);
+        canvas->drawSimpleText("\\", 1, kUTF8_SkTextEncoding, pos.fX, pos.fY, font, paint);
     }
 
 private:
diff --git a/gm/bitmapcopy.cpp b/gm/bitmapcopy.cpp
index 28c6e79..469bf71 100644
--- a/gm/bitmapcopy.cpp
+++ b/gm/bitmapcopy.cpp
@@ -4,8 +4,10 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+
 #include "gm.h"
 #include "sk_tool_utils.h"
+#include "SkFont.h"
 
 namespace skiagm {
 
@@ -84,16 +86,17 @@
 
         canvas->clear(0xFFDDDDDD);
         paint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&paint);
+
+        SkFont font(sk_tool_utils::create_portable_typeface());
 
         SkScalar width = SkIntToScalar(40);
         SkScalar height = SkIntToScalar(40);
-        if (paint.getFontSpacing() > height) {
-            height = paint.getFontSpacing();
+        if (font.getSpacing() > height) {
+            height = font.getSpacing();
         }
         for (unsigned i = 0; i < NUM_CONFIGS; i++) {
             const char* name = color_type_name(src.colorType());
-            SkScalar textWidth = paint.measureText(name, strlen(name));
+            SkScalar textWidth = font.measureText(name, strlen(name), kUTF8_SkTextEncoding);
             if (textWidth > width) {
                 width = textWidth;
             }
@@ -106,10 +109,10 @@
             canvas->save();
             // Draw destination config name
             const char* name = color_type_name(fDst[i].colorType());
-            SkScalar textWidth = paint.measureText(name, strlen(name));
+            SkScalar textWidth = font.measureText(name, strlen(name), kUTF8_SkTextEncoding);
             SkScalar x = (width - textWidth) / SkScalar(2);
-            SkScalar y = paint.getFontSpacing() / SkScalar(2);
-            canvas->drawString(name, x, y, paint);
+            SkScalar y = font.getSpacing() / SkScalar(2);
+            canvas->drawSimpleText(name, strlen(name), kUTF8_SkTextEncoding, x, y, font, paint);
 
             // Draw destination bitmap
             canvas->translate(0, vertOffset);
diff --git a/gm/bitmapfilters.cpp b/gm/bitmapfilters.cpp
index 0551c02..98038ca 100644
--- a/gm/bitmapfilters.cpp
+++ b/gm/bitmapfilters.cpp
@@ -44,14 +44,15 @@
     SkAutoCanvasRestore acr(canvas, true);
 
     SkPaint paint;
+    paint.setAntiAlias(true);
+
     SkScalar x = 0;
     const int scale = 32;
 
-    paint.setAntiAlias(true);
-    sk_tool_utils::set_portable_typeface(&paint);
+    SkFont font(sk_tool_utils::create_portable_typeface());
     const char* name = sk_tool_utils::colortype_name(bm.colorType());
     canvas->drawString(name, x, SkIntToScalar(bm.height())*scale*5/8,
-                     paint);
+                       font, paint);
     canvas->translate(SkIntToScalar(48), 0);
 
     canvas->scale(SkIntToScalar(scale), SkIntToScalar(scale));
diff --git a/gm/blurignorexform.cpp b/gm/blurignorexform.cpp
index 1e89dec..28fc19d 100644
--- a/gm/blurignorexform.cpp
+++ b/gm/blurignorexform.cpp
@@ -95,17 +95,15 @@
 
     void drawOverlay(SkCanvas* canvas) {
         canvas->translate(10, 0);
-        SkPaint textPaint;
-        sk_tool_utils::set_portable_typeface(&textPaint);
-        textPaint.setAntiAlias(true);
+        SkFont font(sk_tool_utils::create_portable_typeface());
         canvas->save();
         for (int i = 0; i < kNumBlurs; ++i) {
-            canvas->drawString(kBlurFlags[i].fName, 100, 0, textPaint);
+            canvas->drawString(kBlurFlags[i].fName, 100, 0, font, SkPaint());
             canvas->translate(SkIntToScalar(130), 0);
         }
         canvas->restore();
         for (auto scale : kMatrixScales) {
-            canvas->drawString(scale.fName, 0, 50, textPaint);
+            canvas->drawString(scale.fName, 0, 50, font, SkPaint());
             canvas->translate(0, SkIntToScalar(150));
         }
     }
diff --git a/gm/blurimagevmask.cpp b/gm/blurimagevmask.cpp
index 9440959..c2dd516 100644
--- a/gm/blurimagevmask.cpp
+++ b/gm/blurimagevmask.cpp
@@ -16,15 +16,12 @@
     paint.setAntiAlias(true);
     paint.setColor(SK_ColorBLACK);
 
-    SkPaint textPaint;
-    textPaint.setAntiAlias(true);
-    sk_tool_utils::set_portable_typeface(&textPaint);
-    textPaint.setTextSize(SkIntToScalar(25));
+    SkFont font(sk_tool_utils::create_portable_typeface(), 25);
 
     const double sigmas[] = {3.0, 8.0, 16.0, 24.0, 32.0};
 
-    canvas->drawString("mask blur",  285, 50, textPaint);
-    canvas->drawString("image blur", 285 + 250, 50, textPaint);
+    canvas->drawString("mask blur",  285, 50, font, paint);
+    canvas->drawString("image blur", 285 + 250, 50, font, paint);
 
 
     SkRect r = {35, 100, 135, 200};
@@ -34,7 +31,7 @@
 
         char out[100];
         sprintf(out, "Sigma: %g", sigma);
-        canvas->drawString(out, r.left(), r.bottom() + 35, textPaint);
+        canvas->drawString(out, r.left(), r.bottom() + 35, font, paint);
 
         r.offset(250, 0);
 
diff --git a/gm/blurs.cpp b/gm/blurs.cpp
index bd666bb..6892ad3 100644
--- a/gm/blurs.cpp
+++ b/gm/blurs.cpp
@@ -14,53 +14,53 @@
 #include "SkPath.h"
 
 DEF_SIMPLE_GM_BG(blurs, canvas, 700, 500, 0xFFDDDDDD) {
-        SkBlurStyle NONE = SkBlurStyle(-999);
-        const struct {
-            SkBlurStyle fStyle;
-            int         fCx, fCy;
-        } gRecs[] = {
-            { NONE,                 0,  0 },
-            { kInner_SkBlurStyle,  -1,  0 },
-            { kNormal_SkBlurStyle,  0,  1 },
-            { kSolid_SkBlurStyle,   0, -1 },
-            { kOuter_SkBlurStyle,   1,  0 },
-        };
+    SkBlurStyle NONE = SkBlurStyle(-999);
+    const struct {
+        SkBlurStyle fStyle;
+        int         fCx, fCy;
+    } gRecs[] = {
+        { NONE,                 0,  0 },
+        { kInner_SkBlurStyle,  -1,  0 },
+        { kNormal_SkBlurStyle,  0,  1 },
+        { kSolid_SkBlurStyle,   0, -1 },
+        { kOuter_SkBlurStyle,   1,  0 },
+    };
 
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(SkIntToScalar(25));
-        canvas->translate(SkIntToScalar(-40), SkIntToScalar(0));
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(SK_ColorBLUE);
 
-        paint.setColor(SK_ColorBLUE);
-        for (size_t i = 0; i < SK_ARRAY_COUNT(gRecs); i++) {
-            if (gRecs[i].fStyle != NONE) {
-                paint.setMaskFilter(SkMaskFilter::MakeBlur(gRecs[i].fStyle,
-                                       SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(20))));
-            } else {
-                paint.setMaskFilter(nullptr);
-            }
-            canvas->drawCircle(SkIntToScalar(200 + gRecs[i].fCx*100),
-                               SkIntToScalar(200 + gRecs[i].fCy*100),
-                               SkIntToScalar(50),
-                               paint);
-        }
-        // draw text
-        {
-            paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
-                                       SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(4))));
-            SkScalar x = SkIntToScalar(70);
-            SkScalar y = SkIntToScalar(400);
-            paint.setColor(SK_ColorBLACK);
-            canvas->drawString("Hamburgefons Style", x, y, paint);
-            canvas->drawString("Hamburgefons Style",
-                             x, y + SkIntToScalar(50), paint);
+    canvas->translate(SkIntToScalar(-40), SkIntToScalar(0));
+
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gRecs); i++) {
+        if (gRecs[i].fStyle != NONE) {
+            paint.setMaskFilter(SkMaskFilter::MakeBlur(gRecs[i].fStyle,
+                                   SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(20))));
+        } else {
             paint.setMaskFilter(nullptr);
-            paint.setColor(SK_ColorWHITE);
-            x -= SkIntToScalar(2);
-            y -= SkIntToScalar(2);
-            canvas->drawString("Hamburgefons Style", x, y, paint);
         }
+        canvas->drawCircle(SkIntToScalar(200 + gRecs[i].fCx*100),
+                           SkIntToScalar(200 + gRecs[i].fCy*100),
+                           SkIntToScalar(50),
+                           paint);
+    }
+    // draw text
+    {
+        SkFont font(sk_tool_utils::create_portable_typeface(), 25);
+        paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
+                                   SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(4))));
+        SkScalar x = SkIntToScalar(70);
+        SkScalar y = SkIntToScalar(400);
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawString("Hamburgefons Style", x, y, font, paint);
+        canvas->drawString("Hamburgefons Style",
+                         x, y + SkIntToScalar(50), font, paint);
+        paint.setMaskFilter(nullptr);
+        paint.setColor(SK_ColorWHITE);
+        x -= SkIntToScalar(2);
+        y -= SkIntToScalar(2);
+        canvas->drawString("Hamburgefons Style", x, y, font, paint);
+    }
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/gm/blurtextsmallradii.cpp b/gm/blurtextsmallradii.cpp
index 62572ce..e1e7aa1 100644
--- a/gm/blurtextsmallradii.cpp
+++ b/gm/blurtextsmallradii.cpp
@@ -7,6 +7,7 @@
 
 #include "gm.h"
 #include "SkColor.h"
+#include "SkFont.h"
 #include "SkMaskFilter.h"
 
 // GM to check the behavior from chrome bug:745290
@@ -18,11 +19,11 @@
         paint.setColor(SK_ColorBLACK);
         paint.setAntiAlias(true);
         paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, sigma));
-        canvas->drawString("Guest", 20, 10, paint);
+        canvas->drawString("Guest", 20, 10, SkFont(), paint);
 
         paint.setMaskFilter(nullptr);
         paint.setColor(SK_ColorWHITE);
-        canvas->drawString("Guest", 20, 10, paint);
+        canvas->drawString("Guest", 20, 10, SkFont(), paint);
         canvas->translate(0, 20);
     }
 }
diff --git a/gm/bmpfilterqualityrepeat.cpp b/gm/bmpfilterqualityrepeat.cpp
index 313fd93..ba41c3a 100644
--- a/gm/bmpfilterqualityrepeat.cpp
+++ b/gm/bmpfilterqualityrepeat.cpp
@@ -64,11 +64,12 @@
         lm.setTranslateY(330);
 
         SkPaint textPaint;
-        sk_tool_utils::set_portable_typeface(&textPaint);
         textPaint.setAntiAlias(true);
 
         SkPaint bmpPaint(textPaint);
 
+        SkFont font(sk_tool_utils::create_portable_typeface());
+
         SkAutoCanvasRestore acr(canvas, true);
 
         for (size_t q = 0; q < SK_ARRAY_COUNT(kQualities); ++q) {
@@ -76,7 +77,7 @@
             bmpPaint.setShader(SkShader::MakeBitmapShader(fBmp, kTM, kTM, &lm));
             bmpPaint.setFilterQuality(kQualities[q].fQuality);
             canvas->drawRect(rect, bmpPaint);
-            canvas->drawString(kQualities[q].fName, 20, 40, textPaint);
+            canvas->drawString(kQualities[q].fName, 20, 40, font, textPaint);
             canvas->translate(250, 0);
         }
 
diff --git a/gm/clip_error.cpp b/gm/clip_error.cpp
index 49c51a7..7e9c4ce 100644
--- a/gm/clip_error.cpp
+++ b/gm/clip_error.cpp
@@ -41,13 +41,8 @@
     void onDraw(SkCanvas* canvas) override {
         SkPaint paint;
         paint.setAntiAlias(true);
-        paint.setStyle(SkPaint::kFill_Style);
 
-        const char text[] = "hambur";
-
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(256);
-        paint.setAntiAlias(true);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 256);
 
         // setup up maskfilter
         const SkScalar kSigma = SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(50));
@@ -55,11 +50,8 @@
         SkPaint blurPaint(paint);
         blurPaint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, kSigma));
 
-        SkTextBlobBuilder builder;
-
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, 0);
-
-        sk_sp<SkTextBlob> blob(builder.make());
+        const char text[] = "hambur";
+        auto blob = SkTextBlob::MakeFromText(text, strlen(text), font);
 
         SkPaint clearPaint(paint);
         clearPaint.setColor(SK_ColorWHITE);
diff --git a/gm/coloremoji.cpp b/gm/coloremoji.cpp
index 290c747..513d2c5 100644
--- a/gm/coloremoji.cpp
+++ b/gm/coloremoji.cpp
@@ -13,6 +13,7 @@
 #include "SkCanvas.h"
 #include "SkColorFilterImageFilter.h"
 #include "SkColorMatrixFilter.h"
+#include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkStream.h"
 #include "SkTypeface.h"
@@ -76,8 +77,7 @@
 
         canvas->drawColor(SK_ColorGRAY);
 
-        SkPaint paint;
-        paint.setTypeface(emojiFont.typeface);
+        SkFont font(emojiFont.typeface);
         const char* text = emojiFont.text;
 
         // draw text at different point sizes
@@ -85,12 +85,12 @@
         SkFontMetrics metrics;
         SkScalar y = 0;
         for (const bool& fakeBold : { false, true }) {
-            paint.setFakeBoldText(fakeBold);
+            font.setEmbolden(fakeBold);
             for (const SkScalar& textSize : textSizes) {
-                paint.setTextSize(textSize);
-                paint.getFontMetrics(&metrics);
+                font.setSize(textSize);
+                font.getMetrics(&metrics);
                 y += -metrics.fAscent;
-                canvas->drawString(text, 10, y, paint);
+                canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 10, y, font, SkPaint());
                 y += metrics.fDescent + metrics.fLeading;
             }
         }
@@ -103,8 +103,8 @@
                 for (int makeGray = 0; makeGray < 2; makeGray++) {
                     for (int makeMode = 0; makeMode < 2; ++makeMode) {
                         for (int alpha = 0; alpha < 2; ++alpha) {
+                            SkFont shaderFont(font.refTypeface());
                             SkPaint shaderPaint;
-                            shaderPaint.setTypeface(sk_ref_sp(paint.getTypeface()));
                             if (SkToBool(makeLinear)) {
                                 shaderPaint.setShader(MakeLinear());
                             }
@@ -124,10 +124,11 @@
                             if (alpha) {
                                 shaderPaint.setAlpha(0x80);
                             }
-                            shaderPaint.setTextSize(30);
-                            shaderPaint.getFontMetrics(&metrics);
+                            shaderFont.setSize(30);
+                            shaderFont.getMetrics(&metrics);
                             y += -metrics.fAscent;
-                            canvas->drawString(text, 380, y, shaderPaint);
+                            canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 380, y,
+                                                   shaderFont, shaderPaint);
                             y += metrics.fDescent + metrics.fLeading;
                         }
                     }
@@ -136,11 +137,11 @@
         }
         // setup work needed to draw text with different clips
         canvas->translate(10, savedY);
-        paint.setTextSize(40);
+        font.setSize(40);
 
         // compute the bounds of the text
         SkRect bounds;
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
 
         const SkScalar boundsHalfWidth = bounds.width() * SK_ScalarHalf;
         const SkScalar boundsHalfHeight = bounds.height() * SK_ScalarHalf;
@@ -160,15 +161,16 @@
         clipHairline.setColor(SK_ColorWHITE);
         clipHairline.setStyle(SkPaint::kStroke_Style);
 
+        SkPaint paint;
         for (const SkRect& clipRect : clipRects) {
             canvas->translate(0, bounds.height());
             canvas->save();
             canvas->drawRect(clipRect, clipHairline);
             paint.setAlpha(0x20);
-            canvas->drawString(text, 0, 0, paint);
+            canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 0, 0, font, paint);
             canvas->clipRect(clipRect);
             paint.setAlpha(0xFF);
-            canvas->drawString(text, 0, 0, paint);
+            canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 0, 0, font, paint);
             canvas->restore();
             canvas->translate(0, SkIntToScalar(25));
         }
diff --git a/gm/coloremoji_blendmodes.cpp b/gm/coloremoji_blendmodes.cpp
index 91b17dc..a28389e 100644
--- a/gm/coloremoji_blendmodes.cpp
+++ b/gm/coloremoji_blendmodes.cpp
@@ -114,14 +114,11 @@
         auto s = SkShader::MakeBitmapShader(fBG, SkShader::kRepeat_TileMode,
                                             SkShader::kRepeat_TileMode, &m);
 
-        SkPaint labelP;
-        labelP.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&labelP);
+        SkFont labelFont(sk_tool_utils::create_portable_typeface());
 
         SkPaint textP;
         textP.setAntiAlias(true);
-        textP.setTypeface(fColorType);
-        textP.setTextSize(SkIntToScalar(70));
+        SkFont textFont(fColorType, 70);
 
         const int W = 5;
 
@@ -146,15 +143,15 @@
                 SkAutoCanvasRestore arc(canvas, true);
                 canvas->clipRect(r);
                 textP.setBlendMode(gModes[i]);
-                textP.setTextEncoding(kUTF32_SkTextEncoding);
                 const char* text = sk_tool_utils::emoji_sample_text();
                 SkUnichar unichar = SkUTF::NextUTF8(&text, text + strlen(text));
                 SkASSERT(unichar >= 0);
-                canvas->drawText(&unichar, 4, x+ w/10.f, y + 7.f*h/8.f, textP);
+                canvas->drawSimpleText(&unichar, 4, kUTF32_SkTextEncoding, x+ w/10.f, y + 7.f*h/8.f,
+                                       textFont, textP);
             }
 #if 1
             const char* label = SkBlendMode_Name(gModes[i]);
-            SkTextUtils::DrawString(canvas, label, x + w/2, y - labelP.getTextSize()/2, labelP,
+            SkTextUtils::DrawString(canvas, label, x + w/2, y - labelFont.getSize()/2, labelFont, SkPaint(),
                                     SkTextUtils::kCenter_Align);
 #endif
             x += w + SkIntToScalar(10);
diff --git a/gm/colorwheel.cpp b/gm/colorwheel.cpp
index e970af5..230b85c 100644
--- a/gm/colorwheel.cpp
+++ b/gm/colorwheel.cpp
@@ -40,22 +40,22 @@
 
 DEF_SIMPLE_GM(colorwheelnative, canvas, 128, 28) {
     SkPaint paint;
-    sk_tool_utils::set_portable_typeface(&paint, "sans-serif", SkFontStyle::Bold());
-    paint.setTextSize(18.0f);
+    SkFont font(sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Bold()), 18);
+    font.setEdging(SkFont::Edging::kAlias);
 
     canvas->clear(SK_ColorLTGRAY);
     paint.setColor(SK_ColorRED);
-    canvas->drawString("R", 8.0f, 20.0f, paint);
+    canvas->drawString("R", 8.0f, 20.0f, font, paint);
     paint.setColor(SK_ColorGREEN);
-    canvas->drawString("G", 24.0f, 20.0f, paint);
+    canvas->drawString("G", 24.0f, 20.0f, font, paint);
     paint.setColor(SK_ColorBLUE);
-    canvas->drawString("B", 40.0f, 20.0f, paint);
+    canvas->drawString("B", 40.0f, 20.0f, font, paint);
     paint.setColor(SK_ColorCYAN);
-    canvas->drawString("C", 56.0f, 20.0f, paint);
+    canvas->drawString("C", 56.0f, 20.0f, font, paint);
     paint.setColor(SK_ColorMAGENTA);
-    canvas->drawString("M", 72.0f, 20.0f, paint);
+    canvas->drawString("M", 72.0f, 20.0f, font, paint);
     paint.setColor(SK_ColorYELLOW);
-    canvas->drawString("Y", 88.0f, 20.0f, paint);
+    canvas->drawString("Y", 88.0f, 20.0f, font, paint);
     paint.setColor(SK_ColorBLACK);
-    canvas->drawString("K", 104.0f, 20.0f, paint);
+    canvas->drawString("K", 104.0f, 20.0f, font, paint);
 }
diff --git a/gm/complexclip.cpp b/gm/complexclip.cpp
index 1d3359f..415151f 100644
--- a/gm/complexclip.cpp
+++ b/gm/complexclip.cpp
@@ -8,6 +8,7 @@
 #include "gm.h"
 #include "sk_tool_utils.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkPath.h"
 
 namespace skiagm {
@@ -68,10 +69,7 @@
         SkPath clipB;
         clipB.addPoly({{40,  10}, {190, 15}, {195, 190}, {40,  185}, {155, 100}}, false).close();
 
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(20);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 20);
 
         constexpr struct {
             SkClipOp fOp;
@@ -137,17 +135,19 @@
                 canvas->restore();
 
 
+                SkPaint paint;
                 SkScalar txtX = 45;
                 paint.setColor(gClipAColor);
                 const char* aTxt = doInvA ? "InvA " : "A ";
-                canvas->drawString(aTxt, txtX, 220, paint);
-                txtX += paint.measureText(aTxt, strlen(aTxt));
+                canvas->drawSimpleText(aTxt, strlen(aTxt), kUTF8_SkTextEncoding, txtX, 220, font, paint);
+                txtX += font.measureText(aTxt, strlen(aTxt), kUTF8_SkTextEncoding);
                 paint.setColor(SK_ColorBLACK);
-                canvas->drawString(gOps[op].fName, txtX, 220, paint);
-                txtX += paint.measureText(gOps[op].fName, strlen(gOps[op].fName));
+                canvas->drawSimpleText(gOps[op].fName, strlen(gOps[op].fName), kUTF8_SkTextEncoding, txtX, 220,
+                                       font, paint);
+                txtX += font.measureText(gOps[op].fName, strlen(gOps[op].fName), kUTF8_SkTextEncoding);
                 paint.setColor(gClipBColor);
                 const char* bTxt = doInvB ? "InvB " : "B ";
-                canvas->drawString(bTxt, txtX, 220, paint);
+                canvas->drawSimpleText(bTxt, strlen(bTxt), kUTF8_SkTextEncoding, txtX, 220, font, paint);
 
                 canvas->translate(250,0);
             }
diff --git a/gm/complexclip3.cpp b/gm/complexclip3.cpp
index 421b3d8..afb5a5a 100644
--- a/gm/complexclip3.cpp
+++ b/gm/complexclip3.cpp
@@ -52,8 +52,8 @@
 
         SkPaint paint;
         paint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(SkIntToScalar(20));
+
+        SkFont font(sk_tool_utils::create_portable_typeface(), 20);
 
         constexpr struct {
             SkClipOp    fOp;
@@ -106,7 +106,7 @@
                                                    doAAB ? "A" : "B",
                                                    doInvB ? "I" : "N");
 
-                        canvas->drawString(str.c_str(), txtX, SkIntToScalar(130), paint);
+                        canvas->drawString(str.c_str(), txtX, SkIntToScalar(130), font, paint);
                         if (doInvB) {
                             canvas->translate(SkIntToScalar(150),0);
                         } else {
diff --git a/gm/constcolorprocessor.cpp b/gm/constcolorprocessor.cpp
index 1079eeb..2e676a2 100644
--- a/gm/constcolorprocessor.cpp
+++ b/gm/constcolorprocessor.cpp
@@ -16,7 +16,7 @@
 #include "SkGradientShader.h"
 #include "effects/GrConstColorProcessor.h"
 #include "ops/GrDrawOp.h"
-#include "ops/GrRectOpFactory.h"
+#include "ops/GrFillRectOp.h"
 
 namespace skiagm {
 /**
@@ -108,8 +108,8 @@
 
                     grPaint.addColorFragmentProcessor(std::move(fp));
                     renderTargetContext->priv().testingOnly_addDrawOp(
-                            GrRectOpFactory::MakeNonAAFill(context, std::move(grPaint), viewMatrix,
-                                                           renderRect, GrAAType::kNone));
+                            GrFillRectOp::Make(context, std::move(grPaint), GrAAType::kNone,
+                                               viewMatrix, renderRect));
 
                     // Draw labels for the input to the processor and the processor to the right of
                     // the test rect. The input label appears above the processor label.
@@ -133,19 +133,17 @@
                     // get the bounds of the text in order to position it
                     labelFont.measureText(inputLabel.c_str(), inputLabel.size(),
                                           kUTF8_SkTextEncoding, &inputLabelBounds);
-                    canvas->drawSimpleText(inputLabel.c_str(), inputLabel.size(), kUTF8_SkTextEncoding,
-                                     renderRect.fRight + kPad,
-                                     -inputLabelBounds.fTop, labelFont, labelPaint);
+                    canvas->drawString(inputLabel, renderRect.fRight + kPad, -inputLabelBounds.fTop,
+                                       labelFont, labelPaint);
                     // update the bounds to reflect the offset we used to draw it.
                     inputLabelBounds.offset(renderRect.fRight + kPad, -inputLabelBounds.fTop);
 
                     SkRect procLabelBounds;
                     labelFont.measureText(procLabel.c_str(), procLabel.size(),
                                           kUTF8_SkTextEncoding, &procLabelBounds);
-                    canvas->drawSimpleText(procLabel.c_str(), procLabel.size(), kUTF8_SkTextEncoding,
-                                     renderRect.fRight + kPad,
-                                     inputLabelBounds.fBottom + 2.f - procLabelBounds.fTop,
-                                     labelFont, labelPaint);
+                    canvas->drawString(procLabel, renderRect.fRight + kPad,
+                                       inputLabelBounds.fBottom + 2.f - procLabelBounds.fTop,
+                                       labelFont, labelPaint);
                     procLabelBounds.offset(renderRect.fRight + kPad,
                                            inputLabelBounds.fBottom + 2.f - procLabelBounds.fTop);
 
diff --git a/gm/convex_all_line_paths.cpp b/gm/convex_all_line_paths.cpp
index 6d7155d..e4a959c 100644
--- a/gm/convex_all_line_paths.cpp
+++ b/gm/convex_all_line_paths.cpp
@@ -56,9 +56,13 @@
 const SkPoint gPoints4[] = {
     { -6.0f, -50.0f },
     { 4.0f, -50.0f },
-    { 5.0f, -25.0f },
+#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE == 0
+    { 5.0f, -25.0f },  // remove if collinear diagonal points are not concave
+#endif
     { 6.0f,   0.0f },
-    { 5.0f,  25.0f },
+#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE == 0
+    { 5.0f,  25.0f },  // remove if collinear diagonal points are not concave
+#endif
     { 4.0f,  50.0f },
     { -4.0f,  50.0f }
 };
diff --git a/gm/convexpolyclip.cpp b/gm/convexpolyclip.cpp
index 3cc64dc..2ab41ca 100644
--- a/gm/convexpolyclip.cpp
+++ b/gm/convexpolyclip.cpp
@@ -9,6 +9,7 @@
 #include "sk_tool_utils.h"
 
 #include "SkBitmap.h"
+#include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkPath.h"
 #include "SkTLList.h"
@@ -56,18 +57,19 @@
         mat.postScale(SK_Scalar1 / 3, SK_Scalar1 / 3);
     }
 
-    paint.setAntiAlias(true);
-    sk_tool_utils::set_portable_typeface(&paint);
-    paint.setTextSize(wScalar / 2.2f);
+    SkFont font(sk_tool_utils::create_portable_typeface(), wScalar / 2.2f);
+
     paint.setShader(nullptr);
     paint.setColor(SK_ColorLTGRAY);
     constexpr char kTxt[] = "Skia";
-    SkPoint texPos = { wScalar / 17, hScalar / 2 + paint.getTextSize() / 2.5f };
-    canvas.drawText(kTxt, SK_ARRAY_COUNT(kTxt)-1, texPos.fX, texPos.fY, paint);
+    SkPoint texPos = { wScalar / 17, hScalar / 2 + font.getSize() / 2.5f };
+    canvas.drawSimpleText(kTxt, SK_ARRAY_COUNT(kTxt)-1, kUTF8_SkTextEncoding,
+                          texPos.fX, texPos.fY, font, paint);
     paint.setColor(SK_ColorBLACK);
     paint.setStyle(SkPaint::kStroke_Style);
     paint.setStrokeWidth(SK_Scalar1);
-    canvas.drawText(kTxt, SK_ARRAY_COUNT(kTxt)-1, texPos.fX, texPos.fY, paint);
+    canvas.drawSimpleText(kTxt, SK_ARRAY_COUNT(kTxt)-1, kUTF8_SkTextEncoding,
+                          texPos.fX, texPos.fY, font, paint);
     return bmp;
 }
 
@@ -148,12 +150,10 @@
         canvas->drawBitmapRect(fBmp, SkRect::MakeIWH(size.fWidth, size.fHeight), &bgPaint);
 
         constexpr char kTxt[] = "Clip Me!";
+        SkFont font(sk_tool_utils::create_portable_typeface(), 23);
+        SkScalar textW = font.measureText(kTxt, SK_ARRAY_COUNT(kTxt)-1, kUTF8_SkTextEncoding);
         SkPaint txtPaint;
-        txtPaint.setTextSize(23.f);
-        txtPaint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&txtPaint);
         txtPaint.setColor(SK_ColorDKGRAY);
-        SkScalar textW = txtPaint.measureText(kTxt, SK_ARRAY_COUNT(kTxt)-1);
 
         SkScalar startX = 0;
         int testLayers = kBench_Mode != this->getMode();
@@ -202,9 +202,8 @@
                     canvas->drawPath(closedClipPath, clipOutlinePaint);
                     clip->setOnCanvas(canvas, kIntersect_SkClipOp, SkToBool(aa));
                     canvas->scale(1.f, 1.8f);
-                    canvas->drawText(kTxt, SK_ARRAY_COUNT(kTxt)-1,
-                                     0, 1.5f * txtPaint.getTextSize(),
-                                     txtPaint);
+                    canvas->drawSimpleText(kTxt, SK_ARRAY_COUNT(kTxt)-1, kUTF8_SkTextEncoding,
+                                     0, 1.5f * font.getSize(), font, txtPaint);
                     canvas->restore();
                     x += textW + 2 * kMargin;
                 }
diff --git a/gm/crbug_918512.cpp b/gm/crbug_918512.cpp
new file mode 100644
index 0000000..cb91a00
--- /dev/null
+++ b/gm/crbug_918512.cpp
@@ -0,0 +1,28 @@
+// Copyright 2019 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+
+#include "gm.h"
+
+#include "SkLumaColorFilter.h"
+
+// PDF backend should produce correct results.
+DEF_SIMPLE_GM(crbug_918512, canvas, 256, 256) {
+    canvas->drawColor(SK_ColorYELLOW);
+    {
+        SkAutoCanvasRestore autoCanvasRestore1(canvas, false);
+        canvas->saveLayer(nullptr, nullptr);
+        canvas->drawColor(SK_ColorCYAN);
+        {
+            SkAutoCanvasRestore autoCanvasRestore2(canvas, false);
+            SkPaint lumaFilter;
+            lumaFilter.setBlendMode(SkBlendMode::kDstIn);
+            lumaFilter.setColorFilter(SkLumaColorFilter::Make());
+            canvas->saveLayer(nullptr, &lumaFilter);
+
+            canvas->drawColor(SK_ColorTRANSPARENT);
+            SkPaint paint;
+            paint.setColor(SK_ColorGRAY);
+            canvas->drawRect(SkRect{0, 0, 128, 256}, paint);
+        }
+    }
+}
diff --git a/gm/cubicpaths.cpp b/gm/cubicpaths.cpp
index 5c56167..14f8ae6 100644
--- a/gm/cubicpaths.cpp
+++ b/gm/cubicpaths.cpp
@@ -198,14 +198,10 @@
         SkPaint titlePaint;
         titlePaint.setColor(SK_ColorBLACK);
         titlePaint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&titlePaint);
-        titlePaint.setTextSize(15 * SK_Scalar1);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 15);
         const char title[] = "Cubic Drawn Into Rectangle Clips With "
                              "Indicated Style, Fill and Linecaps, with stroke width 10";
-        canvas->drawString(title,
-                           20 * SK_Scalar1,
-                           20 * SK_Scalar1,
-                           titlePaint);
+        canvas->drawString(title, 20, 20, font, titlePaint);
 
         SkRandom rand;
         SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
@@ -241,18 +237,10 @@
 
                     SkPaint labelPaint;
                     labelPaint.setColor(color);
-                    labelPaint.setAntiAlias(true);
-                    sk_tool_utils::set_portable_typeface(&labelPaint);
-                    labelPaint.setTextSize(10 * SK_Scalar1);
-                    canvas->drawString(gStyles[style].fName,
-                                       0, rect.height() + 12 * SK_Scalar1,
-                                       labelPaint);
-                    canvas->drawString(gFills[fill].fName,
-                                       0, rect.height() + 24 * SK_Scalar1,
-                                       labelPaint);
-                    canvas->drawString(gCaps[cap].fName,
-                                       0, rect.height() + 36 * SK_Scalar1,
-                                       labelPaint);
+                    font.setSize(10);
+                    canvas->drawString(gStyles[style].fName, 0, rect.height() + 12, font, labelPaint);
+                    canvas->drawString(gFills[fill].fName, 0, rect.height() + 24, font, labelPaint);
+                    canvas->drawString(gCaps[cap].fName, 0, rect.height() + 36, font, labelPaint);
                 }
                 canvas->restore();
             }
@@ -340,14 +328,10 @@
         SkPaint titlePaint;
         titlePaint.setColor(SK_ColorBLACK);
         titlePaint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&titlePaint);
-        titlePaint.setTextSize(15 * SK_Scalar1);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 15);
         const char title[] = "Cubic Closed Drawn Into Rectangle Clips With "
                              "Indicated Style, Fill and Linecaps, with stroke width 10";
-        canvas->drawString(title,
-                           20 * SK_Scalar1,
-                           20 * SK_Scalar1,
-                           titlePaint);
+        canvas->drawString(title, 20, 20, font, titlePaint);
 
         SkRandom rand;
         SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
@@ -384,17 +368,10 @@
                     SkPaint labelPaint;
                     labelPaint.setColor(color);
                     labelPaint.setAntiAlias(true);
-                    sk_tool_utils::set_portable_typeface(&labelPaint);
-                    labelPaint.setTextSize(10 * SK_Scalar1);
-                    canvas->drawString(gStyles[style].fName,
-                                       0, rect.height() + 12 * SK_Scalar1,
-                                       labelPaint);
-                    canvas->drawString(gFills[fill].fName,
-                                       0, rect.height() + 24 * SK_Scalar1,
-                                       labelPaint);
-                    canvas->drawString(gCaps[cap].fName,
-                                       0, rect.height() + 36 * SK_Scalar1,
-                                       labelPaint);
+                    font.setSize(10);
+                    canvas->drawString(gStyles[style].fName, 0, rect.height() + 12, font, labelPaint);
+                    canvas->drawString(gFills[fill].fName, 0, rect.height() + 24, font, labelPaint);
+                    canvas->drawString(gCaps[cap].fName, 0, rect.height() + 36, font, labelPaint);
                 }
                 canvas->restore();
             }
diff --git a/gm/daa.cpp b/gm/daa.cpp
new file mode 100644
index 0000000..3b049f7
--- /dev/null
+++ b/gm/daa.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkFont.h"
+#include "SkPath.h"
+
+// This GM shows off a flaw in delta-based rasterizers (DAA, CCPR, etc.).
+// See also the bottom of dashing4 and skia:6886.
+
+static const int K = 50;
+
+DEF_SIMPLE_GM(daa, canvas, K+350, K) {
+    SkPaint paint;
+    paint.setAntiAlias(true);
+
+    paint.setColor(SK_ColorBLACK);
+    canvas->drawString("Should be a green square with no red showing through.",
+                       K*1.5f, K/2, SkFont(), paint);
+
+    paint.setColor(SK_ColorRED);
+    canvas->drawRect({0,0,K,K}, paint);
+
+    SkPath path;
+    SkPoint tri1[] = {{0,0},{K,K},{0,K},{0,0}};
+    SkPoint tri2[] = {{0,0},{K,K},{K,0},{0,0}};
+    path.addPoly(tri1, SK_ARRAY_COUNT(tri1), false);
+    path.addPoly(tri2, SK_ARRAY_COUNT(tri2), false);
+
+    paint.setColor(SK_ColorGREEN);
+    canvas->drawPath(path, paint);
+}
diff --git a/gm/dashcircle.cpp b/gm/dashcircle.cpp
index 72e3d1c..3168a0e 100644
--- a/gm/dashcircle.cpp
+++ b/gm/dashcircle.cpp
@@ -225,3 +225,39 @@
 };
 
 DEF_GM(return new DashCircle2GM;)
+
+DEF_SIMPLE_GM(maddash, canvas, 1600, 1600) {
+    canvas->drawRect({0, 0, 1600, 1600}, SkPaint());
+    SkPaint p;
+    p.setColor(SK_ColorRED);
+    p.setAntiAlias(true);
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setStrokeWidth(380);
+
+    SkScalar intvls[] = { 2.5, 10 /* 1200 */ };
+    p.setPathEffect(SkDashPathEffect::Make(intvls, 2, 0));
+
+    canvas->drawCircle(400, 400, 200, p);
+
+    SkPath path;
+    path.moveTo(800, 400);
+    path.quadTo(1000, 400, 1000, 600);
+    path.quadTo(1000, 800, 800, 800);
+    path.quadTo(600, 800, 600, 600);
+    path.quadTo(600, 400, 800, 400);
+    path.close();
+    canvas->translate(350, 150);
+    p.setStrokeWidth(320);
+    canvas->drawPath(path, p);
+
+    path.reset();
+    path.moveTo(800, 400);
+    path.cubicTo(900, 400, 1000, 500, 1000, 600);
+    path.cubicTo(1000, 700, 900, 800, 800, 800);
+    path.cubicTo(700, 800, 600, 700, 600, 600);
+    path.cubicTo(600, 500, 700, 400, 800, 400);
+    path.close();
+    canvas->translate(-550, 500);
+    p.setStrokeWidth(300);
+    canvas->drawPath(path, p);
+}
diff --git a/gm/dashing.cpp b/gm/dashing.cpp
index 9a52689..cacdc00 100644
--- a/gm/dashing.cpp
+++ b/gm/dashing.cpp
@@ -560,12 +560,13 @@
     p.setStrokeWidth(10);
     p.setStrokeCap(SkPaint::kRound_Cap);
     p.setStrokeJoin(SkPaint::kRound_Join);
-    p.setTextSize(100);
     p.setARGB(0xff, 0xbb, 0x00, 0x00);
-    sk_tool_utils::set_portable_typeface(&p);
+
+    SkFont font(sk_tool_utils::create_portable_typeface(), 100);
+
     const SkScalar intervals[] = { 12, 12 };
     p.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), 0));
-    canvas->drawString("Sausages", 10, 90, p);
+    canvas->drawString("Sausages", 10, 90, font, p);
     canvas->drawLine(8, 120, 456, 120, p);
 }
 
diff --git a/gm/degeneratesegments.cpp b/gm/degeneratesegments.cpp
index 9de66ea..a2c9e43 100644
--- a/gm/degeneratesegments.cpp
+++ b/gm/degeneratesegments.cpp
@@ -289,15 +289,11 @@
         SkPaint titlePaint;
         titlePaint.setColor(SK_ColorBLACK);
         titlePaint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&titlePaint);
-        titlePaint.setTextSize(15 * SK_Scalar1);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 15);
         const char title[] = "Random Paths Drawn Into Rectangle Clips With "
                              "Indicated Style, Fill and Linecaps, "
                              "with Stroke width 6";
-        canvas->drawString(title,
-                           20 * SK_Scalar1,
-                           20 * SK_Scalar1,
-                           titlePaint);
+        canvas->drawString(title, 20, 20, font, titlePaint);
 
         SkRandom rand;
         SkRect rect = SkRect::MakeWH(220*SK_Scalar1, 50*SK_Scalar1);
@@ -349,32 +345,15 @@
                 SkPaint labelPaint;
                 labelPaint.setColor(color);
                 labelPaint.setAntiAlias(true);
-                sk_tool_utils::set_portable_typeface(&labelPaint);
-                labelPaint.setTextSize(10 * SK_Scalar1);
-                canvas->drawString(style.fName,
-                                   0, rect.height() + 12 * SK_Scalar1,
-                                   labelPaint);
-                canvas->drawString(fill.fName,
-                                   0, rect.height() + 24 * SK_Scalar1,
-                                   labelPaint);
-                canvas->drawString(cap.fName,
-                                   0, rect.height() + 36 * SK_Scalar1,
-                                   labelPaint);
-                canvas->drawString(gSegmentNames[s1],
-                                   0, rect.height() + 48 * SK_Scalar1,
-                                   labelPaint);
-                canvas->drawString(gSegmentNames[s2],
-                                   0, rect.height() + 60 * SK_Scalar1,
-                                   labelPaint);
-                canvas->drawString(gSegmentNames[s3],
-                                   0, rect.height() + 72 * SK_Scalar1,
-                                   labelPaint);
-                canvas->drawString(gSegmentNames[s4],
-                                   0, rect.height() + 84 * SK_Scalar1,
-                                   labelPaint);
-                canvas->drawString(gSegmentNames[s5],
-                                   0, rect.height() + 96 * SK_Scalar1,
-                                   labelPaint);
+                font.setSize(10);
+                canvas->drawString(style.fName, 0, rect.height() + 12, font, labelPaint);
+                canvas->drawString(fill.fName, 0, rect.height() + 24, font, labelPaint);
+                canvas->drawString(cap.fName, 0, rect.height() + 36, font, labelPaint);
+                canvas->drawString(gSegmentNames[s1], 0, rect.height() + 48, font, labelPaint);
+                canvas->drawString(gSegmentNames[s2], 0, rect.height() + 60, font, labelPaint);
+                canvas->drawString(gSegmentNames[s3], 0, rect.height() + 72, font, labelPaint);
+                canvas->drawString(gSegmentNames[s4], 0, rect.height() + 84, font, labelPaint);
+                canvas->drawString(gSegmentNames[s5], 0, rect.height() + 96, font, labelPaint);
             }
             canvas->restore();
         }
diff --git a/gm/dftext.cpp b/gm/dftext.cpp
index 1fbb131..d0bf1af 100644
--- a/gm/dftext.cpp
+++ b/gm/dftext.cpp
@@ -7,8 +7,10 @@
 
 #include "Resources.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkStream.h"
 #include "SkSurface.h"
+#include "SkTextBlob.h"
 #include "SkTo.h"
 #include "SkTypeface.h"
 #include "gm.h"
@@ -57,9 +59,9 @@
 
         SkPaint paint;
         paint.setAntiAlias(true);
-        paint.setSubpixelText(true);
 
-        sk_tool_utils::set_portable_typeface(&paint, "serif");
+        SkFont font(sk_tool_utils::create_portable_typeface("serif", SkFontStyle()));
+        font.setSubpixel(true);
 
         const char* text = "Hamburgefons";
         const size_t textLen = strlen(text);
@@ -71,9 +73,9 @@
             SkAutoCanvasRestore acr(canvas, true);
             canvas->translate(x, y);
             canvas->scale(scales[i], scales[i]);
-            paint.setTextSize(textSizes[i]);
-            canvas->drawText(text, textLen, 0, 0, paint);
-            y += paint.getFontMetrics(nullptr)*scales[i];
+            font.setSize(textSizes[i]);
+            canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, 0, 0, font, paint);
+            y += font.getMetrics(nullptr)*scales[i];
         }
 
         // check rotation
@@ -85,14 +87,14 @@
             canvas->translate(SkIntToScalar(10 + i * 200), -80);
             canvas->rotate(SkIntToScalar(i * 5), rotX, rotY);
             for (int ps = 6; ps <= 32; ps += 3) {
-                paint.setTextSize(SkIntToScalar(ps));
-                canvas->drawText(text, textLen, rotX, rotY, paint);
-                rotY += paint.getFontMetrics(nullptr);
+                font.setSize(SkIntToScalar(ps));
+                canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, rotX, rotY, font, paint);
+                rotY += font.getMetrics(nullptr);
             }
         }
 
         // check scaling down
-        paint.setLCDRenderText(true);
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
         x = SkIntToScalar(680);
         y = SkIntToScalar(20);
         size_t arraySize = SK_ARRAY_COUNT(textSizes);
@@ -101,9 +103,9 @@
             canvas->translate(x, y);
             SkScalar scaleFactor = SkScalarInvert(scales[arraySize - i - 1]);
             canvas->scale(scaleFactor, scaleFactor);
-            paint.setTextSize(textSizes[i]);
-            canvas->drawText(text, textLen, 0, 0, paint);
-            y += paint.getFontMetrics(nullptr)*scaleFactor;
+            font.setSize(textSizes[i]);
+            canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, 0, 0, font, paint);
+            y += font.getMetrics(nullptr)*scaleFactor;
         }
 
         // check pos text
@@ -112,20 +114,15 @@
 
             canvas->scale(2.0f, 2.0f);
 
-            SkAutoTArray<SkPoint>  pos(SkToInt(textLen));
-            SkAutoTArray<SkScalar> widths(SkToInt(textLen));
-            paint.setTextSize(textSizes[0]);
+            SkAutoTArray<SkGlyphID> glyphs(SkToInt(textLen));
+            int count = font.textToGlyphs(text, textLen, kUTF8_SkTextEncoding, glyphs.get(), textLen);
+            SkAutoTArray<SkPoint>  pos(count);
+            font.setSize(textSizes[0]);
+            font.getPos(glyphs.get(), count, pos.get(), {340, 75});
 
-            paint.getTextWidths(text, textLen, &widths[0]);
-
-            SkScalar x = SkIntToScalar(340);
-            SkScalar y = SkIntToScalar(75);
-            for (unsigned int i = 0; i < textLen; ++i) {
-                pos[i].set(x, y);
-                x += widths[i];
-            }
-
-            canvas->drawPosText(text, textLen, &pos[0], paint);
+            auto blob = SkTextBlob::MakeFromPosText(glyphs.get(), count * sizeof(SkGlyphID),
+                                                    pos.get(), font, kGlyphID_SkTextEncoding);
+            canvas->drawTextBlob(blob, 0, 0, paint);
         }
 
 
@@ -143,12 +140,12 @@
 
         x = SkIntToScalar(680);
         y = SkIntToScalar(235);
-        paint.setTextSize(SkIntToScalar(19));
+        font.setSize(SkIntToScalar(19));
         for (size_t i = 0; i < SK_ARRAY_COUNT(fg); ++i) {
             paint.setColor(fg[i]);
 
-            canvas->drawText(text, textLen, x, y, paint);
-            y += paint.getFontMetrics(nullptr);
+            canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, x, y, font, paint);
+            y += font.getMetrics(nullptr);
         }
 
         paint.setColor(0xFF181C18);
@@ -157,33 +154,33 @@
 
         x = SkIntToScalar(830);
         y = SkIntToScalar(235);
-        paint.setTextSize(SkIntToScalar(19));
+        font.setSize(SkIntToScalar(19));
         for (size_t i = 0; i < SK_ARRAY_COUNT(fg); ++i) {
             paint.setColor(fg[i]);
 
-            canvas->drawText(text, textLen, x, y, paint);
-            y += paint.getFontMetrics(nullptr);
+            canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, x, y, font, paint);
+            y += font.getMetrics(nullptr);
         }
 
         // check skew
         {
-            paint.setLCDRenderText(false);
+            font.setEdging(SkFont::Edging::kAntiAlias);
             SkAutoCanvasRestore acr(canvas, true);
             canvas->skew(0.0f, 0.151515f);
-            paint.setTextSize(SkIntToScalar(32));
-            canvas->drawText(text, textLen, 745, 70, paint);
+            font.setSize(SkIntToScalar(32));
+            canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, 745, 70, font, paint);
         }
         {
-            paint.setLCDRenderText(true);
+            font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
             SkAutoCanvasRestore acr(canvas, true);
             canvas->skew(0.5f, 0.0f);
-            paint.setTextSize(SkIntToScalar(32));
-            canvas->drawText(text, textLen, 580, 125, paint);
+            font.setSize(SkIntToScalar(32));
+            canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, 580, 125, font, paint);
         }
 
         // check perspective
         {
-            paint.setLCDRenderText(false);
+            font.setEdging(SkFont::Edging::kAntiAlias);
             SkAutoCanvasRestore acr(canvas, true);
             SkMatrix persp;
             persp.setAll(0.9839f, 0, 0,
@@ -191,12 +188,12 @@
                          0.0002352f, -0.0003844f, 1);
             canvas->concat(persp);
             canvas->translate(1100, -295);
-            paint.setTextSize(37.5f);
-            canvas->drawText(text, textLen, 0, 0, paint);
+            font.setSize(37.5f);
+            canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, 0, 0, font, paint);
         }
         {
-            paint.setSubpixelText(false);
-            paint.setAntiAlias(false);
+            font.setSubpixel(false);
+            font.setEdging(SkFont::Edging::kAlias);
             SkAutoCanvasRestore acr(canvas, true);
             SkMatrix persp;
             persp.setAll(0.9839f, 0, 0,
@@ -205,18 +202,17 @@
             canvas->concat(persp);
             canvas->translate(1075, -245);
             canvas->scale(375, 375);
-            paint.setTextSize(0.1f);
-            canvas->drawText(text, textLen, 0, 0, paint);
+            font.setSize(0.1f);
+            canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, 0, 0, font, paint);
         }
 
         // check color emoji
         if (fEmojiTypeface) {
-            SkPaint emojiPaint;
-            emojiPaint.setSubpixelText(true);
-            emojiPaint.setAntiAlias(true);
-            emojiPaint.setTypeface(fEmojiTypeface);
-            emojiPaint.setTextSize(SkIntToScalar(19));
-            canvas->drawString(fEmojiText, 670, 90, emojiPaint);
+            SkFont emoiFont;
+            emoiFont.setSubpixel(true);
+            emoiFont.setTypeface(fEmojiTypeface);
+            emoiFont.setSize(SkIntToScalar(19));
+            canvas->drawSimpleText(fEmojiText, strlen(fEmojiText), kUTF8_SkTextEncoding, 670, 90, emoiFont, paint);
         }
 
         // render offscreen buffer
diff --git a/gm/dftext_blob_persp.cpp b/gm/dftext_blob_persp.cpp
index e7a01e7..4a918cb 100644
--- a/gm/dftext_blob_persp.cpp
+++ b/gm/dftext_blob_persp.cpp
@@ -33,18 +33,19 @@
 
     void onOnceBeforeDraw() override {
         for (int i = 0; i < 3; ++i) {
-            SkPaint paint;
-            paint.setTextSize(32);
-            paint.setAntiAlias(i > 0);
-            paint.setLCDRenderText(i > 1);
-            paint.setSubpixelText(true);
+            SkFont font;
+            font.setSize(32);
+            font.setEdging(i == 0 ? SkFont::Edging::kAlias :
+                           (i == 1 ? SkFont::Edging::kAntiAlias :
+                            SkFont::Edging::kSubpixelAntiAlias));
+            font.setSubpixel(true);
             SkTextBlobBuilder builder;
-            sk_tool_utils::add_to_text_blob(&builder, "SkiaText", paint, 0, 0);
+            sk_tool_utils::add_to_text_blob(&builder, "SkiaText", font, 0, 0);
             fBlobs.emplace_back(builder.make());
         }
     }
 
-    virtual void onDraw(SkCanvas* inputCanvas) override {
+    void onDraw(SkCanvas* inputCanvas) override {
     // set up offscreen rendering with distance field text
         GrContext* ctx = inputCanvas->getGrContext();
         SkISize size = this->onISize();
diff --git a/gm/downsamplebitmap.cpp b/gm/downsamplebitmap.cpp
index 8fef1d0..cc521d0 100644
--- a/gm/downsamplebitmap.cpp
+++ b/gm/downsamplebitmap.cpp
@@ -83,18 +83,18 @@
     canvas.drawColor(SK_ColorWHITE);
 
     SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setSubpixelText(true);
-    paint.setTextSize(textSize);
+    SkFont font;
+    font.setSubpixel(true);
+    font.setSize(textSize);
 
-    sk_tool_utils::set_portable_typeface(&paint, "serif", SkFontStyle());
-    canvas.drawString("Hamburgefons", textSize/2, 1.2f*textSize, paint);
-    sk_tool_utils::set_portable_typeface(&paint, "serif", SkFontStyle::Bold());
-    canvas.drawString("Hamburgefons", textSize/2, 2.4f*textSize, paint);
-    sk_tool_utils::set_portable_typeface(&paint, "serif", SkFontStyle::Italic());
-    canvas.drawString("Hamburgefons", textSize/2, 3.6f*textSize, paint);
-    sk_tool_utils::set_portable_typeface(&paint, "serif", SkFontStyle::BoldItalic());
-    canvas.drawString("Hamburgefons", textSize/2, 4.8f*textSize, paint);
+    font.setTypeface(sk_tool_utils::create_portable_typeface("serif", SkFontStyle()));
+    canvas.drawString("Hamburgefons", textSize/2, 1.2f*textSize, font, paint);
+    font.setTypeface(sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Bold()));
+    canvas.drawString("Hamburgefons", textSize/2, 2.4f*textSize, font, paint);
+    font.setTypeface(sk_tool_utils::create_portable_typeface("serif", SkFontStyle::Italic()));
+    canvas.drawString("Hamburgefons", textSize/2, 3.6f*textSize, font, paint);
+    font.setTypeface(sk_tool_utils::create_portable_typeface("serif", SkFontStyle::BoldItalic()));
+    canvas.drawString("Hamburgefons", textSize/2, 4.8f*textSize, font, paint);
 
     return bm;
 }
diff --git a/gm/drawatlas.cpp b/gm/drawatlas.cpp
index cf237ae..5997bf6 100644
--- a/gm/drawatlas.cpp
+++ b/gm/drawatlas.cpp
@@ -11,6 +11,7 @@
 #include "SkCanvas.h"
 #include "SkRSXform.h"
 #include "SkSurface.h"
+#include "SkTextBlob.h"
 #include "sk_tool_utils.h"
 
 class DrawAtlasGM : public skiagm::GM {
@@ -98,27 +99,31 @@
 DEF_GM( return new DrawAtlasGM; )
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+#include "SkFont.h"
+#include "SkFontPriv.h"
 #include "SkPath.h"
 #include "SkPathMeasure.h"
 
 static void draw_text_on_path(SkCanvas* canvas, const void* text, size_t length,
-                              const SkPoint xy[], const SkPath& path, const SkPaint& paint,
+                              const SkPoint xy[], const SkPath& path, const SkFont& font, const SkPaint& paint,
                               float baseline_offset) {
     SkPathMeasure meas(path, false);
 
-    int count = paint.countText(text, length);
+    int count = font.countText(text, length, kUTF8_SkTextEncoding);
     size_t size = count * (sizeof(SkRSXform) + sizeof(SkScalar));
     SkAutoSMalloc<512> storage(size);
     SkRSXform* xform = (SkRSXform*)storage.get();
     SkScalar* widths = (SkScalar*)(xform + count);
 
     // Compute a conservative bounds so we can cull the draw
-    const SkRect font = paint.getFontBounds();
-    const SkScalar max = SkTMax(SkTMax(SkScalarAbs(font.fLeft), SkScalarAbs(font.fRight)),
-                                SkTMax(SkScalarAbs(font.fTop), SkScalarAbs(font.fBottom)));
+    const SkRect fontb = SkFontPriv::GetFontBounds(font);
+    const SkScalar max = SkTMax(SkTMax(SkScalarAbs(fontb.fLeft), SkScalarAbs(fontb.fRight)),
+                                SkTMax(SkScalarAbs(fontb.fTop), SkScalarAbs(fontb.fBottom)));
     const SkRect bounds = path.getBounds().makeOutset(max, max);
 
-    paint.getTextWidths(text, length, widths);
+    SkAutoTArray<SkGlyphID> glyphs(count);
+    font.textToGlyphs(text, length, kUTF8_SkTextEncoding, glyphs.get(), count);
+    font.getWidths(glyphs.get(), count, widths);
 
     for (int i = 0; i < count; ++i) {
         // we want to position each character on the center of its advance
@@ -137,7 +142,9 @@
         xform[i].fTy   = pos.y() + tan.x() * xy[i].y() - tan.y() * offset;
     }
 
-    canvas->drawTextRSXform(text, length, &xform[0], &bounds, paint);
+    canvas->drawTextBlob(SkTextBlob::MakeFromRSXform(glyphs.get(), count * sizeof(SkGlyphID),
+                                         &xform[0], font, kGlyphID_SkTextEncoding),
+                         0, 0, paint);
 
     if (true) {
         SkPaint p;
@@ -158,10 +165,12 @@
     const int N = sizeof(text0) - 1;
     SkPoint pos[N];
 
+    SkFont font;
+    font.setSize(100);
+
     SkPaint paint;
     paint.setShader(make_shader());
     paint.setAntiAlias(true);
-    paint.setTextSize(100);
     if (doStroke) {
         paint.setStyle(SkPaint::kStroke_Style);
         paint.setStrokeWidth(2.25f);
@@ -171,7 +180,7 @@
     SkScalar x = 0;
     for (int i = 0; i < N; ++i) {
         pos[i].set(x, 0);
-        x += paint.measureText(&text0[i], 1);
+        x += font.measureText(&text0[i], 1, kUTF8_SkTextEncoding, nullptr, &paint);
     }
 
     SkPath path;
@@ -183,7 +192,7 @@
     for (auto d : dirs) {
         path.reset();
         path.addOval(SkRect::MakeXYWH(160, 160, 540, 540), d);
-        draw_text_on_path(canvas, text0, N, pos, path, paint, baseline_offset);
+        draw_text_on_path(canvas, text0, N, pos, path, font, paint, baseline_offset);
     }
 
     paint.reset();
@@ -200,6 +209,34 @@
     }
 }
 
+// Exercise xform blob and its bounds
+DEF_SIMPLE_GM(blob_rsxform, canvas, 500, 100) {
+    SkFont font;
+    font.setTypeface(sk_tool_utils::create_portable_typeface());
+    font.setSize(50);
+
+    const char text[] = "CrazyXform";
+    constexpr size_t len = sizeof(text) - 1;
+
+    SkRSXform xforms[len];
+    SkScalar scale = 1;
+    SkScalar x = 0, y = 0;
+    for (size_t i = 0; i < len; ++i) {
+        scale = SkScalarSin(i * SK_ScalarPI / (len-1)) * 0.75f + 0.5f;
+        xforms[i] = SkRSXform::Make(scale, 0, x, y);
+        x += 50 * scale;
+    }
+
+    auto blob = SkTextBlob::MakeFromRSXform(text, len, xforms, font);
+
+    SkPoint offset = { 20, 70 };
+    SkPaint paint;
+    paint.setColor(0xFFCCCCCC);
+    canvas->drawRect(blob->bounds().makeOffset(offset.fX, offset.fY), paint);
+    paint.setColor(SK_ColorBLACK);
+    canvas->drawTextBlob(blob, offset.fX, offset.fY, paint);
+}
+
 #include "Resources.h"
 #include "SkColorFilter.h"
 #include "SkVertices.h"
diff --git a/gm/drawatlascolor.cpp b/gm/drawatlascolor.cpp
index 3122777..82bf998 100644
--- a/gm/drawatlascolor.cpp
+++ b/gm/drawatlascolor.cpp
@@ -128,16 +128,12 @@
             quadColors[i] = gColors[i];
         }
 
-        SkPaint textP;
-        textP.setTextSize(SkIntToScalar(kTextPad));
-        textP.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&textP, nullptr);
+        SkFont font(sk_tool_utils::create_portable_typeface(), kTextPad);
 
         for (int i = 0; i < numModes; ++i) {
             const char* label = SkBlendMode_Name(gModes[i]);
-            canvas->drawString(label,
-                             i*(target.width()+kPad)+kPad, SkIntToScalar(kTextPad),
-                             textP);
+            canvas->drawString(label, i*(target.width()+kPad)+kPad, SkIntToScalar(kTextPad),
+                               font, paint);
         }
 
         for (int i = 0; i < numModes; ++i) {
diff --git a/gm/drawbitmaprect.cpp b/gm/drawbitmaprect.cpp
index 77125c5..251c247 100644
--- a/gm/drawbitmaprect.cpp
+++ b/gm/drawbitmaprect.cpp
@@ -170,13 +170,13 @@
         SkPaint blackPaint;
         SkScalar titleHeight = SK_Scalar1 * 24;
         blackPaint.setColor(SK_ColorBLACK);
-        blackPaint.setTextSize(titleHeight);
         blackPaint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&blackPaint);
+
+        SkFont font(sk_tool_utils::create_portable_typeface(), titleHeight);
+
         SkString title;
         title.printf("Bitmap size: %d x %d", gBmpSize, gBmpSize);
-        canvas->drawString(title, 0,
-                         titleHeight, blackPaint);
+        canvas->drawString(title, 0, titleHeight, font, blackPaint);
 
         canvas->translate(0, SK_Scalar1 * kPadY / 2  + titleHeight);
         int rowCount = 0;
@@ -191,12 +191,9 @@
                 label.appendf("%d x %d", w, h);
                 blackPaint.setAntiAlias(true);
                 blackPaint.setStyle(SkPaint::kFill_Style);
-                blackPaint.setTextSize(SK_Scalar1 * 10);
-                SkScalar baseline = dstRect.height() +
-                                    blackPaint.getTextSize() + SK_Scalar1 * 3;
-                canvas->drawString(label,
-                                    0, baseline,
-                                    blackPaint);
+                font.setSize(SK_Scalar1 * 10);
+                SkScalar baseline = dstRect.height() + font.getSize() + SK_Scalar1 * 3;
+                canvas->drawString(label, 0, baseline, font, blackPaint);
                 blackPaint.setStyle(SkPaint::kStroke_Style);
                 blackPaint.setStrokeWidth(SK_Scalar1);
                 blackPaint.setAntiAlias(false);
diff --git a/gm/drawlooper.cpp b/gm/drawlooper.cpp
index 2bb69f2..a9fe09f 100644
--- a/gm/drawlooper.cpp
+++ b/gm/drawlooper.cpp
@@ -37,13 +37,13 @@
 
         SkPaint  paint;
         paint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(SkIntToScalar(72));
         paint.setLooper(fLooper);
 
+        SkFont font(sk_tool_utils::create_portable_typeface(), 72);
+
         canvas->drawCircle(50, 50, 30, paint);
         canvas->drawRect({ 150, 50, 200, 100 }, paint);
-        canvas->drawString("Looper", 230, 100, paint);
+        canvas->drawString("Looper", 230, 100, font, paint);
     }
 
 private:
diff --git a/gm/drawquadset.cpp b/gm/drawquadset.cpp
index 9c3c6f0..f6aaa88 100644
--- a/gm/drawquadset.cpp
+++ b/gm/drawquadset.cpp
@@ -21,12 +21,7 @@
 static constexpr int kColCount = 3;
 
 static void draw_text(SkCanvas* canvas, const char* text) {
-    SkPaint paint;
-    paint.setColor(SK_ColorBLACK);
-    paint.setTextSize(12.0f);
-    paint.setAntiAlias(true);
-
-    canvas->drawString(text, 0, 0, paint);
+    canvas->drawString(text, 0, 0, SkFont(nullptr, 12), SkPaint());
 }
 
 static void draw_gradient_tiles(GrRenderTargetContext* rtc, const SkMatrix& view,
diff --git a/gm/dropshadowimagefilter.cpp b/gm/dropshadowimagefilter.cpp
index f1dfb6d..1710dbd 100644
--- a/gm/dropshadowimagefilter.cpp
+++ b/gm/dropshadowimagefilter.cpp
@@ -40,12 +40,11 @@
     paint.setImageFilter(std::move(imf));
     paint.setColor(SK_ColorGREEN);
     paint.setAntiAlias(true);
-    sk_tool_utils::set_portable_typeface(&paint);
-    paint.setTextSize(r.height()/2);
+
+    SkFont font(sk_tool_utils::create_portable_typeface(), r.height()/2);
     canvas->save();
     canvas->clipRect(r);
-    SkTextUtils::DrawString(canvas, "Text", r.centerX(), r.centerY(), paint,
-                            SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, "Text", r.centerX(), r.centerY(), font, paint, SkTextUtils::kCenter_Align);
     canvas->restore();
 }
 
diff --git a/gm/dstreadshuffle.cpp b/gm/dstreadshuffle.cpp
index c4c19fc..68dcfc4 100644
--- a/gm/dstreadshuffle.cpp
+++ b/gm/dstreadshuffle.cpp
@@ -82,10 +82,9 @@
                 break;
             case kText_ShapeType: {
                 const char* text = "N";
-                paint->setTextSize(100.f);
-                paint->setFakeBoldText(true);
-                sk_tool_utils::set_portable_typeface(paint);
-                canvas->drawString(text, 0.f, 100.f, *paint);
+                SkFont font(sk_tool_utils::create_portable_typeface(), 100);
+                font.setEmbolden(true);
+                canvas->drawString(text, 0.f, 100.f, font, *paint);
             }
             default:
                 break;
diff --git a/gm/emboss.cpp b/gm/emboss.cpp
index a8483d0..2a96247 100644
--- a/gm/emboss.cpp
+++ b/gm/emboss.cpp
@@ -6,10 +6,12 @@
  */
 
 #include "gm.h"
+
 #include "SkBlurMask.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
 #include "SkEmbossMaskFilter.h"
+#include "SkFont.h"
 
 static SkBitmap make_bm() {
     SkBitmap bm;
@@ -69,8 +71,7 @@
         canvas->translate(SkIntToScalar(100), 0);
 
         paint.setStyle(SkPaint::kFill_Style);
-        paint.setTextSize(50);
-        canvas->drawText("Hello", 5, 0, 50, paint);
+        canvas->drawString("Hello", 0, 50, SkFont(nullptr, 50), paint);
     }
 
 private:
diff --git a/gm/emptypath.cpp b/gm/emptypath.cpp
index 67da93f..1c77580 100644
--- a/gm/emptypath.cpp
+++ b/gm/emptypath.cpp
@@ -61,17 +61,10 @@
             {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
         };
 
-        SkPaint titlePaint;
-        titlePaint.setColor(SK_ColorBLACK);
-        titlePaint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&titlePaint);
-        titlePaint.setTextSize(15 * SK_Scalar1);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 15);
         const char title[] = "Empty Paths Drawn Into Rectangle Clips With "
                              "Indicated Style and Fill";
-        canvas->drawString(title,
-                           20 * SK_Scalar1,
-                           20 * SK_Scalar1,
-                           titlePaint);
+        canvas->drawString(title, 20.0f, 20.0f, font, SkPaint());
 
         SkRandom rand;
         SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
@@ -106,15 +99,11 @@
 
                 SkPaint labelPaint;
                 labelPaint.setColor(color);
-                labelPaint.setAntiAlias(true);
-                sk_tool_utils::set_portable_typeface(&labelPaint);
-                labelPaint.setTextSize(12 * SK_Scalar1);
-                canvas->drawString(gStyles[style].fName,
-                                   0, rect.height() + 15 * SK_Scalar1,
-                                   labelPaint);
-                canvas->drawString(gFills[fill].fName,
-                                   0, rect.height() + 28 * SK_Scalar1,
-                                   labelPaint);
+                SkFont labelFont(sk_tool_utils::create_portable_typeface(), 12);
+                canvas->drawString(gStyles[style].fName, 0, rect.height() + 15.0f,
+                                   labelFont, labelPaint);
+                canvas->drawString(gFills[fill].fName, 0, rect.height() + 28.0f,
+                                   labelFont, labelPaint);
             }
         }
         canvas->restore();
diff --git a/gm/filterbitmap.cpp b/gm/filterbitmap.cpp
index 10f6c59..9694155 100644
--- a/gm/filterbitmap.cpp
+++ b/gm/filterbitmap.cpp
@@ -13,8 +13,8 @@
 #include "SkStream.h"
 #include "SkTypeface.h"
 
-static void setTypeface(SkPaint* paint, const char name[], SkFontStyle style) {
-    sk_tool_utils::set_portable_typeface(paint, name, style);
+static void setTypeface(SkFont* font, const char name[], SkFontStyle style) {
+    font->setTypeface(sk_tool_utils::create_portable_typeface(name, style));
 }
 
 static SkSize computeSize(const SkBitmap& bm, const SkMatrix& mat) {
@@ -120,18 +120,18 @@
           canvas.drawColor(SK_ColorWHITE);
 
           SkPaint paint;
-          paint.setAntiAlias(true);
-          paint.setSubpixelText(true);
-          paint.setTextSize(fTextSize);
+          SkFont font;
+          font.setSize(fTextSize);
+          font.setSubpixel(true);
 
-          setTypeface(&paint, "serif", SkFontStyle::Normal());
-          canvas.drawString("Hamburgefons", fTextSize/2, 1.2f*fTextSize, paint);
-          setTypeface(&paint, "serif", SkFontStyle::Bold());
-          canvas.drawString("Hamburgefons", fTextSize/2, 2.4f*fTextSize, paint);
-          setTypeface(&paint, "serif", SkFontStyle::Italic());
-          canvas.drawString("Hamburgefons", fTextSize/2, 3.6f*fTextSize, paint);
-          setTypeface(&paint, "serif", SkFontStyle::BoldItalic());
-          canvas.drawString("Hamburgefons", fTextSize/2, 4.8f*fTextSize, paint);
+          setTypeface(&font, "serif", SkFontStyle::Normal());
+          canvas.drawString("Hamburgefons", fTextSize/2, 1.2f*fTextSize, font, paint);
+          setTypeface(&font, "serif", SkFontStyle::Bold());
+          canvas.drawString("Hamburgefons", fTextSize/2, 2.4f*fTextSize, font, paint);
+          setTypeface(&font, "serif", SkFontStyle::Italic());
+          canvas.drawString("Hamburgefons", fTextSize/2, 3.6f*fTextSize, font, paint);
+          setTypeface(&font, "serif", SkFontStyle::BoldItalic());
+          canvas.drawString("Hamburgefons", fTextSize/2, 4.8f*fTextSize, font, paint);
       }
   private:
       typedef FilterBitmapGM INHERITED;
diff --git a/gm/flippity.cpp b/gm/flippity.cpp
index a040ba1..3d51cdd 100644
--- a/gm/flippity.cpp
+++ b/gm/flippity.cpp
@@ -116,7 +116,7 @@
     }
 
     return sk_make_sp<SkImage_Gpu>(sk_ref_sp(context), kNeedNewImageUniqueID, kOpaque_SkAlphaType,
-                                   std::move(proxy), nullptr, SkBudgeted::kYes);
+                                   std::move(proxy), nullptr);
 }
 
 // Here we're converting from a matrix that is intended for UVs to a matrix that is intended
diff --git a/gm/fontcache.cpp b/gm/fontcache.cpp
index 44732ab..052de62 100644
--- a/gm/fontcache.cpp
+++ b/gm/fontcache.cpp
@@ -23,7 +23,7 @@
 static SkScalar draw_string(SkCanvas* canvas, const SkString& text, SkScalar x,
                            SkScalar y, const SkFont& font) {
     SkPaint paint;
-    canvas->drawSimpleText(text.c_str(), text.size(), kUTF8_SkTextEncoding, x, y, font, paint);
+    canvas->drawString(text, x, y, font, paint);
     return x + font.measureText(text.c_str(), text.size(), kUTF8_SkTextEncoding);
 }
 
@@ -68,7 +68,6 @@
             return;
         }
 
-        canvas->clear(SK_ColorLTGRAY);
         this->drawText(canvas);
         //  Debugging tool for GPU.
         static const bool kShowAtlas = false;
diff --git a/gm/fontmgr.cpp b/gm/fontmgr.cpp
index 351b7c7..3d367b4 100644
--- a/gm/fontmgr.cpp
+++ b/gm/fontmgr.cpp
@@ -10,6 +10,7 @@
 #include "SkCanvas.h"
 #include "SkCommonFlags.h"
 #include "SkFontMgr.h"
+#include "SkFontPriv.h"
 #include "SkPath.h"
 #include "SkGraphics.h"
 #include "SkTypeface.h"
@@ -18,22 +19,23 @@
 #define MAX_FAMILIES    30
 
 static SkScalar drawString(SkCanvas* canvas, const SkString& text, SkScalar x,
-                           SkScalar y, const SkPaint& paint) {
-    canvas->drawString(text, x, y, paint);
-    return x + paint.measureText(text.c_str(), text.size());
+                           SkScalar y, const SkFont& font) {
+    canvas->drawString(text, x, y, font, SkPaint());
+    return x + font.measureText(text.c_str(), text.size(), kUTF8_SkTextEncoding);
 }
 
 static SkScalar drawCharacter(SkCanvas* canvas, uint32_t character, SkScalar x,
-                              SkScalar y, SkPaint& paint, SkFontMgr* fm,
+                              SkScalar y, const SkFont& origFont, SkFontMgr* fm,
                               const char* fontName, const char* bcp47[], int bcp47Count,
                               const SkFontStyle& fontStyle) {
+    SkFont font = origFont;
     // find typeface containing the requested character and draw it
     SkString ch;
     ch.appendUnichar(character);
     sk_sp<SkTypeface> typeface(fm->matchFamilyStyleCharacter(fontName, fontStyle,
                                                              bcp47, bcp47Count, character));
-    paint.setTypeface(typeface);
-    x = drawString(canvas, ch, x, y, paint) + 20;
+    font.setTypeface(typeface);
+    x = drawString(canvas, ch, x, y, font) + 20;
 
     if (nullptr == typeface) {
         return x;
@@ -44,8 +46,8 @@
     // it expects to get the same glyph when following this pattern.
     SkString familyName;
     typeface->getFamilyName(&familyName);
-    paint.setTypeface(fm->legacyMakeTypeface(familyName.c_str(), typeface->fontStyle()));
-    return drawString(canvas, ch, x, y, paint) + 20;
+    font.setTypeface(fm->legacyMakeTypeface(familyName.c_str(), typeface->fontStyle()));
+    return drawString(canvas, ch, x, y, font) + 20;
 }
 
 static const char* zh = "zh";
@@ -72,11 +74,10 @@
 
     void onDraw(SkCanvas* canvas) override {
         SkScalar y = 20;
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setLCDRenderText(true);
-        paint.setSubpixelText(true);
-        paint.setTextSize(17);
+        SkFont font;
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
+        font.setSubpixel(true);
+        font.setSize(17);
 
         SkFontMgr* fm = fFM.get();
         int count = SkMin32(fm->countFamilies(), MAX_FAMILIES);
@@ -84,8 +85,8 @@
         for (int i = 0; i < count; ++i) {
             SkString familyName;
             fm->getFamilyName(i, &familyName);
-            paint.setTypeface(nullptr);
-            (void)drawString(canvas, familyName, 20, y, paint);
+            font.setTypeface(nullptr);
+            (void)drawString(canvas, familyName, 20, y, font);
 
             SkScalar x = 220;
 
@@ -96,14 +97,14 @@
                 set->getStyle(j, &fs, &sname);
                 sname.appendf(" [%d %d %d]", fs.weight(), fs.width(), fs.slant());
 
-                paint.setTypeface(sk_sp<SkTypeface>(set->createTypeface(j)));
-                x = drawString(canvas, sname, x, y, paint) + 20;
+                font.setTypeface(sk_sp<SkTypeface>(set->createTypeface(j)));
+                x = drawString(canvas, sname, x, y, font) + 20;
 
                 // check to see that we get different glyphs in japanese and chinese
-                x = drawCharacter(canvas, 0x5203, x, y, paint, fm, familyName.c_str(), &zh, 1, fs);
-                x = drawCharacter(canvas, 0x5203, x, y, paint, fm, familyName.c_str(), &ja, 1, fs);
+                x = drawCharacter(canvas, 0x5203, x, y, font, fm, familyName.c_str(), &zh, 1, fs);
+                x = drawCharacter(canvas, 0x5203, x, y, font, fm, familyName.c_str(), &ja, 1, fs);
                 // check that emoji characters are found
-                x = drawCharacter(canvas, 0x1f601, x, y, paint, fm, familyName.c_str(), nullptr,0, fs);
+                x = drawCharacter(canvas, 0x1f601, x, y, font, fm, familyName.c_str(), nullptr,0, fs);
             }
             y += 24;
         }
@@ -134,9 +135,8 @@
         return SkISize::Make(640, 1024);
     }
 
-    void iterateFamily(SkCanvas* canvas, const SkPaint& paint,
-                       SkFontStyleSet* fset) {
-        SkPaint p(paint);
+    void iterateFamily(SkCanvas* canvas, const SkFont& font, SkFontStyleSet* fset) {
+        SkFont f(font);
         SkScalar y = 0;
 
         for (int j = 0; j < fset->count(); ++j) {
@@ -146,15 +146,14 @@
 
             sname.appendf(" [%d %d]", fs.weight(), fs.width());
 
-            p.setTypeface(sk_sp<SkTypeface>(fset->createTypeface(j)));
-            (void)drawString(canvas, sname, 0, y, p);
+            f.setTypeface(sk_sp<SkTypeface>(fset->createTypeface(j)));
+            (void)drawString(canvas, sname, 0, y, f);
             y += 24;
         }
     }
 
-    void exploreFamily(SkCanvas* canvas, const SkPaint& paint,
-                       SkFontStyleSet* fset) {
-        SkPaint p(paint);
+    void exploreFamily(SkCanvas* canvas, const SkFont& font, SkFontStyleSet* fset) {
+        SkFont f(font);
         SkScalar y = 0;
 
         for (int weight = 100; weight <= 900; weight += 200) {
@@ -164,8 +163,8 @@
                 if (face) {
                     SkString str;
                     str.printf("request [%d %d]", fs.weight(), fs.width());
-                    p.setTypeface(std::move(face));
-                    (void)drawString(canvas, str, 0, y, p);
+                    f.setTypeface(std::move(face));
+                    (void)drawString(canvas, str, 0, y, f);
                     y += 24;
                 }
             }
@@ -173,11 +172,10 @@
     }
 
     void onDraw(SkCanvas* canvas) override {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setLCDRenderText(true);
-        paint.setSubpixelText(true);
-        paint.setTextSize(17);
+        SkFont font;
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
+        font.setSubpixel(true);
+        font.setSize(17);
 
         const char* gNames[] = {
             "Helvetica Neue", "Arial", "sans"
@@ -195,9 +193,9 @@
         }
 
         canvas->translate(20, 40);
-        this->exploreFamily(canvas, paint, fset.get());
+        this->exploreFamily(canvas, font, fset.get());
         canvas->translate(150, 0);
-        this->iterateFamily(canvas, paint, fset.get());
+        this->iterateFamily(canvas, font, fset.get());
     }
 
 private:
@@ -218,19 +216,19 @@
         fFM = SkFontMgr::RefDefault();
     }
 
-    static void show_bounds(SkCanvas* canvas, const SkPaint& paint, SkScalar x, SkScalar y,
+    static void show_bounds(SkCanvas* canvas, const SkFont& font, SkScalar x, SkScalar y,
                             SkColor boundsColor)
     {
-        SkPaint glyphPaint(paint);
-        SkRect fontBounds = glyphPaint.getFontBounds();
-        fontBounds.offset(x, y);
-        SkPaint boundsPaint(glyphPaint);
+        SkRect fontBounds = SkFontPriv::GetFontBounds(font).makeOffset(x, y);
+
+        SkPaint boundsPaint;
+        boundsPaint.setAntiAlias(true);
         boundsPaint.setColor(boundsColor);
         boundsPaint.setStyle(SkPaint::kStroke_Style);
         canvas->drawRect(fontBounds, boundsPaint);
 
         SkFontMetrics fm;
-        glyphPaint.getFontMetrics(&fm);
+        font.getMetrics(&fm);
         SkPaint metricsPaint(boundsPaint);
         metricsPaint.setStyle(SkPaint::kFill_Style);
         metricsPaint.setAlpha(0x40);
@@ -252,13 +250,12 @@
 
         SkGlyphID left = 0, right = 0, top = 0, bottom = 0;
         {
-            int numGlyphs = glyphPaint.getTypeface()->countGlyphs();
+            int numGlyphs = font.getTypeface()->countGlyphs();
             SkRect min = {0, 0, 0, 0};
-            glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding);
             for (int i = 0; i < numGlyphs; ++i) {
                 SkGlyphID glyphId = i;
                 SkRect cur;
-                glyphPaint.measureText(&glyphId, sizeof(glyphId), &cur);
+                font.getBounds(&glyphId, 1, &cur, nullptr);
                 if (cur.fLeft   < min.fLeft  ) { min.fLeft   = cur.fLeft;   left   = i; }
                 if (cur.fTop    < min.fTop   ) { min.fTop    = cur.fTop ;   top    = i; }
                 if (min.fRight  < cur.fRight ) { min.fRight  = cur.fRight;  right  = i; }
@@ -273,27 +270,28 @@
             {fontBounds.centerX(), fontBounds.bottom()}
         };
 
-        SkPaint labelPaint;
-        labelPaint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&labelPaint);
+        SkFont labelFont;
+        labelFont.setEdging(SkFont::Edging::kAntiAlias);
+        labelFont.setTypeface(sk_tool_utils::create_portable_typeface());
+
         if (FLAGS_veryVerbose) {
             SkString name;
-            paint.getTypeface()->getFamilyName(&name);
-            canvas->drawText(name.c_str(), name.size(),
-                             fontBounds.fLeft, fontBounds.fBottom, labelPaint);
+            font.getTypeface()->getFamilyName(&name);
+            canvas->drawString(name, fontBounds.fLeft, fontBounds.fBottom, labelFont, SkPaint());
         }
         for (size_t i = 0; i < SK_ARRAY_COUNT(str); ++i) {
             SkPath path;
-            glyphPaint.getTextPath(&str[i], sizeof(str[0]), x, y, &path);
+            font.getPath(str[i], &path);
+            path.offset(x, y);
             SkPaint::Style style = path.isEmpty() ? SkPaint::kFill_Style : SkPaint::kStroke_Style;
+            SkPaint glyphPaint;
             glyphPaint.setStyle(style);
-            canvas->drawText(&str[i], sizeof(str[0]), x, y, glyphPaint);
+            canvas->drawSimpleText(&str[i], sizeof(str[0]), kGlyphID_SkTextEncoding, x, y, font, glyphPaint);
 
             if (FLAGS_veryVerbose) {
                 SkString glyphStr;
                 glyphStr.appendS32(str[i]);
-                canvas->drawText(glyphStr.c_str(), glyphStr.size(),
-                                 location[i].fX, location[i].fY, labelPaint);
+                canvas->drawString(glyphStr, location[i].fX, location[i].fY, labelFont, SkPaint());
             }
 
         }
@@ -310,12 +308,12 @@
     }
 
     void onDraw(SkCanvas* canvas) override {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setSubpixelText(true);
-        paint.setTextSize(100);
-        paint.setTextScaleX(fScaleX);
-        paint.setTextSkewX(fSkewX);
+        SkFont font;
+        font.setEdging(SkFont::Edging::kAntiAlias);
+        font.setSubpixel(true);
+        font.setSize(100);
+        font.setScaleX(fScaleX);
+        font.setSkewX(fSkewX);
 
         const SkColor boundsColors[2] = { SK_ColorRED, SK_ColorBLUE };
 
@@ -330,13 +328,13 @@
         for (int i = 0; i < count; ++i) {
             sk_sp<SkFontStyleSet> set(fm->createStyleSet(i));
             for (int j = 0; j < set->count() && j < 3; ++j) {
-                paint.setTypeface(sk_sp<SkTypeface>(set->createTypeface(j)));
+                font.setTypeface(sk_sp<SkTypeface>(set->createTypeface(j)));
                 // Fonts with lots of glyphs are interesting, but can take a long time to find
                 // the glyphs which make up the maximum extent.
-                if (paint.getTypeface() && paint.getTypeface()->countGlyphs() < 1000) {
-                    SkRect fontBounds = paint.getFontBounds();
+                if (font.getTypeface() && font.getTypeface()->countGlyphs() < 1000) {
+                    SkRect fontBounds = SkFontPriv::GetFontBounds(font);
                     x -= fontBounds.fLeft;
-                    show_bounds(canvas, paint, x, y, boundsColors[index & 1]);
+                    show_bounds(canvas, font, x, y, boundsColors[index & 1]);
                     x += fontBounds.fRight + 20;
                     index += 1;
                     if (x > 900) {
diff --git a/gm/fontregen.cpp b/gm/fontregen.cpp
new file mode 100644
index 0000000..3d7dbee
--- /dev/null
+++ b/gm/fontregen.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// GM to stress TextBlob regeneration and the GPU font cache
+// It's not necessary to run this with CPU configs
+//
+// The point here is to draw a set of text that will fit in one Plot, and then some large
+// text. After a flush we draw the first set of text again with a slightly different color,
+// and then enough new large text to spill the entire atlas. What *should* happen is that
+// the Plot with the first set of text will not get overwritten by the new large text.
+
+#include "gm.h"
+
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "GrContextOptions.h"
+#include "SkCanvas.h"
+#include "SkGraphics.h"
+#include "SkImage.h"
+#include "SkTypeface.h"
+#include "gm.h"
+
+#include "sk_tool_utils.h"
+
+static sk_sp<SkTextBlob> make_blob(const SkString& text, const SkFont& font) {
+    size_t len = text.size();
+    SkAutoTArray<SkScalar>  pos(len);
+    SkAutoTArray<SkGlyphID> glyphs(len);
+
+    font.textToGlyphs(text.c_str(), len, SkTextEncoding::kUTF8, glyphs.get(), len);
+    font.getXPos(glyphs.get(), len, pos.get());
+    return SkTextBlob::MakeFromPosTextH(text.c_str(), len, pos.get(), 0, font);
+}
+
+class FontRegenGM : public skiagm::GM {
+public:
+    FontRegenGM() {
+        this->setBGColor(SK_ColorLTGRAY);
+    }
+
+    void modifyGrContextOptions(GrContextOptions* options) override {
+        options->fGlyphCacheTextureMaximumBytes = 0;
+        options->fAllowMultipleGlyphCacheTextures = GrContextOptions::Enable::kNo;
+    }
+
+protected:
+    SkString onShortName() override {
+        SkString name("fontregen");
+        return name;
+    }
+
+    SkISize onISize() override { return SkISize::Make(kSize, kSize); }
+
+    void onOnceBeforeDraw() override {
+        auto tf = sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Normal());
+
+        static const SkString kTexts[] = {
+            SkString("abcdefghijklmnopqrstuvwxyz"),
+            SkString("ABCDEFGHI"),
+            SkString("NOPQRSTUV")
+        };
+
+        SkFont font;
+        font.setEdging(SkFont::Edging::kAntiAlias);
+        font.setSubpixel(false);
+        font.setSize(80);
+        font.setTypeface(tf);
+
+        fBlobs[0] = make_blob(kTexts[0], font);
+        font.setSize(162);
+        fBlobs[1] = make_blob(kTexts[1], font);
+        fBlobs[2] = make_blob(kTexts[2], font);
+    }
+
+    void onDraw(SkCanvas* canvas) override {
+        GrRenderTargetContext* renderTargetContext =
+            canvas->internal_private_accessTopLayerRenderTargetContext();
+        if (!renderTargetContext) {
+            skiagm::GM::DrawGpuOnlyMessage(canvas);
+            return;
+        }
+
+        SkPaint paint;
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawTextBlob(fBlobs[0], 10, 80, paint);
+        canvas->drawTextBlob(fBlobs[1], 10, 225, paint);
+        canvas->flush();
+
+        paint.setColor(0xFF010101);
+        canvas->drawTextBlob(fBlobs[0], 10, 305, paint);
+        canvas->drawTextBlob(fBlobs[2], 10, 465, paint);
+
+        //  Debugging tool for GPU.
+        static const bool kShowAtlas = false;
+        if (kShowAtlas) {
+            if (auto ctx = canvas->getGrContext()) {
+                auto img = ctx->contextPriv().getFontAtlasImage_ForTesting(kA8_GrMaskFormat);
+                canvas->drawImage(img, 200, 0);
+            }
+        }
+    }
+
+private:
+    static constexpr SkScalar kSize = 512;
+
+    sk_sp<SkTextBlob> fBlobs[3];
+    typedef GM INHERITED;
+};
+
+constexpr SkScalar FontRegenGM::kSize;
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_GM(return new FontRegenGM())
diff --git a/gm/fontscaler.cpp b/gm/fontscaler.cpp
index 14f6138..72071cb 100644
--- a/gm/fontscaler.cpp
+++ b/gm/fontscaler.cpp
@@ -4,6 +4,7 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+#include <SkFont.h>
 #include "gm.h"
 #include "sk_tool_utils.h"
 #include "SkTypeface.h"
@@ -29,13 +30,11 @@
     }
 
     void onDraw(SkCanvas* canvas) override {
-        SkPaint paint;
-
-        paint.setAntiAlias(true);
-        paint.setLCDRenderText(true);
+        SkFont font;
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
         //With freetype the default (normal hinting) can be really ugly.
         //Most distros now set slight (vertical hinting only) in any event.
-        paint.setHinting(kSlight_SkFontHinting);
+        font.setHinting(kSlight_SkFontHinting);
 
         const char* text = "Hamburgefons ooo mmm";
         const size_t textLen = strlen(text);
@@ -61,13 +60,13 @@
                 }
 
                 for (int ps = 6; ps <= 22; ps++) {
-                    paint.setTextSize(SkIntToScalar(ps));
-                    canvas->drawText(text, textLen, x, y, paint);
-                    y += paint.getFontMetrics(nullptr);
+                    font.setSize(SkIntToScalar(ps));
+                    canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, x, y, font, SkPaint());
+                    y += font.getMetrics(nullptr);
                 }
             }
             canvas->translate(0, SkIntToScalar(360));
-            paint.setSubpixelText(true);
+            font.setSubpixel(true);
         }
     }
 
diff --git a/gm/fontscalerdistortable.cpp b/gm/fontscalerdistortable.cpp
index 432fca8..0d2dc2d 100644
--- a/gm/fontscalerdistortable.cpp
+++ b/gm/fontscalerdistortable.cpp
@@ -4,6 +4,7 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+#include <SkFont.h>
 #include "gm.h"
 #include "Resources.h"
 #include "SkFixed.h"
@@ -32,7 +33,8 @@
     void onDraw(SkCanvas* canvas) override {
         SkPaint paint;
         paint.setAntiAlias(true);
-        paint.setLCDRenderText(true);
+        SkFont font;
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
         sk_sp<SkFontMgr> fontMgr(SkFontMgr::RefDefault());
 
         std::unique_ptr<SkStreamAsset> distortableStream(GetResourceAsStream("fonts/Distortable.ttf"));
@@ -55,11 +57,11 @@
                 SkFontArguments::VariationPosition position =
                         { coordinates, SK_ARRAY_COUNT(coordinates) };
                 if (j == 0 && distortable) {
-                    paint.setTypeface(sk_sp<SkTypeface>(
+                    font.setTypeface(sk_sp<SkTypeface>(
                         distortable->makeClone(
                             SkFontArguments().setVariationDesignPosition(position))));
                 } else {
-                    paint.setTypeface(sk_sp<SkTypeface>(fontMgr->makeFromStream(
+                    font.setTypeface(sk_sp<SkTypeface>(fontMgr->makeFromStream(
                         distortableStream->duplicate(),
                         SkFontArguments().setVariationDesignPosition(position))));
                 }
@@ -78,13 +80,13 @@
                 }
 
                 for (int ps = 6; ps <= 22; ps++) {
-                    paint.setTextSize(SkIntToScalar(ps));
-                    canvas->drawText(text, textLen, x, y, paint);
-                    y += paint.getFontMetrics(nullptr);
+                    font.setSize(SkIntToScalar(ps));
+                    canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, x, y, font, paint);
+                    y += font.getMetrics(nullptr);
                 }
             }
             canvas->translate(0, SkIntToScalar(360));
-            paint.setSubpixelText(true);
+            font.setSubpixel(true);
         }
     }
 
diff --git a/gm/fwidth_squircle.cpp b/gm/fwidth_squircle.cpp
index 5670d84..4c565cb 100644
--- a/gm/fwidth_squircle.cpp
+++ b/gm/fwidth_squircle.cpp
@@ -175,12 +175,9 @@
     }
 
     if (!ctx->contextPriv().caps()->shaderCaps()->shaderDerivativeSupport()) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setTextSize(15);
-        sk_tool_utils::set_portable_typeface(&paint);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 15);
         SkTextUtils::DrawString(canvas, "Shader derivatives not supported.", 150,
-                                150 - 8, paint, SkTextUtils::kCenter_Align);
+                                150 - 8, font, SkPaint(), SkTextUtils::kCenter_Align);
         return;
     }
 
diff --git a/gm/gamma.cpp b/gm/gamma.cpp
index c1afc69..6c854ae 100644
--- a/gm/gamma.cpp
+++ b/gm/gamma.cpp
@@ -49,10 +49,11 @@
     mipmapPixels[0] = mipmapPixels[3] = SkPackARGB32(0xFF, s25, s25, s25);
     mipmapPixels[1] = mipmapPixels[2] = SkPackARGB32(0xFF, s75, s75, s75);
 
+    SkFont font(sk_tool_utils::create_portable_typeface());
+
     SkPaint textPaint;
     textPaint.setAntiAlias(true);
     textPaint.setColor(SK_ColorWHITE);
-    sk_tool_utils::set_portable_typeface(&textPaint);
 
     // Helpers:
     auto advance = [&]() {
@@ -60,19 +61,22 @@
         p.reset();
     };
 
+    auto drawString = [&](const char str[], SkScalar x, SkScalar y) {
+        canvas->drawSimpleText(str, strlen(str), kUTF8_SkTextEncoding, x, y, font, textPaint);
+    };
+
     auto nextRect = [&](const char* label, const char* label2) {
         canvas->drawRect(r, p);
-        canvas->drawString(label, 0, sz + textPaint.getFontSpacing(), textPaint);
+        drawString(label, 0, sz + font.getSpacing());
         if (label2) {
-            canvas->drawString(label2, 0, sz + 2 * textPaint.getFontSpacing(),
-                             textPaint);
+            drawString(label2, 0, sz + 2 * font.getSpacing());
         }
         advance();
     };
 
     auto nextBitmap = [&](const SkBitmap& bmp, const char* label) {
         canvas->drawBitmap(bmp, 0, 0);
-        canvas->drawString(label, 0, sz + textPaint.getFontSpacing(), textPaint);
+        drawString(label, 0, sz + font.getSpacing());
         advance();
     };
 
@@ -85,13 +89,10 @@
 
         SkString srcText = SkStringPrintf("%08X", srcColor);
         SkString dstText = SkStringPrintf("%08X", dstColor);
-        canvas->drawString(srcText, 0, sz + textPaint.getFontSpacing(),
-                         textPaint);
+        drawString(srcText.c_str(), 0, sz + font.getSpacing());
         const char* modeName = SkBlendMode_Name(mode);
-        canvas->drawString(modeName, 0, sz + 2 * textPaint.getFontSpacing(),
-                         textPaint);
-        canvas->drawString(dstText, 0, sz + 3 * textPaint.getFontSpacing(),
-                         textPaint);
+        drawString(modeName, 0, sz + 2 * font.getSpacing());
+        drawString(dstText.c_str(), 0, sz + 3 * font.getSpacing());
         advance();
     };
 
diff --git a/gm/gammatext.cpp b/gm/gammatext.cpp
index e248f58..fcdac17 100644
--- a/gm/gammatext.cpp
+++ b/gm/gammatext.cpp
@@ -61,12 +61,10 @@
         };
 
         const char* text = "Hamburgefons";
-        size_t len = strlen(text);
 
         SkPaint paint;
-        paint.setTextSize(SkIntToScalar(16));
-        paint.setAntiAlias(true);
-        paint.setLCDRenderText(true);
+        SkFont font(nullptr, 16);
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
 
         SkScalar x = SkIntToScalar(10);
         for (size_t i = 0; i < SK_ARRAY_COUNT(fg); ++i) {
@@ -75,8 +73,8 @@
             SkScalar y = SkIntToScalar(40);
             SkScalar stopy = SkIntToScalar(HEIGHT);
             while (y < stopy) {
-                canvas->drawText(text, len, x, y, paint);
-                y += paint.getTextSize() * 2;
+                canvas->drawString(text, x, y, font, paint);
+                y += font.getSize() * 2;
             }
             x += SkIntToScalar(1024) / SK_ARRAY_COUNT(fg);
         }
@@ -98,20 +96,16 @@
     return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
 }
 
-static void set_face(SkPaint* paint) {
-    paint->setTypeface(SkTypeface::MakeFromName("serif", SkFontStyle::Italic()));
-}
-
-static void draw_pair(SkCanvas* canvas, SkPaint* paint, const sk_sp<SkShader>& shader) {
-    const char text[] = "Now is the time for all good";
-    const size_t len = strlen(text);
-
-    paint->setShader(nullptr);
-    canvas->drawText(text, len, 10, 20, *paint);
-    paint->setShader(SkShader::MakeColorShader(paint->getColor()));
-    canvas->drawText(text, len, 10, 40, *paint);
-    paint->setShader(shader);
-    canvas->drawText(text, len, 10, 60, *paint);
+static void draw_pair(SkCanvas* canvas, const SkFont& font, SkColor color,
+                      const sk_sp<SkShader>& shader) {
+    static const char text[] = "Now is the time for all good";
+    SkPaint paint;
+    paint.setColor(color);
+    canvas->drawString(text, 10, 20, font, paint);
+    paint.setShader(SkShader::MakeColorShader(paint.getColor()));
+    canvas->drawString(text, 10, 40, font, paint);
+    paint.setShader(shader);
+    canvas->drawString(text, 10, 60, font, paint);
 }
 
 class GammaShaderTextGM : public skiagm::GM {
@@ -144,13 +138,11 @@
     void onDraw(SkCanvas* canvas) override {
         SkPaint paint;
         paint.setAntiAlias(true);
-        paint.setLCDRenderText(true);
-        paint.setTextSize(18);
-        set_face(&paint);
+        SkFont font(SkTypeface::MakeFromName("serif", SkFontStyle::Italic()), 18);
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
 
         for (size_t i = 0; i < SK_ARRAY_COUNT(fShaders); ++i) {
-            paint.setColor(fColors[i]);
-            draw_pair(canvas, &paint, fShaders[i]);
+            draw_pair(canvas, font, fColors[i], fShaders[i]);
             canvas->translate(0, 80);
         }
     }
diff --git a/gm/getpostextpath.cpp b/gm/getpostextpath.cpp
index 1fcce5f..cde2099 100644
--- a/gm/getpostextpath.cpp
+++ b/gm/getpostextpath.cpp
@@ -9,52 +9,57 @@
 #include "sk_tool_utils.h"
 
 #include "SkCanvas.h"
+#include "SkFontPriv.h"
 #include "SkPaint.h"
 #include "SkPath.h"
 #include "SkRandom.h"
 #include "SkTemplates.h"
-#include "SkTo.h"
+#include "SkTextBlob.h"
 
 static void strokePath(SkCanvas* canvas, const SkPath& path) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setColor(SK_ColorRED);
-        paint.setStyle(SkPaint::kStroke_Style);
-        canvas->drawPath(path, paint);
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(SK_ColorRED);
+    paint.setStyle(SkPaint::kStroke_Style);
+    canvas->drawPath(path, paint);
 }
 DEF_SIMPLE_GM(getpostextpath, canvas, 480, 780) {
-        // explicitly add spaces, to test a prev. bug
-        const char* text = "Ham bur ge fons";
-        int len = SkToInt(strlen(text));
-        SkPath path;
+    // explicitly add spaces, to test a prev. bug
+    const char* text = "Ham bur ge fons";
+    size_t len = strlen(text);
+    SkPath path;
 
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(SkIntToScalar(48));
+    SkFont font;
+    font.setTypeface(sk_tool_utils::create_portable_typeface());
+    font.setSize(48);
 
-        canvas->translate(SkIntToScalar(10), SkIntToScalar(64));
+    SkPaint paint;
+    paint.setAntiAlias(true);
 
-        canvas->drawText(text, len, 0, 0, paint);
-        paint.getTextPath(text, len, 0, 0, &path);
-        strokePath(canvas, path);
-        path.reset();
+    canvas->translate(SkIntToScalar(10), SkIntToScalar(64));
 
-        SkAutoTArray<SkPoint>  pos(len);
-        SkAutoTArray<SkScalar> widths(len);
-        paint.getTextWidths(text, len, &widths[0]);
+    canvas->drawSimpleText(text, len, kUTF8_SkTextEncoding, 0, 0, font, paint);
+    sk_tool_utils::get_text_path(font, text, len, kUTF8_SkTextEncoding, &path, nullptr);
+    strokePath(canvas, path);
+    path.reset();
 
-        SkRandom rand;
-        SkScalar x = SkIntToScalar(20);
-        SkScalar y = SkIntToScalar(100);
-        for (int i = 0; i < len; ++i) {
-            pos[i].set(x, y + rand.nextSScalar1() * 24);
-            x += widths[i];
-        }
+    SkAutoToGlyphs atg(font, text, len, kUTF8_SkTextEncoding);
+    const int count = atg.count();
+    SkAutoTArray<SkPoint>  pos(count);
+    SkAutoTArray<SkScalar> widths(count);
+    font.getWidths(atg.glyphs(), count, &widths[0]);
 
-        canvas->translate(0, SkIntToScalar(64));
+    SkRandom rand;
+    SkScalar x = SkIntToScalar(20);
+    SkScalar y = SkIntToScalar(100);
+    for (int i = 0; i < count; ++i) {
+        pos[i].set(x, y + rand.nextSScalar1() * 24);
+        x += widths[i];
+    }
 
-        canvas->drawPosText(text, len, &pos[0], paint);
-        paint.getPosTextPath(text, len, &pos[0], &path);
-        strokePath(canvas, path);
+    canvas->translate(0, SkIntToScalar(64));
+
+    canvas->drawTextBlob(SkTextBlob::MakeFromPosText(text, len, &pos[0], font), 0, 0, paint);
+    sk_tool_utils::get_text_path(font, text, len, kUTF8_SkTextEncoding, &path, &pos[0]);
+    strokePath(canvas, path);
 }
diff --git a/gm/glyph_pos.cpp b/gm/glyph_pos.cpp
index ff2a432..c5e43c8 100644
--- a/gm/glyph_pos.cpp
+++ b/gm/glyph_pos.cpp
@@ -8,6 +8,7 @@
 #include "gm.h"
 #include "sk_tool_utils.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkTypeface.h"
 
 /* This test tries to define the effect of using hairline strokes on text.
@@ -80,57 +81,61 @@
                          SkScalar textScale,
                          SkScalar strokeWidth,
                          SkPaint::Style strokeStyle) {
-        SkPaint paint;
-        paint.setColor(SK_ColorBLACK);
-        paint.setAntiAlias(true);
-        paint.setTextSize(kTextHeight * textScale);
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setStrokeWidth(strokeWidth);
-        paint.setStyle(strokeStyle);
+    SkPaint paint;
+    paint.setColor(SK_ColorBLACK);
+    paint.setAntiAlias(true);
+    paint.setStrokeWidth(strokeWidth);
+    paint.setStyle(strokeStyle);
 
-        // This demonstrates that we can not measure the text if
-        // there's a device transform. The canvas total matrix will
-        // end up being a device transform.
-        bool drawRef = !(canvas->getTotalMatrix().getType() &
-                         ~(SkMatrix::kIdentity_Mask | SkMatrix::kTranslate_Mask));
+    SkFont font(sk_tool_utils::create_portable_typeface(), kTextHeight * textScale);
 
-        SkRect bounds;
-        if (drawRef) {
-            SkScalar advance = paint.measureText(kText, sizeof(kText) - 1, &bounds);
+    // This demonstrates that we can not measure the text if
+    // there's a device transform. The canvas total matrix will
+    // end up being a device transform.
+    bool drawRef = !(canvas->getTotalMatrix().getType() &
+                     ~(SkMatrix::kIdentity_Mask | SkMatrix::kTranslate_Mask));
 
-            paint.setStrokeWidth(0.0f);
-            paint.setStyle(SkPaint::kStroke_Style);
+    SkRect bounds;
+    if (drawRef) {
+        SkScalar advance = font.measureText(kText, sizeof(kText) - 1, kUTF8_SkTextEncoding,
+                                            &bounds, &paint);
 
-            // Green box is the measured text bounds.
-            paint.setColor(SK_ColorGREEN);
-            canvas->drawRect(bounds, paint);
+        paint.setStrokeWidth(0.0f);
+        paint.setStyle(SkPaint::kStroke_Style);
 
-            // Red line is the measured advance from the 0,0 of the text position.
-            paint.setColor(SK_ColorRED);
-            canvas->drawLine(0.0f, 0.0f, advance, 0.0f, paint);
+        // Green box is the measured text bounds.
+        paint.setColor(SK_ColorGREEN);
+        canvas->drawRect(bounds, paint);
+
+        // Red line is the measured advance from the 0,0 of the text position.
+        paint.setColor(SK_ColorRED);
+        canvas->drawLine(0.0f, 0.0f, advance, 0.0f, paint);
+    }
+
+    // Black text is the testcase, eg. the text.
+    paint.setColor(SK_ColorBLACK);
+    paint.setStrokeWidth(strokeWidth);
+    paint.setStyle(strokeStyle);
+    canvas->drawSimpleText(kText, sizeof(kText) - 1, kUTF8_SkTextEncoding, 0.0f, 0.0f, font, paint);
+
+    if (drawRef) {
+        const size_t len = sizeof(kText) - 1;
+        SkGlyphID glyphs[len];
+        const int count = font.textToGlyphs(kText, len, kUTF8_SkTextEncoding, glyphs, len);
+        SkScalar widths[len]; // len is conservative. we really only need 'count'
+        font.getWidthsBounds(glyphs, count, widths, nullptr, &paint);
+
+        paint.setStrokeWidth(0.0f);
+        paint.setStyle(SkPaint::kStroke_Style);
+
+        // Magenta lines are the positions for the characters.
+        paint.setColor(SK_ColorMAGENTA);
+        SkScalar w = bounds.x();
+        for (size_t i = 0; i < sizeof(kText) - 1; ++i) {
+            canvas->drawLine(w, 0.0f, w, 5.0f, paint);
+            w += widths[i];
         }
-
-        // Black text is the testcase, eg. the text.
-        paint.setColor(SK_ColorBLACK);
-        paint.setStrokeWidth(strokeWidth);
-        paint.setStyle(strokeStyle);
-        canvas->drawText(kText, sizeof(kText) - 1, 0.0f, 0.0f, paint);
-
-        if (drawRef) {
-            SkScalar widths[sizeof(kText) - 1];
-            paint.getTextWidths(kText, sizeof(kText) - 1, widths, nullptr);
-
-            paint.setStrokeWidth(0.0f);
-            paint.setStyle(SkPaint::kStroke_Style);
-
-            // Magenta lines are the positions for the characters.
-            paint.setColor(SK_ColorMAGENTA);
-            SkScalar w = bounds.x();
-            for (size_t i = 0; i < sizeof(kText) - 1; ++i) {
-                canvas->drawLine(w, 0.0f, w, 5.0f, paint);
-                w += widths[i];
-            }
-        }
+    }
 }
 
 DEF_SIMPLE_GM(glyph_pos_h_b, c, 800, 600) {
diff --git a/gm/gm.cpp b/gm/gm.cpp
index a4b8f02..8deba84 100644
--- a/gm/gm.cpp
+++ b/gm/gm.cpp
@@ -32,6 +32,7 @@
         fHaveCalledOnceBeforeDraw = true;
         this->onOnceBeforeDraw();
     }
+    SkAutoCanvasRestore acr(canvas, true);
     this->onDraw(canvas);
 }
 
@@ -41,6 +42,7 @@
         fHaveCalledOnceBeforeDraw = true;
         this->onOnceBeforeDraw();
     }
+    SkAutoCanvasRestore acr(canvas, true);
     this->onDrawBackground(canvas);
 }
 
@@ -79,13 +81,10 @@
     bmp.allocN32Pixels(128, 64);
     SkCanvas bmpCanvas(bmp);
     bmpCanvas.drawColor(SK_ColorWHITE);
+    SkFont font(sk_tool_utils::create_portable_typeface(), 20);
     SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(20);
     paint.setColor(SK_ColorRED);
-    sk_tool_utils::set_portable_typeface(&paint);
-    constexpr char kTxt[] = "GPU Only";
-    bmpCanvas.drawString(kTxt, 20, 40, paint);
+    bmpCanvas.drawString("GPU Only", 20, 40, font, paint);
     SkMatrix localM;
     localM.setRotate(35.f);
     localM.postTranslate(10.f, 0.f);
diff --git a/gm/gradients_degenerate.cpp b/gm/gradients_degenerate.cpp
index 583bbed..e6a3f87 100644
--- a/gm/gradients_degenerate.cpp
+++ b/gm/gradients_degenerate.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "gm.h"
+#include "SkFont.h"
 #include "SkGradientShader.h"
 
 // NOTE: The positions define hardstops for the red and green borders. For the repeating degenerate
@@ -33,13 +34,8 @@
 static void draw_tile_header(SkCanvas* canvas) {
     canvas->save();
 
-    SkPaint paint;
-    paint.setColor(SK_ColorBLACK);
-    paint.setTextSize(12.0f);
-    paint.setAntiAlias(true);
-
     for (int i = 0; i < TILE_MODE_CT; ++i) {
-        canvas->drawString(TILE_NAMES[i], 0, 0, paint);
+        canvas->drawString(TILE_NAMES[i], 0, 0, SkFont(), SkPaint());
         canvas->translate(TILE_SIZE + TILE_GAP, 0);
     }
 
@@ -53,12 +49,10 @@
     canvas->save();
 
     SkPaint text;
-    text.setColor(SK_ColorBLACK);
-    text.setTextSize(12.0f);
     text.setAntiAlias(true);
 
     canvas->translate(0, TILE_GAP);
-    canvas->drawString(desc, 0, 0, text);
+    canvas->drawString(desc, 0, 0, SkFont(), text);
     canvas->translate(0, TILE_GAP);
 
     SkPaint paint;
diff --git a/gm/gradtext.cpp b/gm/gradtext.cpp
index cc0c079..b9cdb12 100644
--- a/gm/gradtext.cpp
+++ b/gm/gradtext.cpp
@@ -45,7 +45,6 @@
     virtual SkISize onISize() { return SkISize::Make(500, 480); }
     virtual void onDraw(SkCanvas* canvas) {
         SkPaint paint;
-        sk_tool_utils::set_portable_typeface(&paint);
         SkRect r = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(100));
 
         canvas->clipRect(r);
@@ -55,9 +54,11 @@
 
         // Minimal repro doesn't require AA, LCD, or a nondefault typeface
         paint.setShader(make_chrome_solid());
-        paint.setTextSize(SkIntToScalar(500));
 
-        canvas->drawString("I", 0, 100, paint);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 500);
+        font.setEdging(SkFont::Edging::kAlias);
+
+        canvas->drawString("I", 0, 100, font, paint);
     }
 private:
     typedef GM INHERITED;
@@ -74,93 +75,59 @@
     virtual SkISize onISize() { return SkISize::Make(500, 480); }
     virtual void onDraw(SkCanvas* canvas) {
         SkPaint paint;
-        sk_tool_utils::set_portable_typeface(&paint);
+        SkFont font(sk_tool_utils::create_portable_typeface());
+        font.setEdging(SkFont::Edging::kAlias);
 
         paint.setStyle(SkPaint::kFill_Style);
-        canvas->drawString("Normal Fill Text", 0, 50, paint);
+        canvas->drawString("Normal Fill Text", 0, 50, font, paint);
         paint.setStyle(SkPaint::kStroke_Style);
-        canvas->drawString("Normal Stroke Text", 0, 100, paint);
+        canvas->drawString("Normal Stroke Text", 0, 100, font, paint);
 
         // Minimal repro doesn't require AA, LCD, or a nondefault typeface
         paint.setShader(make_chrome_solid());
 
         paint.setStyle(SkPaint::kFill_Style);
-        canvas->drawString("Gradient Fill Text", 0, 150, paint);
+        canvas->drawString("Gradient Fill Text", 0, 150, font, paint);
         paint.setStyle(SkPaint::kStroke_Style);
-        canvas->drawString("Gradient Stroke Text", 0, 200, paint);
+        canvas->drawString("Gradient Stroke Text", 0, 200, font, paint);
     }
 private:
     typedef GM INHERITED;
 };
 
-
-
-class GradTextGM : public GM {
-public:
-    GradTextGM () {}
-
-protected:
-    SkString onShortName() override {
-        return SkString("gradtext");
-    }
-
-    SkISize onISize() override { return SkISize::Make(500, 480); }
-
-    static void draw_text(SkCanvas* canvas, const SkPaint& paint) {
-        const char* text = "When in the course of human events";
-        size_t len = strlen(text);
-        canvas->drawText(text, len, 0, 0, paint);
-    }
-
-    static void draw_text3(SkCanvas* canvas, const SkPaint& paint) {
-        SkPaint p(paint);
-
-        p.setAntiAlias(false);
-        draw_text(canvas, p);
-        p.setAntiAlias(true);
-        canvas->translate(0, paint.getTextSize() * 4/3);
-        draw_text(canvas, p);
-        p.setLCDRenderText(true);
-        canvas->translate(0, paint.getTextSize() * 4/3);
-        draw_text(canvas, p);
-    }
-
-    void onDraw(SkCanvas* canvas) override {
-        SkPaint paint;
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(SkIntToScalar(26));
-
-        const SkISize& size = this->getISize();
-        SkRect r = SkRect::MakeWH(SkIntToScalar(size.width()),
-                                  SkIntToScalar(size.height()) / 2);
-        canvas->drawRect(r, paint);
-
-        canvas->translate(SkIntToScalar(20), paint.getTextSize());
-
-        for (int i = 0; i < 2; ++i) {
-            paint.setShader(make_grad(SkIntToScalar(80)));
-            draw_text3(canvas, paint);
-
-            canvas->translate(0, paint.getTextSize() * 2);
-
-            paint.setShader(make_grad2(SkIntToScalar(80)));
-            draw_text3(canvas, paint);
-
-            canvas->translate(0, paint.getTextSize() * 2);
-        }
-    }
-
-private:
-    typedef GM INHERITED;
-};
-
 //////////////////////////////////////////////////////////////////////////////
 
-static GM* MyFactory(void*) { return new GradTextGM; }
 static GM* CMyFactory(void*) { return new ChromeGradTextGM1; }
 static GM* CMyFactory2(void*) { return new ChromeGradTextGM2; }
 
-static GMRegistry reg(MyFactory);
 static GMRegistry Creg(CMyFactory);
 static GMRegistry Creg2(CMyFactory2);
 }
+
+DEF_SIMPLE_GM(gradtext, canvas, 500, 480) {
+    static constexpr float kTextSize = 26.0f;
+    SkFont font(sk_tool_utils::create_portable_typeface(), kTextSize);
+
+    canvas->drawRect({0, 0, 500, 240}, SkPaint());
+    canvas->translate(20.0f, kTextSize);
+
+    SkPaint paints[2];
+    paints[0].setShader(make_grad(80.0f));
+    paints[1].setShader(make_grad2(80.0f));
+
+    static const SkFont::Edging edgings[3] = {
+        SkFont::Edging::kAlias,
+        SkFont::Edging::kAntiAlias,
+        SkFont::Edging::kSubpixelAntiAlias,
+    };
+    for (int i = 0; i < 2; ++i) {
+        for (const SkPaint& paint : paints) {
+            for (SkFont::Edging edging : edgings) {
+                font.setEdging(edging);
+                canvas->drawString("When in the course of human events", 0, 0, font, paint);
+                canvas->translate(0, kTextSize * 4/3);
+            }
+            canvas->translate(0, kTextSize * 2/3);
+        }
+    }
+}
diff --git a/gm/highcontrastfilter.cpp b/gm/highcontrastfilter.cpp
index 0c7c5e1..d3da2ac 100644
--- a/gm/highcontrastfilter.cpp
+++ b/gm/highcontrastfilter.cpp
@@ -8,6 +8,7 @@
 #include "gm.h"
 #include "sk_tool_utils.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkHighContrastFilter.h"
 
@@ -30,13 +31,15 @@
              invertStr,
              config.fContrast);
 
-    SkPaint paint;
-    sk_tool_utils::set_portable_typeface(&paint);
-    paint.setTextSize(0.05f);
+    SkFont font;
+    font.setTypeface(sk_tool_utils::create_portable_typeface());
+    font.setSize(0.05f);
+    font.setEdging(SkFont::Edging::kAlias);
+
     size_t len = strlen(labelBuffer);
 
-    SkScalar width = paint.measureText(labelBuffer, len);
-    canvas->drawText(labelBuffer, len, 0.5f - width / 2, 0.16f, paint);
+    SkScalar width = font.measureText(labelBuffer, len, kUTF8_SkTextEncoding);
+    canvas->drawSimpleText(labelBuffer, len, kUTF8_SkTextEncoding, 0.5f - width / 2, 0.16f, font, SkPaint());
 }
 
 static void draw_scene(SkCanvas* canvas, const SkHighContrastConfig& config) {
@@ -50,17 +53,19 @@
     paint.setARGB(0xff, 0x66, 0x11, 0x11);
     canvas->drawRect(bounds, paint);
 
+    SkFont font;
+    font.setSize(0.15f);
+    font.setEdging(SkFont::Edging::kAlias);
+
     paint.setARGB(0xff, 0xbb, 0x77, 0x77);
-    paint.setTextSize(0.15f);
-    canvas->drawString("A", 0.15f, 0.35f, paint);
+    canvas->drawString("A", 0.15f, 0.35f, font, paint);
 
     bounds = SkRect::MakeLTRB(0.1f, 0.8f, 0.9f, 1.0f);
     paint.setARGB(0xff, 0xcc, 0xcc, 0xff);
     canvas->drawRect(bounds, paint);
 
     paint.setARGB(0xff, 0x88, 0x88, 0xbb);
-    paint.setTextSize(0.15f);
-    canvas->drawString("Z", 0.75f, 0.95f, paint);
+    canvas->drawString("Z", 0.75f, 0.95f, font, paint);
 
     bounds = SkRect::MakeLTRB(0.1f, 0.4f, 0.9f, 0.6f);
     SkPoint     pts[] = { { 0, 0 }, { 1, 0 } };
diff --git a/gm/image.cpp b/gm/image.cpp
index 13ba101..f1afa94 100644
--- a/gm/image.cpp
+++ b/gm/image.cpp
@@ -113,34 +113,19 @@
     void onDraw(SkCanvas* canvas) override {
         canvas->scale(2, 2);
 
-        const char* kLabel1 = "Original Img";
-        const char* kLabel2 = "Modified Img";
-        const char* kLabel3 = "Cur Surface";
-        const char* kLabel4 = "Full Crop";
-        const char* kLabel5 = "Over-crop";
-        const char* kLabel6 = "Upper-left";
-        const char* kLabel7 = "No Crop";
+        SkFont font(sk_tool_utils::create_portable_typeface(), 8);
 
-        const char* kLabel8 = "Pre-Alloc Img";
-        const char* kLabel9 = "New Alloc Img";
-        const char* kLabel10 = "GPU";
+        canvas->drawString("Original Img",  10,  60, font, SkPaint());
+        canvas->drawString("Modified Img",  10, 140, font, SkPaint());
+        canvas->drawString("Cur Surface",   10, 220, font, SkPaint());
+        canvas->drawString("Full Crop",     10, 300, font, SkPaint());
+        canvas->drawString("Over-crop",     10, 380, font, SkPaint());
+        canvas->drawString("Upper-left",    10, 460, font, SkPaint());
+        canvas->drawString("No Crop",       10, 540, font, SkPaint());
 
-        SkPaint textPaint;
-        textPaint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&textPaint);
-        textPaint.setTextSize(8);
-
-        canvas->drawString(kLabel1, 10,  60, textPaint);
-        canvas->drawString(kLabel2, 10, 140, textPaint);
-        canvas->drawString(kLabel3, 10, 220, textPaint);
-        canvas->drawString(kLabel4, 10, 300, textPaint);
-        canvas->drawString(kLabel5, 10, 380, textPaint);
-        canvas->drawString(kLabel6, 10, 460, textPaint);
-        canvas->drawString(kLabel7, 10, 540, textPaint);
-
-        canvas->drawString(kLabel8, 80, 10, textPaint);
-        canvas->drawString(kLabel9, 160, 10, textPaint);
-        canvas->drawString(kLabel10, 265, 10, textPaint);
+        canvas->drawString("Pre-Alloc Img", 80,  10, font, SkPaint());
+        canvas->drawString("New Alloc Img", 160, 10, font, SkPaint());
+        canvas->drawString( "GPU",          265, 10, font, SkPaint());
 
         canvas->translate(80, 20);
 
diff --git a/gm/image_pict.cpp b/gm/image_pict.cpp
index 1a20be0..4ff341e 100644
--- a/gm/image_pict.cpp
+++ b/gm/image_pict.cpp
@@ -280,9 +280,9 @@
         }
 
         // No API to draw a GrTexture directly, so we cheat and create a private image subclass
-        sk_sp<SkImage> texImage(new SkImage_Gpu(
-                sk_ref_sp(canvas->getGrContext()), image->uniqueID(), kPremul_SkAlphaType,
-                std::move(proxy), image->refColorSpace(), SkBudgeted::kNo));
+        sk_sp<SkImage> texImage(new SkImage_Gpu(sk_ref_sp(canvas->getGrContext()),
+                                                image->uniqueID(), kPremul_SkAlphaType,
+                                                std::move(proxy), image->refColorSpace()));
         canvas->drawImage(texImage.get(), x, y);
     }
 
diff --git a/gm/imageblur.cpp b/gm/imageblur.cpp
index 47d6374..3531658 100644
--- a/gm/imageblur.cpp
+++ b/gm/imageblur.cpp
@@ -21,15 +21,13 @@
 
         SkRandom rand;
         SkPaint textPaint;
-        textPaint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&textPaint);
+        SkFont font(sk_tool_utils::create_portable_typeface());
         for (int i = 0; i < 25; ++i) {
             int x = rand.nextULessThan(WIDTH);
             int y = rand.nextULessThan(HEIGHT);
             textPaint.setColor(sk_tool_utils::color_to_565(rand.nextBits(24) | 0xFF000000));
-            textPaint.setTextSize(rand.nextRangeScalar(0, 300));
-            canvas->drawString(str, SkIntToScalar(x),
-                             SkIntToScalar(y), textPaint);
+            font.setSize(rand.nextRangeScalar(0, 300));
+            canvas->drawString(str, SkIntToScalar(x), SkIntToScalar(y), font, textPaint);
         }
         canvas->restore();
 }
diff --git a/gm/imageblur2.cpp b/gm/imageblur2.cpp
index b6f9ff5..4cb94c9 100644
--- a/gm/imageblur2.cpp
+++ b/gm/imageblur2.cpp
@@ -12,82 +12,48 @@
 
 // TODO deprecate imageblur
 
-#define WIDTH 500
-#define HEIGHT 500
+constexpr int kWidth  = 500;
+constexpr int kHeight = 500;
 
-constexpr float kBlurSigmas[] = {
-        0.0, 0.3f, 0.5f, 2.0f, 32.0f, 80.0f };
-
-const char* kTestStrings[] = {
+DEF_SIMPLE_GM(imageblur2, canvas, kWidth, kHeight) {
+    constexpr float kBlurSigmas[] = { 0.0, 0.3f, 0.5f, 2.0f, 32.0f, 80.0f };
+    const char* kTestStrings[] = {
         "The quick`~",
         "brown fox[]",
         "jumped over",
         "the lazy@#$",
         "dog.{}!%^&",
         "*()+=-\\'\"/",
-};
+    };
+    constexpr int sigmaCount = SK_ARRAY_COUNT(kBlurSigmas);
+    constexpr int testStringCount = SK_ARRAY_COUNT(kTestStrings);
+    constexpr SkScalar dx = kWidth / sigmaCount;
+    constexpr SkScalar dy = kHeight / sigmaCount;
+    constexpr SkScalar textSize = 12;
 
-namespace skiagm {
+    SkFont font(sk_tool_utils::create_portable_typeface(), textSize);
+    font.setEdging(SkFont::Edging::kAlias);
 
-class BlurImageFilter : public GM {
-public:
-    BlurImageFilter() {
-        this->setBGColor(0xFFFFFFFF);
-        fName.printf("imageblur2");
-    }
+    for (int x = 0; x < sigmaCount; x++) {
+        SkScalar sigmaX = kBlurSigmas[x];
+        for (int y = 0; y < sigmaCount; y++) {
+            SkScalar sigmaY = kBlurSigmas[y];
 
-protected:
+            SkPaint paint;
+            paint.setImageFilter(SkBlurImageFilter::Make(sigmaX, sigmaY, nullptr));
+            canvas->saveLayer(nullptr, &paint);
 
-    SkString onShortName() override {
-        return fName;
-    }
-
-    SkISize onISize() override {
-        return SkISize::Make(WIDTH, HEIGHT);
-    }
-
-    void onDraw(SkCanvas* canvas) override {
-        const int sigmaCount = SK_ARRAY_COUNT(kBlurSigmas);
-        const int testStringCount = SK_ARRAY_COUNT(kTestStrings);
-        SkScalar dx = WIDTH / sigmaCount;
-        SkScalar dy = HEIGHT / sigmaCount;
-        const SkScalar textSize = 12;
-
-        for (int x = 0; x < sigmaCount; x++) {
-            SkScalar sigmaX = kBlurSigmas[x];
-            for (int y = 0; y < sigmaCount; y++) {
-                SkScalar sigmaY = kBlurSigmas[y];
-
-                SkPaint paint;
-                paint.setImageFilter(SkBlurImageFilter::Make(sigmaX, sigmaY, nullptr));
-                canvas->saveLayer(nullptr, &paint);
-
-                SkRandom rand;
-                SkPaint textPaint;
-                textPaint.setAntiAlias(false);
-                textPaint.setColor(sk_tool_utils::color_to_565(rand.nextBits(24) | 0xFF000000));
-                sk_tool_utils::set_portable_typeface(&textPaint);
-                textPaint.setTextSize(textSize);
-
-                for (int i = 0; i < testStringCount; i++) {
-                    canvas->drawString(kTestStrings[i],
-                                       SkIntToScalar(x * dx),
-                                       SkIntToScalar(y * dy + textSize * i + textSize),
-                                       textPaint);
-                }
-                canvas->restore();
+            SkRandom rand;
+            SkPaint textPaint;
+            textPaint.setColor(sk_tool_utils::color_to_565(rand.nextBits(24) | 0xFF000000));
+            for (int i = 0; i < testStringCount; i++) {
+                canvas->drawString(kTestStrings[i],
+                                   SkIntToScalar(x * dx),
+                                   SkIntToScalar(y * dy + textSize * i + textSize),
+                                   font,
+                                   textPaint);
             }
+            canvas->restore();
         }
     }
-
-private:
-    SkString fName;
-
-    typedef GM INHERITED;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-DEF_GM(return new BlurImageFilter;)
-
 }
diff --git a/gm/imageblurtiled.cpp b/gm/imageblurtiled.cpp
index 7f1cfbd..5a57721 100644
--- a/gm/imageblurtiled.cpp
+++ b/gm/imageblurtiled.cpp
@@ -46,15 +46,11 @@
                     "jumped over",
                     "the lazy dog.",
                 };
-                SkPaint textPaint;
-                textPaint.setAntiAlias(true);
-                sk_tool_utils::set_portable_typeface(&textPaint);
-                textPaint.setTextSize(SkIntToScalar(100));
+                SkFont font(sk_tool_utils::create_portable_typeface(), 100);
                 int posY = 0;
                 for (unsigned i = 0; i < SK_ARRAY_COUNT(str); i++) {
                     posY += 100;
-                    canvas->drawString(str[i], SkIntToScalar(0),
-                                     SkIntToScalar(posY), textPaint);
+                    canvas->drawString(str[i], 0, SkIntToScalar(posY), font, SkPaint());
                 }
                 canvas->restore();
                 canvas->restore();
diff --git a/gm/imagefiltersbase.cpp b/gm/imagefiltersbase.cpp
index 83b62f4..40bb810 100644
--- a/gm/imagefiltersbase.cpp
+++ b/gm/imagefiltersbase.cpp
@@ -127,10 +127,8 @@
     SkPaint paint;
     paint.setImageFilter(imf);
     paint.setColor(SK_ColorCYAN);
-    paint.setAntiAlias(true);
-    sk_tool_utils::set_portable_typeface(&paint);
-    paint.setTextSize(r.height()/2);
-    SkTextUtils::DrawString(canvas, "Text", r.centerX(), r.centerY(), paint,
+    SkFont font(sk_tool_utils::create_portable_typeface(), r.height()/2);
+    SkTextUtils::DrawString(canvas, "Text", r.centerX(), r.centerY(), font, paint,
                             SkTextUtils::kCenter_Align);
 }
 
@@ -237,20 +235,18 @@
 
     SkISize onISize() override { return SkISize::Make(512, 342); }
 
-    void drawWaterfall(SkCanvas* canvas, const SkPaint& origPaint) {
-        const uint32_t flags[] = {
-            0,
-            SkPaint::kAntiAlias_Flag,
-            SkPaint::kAntiAlias_Flag | SkPaint::kLCDRenderText_Flag,
+    void drawWaterfall(SkCanvas* canvas, const SkPaint& paint) {
+        static const SkFont::Edging kEdgings[3] = {
+            SkFont::Edging::kAlias,
+            SkFont::Edging::kAntiAlias,
+            SkFont::Edging::kSubpixelAntiAlias,
         };
-        SkPaint paint(origPaint);
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(30);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 30);
 
         SkAutoCanvasRestore acr(canvas, true);
-        for (size_t i = 0; i < SK_ARRAY_COUNT(flags); ++i) {
-            paint.setFlags(flags[i]);
-            canvas->drawString("Hamburgefon", 0, 0, paint);
+        for (SkFont::Edging edging : kEdgings) {
+            font.setEdging(edging);
+            canvas->drawString("Hamburgefon", 0, 0, font, paint);
             canvas->translate(0, 40);
         }
     }
diff --git a/gm/imagefilterscropped.cpp b/gm/imagefilterscropped.cpp
index 225e881..179fdc3 100644
--- a/gm/imagefilterscropped.cpp
+++ b/gm/imagefilterscropped.cpp
@@ -43,11 +43,9 @@
     SkPaint paint;
     paint.setImageFilter(std::move(imf));
     paint.setColor(SK_ColorGREEN);
-    paint.setAntiAlias(true);
-    sk_tool_utils::set_portable_typeface(&paint);
-    paint.setTextSize(r.height()/2);
-    SkTextUtils::DrawString(canvas, "Text", r.centerX(), r.centerY(), paint,
-                            SkTextUtils::kCenter_Align);
+
+    SkFont font(sk_tool_utils::create_portable_typeface(), r.height()/2);
+    SkTextUtils::DrawString(canvas, "Text", r.centerX(), r.centerY(), font, paint, SkTextUtils::kCenter_Align);
 }
 
 static void draw_bitmap(SkCanvas* canvas, const SkRect& r, sk_sp<SkImageFilter> imf) {
diff --git a/gm/imagemagnifier.cpp b/gm/imagemagnifier.cpp
index 2585ea7..8fbc58d 100644
--- a/gm/imagemagnifier.cpp
+++ b/gm/imagemagnifier.cpp
@@ -27,16 +27,14 @@
         canvas->saveLayer(nullptr, &filterPaint);
         const char* str = "The quick brown fox jumped over the lazy dog.";
         SkRandom rand;
+        SkFont font(sk_tool_utils::create_portable_typeface());
         for (int i = 0; i < 25; ++i) {
             int x = rand.nextULessThan(WIDTH);
             int y = rand.nextULessThan(HEIGHT);
             SkPaint paint;
-            sk_tool_utils::set_portable_typeface(&paint);
             paint.setColor(sk_tool_utils::color_to_565(rand.nextBits(24) | 0xFF000000));
-            paint.setTextSize(rand.nextRangeScalar(0, 300));
-            paint.setAntiAlias(true);
-            canvas->drawString(str, SkIntToScalar(x),
-                             SkIntToScalar(y), paint);
+            font.setSize(rand.nextRangeScalar(0, 300));
+            canvas->drawString(str, SkIntToScalar(x), SkIntToScalar(y), font, paint);
         }
         canvas->restore();
 }
diff --git a/gm/imageresizetiled.cpp b/gm/imageresizetiled.cpp
index 257e0b2..c242cc5 100644
--- a/gm/imageresizetiled.cpp
+++ b/gm/imageresizetiled.cpp
@@ -22,6 +22,8 @@
         paint.setImageFilter(SkImageFilter::MakeMatrixFilter(matrix,
                                                              kNone_SkFilterQuality,
                                                              nullptr));
+
+        SkFont font(sk_tool_utils::create_portable_typeface(), 100);
         const SkScalar tile_size = SkIntToScalar(100);
         for (SkScalar y = 0; y < HEIGHT; y += tile_size) {
             for (SkScalar x = 0; x < WIDTH; x += tile_size) {
@@ -36,15 +38,10 @@
                     "jumped over",
                     "the lazy dog.",
                 };
-                SkPaint textPaint;
-                textPaint.setAntiAlias(true);
-                sk_tool_utils::set_portable_typeface(&textPaint);
-                textPaint.setTextSize(SkIntToScalar(100));
-                int posY = 0;
+                float posY = 0;
                 for (unsigned i = 0; i < SK_ARRAY_COUNT(str); i++) {
-                    posY += 100;
-                    canvas->drawString(str[i], SkIntToScalar(0),
-                                     SkIntToScalar(posY), textPaint);
+                    posY += 100.0f;
+                    canvas->drawString(str[i], 0.0f, posY, font, SkPaint());
                 }
                 canvas->restore();
                 canvas->restore();
diff --git a/gm/internal_links.cpp b/gm/internal_links.cpp
index 29be22e..af54b5f 100644
--- a/gm/internal_links.cpp
+++ b/gm/internal_links.cpp
@@ -60,11 +60,9 @@
                                        SkIntToScalar(50), SkIntToScalar(20));
         canvas->drawRect(rect, paint);
 
-        paint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(SkIntToScalar(25));
+        SkFont font(sk_tool_utils::create_portable_typeface(), 25);
         paint.setColor(SK_ColorBLACK);
-        canvas->drawString(text, x, y, paint);
+        canvas->drawString(text, x, y, font, paint);
     }
 
     typedef GM INHERITED;
diff --git a/gm/largeglyphblur.cpp b/gm/largeglyphblur.cpp
index 40390cf..deed7dc 100644
--- a/gm/largeglyphblur.cpp
+++ b/gm/largeglyphblur.cpp
@@ -16,28 +16,21 @@
 // This test ensures that glyphs whose point size is less than the SkGlyphCache's maxmium, but
 // who have a large blur, are still handled correctly
 DEF_SIMPLE_GM(largeglyphblur, canvas, 1920, 600) {
-        const char text[] = "Hamburgefons";
+    const char text[] = "Hamburgefons";
 
-        SkPaint paint;
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(256);
-        paint.setAntiAlias(true);
+    SkFont font(sk_tool_utils::create_portable_typeface(), 256);
+    auto blob = SkTextBlob::MakeFromText(text, strlen(text), font);
 
-        // setup up maskfilter
-        const SkScalar kSigma = SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(40));
+    // setup up maskfilter
+    const SkScalar kSigma = SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(40));
 
-        SkPaint blurPaint(paint);
-        blurPaint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, kSigma));
+    SkPaint blurPaint;
+    blurPaint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, kSigma));
 
-        SkTextBlobBuilder builder;
+    canvas->drawTextBlob(blob, 10, 200, blurPaint);
+    canvas->drawTextBlob(blob, 10, 200, SkPaint());
 
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, 0);
-
-        sk_sp<SkTextBlob> blob(builder.make());
-        canvas->drawTextBlob(blob, 10, 200, blurPaint);
-        canvas->drawTextBlob(blob, 10, 200, paint);
-
-        size_t len = strlen(text);
-        canvas->drawText(text, len, 10, 500, blurPaint);
-        canvas->drawText(text, len, 10, 500, paint);
+    size_t len = strlen(text);
+    canvas->drawSimpleText(text, len, kUTF8_SkTextEncoding, 10, 500, font, blurPaint);
+    canvas->drawSimpleText(text, len, kUTF8_SkTextEncoding, 10, 500, font, SkPaint());
 }
diff --git a/gm/lattice.cpp b/gm/lattice.cpp
index eec3c64..5c2bd50 100644
--- a/gm/lattice.cpp
+++ b/gm/lattice.cpp
@@ -337,5 +337,25 @@
 };
 DEF_GM( return new LatticeGM2; )
 
+// Code paths that incorporate the paint color when drawing the lattice (using an alpha image)
+DEF_SIMPLE_GM_BG(lattice_alpha, canvas, 120, 120, SK_ColorWHITE) {
+    auto surface = sk_tool_utils::makeSurface(canvas, SkImageInfo::MakeA8(100, 100));
+    surface->getCanvas()->clear(0);
+    surface->getCanvas()->drawCircle(50, 50, 50, SkPaint());
+    auto image = surface->makeImageSnapshot();
 
+    int divs[] = { 20, 40, 60, 80 };
 
+    SkCanvas::Lattice lattice;
+    lattice.fXCount = 4;
+    lattice.fXDivs = divs;
+    lattice.fYCount = 4;
+    lattice.fYDivs = divs;
+    lattice.fRectTypes = nullptr;
+    lattice.fColors = nullptr;
+    lattice.fBounds = nullptr;
+
+    SkPaint paint;
+    paint.setColor(SK_ColorMAGENTA);
+    canvas->drawImageLattice(image.get(), lattice, SkRect::MakeWH(120, 120), &paint);
+}
diff --git a/gm/lcdblendmodes.cpp b/gm/lcdblendmodes.cpp
index 1627654..02650f0 100644
--- a/gm/lcdblendmodes.cpp
+++ b/gm/lcdblendmodes.cpp
@@ -118,19 +118,17 @@
         for (size_t m = 0; m < SK_ARRAY_COUNT(gModes); m++) {
             SkPaint paint;
             paint.setColor(textColor);
-            paint.setAntiAlias(true);
-            paint.setSubpixelText(true);
-            paint.setLCDRenderText(true);
-            paint.setTextSize(fTextHeight);
             paint.setBlendMode(gModes[m]);
-            sk_tool_utils::set_portable_typeface(&paint);
+            SkFont font(sk_tool_utils::create_portable_typeface(), fTextHeight);
+            font.setSubpixel(true);
+            font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
             if (useGrad) {
                 SkRect r;
                 r.setXYWH(0, y - fTextHeight, SkIntToScalar(kColWidth), fTextHeight);
                 paint.setShader(make_shader(r));
             }
             SkString string(SkBlendMode_Name(gModes[m]));
-            canvas->drawString(string, 0, y, paint);
+            canvas->drawString(string, 0, y, font, paint);
             y+=fTextHeight;
         }
     }
diff --git a/gm/lcdoverlap.cpp b/gm/lcdoverlap.cpp
index ffb6cd7..6dd54a0 100644
--- a/gm/lcdoverlap.cpp
+++ b/gm/lcdoverlap.cpp
@@ -37,14 +37,12 @@
         // build text blob
         SkTextBlobBuilder builder;
 
-        SkPaint paint;
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(32);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 32);
         const char* text = "able was I ere I saw elba";
-        paint.setAntiAlias(true);
-        paint.setSubpixelText(true);
-        paint.setLCDRenderText(true);
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, 0);
+        font.setSubpixel(true);
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
+        // If we use SkTextBlob::MakeFromText, we get very different positioning ... why?
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, 0);
         fBlob = builder.make();
     }
 
diff --git a/gm/lcdtext.cpp b/gm/lcdtext.cpp
index 0a2788d..e605ac0 100644
--- a/gm/lcdtext.cpp
+++ b/gm/lcdtext.cpp
@@ -53,12 +53,14 @@
         SkPaint paint;
         paint.setColor(SK_ColorBLACK);
         paint.setDither(true);
-        paint.setAntiAlias(true);
-        paint.setSubpixelText(subpixelTextEnabled);
-        paint.setLCDRenderText(lcdRenderTextEnabled);
-        paint.setTextSize(textHeight);
-
-        canvas->drawString(string, 0, y, paint);
+        SkFont font(nullptr, textHeight);
+        if (subpixelTextEnabled) {
+            font.setSubpixel(true);
+        }
+        if (lcdRenderTextEnabled) {
+            font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
+        }
+        canvas->drawString(string, 0, y, font, paint);
         y += textHeight;
     }
 
@@ -98,10 +100,6 @@
         const char* lcd_text = "LCD";
         const char* gray_text = "GRAY";
 
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setLCDRenderText(true);
-
         const struct {
             SkPoint     fLoc;
             SkScalar    fTextSize;
@@ -118,9 +116,11 @@
             const SkPoint loc = rec[i].fLoc;
             SkAutoCanvasRestore acr(canvas, true);
 
-            paint.setTextSize(rec[i].fTextSize);
+            SkFont font(nullptr, rec[i].fTextSize);
+            font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
+
             ScaleAbout(canvas, rec[i].fScale, rec[i].fScale, loc.x(), loc.y());
-            canvas->drawString(rec[i].fText, loc.x(), loc.y(), paint);
+            canvas->drawString(rec[i].fText, loc.x(), loc.y(), font, SkPaint());
         }
     }
 
@@ -129,37 +129,3 @@
 };
 DEF_GM( return new LcdTextGM; )
 DEF_GM( return new LcdTextSizeGM; )
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-DEF_SIMPLE_GM(savelayer_lcdtext, canvas, 620, 260) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setLCDRenderText(true);
-    paint.setTextSize(20);
-
-    canvas->drawString("Hamburgefons", 30, 30, paint);
-
-    const bool gPreserveLCDText[] = { false, true };
-
-    canvas->translate(0, 20);
-    for (auto preserve : gPreserveLCDText) {
-        preserve ? canvas->saveLayerPreserveLCDTextRequests(nullptr, nullptr)
-                 : canvas->saveLayer(nullptr, nullptr);
-        if (preserve && !canvas->imageInfo().colorSpace()) {
-            SkPaint noLCD = paint;
-            noLCD.setLCDRenderText(false);
-            canvas->drawString("LCD not supported", 30, 60, noLCD);
-        } else {
-            canvas->drawString("Hamburgefons", 30, 60, paint);
-        }
-
-        SkPaint p;
-        p.setColor(0xFFCCCCCC);
-        canvas->drawRect(SkRect::MakeLTRB(25, 70, 200, 100), p);
-        canvas->drawString("Hamburgefons", 30, 90, paint);
-
-        canvas->restore();
-        canvas->translate(0, 80);
-    }
-}
diff --git a/gm/lightingshader2.cpp b/gm/lightingshader2.cpp
index 982e46a..62d9c2a 100644
--- a/gm/lightingshader2.cpp
+++ b/gm/lightingshader2.cpp
@@ -143,9 +143,8 @@
 
     void onDraw(SkCanvas* canvas) override {
         SkPaint labelPaint;
-        labelPaint.setTypeface(sk_tool_utils::create_portable_typeface("sans-serif",SkFontStyle()));
-        labelPaint.setAntiAlias(true);
-        labelPaint.setTextSize(kLabelSize);
+        SkFont font(
+                sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle()), kLabelSize);
 
         int gridNum = 0;
 
@@ -170,25 +169,25 @@
                             canvas->translate(0.0f, kLabelSize);
                             SkString label;
                             label.appendf("useNormalSource: %d", useNormalSource);
-                            canvas->drawString(label, 0.0f, 0.0f, labelPaint);
+                            canvas->drawString(label, 0.0f, 0.0f, font, labelPaint);
                         }
                         {
                             canvas->translate(0.0f, kLabelSize);
                             SkString label;
                             label.appendf("useDiffuseShader: %d", useDiffuseShader);
-                            canvas->drawString(label, 0.0f, 0.0f, labelPaint);
+                            canvas->drawString(label, 0.0f, 0.0f, font, labelPaint);
                         }
                         {
                             canvas->translate(0.0f, kLabelSize);
                             SkString label;
                             label.appendf("useTranslucentPaint: %d", useTranslucentPaint);
-                            canvas->drawString(label, 0.0f, 0.0f, labelPaint);
+                            canvas->drawString(label, 0.0f, 0.0f, font, labelPaint);
                         }
                         {
                             canvas->translate(0.0f, kLabelSize);
                             SkString label;
                             label.appendf("useTranslucentShader: %d", useTranslucentShader);
-                            canvas->drawString(label, 0.0f, 0.0f, labelPaint);
+                            canvas->drawString(label, 0.0f, 0.0f, font, labelPaint);
                         }
 
                         canvas->restore();
diff --git a/gm/lumafilter.cpp b/gm/lumafilter.cpp
index 07e9b99..82cb1a0 100644
--- a/gm/lumafilter.cpp
+++ b/gm/lumafilter.cpp
@@ -19,13 +19,14 @@
 
 static void draw_label(SkCanvas* canvas, const char* label,
                        const SkPoint& offset) {
-    SkPaint paint;
-    sk_tool_utils::set_portable_typeface(&paint);
+    SkFont font(sk_tool_utils::create_portable_typeface());
+    font.setEdging(SkFont::Edging::kAlias);
+
     size_t len = strlen(label);
 
-    SkScalar width = paint.measureText(label, len);
-    canvas->drawText(label, len, offset.x() - width / 2, offset.y(),
-                     paint);
+    SkScalar width = font.measureText(label, len, kUTF8_SkTextEncoding);
+    canvas->drawSimpleText(label, len, kUTF8_SkTextEncoding, offset.x() - width / 2, offset.y(),
+                           font, SkPaint());
 }
 
 static void draw_scene(SkCanvas* canvas, const sk_sp<SkColorFilter>& filter, SkBlendMode mode,
diff --git a/gm/makecolorspace.cpp b/gm/makecolorspace.cpp
index c7e603d..2f5acd6 100644
--- a/gm/makecolorspace.cpp
+++ b/gm/makecolorspace.cpp
@@ -48,8 +48,8 @@
     }
 
     void onDraw(SkCanvas* canvas) override {
-        sk_sp<SkColorSpace> wideGamut = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                                              SkColorSpace::kAdobeRGB_Gamut);
+        sk_sp<SkColorSpace> wideGamut = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
+                                                              SkNamedGamut::kAdobeRGB);
         sk_sp<SkColorSpace> wideGamutLinear = wideGamut->makeLinearGamma();
 
         // Lazy images
diff --git a/gm/mixedtextblobs.cpp b/gm/mixedtextblobs.cpp
index a7a9f81..afdd736 100644
--- a/gm/mixedtextblobs.cpp
+++ b/gm/mixedtextblobs.cpp
@@ -48,16 +48,15 @@
 
         // make textblob
         // Text so large we draw as paths
-        SkPaint paint;
-        paint.setTextSize(385);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 385);
+        font.setEdging(SkFont::Edging::kAlias);
         const char* text = "O";
-        sk_tool_utils::set_portable_typeface(&paint);
 
         SkRect bounds;
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
 
         SkScalar yOffset = bounds.height();
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 10, yOffset);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 10, yOffset);
         SkScalar corruptedAx = bounds.width();
         SkScalar corruptedAy = yOffset;
 
@@ -68,33 +67,31 @@
         yOffset = boundsHalfHeight;
 
         // LCD
-        paint.setTextSize(32);
+        font.setSize(32);
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
+        font.setSubpixel(true);
         text = "LCD!!!!!";
-        paint.setAntiAlias(true);
-        paint.setSubpixelText(true);
-        paint.setLCDRenderText(true);
-        paint.measureText(text, strlen(text), &bounds);
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, xOffset - bounds.width() * 0.25f,
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, xOffset - bounds.width() * 0.25f,
                                         yOffset - bounds.height() * 0.5f);
         yOffset += bounds.height();
 
         // color emoji
         if (fEmojiTypeface) {
-            paint.setAntiAlias(false);
-            paint.setSubpixelText(false);
-            paint.setLCDRenderText(false);
-            paint.setTypeface(fEmojiTypeface);
+            font.setEdging(SkFont::Edging::kAlias);
+            font.setSubpixel(false);
+            font.setTypeface(fEmojiTypeface);
             text = fEmojiText;
-            paint.measureText(text, strlen(text), &bounds);
-            sk_tool_utils::add_to_text_blob(&builder, text, paint, xOffset - bounds.width() * 0.3f,
+            font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
+            sk_tool_utils::add_to_text_blob(&builder, text, font, xOffset - bounds.width() * 0.3f,
                                             yOffset);
         }
 
         // Corrupted font
-        paint.setTextSize(12);
+        font.setSize(12);
         text = "aA";
-        paint.setTypeface(fReallyBigATypeface);
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, corruptedAx, corruptedAy);
+        font.setTypeface(fReallyBigATypeface);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, corruptedAx, corruptedAy);
         fBlob = builder.make();
     }
 
diff --git a/gm/p3.cpp b/gm/p3.cpp
index 28e8644..a67ab72 100644
--- a/gm/p3.cpp
+++ b/gm/p3.cpp
@@ -94,8 +94,7 @@
 }
 
 DEF_SIMPLE_GM(p3, canvas, 450, 1300) {
-    auto p3 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                    SkColorSpace::kDCIP3_D65_Gamut);
+    auto p3 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
     auto srgb = SkColorSpace::MakeSRGB();
 
     auto p3_to_srgb = [&](SkColor4f c) {
@@ -356,8 +355,7 @@
 }
 
 DEF_SIMPLE_GM(p3_ovals, canvas, 450, 320) {
-    auto p3 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                    SkColorSpace::kDCIP3_D65_Gamut);
+    auto p3 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
 
     // Test cases that exercise each Op in GrOvalOpFactory.cpp
 
diff --git a/gm/pdf_never_embed.cpp b/gm/pdf_never_embed.cpp
index adaf78b..583ff3e 100644
--- a/gm/pdf_never_embed.cpp
+++ b/gm/pdf_never_embed.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "Resources.h"
+#include "SkTextBlob.h"
 #include "SkTo.h"
 #include "SkTypeface.h"
 #include "gm.h"
@@ -13,48 +14,44 @@
 static void excercise_draw_pos_text(SkCanvas* canvas,
                                     const char* text,
                                     SkScalar x, SkScalar y,
+                                    const SkFont& font,
                                     const SkPaint& paint) {
-    size_t textLen = strlen(text);
-    SkAutoTArray<SkScalar> widths(SkToInt(textLen));
-    paint.getTextWidths(text, textLen, &widths[0]);
-    SkAutoTArray<SkPoint> pos(SkToInt(textLen));
-    for (int i = 0; i < SkToInt(textLen); ++i) {
-        pos[i].set(x, y);
-        x += widths[i];
-    }
-    canvas->drawPosText(text, textLen, &pos[0], paint);
+    const int count = font.countText(text, strlen(text), kUTF8_SkTextEncoding);
+    SkTextBlobBuilder builder;
+    auto rec = builder.allocRunPos(font, count);
+    font.textToGlyphs(text, strlen(text), kUTF8_SkTextEncoding, rec.glyphs, count);
+    font.getPos(rec.glyphs, count, rec.points());
+    canvas->drawTextBlob(builder.make(), x, y, paint);
 }
 
 DEF_SIMPLE_GM(pdf_never_embed, canvas, 512, 512) {
     SkPaint p;
-    p.setTextSize(60);
-    p.setTypeface(MakeResourceAsTypeface("fonts/Roboto2-Regular_NoEmbed.ttf"));
-    p.setAntiAlias(true);
 
-    if (!p.getTypeface()) {
+    SkFont font(MakeResourceAsTypeface("fonts/Roboto2-Regular_NoEmbed.ttf"), 60);
+    if (!font.getTypeface()) {
         return;
     }
 
     const char text[] = "HELLO, WORLD!";
 
     canvas->drawColor(SK_ColorWHITE);
-    excercise_draw_pos_text(canvas, text, 30, 90, p);
+    excercise_draw_pos_text(canvas, text, 30, 90, font, p);
 
     canvas->save();
     canvas->rotate(45.0f);
     p.setColor(0xF0800000);
-    excercise_draw_pos_text(canvas, text, 30, 45, p);
+    excercise_draw_pos_text(canvas, text, 30, 45, font, p);
     canvas->restore();
 
     canvas->save();
     canvas->scale(1, 4.0);
     p.setColor(0xF0008000);
-    excercise_draw_pos_text(canvas, text, 15, 70, p);
+    excercise_draw_pos_text(canvas, text, 15, 70, font, p);
     canvas->restore();
 
     canvas->scale(1.0, 0.5);
     p.setColor(0xF0000080);
-    canvas->drawString(text, 30, 700, p);
+    canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 30, 700, font, p);
 }
 
 
@@ -67,4 +64,3 @@
     canvas->translate(0, -816);
     canvas->drawRect({0, 0, 1224, 1500}, SkPaint());
 }
-
diff --git a/gm/pictureimagefilter.cpp b/gm/pictureimagefilter.cpp
index c9a0d39..899bbb9 100644
--- a/gm/pictureimagefilter.cpp
+++ b/gm/pictureimagefilter.cpp
@@ -123,7 +123,7 @@
                 canvas->scale(4, 4);
                 canvas->translate(-0.9f*srcRect.fLeft, -2.45f*srcRect.fTop);
 
-                canvas->saveLayerPreserveLCDTextRequests(&bounds, &paint);
+                canvas->saveLayer(&bounds, &paint);
                 canvas->restore();
             }
 
diff --git a/gm/pictureimagegenerator.cpp b/gm/pictureimagegenerator.cpp
index 4e465d1..04b7064 100644
--- a/gm/pictureimagegenerator.cpp
+++ b/gm/pictureimagegenerator.cpp
@@ -9,6 +9,7 @@
 #include "sk_tool_utils.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
+#include "SkFontPriv.h"
 #include "SkGradientShader.h"
 #include "SkImageGenerator.h"
 #include "SkPaint.h"
@@ -16,6 +17,7 @@
 #include "SkPathOps.h"
 #include "SkPicture.h"
 #include "SkPictureRecorder.h"
+#include "SkTextUtils.h"
 
 static void draw_vector_logo(SkCanvas* canvas, const SkRect& viewBox) {
     constexpr char kSkiaStr[] = "SKIA";
@@ -25,20 +27,21 @@
 
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setSubpixelText(true);
-    paint.setFakeBoldText(true);
-    sk_tool_utils::set_portable_typeface(&paint);
+
+    SkFont font(sk_tool_utils::create_portable_typeface());
+    font.setSubpixel(true);
+    font.setEmbolden(true);
 
     SkPath path;
     SkRect iBox, skiBox, skiaBox;
-    paint.getTextPath("SKI", 3, 0, 0, &path);
+    SkTextUtils::GetPath("SKI", 3, kUTF8_SkTextEncoding, 0, 0, font, &path);
     TightBounds(path, &skiBox);
-    paint.getTextPath("I", 1, 0, 0, &path);
+    SkTextUtils::GetPath("I", 1, kUTF8_SkTextEncoding, 0, 0, font, &path);
     TightBounds(path, &iBox);
     iBox.offsetTo(skiBox.fRight - iBox.width(), iBox.fTop);
 
     const size_t textLen = strlen(kSkiaStr);
-    paint.getTextPath(kSkiaStr, textLen, 0, 0, &path);
+    SkTextUtils::GetPath(kSkiaStr, textLen, kUTF8_SkTextEncoding, 0, 0, font, &path);
     TightBounds(path, &skiaBox);
     skiaBox.outset(0, 2 * iBox.width() * (kVerticalSpacing + 1));
 
@@ -90,7 +93,7 @@
     SkASSERT(SK_ARRAY_COUNT(pos2) == SK_ARRAY_COUNT(colors2));
     paint.setShader(SkGradientShader::MakeLinear(pts2, colors2, pos2, SK_ARRAY_COUNT(pos2),
                                                  SkShader::kClamp_TileMode));
-    canvas->drawText(kSkiaStr, textLen, 0, 0, paint);
+    canvas->drawSimpleText(kSkiaStr, textLen, kUTF8_SkTextEncoding, 0, 0, font, paint);
 }
 
 // This GM exercises SkPictureImageGenerator features
diff --git a/gm/pictureshadercache.cpp b/gm/pictureshadercache.cpp
index b3e2c26..e3badd8 100644
--- a/gm/pictureshadercache.cpp
+++ b/gm/pictureshadercache.cpp
@@ -60,9 +60,12 @@
 
         {
             // Render in a funny color space that converts green to yellow.
-            SkMatrix44 greenToYellow;
-            greenToYellow.setFloat(0, 1, 1.0f);
-            sk_sp<SkColorSpace> gty = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
+            skcms_Matrix3x3 greenToYellow = {{
+                { 1, 1, 0 },
+                { 0, 1, 0 },
+                { 0, 0, 1 },
+            }};
+            sk_sp<SkColorSpace> gty = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
                                                             greenToYellow);
             SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100, std::move(gty));
             sk_sp<SkSurface> surface(SkSurface::MakeRaster(info));
diff --git a/gm/poly2poly.cpp b/gm/poly2poly.cpp
index f298412..7b72aa4 100644
--- a/gm/poly2poly.cpp
+++ b/gm/poly2poly.cpp
@@ -192,7 +192,7 @@
         return SkISize::Make(835, 840);
     }
 
-    static void doDraw(SkCanvas* canvas, SkPaint* paint, const int isrc[],
+    static void doDraw(SkCanvas* canvas, const SkFont& font, SkPaint* paint, const int isrc[],
                        const int idst[], int count) {
         SkMatrix matrix;
         SkPoint src[4], dst[4];
@@ -214,14 +214,14 @@
         canvas->drawLine(0, D, D, 0, *paint);
 
         SkFontMetrics fm;
-        paint->getFontMetrics(&fm);
+        font.getMetrics(&fm);
         paint->setColor(SK_ColorRED);
         paint->setStyle(SkPaint::kFill_Style);
         SkScalar x = D/2;
         SkScalar y = D/2 - (fm.fAscent + fm.fDescent)/2;
         uint16_t glyphID = 3; // X
-        SkTextUtils::DrawText(canvas, &glyphID, sizeof(glyphID), x, y, *paint,
-                              SkTextUtils::kCenter_Align);
+        SkTextUtils::Draw(canvas, &glyphID, sizeof(glyphID), kGlyphID_SkTextEncoding, x, y,
+                          font, *paint, SkTextUtils::kCenter_Align);
         canvas->restore();
     }
 
@@ -234,17 +234,15 @@
 
         SkPaint paint;
         paint.setAntiAlias(true);
-        paint.setTypeface(fEmFace);
-        paint.setTextEncoding(kGlyphID_SkTextEncoding);
         paint.setStrokeWidth(SkIntToScalar(4));
-        paint.setTextSize(SkIntToScalar(40));
+        SkFont font(fEmFace, 40);
 
         canvas->save();
         canvas->translate(SkIntToScalar(10), SkIntToScalar(10));
         // translate (1 point)
         const int src1[] = { 0, 0 };
         const int dst1[] = { 5, 5 };
-        doDraw(canvas, &paint, src1, dst1, 1);
+        doDraw(canvas, font, &paint, src1, dst1, 1);
         canvas->restore();
 
         canvas->save();
@@ -252,7 +250,7 @@
         // rotate/uniform-scale (2 points)
         const int src2[] = { 32, 32, 64, 32 };
         const int dst2[] = { 32, 32, 64, 48 };
-        doDraw(canvas, &paint, src2, dst2, 2);
+        doDraw(canvas, font, &paint, src2, dst2, 2);
         canvas->restore();
 
         canvas->save();
@@ -260,7 +258,7 @@
         // rotate/skew (3 points)
         const int src3[] = { 0, 0, 64, 0, 0, 64 };
         const int dst3[] = { 0, 0, 96, 0, 24, 64 };
-        doDraw(canvas, &paint, src3, dst3, 3);
+        doDraw(canvas, font, &paint, src3, dst3, 3);
         canvas->restore();
 
         canvas->save();
@@ -268,7 +266,7 @@
         // perspective (4 points)
         const int src4[] = { 0, 0, 64, 0, 64, 64, 0, 64 };
         const int dst4[] = { 0, 0, 96, 0, 64, 96, 0, 64 };
-        doDraw(canvas, &paint, src4, dst4, 4);
+        doDraw(canvas, font, &paint, src4, dst4, 4);
         canvas->restore();
     }
 
diff --git a/gm/readpixels.cpp b/gm/readpixels.cpp
index 8250860..e69803b 100644
--- a/gm/readpixels.cpp
+++ b/gm/readpixels.cpp
@@ -62,10 +62,9 @@
 }
 
 static sk_sp<SkColorSpace> make_parametric_transfer_fn(const SkColorSpacePrimaries& primaries) {
-    SkMatrix44 toXYZD50;
+    skcms_Matrix3x3 toXYZD50;
     SkAssertResult(primaries.toXYZD50(&toXYZD50));
-    SkColorSpaceTransferFn fn;
-    fn.fA = 1.f; fn.fB = 0.f; fn.fC = 0.f; fn.fD = 0.f; fn.fE = 0.f; fn.fF = 0.f; fn.fG = 1.8f;
+    skcms_TransferFunction fn = { 1.8f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f };
     return SkColorSpace::MakeRGB(fn, toXYZD50);
 }
 
diff --git a/gm/rectangletexture.cpp b/gm/rectangletexture.cpp
index 325e29a..4d4e1df 100644
--- a/gm/rectangletexture.cpp
+++ b/gm/rectangletexture.cpp
@@ -30,9 +30,7 @@
         return SkString("rectangle_texture");
     }
 
-    SkISize onISize() override {
-        return SkISize::Make(1035, 240);
-    }
+    SkISize onISize() override { return SkISize::Make(1200, 500); }
 
     void fillPixels(int width, int height, void *pixels) {
         SkBitmap bmp;
@@ -46,7 +44,7 @@
                                                      SkShader::kClamp_TileMode));
         canvas.drawPaint(paint);
 
-        SkColor colors1[] = { 0xFFA07010 , 0xFFA02080 };
+        SkColor colors1[] = {0xFFA07010, 0xFFA02080};
         paint.setAntiAlias(true);
         paint.setShader(SkGradientShader::MakeLinear(pts, colors1, nullptr, 2,
                                                      SkShader::kClamp_TileMode));
@@ -54,8 +52,8 @@
                           SkIntToScalar(width + height) / 5, paint);
     }
 
-    sk_sp<SkImage> createRectangleTextureImg(GrContext* context, int width, int height,
-                                             void* pixels) {
+    sk_sp<SkImage> createRectangleTextureImg(GrContext* context, GrSurfaceOrigin origin, int width,
+                                             int height, const uint32_t* pixels) {
         if (!context) {
             return nullptr;
         }
@@ -88,34 +86,36 @@
         }
 
         const GrGLInterface* gl = glCtx->interface();
-// Useful for debugging whether errors result from use of RECTANGLE
-// #define TARGET GR_GL_TEXTURE_2D
-#define TARGET GR_GL_TEXTURE_RECTANGLE
+        // Useful for debugging whether errors result from use of RECTANGLE
+        // static constexpr GrGLenum kTarget = GR_GL_TEXTURE_2D;
+        static constexpr GrGLenum kTarget = GR_GL_TEXTURE_RECTANGLE;
         GrGLuint id = 0;
         GR_GL_CALL(gl, GenTextures(1, &id));
-        GR_GL_CALL(gl, BindTexture(TARGET, id));
-        GR_GL_CALL(gl, TexParameteri(TARGET, GR_GL_TEXTURE_MAG_FILTER,
-                                     GR_GL_NEAREST));
-        GR_GL_CALL(gl, TexParameteri(TARGET, GR_GL_TEXTURE_MIN_FILTER,
-                                     GR_GL_NEAREST));
-        GR_GL_CALL(gl, TexParameteri(TARGET, GR_GL_TEXTURE_WRAP_S,
-                                     GR_GL_CLAMP_TO_EDGE));
-        GR_GL_CALL(gl, TexParameteri(TARGET, GR_GL_TEXTURE_WRAP_T,
-                                     GR_GL_CLAMP_TO_EDGE));
-        GR_GL_CALL(gl, TexImage2D(TARGET, 0, GR_GL_RGBA, width, height, 0,
-                                  format, GR_GL_UNSIGNED_BYTE, pixels));
-
+        GR_GL_CALL(gl, BindTexture(kTarget, id));
+        GR_GL_CALL(gl, TexParameteri(kTarget, GR_GL_TEXTURE_MAG_FILTER, GR_GL_NEAREST));
+        GR_GL_CALL(gl, TexParameteri(kTarget, GR_GL_TEXTURE_MIN_FILTER, GR_GL_NEAREST));
+        GR_GL_CALL(gl, TexParameteri(kTarget, GR_GL_TEXTURE_WRAP_S, GR_GL_CLAMP_TO_EDGE));
+        GR_GL_CALL(gl, TexParameteri(kTarget, GR_GL_TEXTURE_WRAP_T, GR_GL_CLAMP_TO_EDGE));
+        std::unique_ptr<uint32_t[]> tempPixels;
+        if (origin == kBottomLeft_GrSurfaceOrigin) {
+            tempPixels.reset(new uint32_t[width * height]);
+            for (int y = 0; y < height; ++y) {
+                std::copy_n(pixels + width * (height - y - 1), width, tempPixels.get() + width * y);
+            }
+            pixels = tempPixels.get();
+        }
+        GR_GL_CALL(gl, TexImage2D(kTarget, 0, GR_GL_RGBA, width, height, 0, format,
+                                  GR_GL_UNSIGNED_BYTE, pixels));
 
         context->resetContext();
         GrGLTextureInfo info;
         info.fID = id;
-        info.fTarget = TARGET;
+        info.fTarget = kTarget;
         info.fFormat = GR_GL_RGBA8;
 
         GrBackendTexture rectangleTex(width, height, GrMipMapped::kNo, info);
 
-        if (sk_sp<SkImage> image = SkImage::MakeFromAdoptedTexture(context, rectangleTex,
-                                                                   kTopLeft_GrSurfaceOrigin,
+        if (sk_sp<SkImage> image = SkImage::MakeFromAdoptedTexture(context, rectangleTex, origin,
                                                                    kRGBA_8888_SkColorType)) {
             return image;
         }
@@ -136,9 +136,15 @@
 
         SkPMColor pixels[kWidth * kHeight];
         this->fillPixels(kWidth, kHeight, pixels);
-        sk_sp<SkImage> rectImg(this->createRectangleTextureImg(context, kWidth, kHeight, pixels));
 
-        if (!rectImg) {
+        sk_sp<SkImage> rectImgs[] = {
+                this->createRectangleTextureImg(context, kTopLeft_GrSurfaceOrigin, kWidth, kHeight,
+                                                pixels),
+                this->createRectangleTextureImg(context, kBottomLeft_GrSurfaceOrigin, kWidth,
+                                                kHeight, pixels),
+        };
+        SkASSERT(SkToBool(rectImgs[0]) == SkToBool(rectImgs[1]));
+        if (!rectImgs[0]) {
             SkPaint paint;
             paint.setAntiAlias(true);
             const char* kMsg = "Could not create rectangle texture image.";
@@ -147,39 +153,56 @@
         }
 
         constexpr SkFilterQuality kQualities[] = {
-            kNone_SkFilterQuality,
-            kLow_SkFilterQuality,
-            kMedium_SkFilterQuality,
-            kHigh_SkFilterQuality,
+                kNone_SkFilterQuality,
+                kLow_SkFilterQuality,
+                kMedium_SkFilterQuality,
+                kHigh_SkFilterQuality,
         };
 
-        constexpr SkScalar kScales[] = { 1.0f, 1.2f, 0.75f };
+        constexpr SkScalar kScales[] = {1.0f, 1.2f, 0.75f};
 
         canvas->translate(kPad, kPad);
-        for (auto s : kScales) {
-            canvas->save();
-            canvas->scale(s, s);
-            for (auto q : kQualities) {
-                SkPaint plainPaint;
-                plainPaint.setFilterQuality(q);
-                canvas->drawImage(rectImg.get(), 0, 0, &plainPaint);
-                canvas->translate(kWidth + kPad, 0);
+        for (size_t i = 0; i < SK_ARRAY_COUNT(rectImgs); ++i) {
+            for (auto s : kScales) {
+                canvas->save();
+                canvas->scale(s, s);
+                for (auto q : kQualities) {
+                    // drawImage
+                    SkPaint plainPaint;
+                    plainPaint.setFilterQuality(q);
+                    canvas->drawImage(rectImgs[i], 0, 0, &plainPaint);
+                    canvas->translate(kWidth + kPad, 0);
 
-                SkPaint clampPaint;
-                clampPaint.setFilterQuality(q);
-                clampPaint.setShader(rectImg->makeShader());
-                canvas->drawRect(SkRect::MakeWH(1.5f * kWidth, 1.5f * kHeight), clampPaint);
-                canvas->translate(kWidth * 1.5f + kPad, 0);
+                    // clamp/clamp shader
+                    SkPaint clampPaint;
+                    clampPaint.setFilterQuality(q);
+                    clampPaint.setShader(rectImgs[i]->makeShader());
+                    canvas->drawRect(SkRect::MakeWH(1.5f * kWidth, 1.5f * kHeight), clampPaint);
+                    canvas->translate(kWidth * 1.5f + kPad, 0);
 
-                SkPaint repeatPaint;
-                repeatPaint.setFilterQuality(q);
-                repeatPaint.setShader(rectImg->makeShader(SkShader::kRepeat_TileMode,
-                                                          SkShader::kMirror_TileMode));
-                canvas->drawRect(SkRect::MakeWH(1.5f * kWidth, 1.5f * kHeight), repeatPaint);
-                canvas->translate(1.5f * kWidth + kPad, 0);
+                    // repeat/mirror shader
+                    SkPaint repeatPaint;
+                    repeatPaint.setFilterQuality(q);
+                    repeatPaint.setShader(rectImgs[i]->makeShader(SkShader::kRepeat_TileMode,
+                                                                  SkShader::kMirror_TileMode));
+                    canvas->drawRect(SkRect::MakeWH(1.5f * kWidth, 1.5f * kHeight), repeatPaint);
+                    canvas->translate(1.5f * kWidth + kPad, 0);
+
+                    // drawImageRect with kStrict
+                    auto srcRect = SkRect::MakeXYWH(.25f * rectImgs[i]->width(),
+                                                    .25f * rectImgs[i]->height(),
+                                                    .50f * rectImgs[i]->width(),
+                                                    .50f * rectImgs[i]->height());
+                    auto dstRect = SkRect::MakeXYWH(0, 0,
+                                                    .50f * rectImgs[i]->width(),
+                                                    .50f * rectImgs[i]->height());
+                    canvas->drawImageRect(rectImgs[i], srcRect, dstRect, &plainPaint,
+                                          SkCanvas::kStrict_SrcRectConstraint);
+                    canvas->translate(kWidth * .5f + kPad, 0);
+                }
+                canvas->restore();
+                canvas->translate(0, kPad + 1.5f * kHeight * s);
             }
-            canvas->restore();
-            canvas->translate(0, kPad + 1.5f * kHeight * s);
         }
     }
 
diff --git a/gm/rrects.cpp b/gm/rrects.cpp
index 1f9ddeb..ec05d21 100644
--- a/gm/rrects.cpp
+++ b/gm/rrects.cpp
@@ -11,7 +11,7 @@
 #include "GrRenderTargetContextPriv.h"
 #include "effects/GrRRectEffect.h"
 #include "ops/GrDrawOp.h"
-#include "ops/GrRectOpFactory.h"
+#include "ops/GrFillRectOp.h"
 #include "SkRRect.h"
 
 namespace skiagm {
@@ -117,9 +117,8 @@
                             bounds.outset(2.f, 2.f);
 
                             renderTargetContext->priv().testingOnly_addDrawOp(
-                                    GrRectOpFactory::MakeNonAAFill(context, std::move(grPaint),
-                                                                   SkMatrix::I(), bounds,
-                                                                   GrAAType::kNone));
+                                    GrFillRectOp::Make(context, std::move(grPaint), GrAAType::kNone,
+                                                       SkMatrix::I(), bounds));
                         } else {
                             drew = false;
                         }
diff --git a/gm/savelayer.cpp b/gm/savelayer.cpp
index 7721820..bf59c31 100644
--- a/gm/savelayer.cpp
+++ b/gm/savelayer.cpp
@@ -277,3 +277,74 @@
     canvas->restore();
 }
 
+#include "SkFont.h"
+#include "SkGradientShader.h"
+#include "SkTextBlob.h"
+
+static void draw_cell(SkCanvas* canvas, sk_sp<SkTextBlob> blob, SkColor c, SkScalar w, SkScalar h) {
+    SkRect r = SkRect::MakeWH(w, h);
+    SkPaint p;
+    p.setColor(c);
+    p.setBlendMode(SkBlendMode::kSrc);
+    canvas->drawRect(r, p);
+    p.setBlendMode(SkBlendMode::kSrcOver);
+
+    const SkScalar margin = 80;
+    r.fLeft = w - margin;
+
+    // save the behind image
+    SkDEBUGCODE(int sc0 =) canvas->getSaveCount();
+    SkDEBUGCODE(int sc1 =) SkCanvasPriv::SaveBehind(canvas, &r);
+    SkDEBUGCODE(int sc2 =) canvas->getSaveCount();
+    SkASSERT(sc0 == sc1);
+    SkASSERT(sc0 + 1 == sc2);
+
+    // draw the foreground (including over the 'behind' section)
+    p.setColor(SK_ColorBLACK);
+    canvas->drawTextBlob(blob, 10, 30, p);
+
+    // draw the treatment
+    const SkPoint pts[] = { {r.fLeft,0}, {r.fRight, 0} };
+    const SkColor colors[] = { 0x88000000, 0x0 };
+    auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkShader::kClamp_TileMode);
+    p.setShader(sh);
+    p.setBlendMode(SkBlendMode::kDstIn);
+    canvas->drawRect(r, p);
+
+    // this should restore the behind image
+    canvas->restore();
+    SkDEBUGCODE(int sc3 =) canvas->getSaveCount();
+    SkASSERT(sc3 == sc0);
+
+    // just outline where we expect the treatment to appear
+    p.reset();
+    p.setStyle(SkPaint::kStroke_Style);
+    p.setAlpha(0x40);
+    canvas->drawRect(r, p);
+}
+
+static void draw_list(SkCanvas* canvas, sk_sp<SkTextBlob> blob) {
+    SkAutoCanvasRestore acr(canvas, true);
+
+    SkRandom rand;
+    SkScalar w = 400;
+    SkScalar h = 40;
+    for (int i = 0; i < 8; ++i) {
+        SkColor c = rand.nextU();   // ensure we're opaque
+        c = (c & 0xFFFFFF) | 0x80000000;
+        draw_cell(canvas, blob, c, w, h);
+        canvas->translate(0, h);
+    }
+}
+
+DEF_SIMPLE_GM(save_behind, canvas, 400, 670) {
+    SkFont font;
+    font.setSize(30);
+    const char text[] = "This is a very long line of text";
+    auto blob = SkTextBlob::MakeFromText(text, strlen(text), font);
+
+    draw_list(canvas, blob);
+    canvas->translate(0, 350);
+    canvas->saveLayer({0, 0, 400, 320}, nullptr);
+    draw_list(canvas, blob);
+}
diff --git a/gm/scaledemoji.cpp b/gm/scaledemoji.cpp
index 6bae281..7307a75 100644
--- a/gm/scaledemoji.cpp
+++ b/gm/scaledemoji.cpp
@@ -11,9 +11,21 @@
 #include "Resources.h"
 #include "SkCanvas.h"
 #include "SkStream.h"
+#include "SkTextBlob.h"
 #include "SkTo.h"
 #include "SkTypeface.h"
 
+static sk_sp<SkTextBlob> make_hpos_test_blob_utf8(const char* text, const SkFont& font) {
+    constexpr SkTextEncoding enc = SkTextEncoding::kUTF8;
+    SkTextBlobBuilder builder;
+    size_t len = strlen(text);
+    int glyphCount = font.countText(text, len, enc);
+    const auto& buffer = builder.allocRunPosH(font, glyphCount, 0);
+    (void)font.textToGlyphs(text, len, enc, buffer.glyphs, glyphCount);
+    font.getXPos(buffer.glyphs, glyphCount, buffer.pos);
+    return builder.make();
+}
+
 namespace skiagm {
 
 class ScaledEmojiGM : public GM {
@@ -44,7 +56,9 @@
         canvas->drawColor(SK_ColorGRAY);
 
         SkPaint paint;
-        paint.setTypeface(fEmojiFont.fTypeface);
+        SkFont font(fEmojiFont.fTypeface);
+        font.setEdging(SkFont::Edging::kAlias);
+
         const char* text = fEmojiFont.fText;
 
         // draw text at different point sizes
@@ -53,10 +67,10 @@
         SkFontMetrics metrics;
         SkScalar y = 0;
         for (SkScalar textSize : { 70, 180, 270, 340 }) {
-            paint.setTextSize(textSize);
-            paint.getFontMetrics(&metrics);
+            font.setSize(textSize);
+            font.getMetrics(&metrics);
             y += -metrics.fAscent;
-            canvas->drawString(text, 10, y, paint);
+            canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 10, y, font, paint);
             y += metrics.fDescent + metrics.fLeading;
         }
 
@@ -94,7 +108,8 @@
         canvas->drawColor(SK_ColorGRAY);
 
         SkPaint paint;
-        paint.setTypeface(fEmojiFont.fTypeface);
+        SkFont font;
+        font.setTypeface(fEmojiFont.fTypeface);
         const char* text = fEmojiFont.fText;
 
         // draw text at different point sizes
@@ -103,22 +118,20 @@
         SkFontMetrics metrics;
         SkScalar y = 0;
         for (SkScalar textSize : { 70, 180, 270, 340 }) {
-            paint.setTextSize(textSize);
-            paint.getFontMetrics(&metrics);
+            font.setSize(textSize);
+            font.getMetrics(&metrics);
             y += -metrics.fAscent;
 
-            int len = SkToInt(strlen(text));
-            SkAutoTArray<SkPoint>  pos(len);
-            SkAutoTArray<SkScalar> widths(len);
-            paint.getTextWidths(text, len, &widths[0]);
+            sk_sp<SkTextBlob> blob = make_hpos_test_blob_utf8(text, font);
+            // Draw with an origin.
+            canvas->drawTextBlob(blob, 10, y, paint);
 
-            SkScalar x = SkIntToScalar(10);
-            for (int i = 0; i < len; ++i) {
-                pos[i].set(x, y);
-                x += widths[i];
-            }
+            // Draw with shifted canvas.
+            canvas->save();
+            canvas->translate(750, 0);
+            canvas->drawTextBlob(blob, 10, y, paint);
+            canvas->restore();
 
-            canvas->drawPosText(text, len, &pos[0], paint);
             y += metrics.fDescent + metrics.fLeading;
         }
 
diff --git a/gm/scaledemoji_rendering.cpp b/gm/scaledemoji_rendering.cpp
index 6b75dc8..767b6f4 100644
--- a/gm/scaledemoji_rendering.cpp
+++ b/gm/scaledemoji_rendering.cpp
@@ -43,29 +43,22 @@
         SkScalar y = 0;
 
         for (const auto& typeface: typefaces) {
+            SkFont font(typeface);
+            font.setEdging(SkFont::Edging::kAlias);
+
             SkPaint paint;
-            paint.setTypeface(typeface);
             const char* text = sk_tool_utils::emoji_sample_text();
             SkFontMetrics metrics;
 
             for (SkScalar textSize : { 70, 150 }) {
-                paint.setTextSize(textSize);
-                paint.getFontMetrics(&metrics);
+                font.setSize(textSize);
+                font.getMetrics(&metrics);
                 // All typefaces should support subpixel mode
-                paint.setSubpixelText(true);
+                font.setSubpixel(true);
                 y += -metrics.fAscent;
 
-                int len = SkToInt(strlen(text));
-                SkAutoTArray<SkPoint>  pos(len);
-                SkAutoTArray<SkScalar> widths(len);
-                int found = paint.getTextWidths(text, len, &widths[0]);
-                SkScalar x = SkIntToScalar(10);
-                for (int i = 0; i < found; ++i) {
-                    pos[i].set(x, y);
-                    x += widths[i];
-                }
-
-                canvas->drawPosText(text, len, &pos[0], paint);
+                canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding,
+                                       10, y, font, paint);
                 y += metrics.fDescent + metrics.fLeading;
             }
         }
diff --git a/gm/shadermaskfilter.cpp b/gm/shadermaskfilter.cpp
index ee9bb1e..fedab48 100644
--- a/gm/shadermaskfilter.cpp
+++ b/gm/shadermaskfilter.cpp
@@ -109,9 +109,8 @@
     SkPaint paint;
     paint.setColor(SK_ColorRED);
 
-    SkPaint labelP;
-    labelP.setAntiAlias(true);
-    labelP.setTextSize(20);
+    SkFont font;
+    font.setSize(20);
 
     const SkRect r2 = r.makeOutset(1.5f, 1.5f);
     SkPaint strokePaint;
@@ -142,7 +141,7 @@
     canvas->translate(10, 10 + 20);
     canvas->save();
     for (int i = 0; i < 5; ++i) {
-        SkTextUtils::DrawString(canvas, gCoverageName[i], r.width()*0.5f, -10, labelP,
+        SkTextUtils::DrawString(canvas, gCoverageName[i], r.width()*0.5f, -10, font, SkPaint(),
                                        SkTextUtils::kCenter_Align);
 
         SkCoverageMode cmode = static_cast<SkCoverageMode>(i);
diff --git a/gm/shadertext3.cpp b/gm/shadertext3.cpp
index c3345f2..b6e2613 100644
--- a/gm/shadertext3.cpp
+++ b/gm/shadertext3.cpp
@@ -103,15 +103,16 @@
 
                 SkPaint fillPaint;
                 fillPaint.setAntiAlias(true);
-                sk_tool_utils::set_portable_typeface(&fillPaint);
-                fillPaint.setTextSize(SkIntToScalar(kPointSize));
                 fillPaint.setFilterQuality(kLow_SkFilterQuality);
                 fillPaint.setShader(SkShader::MakeBitmapShader(fBmp, kTileModes[tm0],
                                                                kTileModes[tm1], &localM));
 
-                canvas->drawText(kText, kTextLen, 0, 0, fillPaint);
-                canvas->drawText(kText, kTextLen, 0, 0, outlinePaint);
-                SkScalar w = fillPaint.measureText(kText, kTextLen);
+                SkFont font(sk_tool_utils::create_portable_typeface(), kPointSize);
+
+                canvas->drawSimpleText(kText, kTextLen, kUTF8_SkTextEncoding, 0, 0, font, fillPaint);
+                canvas->drawSimpleText(kText, kTextLen, kUTF8_SkTextEncoding, 0, 0, font,
+                                       outlinePaint);
+                SkScalar w = font.measureText(kText, kTextLen, kUTF8_SkTextEncoding);
                 canvas->translate(w + 10.f, 0.f);
                 ++i;
                 if (!(i % 2)) {
diff --git a/gm/skbug_257.cpp b/gm/skbug_257.cpp
index 91c0a16..49f06dc 100644
--- a/gm/skbug_257.cpp
+++ b/gm/skbug_257.cpp
@@ -9,6 +9,7 @@
 #include "sk_tool_utils.h"
 #include "SkImage.h"
 #include "SkRRect.h"
+#include "SkTextBlob.h"
 
 static void rotated_checkerboard_shader(SkPaint* paint,
                                         SkColor c1,
@@ -30,47 +31,40 @@
 static void exercise_draw_pos_text(SkCanvas* canvas,
                                    const char* text,
                                    SkScalar x, SkScalar y,
-                                   const SkPaint& paint) {
-    size_t textLen = strlen(text);
-    int count = paint.countText(text, textLen);
-    SkAutoTArray<SkScalar> widths(count);
-    paint.getTextWidths(text, textLen, &widths[0]);
-    SkAutoTArray<SkPoint> pos(count);
-    for (int i = 0; i < count; ++i) {
-        pos[i].set(x, y);
-        x += widths[i];
-    }
-    canvas->drawPosText(text, textLen, &pos[0], paint);
+                                   const SkFont& font, const SkPaint& paint) {
+    const int count = font.countText(text, strlen(text), kUTF8_SkTextEncoding);
+    SkTextBlobBuilder builder;
+    auto rec = builder.allocRunPos(font, count);
+    font.textToGlyphs(text, strlen(text), kUTF8_SkTextEncoding, rec.glyphs, count);
+    font.getPos(rec.glyphs, count, rec.points(), {x, y});
+    canvas->drawTextBlob(builder.make(), 0, 0, paint);
 }
 
 static void exercise_draw_pos_text_h(SkCanvas* canvas,
                                      const char* text,
                                      SkScalar x, SkScalar y,
-                                     const SkPaint& paint) {
-    size_t textLen = strlen(text);
-    int count = paint.countText(text, textLen);
-    SkAutoTArray<SkScalar> widths(count);
-    paint.getTextWidths(text, textLen, &widths[0]);
-    SkAutoTArray<SkScalar> pos(count);
-    for (int i = 0; i < count; ++i) {
-        pos[i] = x;
-        x += widths[i];
-    }
-    canvas->drawPosTextH(text, textLen, &pos[0], y, paint);
+                                     const SkFont& font, const SkPaint& paint) {
+    const int count = font.countText(text, strlen(text), kUTF8_SkTextEncoding);
+    SkTextBlobBuilder builder;
+    auto rec = builder.allocRunPosH(font, count, 0);
+    font.textToGlyphs(text, strlen(text), kUTF8_SkTextEncoding, rec.glyphs, count);
+    font.getXPos(rec.glyphs, count, rec.pos);
+    canvas->drawTextBlob(builder.make(), x, y, paint);
 }
 
 static void test_text(SkCanvas* canvas, SkScalar size,
                       SkColor color, SkScalar Y) {
+    SkFont font(sk_tool_utils::create_portable_typeface(), 24);
+    font.setEdging(SkFont::Edging::kAlias);
     SkPaint type;
-    type.setTextSize(24);
-    sk_tool_utils::set_portable_typeface(&type);
     type.setColor(color);
     const char text[] = "HELLO WORLD";
-    canvas->drawString(text, 32, size / 2 + Y, type);
-    SkScalar lineSpacing = type.getFontSpacing();
-    exercise_draw_pos_text(canvas, text, 32, size / 2 + Y + lineSpacing, type);
+    canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 32, size / 2 + Y,
+                           font, type);
+    SkScalar lineSpacing = font.getSpacing();
+    exercise_draw_pos_text(canvas, text, 32, size / 2 + Y + lineSpacing, font, type);
     exercise_draw_pos_text_h(canvas, text, 32,
-                             size / 2 + Y + 2 * lineSpacing, type);
+                             size / 2 + Y + 2 * lineSpacing, font, type);
 }
 
 // If this GM works correctly, the cyan layer should be lined up with
diff --git a/gm/skbug_5321.cpp b/gm/skbug_5321.cpp
index be7e756..00021c8 100644
--- a/gm/skbug_5321.cpp
+++ b/gm/skbug_5321.cpp
@@ -6,28 +6,34 @@
  */
 
 #include "gm.h"
+#include "SkTextBlob.h"
 
 // https://bugs.skia.org/5321
 // two strings should draw the same.  PDF did not.
 DEF_SIMPLE_GM(skbug_5321, canvas, 128, 128) {
-    SkPaint paint;
-    paint.setStyle(SkPaint::kFill_Style);
-    paint.setTextSize(30);
+    SkFont font;
+    font.setEdging(SkFont::Edging::kAlias);
+    font.setSize(30);
 
-    paint.setTextEncoding(kUTF8_SkTextEncoding);
     const char text[] = "x\314\200y";  // utf8(u"x\u0300y")
     SkScalar x = 20, y = 45;
-    size_t byteLength = strlen(text);
-    canvas->drawText(text, byteLength, x, y, paint);
 
-    int glyph_count = paint.countText(text, byteLength);
-    SkAutoTMalloc<SkScalar> widths(glyph_count);
-    (void)paint.getTextWidths(text, byteLength, &widths[0]);
+    size_t byteLength = strlen(text);
+    canvas->drawSimpleText(text, byteLength, kUTF8_SkTextEncoding, x, y, font, SkPaint());
+
+    y += font.getMetrics(nullptr);
+    int glyph_count = font.countText(text, byteLength, kUTF8_SkTextEncoding);
+    SkTextBlobBuilder builder;
+
+    auto rec = builder.allocRunPosH(font, glyph_count, y);
+    font.textToGlyphs(text, byteLength, kUTF8_SkTextEncoding, rec.glyphs, glyph_count);
+
+    font.getWidths(rec.glyphs, glyph_count, rec.pos);
     for (int i = 0; i < glyph_count; ++i) {
-        SkScalar w = widths[i];
-        widths[i] = x;
+        SkScalar w = rec.pos[i];
+        rec.pos[i] = x;
         x += w;
     }
-    y += paint.getFontMetrics(nullptr);
-    canvas->drawPosTextH(text, byteLength, &widths[0], y, paint);
+
+    canvas->drawTextBlob(builder.make(), 0, 0, SkPaint());
 }
diff --git a/gm/stroketext.cpp b/gm/stroketext.cpp
index ec939d1..fb76975 100644
--- a/gm/stroketext.cpp
+++ b/gm/stroketext.cpp
@@ -9,6 +9,7 @@
 #include "sk_tool_utils.h"
 #include "SkCanvas.h"
 #include "SkDashPathEffect.h"
+#include "SkTextBlob.h"
 
 static void test_nulldev(SkCanvas* canvas) {
     SkBitmap bm;
@@ -25,31 +26,32 @@
     c.writePixels(src, 0, 0);
 }
 
-static void draw_text_stroked(SkCanvas* canvas, const SkPaint& paint, SkScalar strokeWidth) {
+static void draw_text_stroked(SkCanvas* canvas, const SkPaint& paint, const SkFont& font,
+                              SkScalar strokeWidth) {
     SkPaint p(paint);
     SkPoint loc = { 20, 435 };
 
     if (strokeWidth > 0) {
         p.setStyle(SkPaint::kFill_Style);
-        canvas->drawString("P", loc.fX, loc.fY - 225, p);
-        canvas->drawPosText("P", 1, &loc, p);
+        canvas->drawSimpleText("P", 1, kUTF8_SkTextEncoding, loc.fX, loc.fY - 225, font, p);
+        canvas->drawTextBlob(SkTextBlob::MakeFromPosText("P", 1, &loc, font), 0, 0, p);
     }
 
     p.setColor(SK_ColorRED);
     p.setStyle(SkPaint::kStroke_Style);
     p.setStrokeWidth(strokeWidth);
 
-    canvas->drawString("P", loc.fX, loc.fY - 225, p);
-    canvas->drawPosText("P", 1, &loc, p);
+    canvas->drawSimpleText("P", 1, kUTF8_SkTextEncoding, loc.fX, loc.fY - 225, font, p);
+    canvas->drawTextBlob(SkTextBlob::MakeFromPosText("P", 1, &loc, font), 0, 0, p);
 }
 
-static void draw_text_set(SkCanvas* canvas, const SkPaint& paint) {
+static void draw_text_set(SkCanvas* canvas, const SkPaint& paint, const SkFont& font) {
     SkAutoCanvasRestore acr(canvas, true);
 
-    draw_text_stroked(canvas, paint, 10);
+    draw_text_stroked(canvas, paint, font, 10);
 
     canvas->translate(200, 0);
-    draw_text_stroked(canvas, paint, 0);
+    draw_text_stroked(canvas, paint, font, 0);
 
     const SkScalar intervals[] = { 20, 10, 5, 10 };
     const SkScalar phase = 0;
@@ -57,7 +59,7 @@
     canvas->translate(200, 0);
     SkPaint p(paint);
     p.setPathEffect(SkDashPathEffect::Make(intervals, SK_ARRAY_COUNT(intervals), phase));
-    draw_text_stroked(canvas, p, 10);
+    draw_text_stroked(canvas, p, font, 10);
 }
 
 namespace {
@@ -68,15 +70,15 @@
 }
 
 DEF_SIMPLE_GM(stroketext, canvas, 1200, 480) {
-        if (true) { test_nulldev(canvas); }
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&paint);
+    if (true) { test_nulldev(canvas); }
 
-        paint.setTextSize(kBelowThreshold_TextSize);
-        draw_text_set(canvas, paint);
+    SkPaint paint;
+    paint.setAntiAlias(true);
 
-        canvas->translate(600, 0);
-        paint.setTextSize(kAboveThreshold_TextSize);
-        draw_text_set(canvas, paint);
+    SkFont font(sk_tool_utils::create_portable_typeface(), kBelowThreshold_TextSize);
+    draw_text_set(canvas, paint, font);
+
+    canvas->translate(600, 0);
+    font.setSize(kAboveThreshold_TextSize);
+    draw_text_set(canvas, paint, font);
 }
diff --git a/gm/surface.cpp b/gm/surface.cpp
index 4d302ac..f8424ba 100644
--- a/gm/surface.cpp
+++ b/gm/surface.cpp
@@ -36,7 +36,6 @@
     SkPaint paint;
 
     paint.setAntiAlias(true);
-    paint.setLCDRenderText(true);
     paint.setDither(true);
 
     paint.setShader(make_shader());
@@ -44,9 +43,10 @@
     paint.setShader(nullptr);
 
     paint.setColor(SK_ColorWHITE);
-    paint.setTextSize(32);
-    sk_tool_utils::set_portable_typeface(&paint);
-    SkTextUtils::DrawString(canvas, label, W / 2, H * 3 / 4, paint, SkTextUtils::kCenter_Align);
+    SkFont font(sk_tool_utils::create_portable_typeface(), 32);
+    font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
+    SkTextUtils::DrawString(canvas, label, W / 2, H * 3 / 4, font, paint,
+                            SkTextUtils::kCenter_Align);
 }
 
 class SurfacePropsGM : public skiagm::GM {
diff --git a/gm/text_scale_skew.cpp b/gm/text_scale_skew.cpp
index 992bc6e..977f9e3 100644
--- a/gm/text_scale_skew.cpp
+++ b/gm/text_scale_skew.cpp
@@ -11,16 +11,17 @@
 // http://bug.skia.org/7315
 DEF_SIMPLE_GM(text_scale_skew, canvas, 256, 128) {
     SkPaint p;
-    p.setTextSize(18.0f);
     p.setAntiAlias(true);
+    SkFont font;
+    font.setSize(18.0f);
     float y = 10.0f;
     for (float scale : { 0.5f, 0.71f, 1.0f, 1.41f, 2.0f }) {
-        p.setTextScaleX(scale);
-        y += p.getFontSpacing();
+        font.setScaleX(scale);
+        y += font.getSpacing();
         float x = 50.0f;
         for (float skew : { -0.5f, 0.0f, 0.5f }) {
-            p.setTextSkewX(skew);
-            SkTextUtils::DrawString(canvas, "Skia", x, y, p, SkTextUtils::kCenter_Align);
+            font.setSkewX(skew);
+            SkTextUtils::DrawString(canvas, "Skia", x, y, font, p, SkTextUtils::kCenter_Align);
             x += 78.0f;
         }
     }
diff --git a/gm/textblob.cpp b/gm/textblob.cpp
index 867e6cd..d22a35c 100644
--- a/gm/textblob.cpp
+++ b/gm/textblob.cpp
@@ -85,13 +85,12 @@
 protected:
     void onOnceBeforeDraw() override {
         fTypeface = sk_tool_utils::create_portable_typeface("serif", SkFontStyle());
-        SkPaint p;
-        p.setTypeface(fTypeface);
+        SkFont font(fTypeface);
         size_t txtLen = strlen(fText);
-        int glyphCount = p.textToGlyphs(fText, txtLen, nullptr);
+        int glyphCount = font.countText(fText, txtLen, kUTF8_SkTextEncoding);
 
         fGlyphs.append(glyphCount);
-        p.textToGlyphs(fText, txtLen, fGlyphs.begin());
+        font.textToGlyphs(fText, txtLen, kUTF8_SkTextEncoding, fGlyphs.begin(), glyphCount);
     }
 
     SkString onShortName() override {
diff --git a/gm/textblobblockreordering.cpp b/gm/textblobblockreordering.cpp
index bed5d2f..1121380 100644
--- a/gm/textblobblockreordering.cpp
+++ b/gm/textblobblockreordering.cpp
@@ -24,16 +24,15 @@
 
         // make textblob
         // Large text is used to trigger atlas eviction
-        SkPaint paint;
-        paint.setTextSize(56);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 56);
+        font.setEdging(SkFont::Edging::kAlias);
         const char* text = "AB";
-        sk_tool_utils::set_portable_typeface(&paint);
 
         SkRect bounds;
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
 
         SkScalar yOffset = bounds.height();
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, yOffset - 30);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset - 30);
 
         // build
         fBlob = builder.make();
diff --git a/gm/textblobcolortrans.cpp b/gm/textblobcolortrans.cpp
index cba50a3..40f58ed 100644
--- a/gm/textblobcolortrans.cpp
+++ b/gm/textblobcolortrans.cpp
@@ -29,24 +29,21 @@
 
         // make textblob
         // Large text is used to trigger atlas eviction
-        SkPaint paint;
-        paint.setTextSize(256);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 256);
+        font.setEdging(SkFont::Edging::kAlias);
         const char* text = "AB";
-        sk_tool_utils::set_portable_typeface(&paint);
 
         SkRect bounds;
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
 
         SkScalar yOffset = bounds.height();
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, yOffset - 30);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset - 30);
 
         // A8
-        paint.setTextSize(28);
+        font.setSize(28);
         text = "The quick brown fox jumps over the lazy dog.";
-        paint.setSubpixelText(false);
-        paint.setLCDRenderText(false);
-        paint.measureText(text, strlen(text), &bounds);
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, yOffset - 8);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset - 8);
 
         // build
         fBlob = builder.make();
diff --git a/gm/textblobgeometrychange.cpp b/gm/textblobgeometrychange.cpp
index 57f3962..250d012 100644
--- a/gm/textblobgeometrychange.cpp
+++ b/gm/textblobgeometrychange.cpp
@@ -31,15 +31,12 @@
     void onDraw(SkCanvas* canvas) override {
         const char text[] = "Hamburgefons";
 
-        SkPaint paint;
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setTextSize(20);
-        paint.setAntiAlias(true);
-        paint.setLCDRenderText(true);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 20);
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
 
         SkTextBlobBuilder builder;
 
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 10, 10);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 10, 10);
 
         sk_sp<SkTextBlob> blob(builder.make());
 
@@ -53,12 +50,12 @@
         SkPaint rectPaint;
         rectPaint.setColor(0xffffffff);
         canvas->drawRect(rect, rectPaint);
-        canvas->drawTextBlob(blob, 10, 50, paint);
+        canvas->drawTextBlob(blob, 10, 50, SkPaint());
 
         // This should not look garbled since we should disable LCD text in this case
         // (i.e., unknown pixel geometry)
         c->clear(0x00ffffff);
-        c->drawTextBlob(blob, 10, 150, paint);
+        c->drawTextBlob(blob, 10, 150, SkPaint());
         surface->draw(canvas, 0, 0, nullptr);
     }
 
diff --git a/gm/textblobmixedsizes.cpp b/gm/textblobmixedsizes.cpp
index 4a56cf1..2246350 100644
--- a/gm/textblobmixedsizes.cpp
+++ b/gm/textblobmixedsizes.cpp
@@ -31,54 +31,49 @@
         SkTextBlobBuilder builder;
 
         // make textblob.  To stress distance fields, we choose sizes appropriately
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setSubpixelText(true);
-        paint.setLCDRenderText(true);
-        paint.setTypeface(MakeResourceAsTypeface("fonts/HangingS.ttf"));
+        SkFont font(MakeResourceAsTypeface("fonts/HangingS.ttf"), 262);
+        font.setSubpixel(true);
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
 
         const char* text = "Skia";
 
-        // extra large
-        paint.setTextSize(262);
-
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, 0);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, 0);
 
         // large
         SkRect bounds;
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         SkScalar yOffset = bounds.height();
-        paint.setTextSize(162);
+        font.setSize(162);
 
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, yOffset);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset);
 
         // Medium
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         yOffset += bounds.height();
-        paint.setTextSize(72);
+        font.setSize(72);
 
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, yOffset);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset);
 
         // Small
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         yOffset += bounds.height();
-        paint.setTextSize(32);
+        font.setSize(32);
 
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, yOffset);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset);
 
         // micro (will fall out of distance field text even if distance field text is enabled)
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         yOffset += bounds.height();
-        paint.setTextSize(14);
+        font.setSize(14);
 
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, yOffset);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset);
 
         // Zero size.
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         yOffset += bounds.height();
-        paint.setTextSize(0);
+        font.setSize(0);
 
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, yOffset);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, yOffset);
 
         // build
         fBlob = builder.make();
diff --git a/gm/textblobrandomfont.cpp b/gm/textblobrandomfont.cpp
index 58749e9..b0d64ed 100644
--- a/gm/textblobrandomfont.cpp
+++ b/gm/textblobrandomfont.cpp
@@ -32,50 +32,52 @@
 
         const char* text = "The quick brown fox jumps over the lazy dog.";
 
-        // make textbloben
         SkPaint paint;
-        paint.setTextSize(32);
         paint.setAntiAlias(true);
-        paint.setLCDRenderText(true);
+        paint.setColor(SK_ColorMAGENTA);
+
+        // make textbloben
+        SkFont font;
+        font.setSize(32);
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
 
         // Setup our random scaler context
         auto typeface = sk_tool_utils::create_portable_typeface("sans-serif", SkFontStyle::Bold());
         if (!typeface) {
             typeface = SkTypeface::MakeDefault();
         }
-        paint.setColor(SK_ColorMAGENTA);
-        paint.setTypeface(sk_make_sp<SkRandomTypeface>(std::move(typeface), paint, false));
+        font.setTypeface(sk_make_sp<SkRandomTypeface>(std::move(typeface), paint, false));
 
         SkScalar y = 0;
         SkRect bounds;
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         y -= bounds.fTop;
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, y);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, y);
         y += bounds.fBottom;
 
         // A8
         const char* bigtext1 = "The quick brown fox";
         const char* bigtext2 = "jumps over the lazy dog.";
-        paint.setTextSize(160);
-        paint.setSubpixelText(false);
-        paint.setLCDRenderText(false);
-        paint.measureText(bigtext1, strlen(bigtext1), &bounds);
+        font.setSize(160);
+        font.setSubpixel(false);
+        font.setEdging(SkFont::Edging::kAntiAlias);
+        font.measureText(bigtext1, strlen(bigtext1), kUTF8_SkTextEncoding, &bounds);
         y -= bounds.fTop;
-        sk_tool_utils::add_to_text_blob(&builder, bigtext1, paint, 0, y);
+        sk_tool_utils::add_to_text_blob(&builder, bigtext1, font, 0, y);
         y += bounds.fBottom;
 
-        paint.measureText(bigtext2, strlen(bigtext2), &bounds);
+        font.measureText(bigtext2, strlen(bigtext2), kUTF8_SkTextEncoding, &bounds);
         y -= bounds.fTop;
-        sk_tool_utils::add_to_text_blob(&builder, bigtext2, paint, 0, y);
+        sk_tool_utils::add_to_text_blob(&builder, bigtext2, font, 0, y);
         y += bounds.fBottom;
 
         // color emoji
         if (sk_sp<SkTypeface> origEmoji = sk_tool_utils::emoji_typeface()) {
-            paint.setTypeface(sk_make_sp<SkRandomTypeface>(origEmoji, paint, false));
+            font.setTypeface(sk_make_sp<SkRandomTypeface>(origEmoji, paint, false));
             const char* emojiText = sk_tool_utils::emoji_sample_text();
-            paint.measureText(emojiText, strlen(emojiText), &bounds);
+            font.measureText(emojiText, strlen(emojiText), kUTF8_SkTextEncoding, &bounds);
             y -= bounds.fTop;
-            sk_tool_utils::add_to_text_blob(&builder, emojiText, paint, 0, y);
+            sk_tool_utils::add_to_text_blob(&builder, emojiText, font, 0, y);
             y += bounds.fBottom;
         }
 
diff --git a/gm/textblobshader.cpp b/gm/textblobshader.cpp
index 779b5b9..971f628 100644
--- a/gm/textblobshader.cpp
+++ b/gm/textblobshader.cpp
@@ -19,17 +19,18 @@
 // This GM exercises drawTextBlob offset vs. shader space behavior.
 class TextBlobShaderGM : public skiagm::GM {
 public:
-    TextBlobShaderGM(const char* txt) {
-        SkPaint p;
-        sk_tool_utils::set_portable_typeface(&p);
-        size_t txtLen = strlen(txt);
-        fGlyphs.append(p.textToGlyphs(txt, txtLen, nullptr));
-        p.textToGlyphs(txt, txtLen, fGlyphs.begin());
-    }
+    TextBlobShaderGM() {}
 
-protected:
-
+private:
     void onOnceBeforeDraw() override {
+        {
+            SkFont font(sk_tool_utils::create_portable_typeface());
+            const char* txt = "Blobber";
+            size_t txtLen = strlen(txt);
+            fGlyphs.append(font.countText(txt, txtLen, kUTF8_SkTextEncoding));
+            font.textToGlyphs(txt, txtLen, kUTF8_SkTextEncoding, fGlyphs.begin(), fGlyphs.count());
+        }
+
         SkFont font;
         font.setSubpixel(true);
         font.setEdging(SkFont::Edging::kAntiAlias);
@@ -102,7 +103,6 @@
         }
     }
 
-private:
     SkTDArray<uint16_t> fGlyphs;
     sk_sp<SkTextBlob>   fBlob;
     sk_sp<SkShader>     fShader;
@@ -110,4 +110,4 @@
     typedef skiagm::GM INHERITED;
 };
 
-DEF_GM(return new TextBlobShaderGM("Blobber");)
+DEF_GM(return new TextBlobShaderGM;)
diff --git a/gm/textblobtransforms.cpp b/gm/textblobtransforms.cpp
index 8bf24fd..24cb564 100644
--- a/gm/textblobtransforms.cpp
+++ b/gm/textblobtransforms.cpp
@@ -26,28 +26,27 @@
         SkTextBlobBuilder builder;
 
         // make textblob.  To stress distance fields, we choose sizes appropriately
-        SkPaint paint;
-        paint.setTextSize(162);
+        SkFont font(sk_tool_utils::create_portable_typeface(), 162);
+        font.setEdging(SkFont::Edging::kAlias);
         const char* text = "A";
-        sk_tool_utils::set_portable_typeface(&paint);
 
         SkRect bounds;
-        paint.measureText(text, strlen(text), &bounds);
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 0, 0);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, 0, 0);
 
         // Medium
         SkScalar xOffset = bounds.width() + 5;
-        paint.setTextSize(72);
+        font.setSize(72);
         text = "B";
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, xOffset, 0);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, xOffset, 0);
 
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         SkScalar yOffset = bounds.height();
 
         // Small
-        paint.setTextSize(32);
+        font.setSize(32);
         text = "C";
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, xOffset, -yOffset - 10);
+        sk_tool_utils::add_to_text_blob(&builder, text, font, xOffset, -yOffset - 10);
 
         // build
         fBlob = builder.make();
diff --git a/gm/textblobuseaftergpufree.cpp b/gm/textblobuseaftergpufree.cpp
index f09eb1f..f3f11d1 100644
--- a/gm/textblobuseaftergpufree.cpp
+++ b/gm/textblobuseaftergpufree.cpp
@@ -37,27 +37,19 @@
 
         const char text[] = "Hamburgefons";
 
-        SkPaint paint;
-        sk_tool_utils::set_portable_typeface(&paint);
-        paint.setAntiAlias(true);
-        paint.setTextSize(20);
-
-        SkTextBlobBuilder builder;
-
-        sk_tool_utils::add_to_text_blob(&builder, text, paint, 10, 10);
-
-        sk_sp<SkTextBlob> blob(builder.make());
+        SkFont font(sk_tool_utils::create_portable_typeface(), 20);
+        auto blob = SkTextBlob::MakeFromText(text, strlen(text), font);
 
         // draw textblob
         SkRect rect = SkRect::MakeLTRB(0.f, 0.f, SkIntToScalar(kWidth), kHeight / 2.f);
         SkPaint rectPaint;
         rectPaint.setColor(0xffffffff);
         canvas->drawRect(rect, rectPaint);
-        canvas->drawTextBlob(blob, 10, 50, paint);
+        canvas->drawTextBlob(blob, 20, 60, SkPaint());
 
         // This text should look fine
         canvas->getGrContext()->freeGpuResources();
-        canvas->drawTextBlob(blob, 10, 150, paint);
+        canvas->drawTextBlob(blob, 20, 160, SkPaint());
     }
 
 private:
diff --git a/gm/texteffects.cpp b/gm/texteffects.cpp
index 9841c82..53b0f7f 100644
--- a/gm/texteffects.cpp
+++ b/gm/texteffects.cpp
@@ -17,15 +17,13 @@
 
 #include "Sk2DPathEffect.h"
 
-#ifdef SK_SUPPORT_LEGACY_TEXTINTERCEPTS
-
 static SkPath create_underline(const SkTDArray<SkScalar>& intersections,
         SkScalar last, SkScalar finalPos,
         SkScalar uPos, SkScalar uWidth, SkScalar textSize) {
     SkPath underline;
     SkScalar end = last;
     for (int index = 0; index < intersections.count(); index += 2) {
-        SkScalar start = intersections[index] - uWidth;;
+        SkScalar start = intersections[index] - uWidth;
         end = intersections[index + 1] + uWidth;
         if (start > last && last + textSize / 12 < start) {
             underline.moveTo(last, uPos);
@@ -40,122 +38,17 @@
     return underline;
 }
 
-static void find_intercepts(const char* test, size_t len, SkScalar x, SkScalar y,
-        const SkPaint& paint, SkScalar uWidth, SkTDArray<SkScalar>* intersections) {
-    SkScalar uPos = y + uWidth;
-    SkScalar bounds[2] = { uPos - uWidth / 2, uPos + uWidth / 2 };
-    int count = paint.getTextIntercepts(test, len, x, y, bounds, nullptr);
-    SkASSERT(!(count % 2));
-    if (count) {
-        intersections->setCount(count);
-        paint.getTextIntercepts(test, len, x, y, bounds, intersections->begin());
-    }
-}
-
-DEF_SIMPLE_GM(fancyunderline, canvas, 900, 1350) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const char* fam[] = { "sans-serif", "serif", "monospace" };
-    const char test[] = "aAjJgGyY_|{-(~[,]qQ}pP}zZ";
-    SkPoint textPt = { 10, 80 };
-    for (size_t font = 0; font < SK_ARRAY_COUNT(fam); ++font) {
-        sk_tool_utils::set_portable_typeface(&paint, fam[font]);
-        for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
-            paint.setTextSize(textSize);
-            const SkScalar uWidth = textSize / 15;
-            paint.setStrokeWidth(uWidth);
-            paint.setStyle(SkPaint::kFill_Style);
-            canvas->drawText(test, sizeof(test) - 1, textPt.fX, textPt.fY, paint);
-
-            SkTDArray<SkScalar> intersections;
-            find_intercepts(test, sizeof(test) - 1, textPt.fX, textPt.fY, paint, uWidth,
-                &intersections);
-
-            SkScalar start = textPt.fX;
-            SkScalar end = paint.measureText(test, sizeof(test) - 1) + textPt.fX;
-            SkScalar uPos = textPt.fY + uWidth;
-            SkPath underline = create_underline(intersections, start, end, uPos, uWidth, textSize);
-            paint.setStyle(SkPaint::kStroke_Style);
-            canvas->drawPath(underline, paint);
-
-            canvas->translate(0, textSize * 1.3f);
-        }
-        canvas->translate(0, 60);
-    }
-}
-
-static void find_intercepts(const char* test, size_t len, const SkPoint* pos, const SkPaint& paint,
-        SkScalar uWidth, SkTDArray<SkScalar>* intersections) {
-    SkScalar uPos = pos[0].fY + uWidth;
-    SkScalar bounds[2] = { uPos - uWidth / 2, uPos + uWidth / 2 };
-    int count = paint.getPosTextIntercepts(test, len, pos, bounds, nullptr);
-    SkASSERT(!(count % 2));
-    if (count) {
-        intersections->setCount(count);
-        paint.getPosTextIntercepts(test, len, pos, bounds, intersections->begin());
-    }
-}
-
-DEF_SIMPLE_GM(fancyposunderline, canvas, 900, 1350) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const char* fam[] = { "sans-serif", "serif", "monospace" };
-    const char test[] = "aAjJgGyY_|{-(~[,]qQ}pP}zZ";
-    SkPoint textPt = { 10, 80 };
-    for (size_t font = 0; font < SK_ARRAY_COUNT(fam); ++font) {
-        sk_tool_utils::set_portable_typeface(&paint, fam[font]);
-        for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
-            paint.setTextSize(textSize);
-            const SkScalar uWidth = textSize / 15;
-            paint.setStrokeWidth(uWidth);
-            paint.setStyle(SkPaint::kFill_Style);
-            int widthCount = paint.getTextWidths(test, sizeof(test) - 1, nullptr);
-            SkTDArray<SkScalar> widths;
-            widths.setCount(widthCount);
-            (void) paint.getTextWidths(test, sizeof(test) - 1, widths.begin());
-            SkTDArray<SkPoint> pos;
-            pos.setCount(widthCount);
-            SkScalar posX = textPt.fX;
-            for (int index = 0; index < widthCount; ++index) {
-                pos[index].fX = posX;
-                posX += widths[index];
-                pos[index].fY = textPt.fY + (textSize / 25) * (index % 4);
-            }
-            canvas->drawPosText(test, sizeof(test) - 1, pos.begin(), paint);
-
-            SkTDArray<SkScalar> intersections;
-            find_intercepts(test, sizeof(test) - 1, pos.begin(), paint, uWidth, &intersections);
-
-            SkScalar start = textPt.fX;
-            SkScalar end = posX;
-            SkScalar uPos = textPt.fY + uWidth;
-            SkPath underline = create_underline(intersections, start, end, uPos, uWidth, textSize);
-            paint.setStyle(SkPaint::kStroke_Style);
-            canvas->drawPath(underline, paint);
-
-            canvas->translate(0, textSize * 1.3f);
-        }
-        canvas->translate(0, 60);
-    }
-}
-
 namespace {
 
 sk_sp<SkTextBlob> MakeFancyBlob(const SkPaint& paint, const char* text) {
-    SkPaint blobPaint(paint);
+    const SkFont font = SkFont::LEGACY_ExtractFromPaint(paint);
 
     const size_t textLen = strlen(text);
-    const int glyphCount = blobPaint.textToGlyphs(text, textLen, nullptr);
+    const int glyphCount = font.countText(text, textLen, kUTF8_SkTextEncoding);
     SkAutoTArray<SkGlyphID> glyphs(glyphCount);
-    blobPaint.textToGlyphs(text, textLen, glyphs.get());
-
-    blobPaint.setTextEncoding(kGlyphID_SkTextEncoding);
-    const size_t glyphTextBytes = SkTo<uint32_t>(glyphCount) * sizeof(SkGlyphID);
-    const int widthCount = blobPaint.getTextWidths(glyphs.get(), glyphTextBytes, nullptr);
-    SkAssertResult(widthCount == glyphCount);
-
+    font.textToGlyphs(text, textLen, kUTF8_SkTextEncoding, glyphs.get(), glyphCount);
     SkAutoTArray<SkScalar> widths(glyphCount);
-    blobPaint.getTextWidths(glyphs.get(), glyphTextBytes, widths.get());
+    font.getWidths(glyphs.get(), glyphCount, widths.get());
 
     SkTextBlobBuilder blobBuilder;
     int glyphIndex = 0;
@@ -164,7 +57,7 @@
     // Default-positioned run.
     {
         const int defaultRunLen = glyphCount / 3;
-        const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRun(blobPaint,
+        const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRun(font,
                                                                        defaultRunLen,
                                                                        advance, 0);
         memcpy(buf.glyphs, glyphs.get(), SkTo<uint32_t>(defaultRunLen) * sizeof(SkGlyphID));
@@ -177,7 +70,7 @@
     // Horizontal-positioned run.
     {
         const int horizontalRunLen = glyphCount / 3;
-        const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPosH(blobPaint,
+        const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPosH(font,
                                                                            horizontalRunLen,
                                                                            0);
         memcpy(buf.glyphs, glyphs.get() + glyphIndex,
@@ -191,7 +84,7 @@
     // Full-positioned run.
     {
         const int fullRunLen = glyphCount - glyphIndex;
-        const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPos(blobPaint, fullRunLen);
+        const SkTextBlobBuilder::RunBuffer& buf = blobBuilder.allocRunPos(font, fullRunLen);
         memcpy(buf.glyphs, glyphs.get() + glyphIndex,
                SkTo<uint32_t>(fullRunLen) * sizeof(SkGlyphID));
         for (int i = 0; i < fullRunLen; ++i) {
@@ -226,12 +119,12 @@
 
             const SkScalar uPos = uWidth;
             const SkScalar bounds[2] = { uPos - uWidth / 2, uPos + uWidth / 2 };
-            const int interceptCount = paint.getTextBlobIntercepts(blob.get(), bounds, nullptr);
+            const int interceptCount = blob->getIntercepts(bounds, nullptr, &paint);
             SkASSERT(!(interceptCount % 2));
 
             SkTDArray<SkScalar> intercepts;
             intercepts.setCount(interceptCount);
-            paint.getTextBlobIntercepts(blob.get(), bounds, intercepts.begin());
+            blob->getIntercepts(bounds, intercepts.begin(), &paint);
 
             const SkScalar start = blob->bounds().left();
             const SkScalar end = blob->bounds().right();
@@ -247,44 +140,115 @@
     }
 }
 
-DEF_SIMPLE_GM(fancyunderlinebars, canvas, 1500, 460) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    const char test[] = " .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_ .}]_";
-    SkPoint textPt = { 10, 80 };
-    sk_tool_utils::set_portable_typeface(&paint, "serif");
-    for (SkScalar textSize = 100; textSize > 10; textSize -= 20) {
-        paint.setTextSize(textSize);
-        SkScalar uWidth = textSize / 15;
-        paint.setStrokeWidth(uWidth);
-        paint.setStyle(SkPaint::kFill_Style);
-        int widthCount = paint.getTextWidths(test, sizeof(test) - 1, nullptr);
-        SkTDArray<SkScalar> widths;
-        widths.setCount(widthCount);
-        (void) paint.getTextWidths(test, sizeof(test) - 1, widths.begin());
-        SkTDArray<SkPoint> pos;
-        pos.setCount(widthCount);
-        SkScalar posX = textPt.fX;
-        pos[0] = textPt;
-        posX += widths[0];
-        for (int index = 1; index < widthCount; ++index) {
-            pos[index].fX = posX;
-            posX += widths[index];
-            pos[index].fY = textPt.fY - (textSize / 50) * (index / 5) + textSize / 50 * 4;
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+static sk_sp<SkTextBlob> make_text(const SkFont& font, const SkGlyphID glyphs[], int count) {
+    return SkTextBlob::MakeFromText(glyphs, count * sizeof(SkGlyphID), font,
+                                    kGlyphID_SkTextEncoding);
+}
+
+static sk_sp<SkTextBlob> make_posh(const SkFont& font, const SkGlyphID glyphs[], int count,
+                                   SkScalar spacing) {
+    SkAutoTArray<SkScalar> xpos(count);
+    font.getXPos(glyphs, count, xpos.get());
+    for (int i = 1; i < count; ++i) {
+        xpos[i] += spacing * i;
+    }
+    return SkTextBlob::MakeFromPosTextH(glyphs, count * sizeof(SkGlyphID), xpos.get(), 0, font,
+                                        kGlyphID_SkTextEncoding);
+}
+
+static sk_sp<SkTextBlob> make_pos(const SkFont& font, const SkGlyphID glyphs[], int count,
+                                  SkScalar spacing) {
+    SkAutoTArray<SkPoint> pos(count);
+    font.getPos(glyphs, count, pos.get());
+    for (int i = 1; i < count; ++i) {
+        pos[i].fX += spacing * i;
+    }
+    return SkTextBlob::MakeFromPosText(glyphs, count * sizeof(SkGlyphID), pos.get(), font,
+                                       kGlyphID_SkTextEncoding);
+}
+
+// widen the gaps with a margin (on each side of the gap), elimnating segments that go away
+static int trim_with_halo(SkScalar intervals[], int count, SkScalar margin) {
+    SkASSERT(count > 0 && (count & 1) == 0);
+
+    int n = count;
+    SkScalar* stop = intervals + count;
+    *intervals++ -= margin;
+    while (intervals < stop - 1) {
+        intervals[0] += margin;
+        intervals[1] -= margin;
+        if (intervals[0] >= intervals[1]) { // went away
+            int remaining = stop - intervals - 2;
+            SkASSERT(remaining >= 0 && (remaining & 1) == 1);
+            if (remaining > 0) {
+                memmove(intervals, intervals + 2, remaining * sizeof(SkScalar));
+            }
+            stop -= 2;
+            n -= 2;
+        } else {
+            intervals += 2;
         }
-        canvas->drawPosText(test, sizeof(test) - 1, pos.begin(), paint);
+    }
+    *intervals += margin;
+    return n;
+}
 
-        SkTDArray<SkScalar> intersections;
-        find_intercepts(test, sizeof(test) - 1, pos.begin(), paint, uWidth, &intersections);
+static void draw_blob_adorned(SkCanvas* canvas, sk_sp<SkTextBlob> blob) {
+    SkPaint paint;
 
-        SkScalar start = textPt.fX;
-        SkScalar end = posX;
-        SkScalar uPos = pos[0].fY + uWidth;
-        SkPath underline = create_underline(intersections, start, end, uPos, uWidth, textSize);
-        paint.setStyle(SkPaint::kStroke_Style);
-        canvas->drawPath(underline, paint);
-        canvas->translate(0, textSize * 1.3f);
+    canvas->drawTextBlob(blob.get(), 0, 0, paint);
+
+    const SkScalar yminmax[] = { 8, 16 };
+    int count = blob->getIntercepts(yminmax, nullptr);
+    if (!count) {
+        return;
+    }
+
+    SkAutoTArray<SkScalar> intervals(count);
+    blob->getIntercepts(yminmax, intervals.get());
+    count = trim_with_halo(intervals.get(), count, SkScalarHalf(yminmax[1] - yminmax[0]) * 1.5f);
+    SkASSERT(count >= 2);
+
+    const SkScalar y = SkScalarAve(yminmax[0], yminmax[1]);
+    SkScalar end = 900;
+    SkPath path;
+    path.moveTo({0, y});
+    for (int i = 0; i < count; i += 2) {
+        path.lineTo(intervals[i], y).moveTo(intervals[i+1], y);
+    }
+    if (intervals[count - 1] < end) {
+        path.lineTo(end, y);
+    }
+
+    paint.setAntiAlias(true);
+    paint.setStyle(SkPaint::kStroke_Style);
+    paint.setStrokeWidth(yminmax[1] - yminmax[0]);
+    canvas->drawPath(path, paint);
+}
+
+DEF_SIMPLE_GM(textblob_intercepts, canvas, 940, 800) {
+    const char text[] = "Hyjay {worlp}.";
+    const size_t length = strlen(text);
+    SkFont font;
+    font.setTypeface(sk_tool_utils::create_portable_typeface());
+    font.setSize(100);
+    font.setEdging(SkFont::Edging::kAntiAlias);
+    const int count = font.countText(text, length, kUTF8_SkTextEncoding);
+    SkAutoTArray<SkGlyphID> glyphs(count);
+    font.textToGlyphs(text, length, kUTF8_SkTextEncoding, glyphs.get(), count);
+
+    auto b0 = make_text(font, glyphs.get(), count);
+
+    canvas->translate(20, 120);
+    draw_blob_adorned(canvas, b0);
+    for (SkScalar spacing = 0; spacing < 30; spacing += 20) {
+        auto b1 = make_posh(font, glyphs.get(), count, spacing);
+        auto b2 = make_pos( font, glyphs.get(), count, spacing);
+        canvas->translate(0, 150);
+        draw_blob_adorned(canvas, b1);
+        canvas->translate(0, 150);
+        draw_blob_adorned(canvas, b2);
     }
 }
-#endif
-
diff --git a/gm/texturedomaineffect.cpp b/gm/texturedomaineffect.cpp
index 5777b35..99680f1 100644
--- a/gm/texturedomaineffect.cpp
+++ b/gm/texturedomaineffect.cpp
@@ -15,10 +15,11 @@
 #include "GrRenderTargetContextPriv.h"
 #include "SkGradientShader.h"
 #include "SkImage.h"
+#include "SkImage_Base.h"
 #include "SkSurface.h"
 #include "effects/GrTextureDomain.h"
 #include "ops/GrDrawOp.h"
-#include "ops/GrRectOpFactory.h"
+#include "ops/GrFillRectOp.h"
 
 namespace skiagm {
 /**
@@ -26,13 +27,20 @@
  */
 class TextureDomainEffect : public GM {
 public:
-    TextureDomainEffect() {
+    TextureDomainEffect(GrSamplerState::Filter filter)
+            : fFilter(filter) {
         this->setBGColor(0xFFFFFFFF);
     }
 
 protected:
     SkString onShortName() override {
-        return SkString("texture_domain_effect");
+        SkString name("texture_domain_effect");
+        if (fFilter == GrSamplerState::Filter::kBilerp) {
+            name.append("_bilerp");
+        } else if (fFilter == GrSamplerState::Filter::kMipMap) {
+            name.append("_mipmap");
+        }
+        return name;
     }
 
     SkISize onISize() override {
@@ -88,8 +96,18 @@
         }
 
         GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
-        sk_sp<GrTextureProxy> proxy = proxyProvider->createTextureProxy(
+        sk_sp<GrTextureProxy> proxy;
+        if (fFilter == GrSamplerState::Filter::kMipMap) {
+            SkBitmap copy;
+            SkImageInfo info = as_IB(fImage)->onImageInfo().makeColorType(kN32_SkColorType);
+            if (!copy.tryAllocPixels(info) || !fImage->readPixels(copy.pixmap(), 0, 0)) {
+                return;
+            }
+            proxy = proxyProvider->createMipMapProxyFromBitmap(copy);
+        } else {
+            proxy = proxyProvider->createTextureProxy(
                 fImage, kNone_GrSurfaceFlags, 1, SkBudgeted::kYes, SkBackingFit::kExact);
+        }
         if (!proxy) {
             return;
         }
@@ -100,11 +118,10 @@
         textureMatrices.push_back();
         textureMatrices.back().setRotate(45.f, proxy->width() / 2.f, proxy->height() / 2.f);
 
-
         const SkIRect texelDomains[] = {
             fImage->bounds(),
-            SkIRect::MakeXYWH(fImage->width() / 4, fImage->height() / 4,
-                              fImage->width() / 2, fImage->height() / 2),
+            SkIRect::MakeXYWH(fImage->width() / 4 - 1, fImage->height() / 4 - 1,
+                              fImage->width() / 2 + 2, fImage->height() / 2 + 2),
         };
 
         SkRect renderRect = SkRect::Make(fImage->bounds());
@@ -116,12 +133,18 @@
                 SkScalar x = kDrawPad + kTestPad;
                 for (int m = 0; m < GrTextureDomain::kModeCount; ++m) {
                     GrTextureDomain::Mode mode = (GrTextureDomain::Mode) m;
+                    if (fFilter != GrSamplerState::Filter::kNearest &&
+                        mode == GrTextureDomain::kRepeat_Mode) {
+                        // Repeat mode doesn't produce correct results with bilerp filtering
+                        continue;
+                    }
+
                     GrPaint grPaint;
                     grPaint.setXPFactory(GrPorterDuffXPFactory::Get(SkBlendMode::kSrc));
                     auto fp = GrTextureDomainEffect::Make(
                             proxy, textureMatrices[tm],
-                            GrTextureDomain::MakeTexelDomainForMode(texelDomains[d], mode), mode,
-                            GrSamplerState::Filter::kNearest);
+                            GrTextureDomain::MakeTexelDomain(texelDomains[d], mode),
+                            mode, fFilter);
 
                     if (!fp) {
                         continue;
@@ -129,8 +152,8 @@
                     const SkMatrix viewMatrix = SkMatrix::MakeTrans(x, y);
                     grPaint.addColorFragmentProcessor(std::move(fp));
                     renderTargetContext->priv().testingOnly_addDrawOp(
-                            GrRectOpFactory::MakeNonAAFill(context, std::move(grPaint), viewMatrix,
-                                                           renderRect, GrAAType::kNone));
+                            GrFillRectOp::Make(context, std::move(grPaint), GrAAType::kNone,
+                                               viewMatrix, renderRect));
                     x += renderRect.width() + kTestPad;
                 }
                 y += renderRect.height() + kTestPad;
@@ -140,13 +163,17 @@
 
 private:
     static constexpr SkScalar kDrawPad = 10.f;
-    static constexpr SkScalar kTestPad = 10.f;;
+    static constexpr SkScalar kTestPad = 10.f;
     static constexpr int      kTargetWidth = 100;
     static constexpr int      kTargetHeight = 100;
     sk_sp<SkImage> fImage;
+    GrSamplerState::Filter fFilter;
 
     typedef GM INHERITED;
 };
 
-DEF_GM(return new TextureDomainEffect;)
+DEF_GM(return new TextureDomainEffect(GrSamplerState::Filter::kNearest);)
+DEF_GM(return new TextureDomainEffect(GrSamplerState::Filter::kBilerp);)
+DEF_GM(return new TextureDomainEffect(GrSamplerState::Filter::kMipMap);)
+
 }
diff --git a/gm/tilemodes.cpp b/gm/tilemodes.cpp
index 5f02d79..be98f8c 100644
--- a/gm/tilemodes.cpp
+++ b/gm/tilemodes.cpp
@@ -99,13 +99,12 @@
         for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
             for (size_t ky = 0; ky < SK_ARRAY_COUNT(gModes); ky++) {
                 SkPaint p;
-                SkString str;
-                p.setAntiAlias(true);
-                sk_tool_utils::set_portable_typeface(&p);
                 p.setDither(true);
+                SkString str;
+                SkFont font(sk_tool_utils::create_portable_typeface());
                 str.printf("[%s,%s]", gModeNames[kx], gModeNames[ky]);
 
-                SkTextUtils::DrawString(canvas, str, x + r.width()/2, y, p,
+                SkTextUtils::DrawString(canvas, str.c_str(), x + r.width()/2, y, font, p,
                                         SkTextUtils::kCenter_Align);
 
                 x += r.width() * 4 / 3;
@@ -221,13 +220,12 @@
         SkScalar y = SkIntToScalar(24);
         SkScalar x = SkIntToScalar(66);
 
-        SkPaint p;
-        p.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&p);
+        SkFont font(sk_tool_utils::create_portable_typeface());
 
         for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
             SkString str(gModeNames[kx]);
-            SkTextUtils::DrawString(canvas, str, x + r.width()/2, y, p, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, str.c_str(), x + r.width()/2, y, font, SkPaint(),
+                                    SkTextUtils::kCenter_Align);
             x += r.width() * 4 / 3;
         }
 
@@ -237,7 +235,8 @@
             x = SkIntToScalar(16) + w;
 
             SkString str(gModeNames[ky]);
-            SkTextUtils::DrawString(canvas, str, x, y + h/2, p, SkTextUtils::kRight_Align);
+            SkTextUtils::DrawString(canvas, str.c_str(), x, y + h/2, font, SkPaint(),
+                                    SkTextUtils::kRight_Align);
 
             x += SkIntToScalar(50);
             for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
@@ -265,17 +264,29 @@
 
 #include "SkGradientShader.h"
 
-DEF_SIMPLE_GM(tilemode_decal, canvas, 715, 560) {
+DEF_SIMPLE_GM(tilemode_decal, canvas, 720, 1100) {
     auto img = GetResourceAsImage("images/mandrill_128.png");
     SkPaint bgpaint;
     bgpaint.setColor(SK_ColorYELLOW);
 
     SkRect r = { -20, -20, img->width() + 20.0f, img->height() + 20.0f };
-    canvas->translate(25, 25);
+    canvas->translate(45, 45);
 
     std::function<void(SkPaint*, SkShader::TileMode, SkShader::TileMode)> shader_procs[] = {
         [img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
+            // Test no filtering with decal mode
             paint->setShader(img->makeShader(tx, ty));
+            paint->setFilterQuality(kNone_SkFilterQuality);
+        },
+        [img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
+            // Test bilerp approximation for decal mode (or clamp to border HW)
+            paint->setShader(img->makeShader(tx, ty));
+            paint->setFilterQuality(kLow_SkFilterQuality);
+        },
+        [img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
+            // Test bicubic filter with decal mode
+            paint->setShader(img->makeShader(tx, ty));
+            paint->setFilterQuality(kHigh_SkFilterQuality);
         },
         [img](SkPaint* paint, SkShader::TileMode tx, SkShader::TileMode ty) {
             SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
@@ -306,9 +317,14 @@
         SkPaint paint;
         canvas->save();
         for (const auto& proc : shader_procs) {
+            canvas->save();
+            // Apply a slight rotation to highlight the differences between filtered and unfiltered
+            // decal edges
+            canvas->rotate(4);
             canvas->drawRect(r, bgpaint);
             proc(&paint, p.fX, p.fY);
             canvas->drawRect(r, paint);
+            canvas->restore();
             canvas->translate(0, r.height() + 20);
         }
         canvas->restore();
diff --git a/gm/tilemodes_scaled.cpp b/gm/tilemodes_scaled.cpp
index 093cb34..78c7641 100644
--- a/gm/tilemodes_scaled.cpp
+++ b/gm/tilemodes_scaled.cpp
@@ -99,15 +99,13 @@
         SkScalar y = SkIntToScalar(24);
         SkScalar x = SkIntToScalar(10)/scale;
 
+        SkFont font(sk_tool_utils::create_portable_typeface());
         for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
             for (size_t ky = 0; ky < SK_ARRAY_COUNT(gModes); ky++) {
-                SkPaint p;
                 SkString str;
-                p.setAntiAlias(true);
-                sk_tool_utils::set_portable_typeface(&p);
                 str.printf("[%s,%s]", gModeNames[kx], gModeNames[ky]);
 
-                SkTextUtils::DrawString(canvas, str, scale*(x + r.width()/2), y, p,
+                SkTextUtils::DrawString(canvas, str.c_str(), scale*(x + r.width()/2), y, font, SkPaint(),
                                         SkTextUtils::kCenter_Align);
 
                 x += r.width() * 4 / 3;
@@ -222,13 +220,12 @@
         SkScalar y = SkIntToScalar(24);
         SkScalar x = SkIntToScalar(66);
 
-        SkPaint p;
-        p.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&p);
+        SkFont font(sk_tool_utils::create_portable_typeface());
 
         for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
             SkString str(gModeNames[kx]);
-            SkTextUtils::DrawString(canvas, str, x + r.width()/2, y, p, SkTextUtils::kCenter_Align);
+            SkTextUtils::DrawString(canvas, str.c_str(), x + r.width()/2, y, font, SkPaint(),
+                                    SkTextUtils::kCenter_Align);
             x += r.width() * 4 / 3;
         }
 
@@ -238,7 +235,7 @@
             x = SkIntToScalar(16) + w;
 
             SkString str(gModeNames[ky]);
-            SkTextUtils::DrawString(canvas, str, x, y + h/2, p, SkTextUtils::kRight_Align);
+            SkTextUtils::DrawString(canvas, str.c_str(), x, y + h/2, font, SkPaint(), SkTextUtils::kRight_Align);
 
             x += SkIntToScalar(50);
             for (size_t kx = 0; kx < SK_ARRAY_COUNT(gModes); kx++) {
diff --git a/gm/tosrgb_colorfilter.cpp b/gm/tosrgb_colorfilter.cpp
index d9c7890..8637386 100644
--- a/gm/tosrgb_colorfilter.cpp
+++ b/gm/tosrgb_colorfilter.cpp
@@ -26,20 +26,17 @@
     canvas->drawBitmapRect(bmp, SkRect::MakeXYWH(10, 10, 50, 50), nullptr);
 
     auto srgb = SkColorSpace::MakeSRGB();
-    auto rec2020 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                         SkColorSpace::kRec2020_Gamut);
+    auto rec2020 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kRec2020);
 
     // NarrowGamut RGB (an artifically smaller than sRGB gamut)
-    SkColorSpacePrimaries narrowPrimaries = {
+    skcms_Matrix3x3 narrowGamutRGBMatrix;
+    SkAssertResult(skcms_PrimariesToXYZD50(
         0.54f, 0.33f,     // Rx, Ry
         0.33f, 0.50f,     // Gx, Gy
         0.25f, 0.20f,     // Bx, By
         0.3127f, 0.3290f, // Wx, Wy
-    };
-    SkMatrix44 narrowGamutRGBMatrix;
-    narrowPrimaries.toXYZD50(&narrowGamutRGBMatrix);
-    auto narrow = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                        narrowGamutRGBMatrix);
+        &narrowGamutRGBMatrix));
+    auto narrow = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, narrowGamutRGBMatrix);
 
     SkPaint paint;
 
diff --git a/gm/typeface.cpp b/gm/typeface.cpp
index 5ea0618..bfe9371a 100644
--- a/gm/typeface.cpp
+++ b/gm/typeface.cpp
@@ -14,16 +14,15 @@
 #include "SkMaskFilter.h"
 #include "SkString.h"
 #include "SkSurfaceProps.h"
+#include "SkTextBlob.h"
 #include "SkTypeface.h"
 #include "SkTypes.h"
 
-static void getGlyphPositions(const SkPaint& paint, const uint16_t glyphs[],
+static void getGlyphPositions(const SkFont& font, const uint16_t glyphs[],
                              int count, SkScalar x, SkScalar y, SkPoint pos[]) {
-    SkASSERT(kGlyphID_SkTextEncoding == (SkTextEncoding)paint.getTextEncoding());
-
     SkAutoSTMalloc<128, SkScalar> widthStorage(count);
     SkScalar* widths = widthStorage.get();
-    paint.getTextWidths(glyphs, count * sizeof(uint16_t), widths);
+    font.getWidths(glyphs, count, widths);
 
     for (int i = 0; i < count; ++i) {
         pos[i].set(x, y);
@@ -32,8 +31,8 @@
 }
 
 static void applyKerning(SkPoint pos[], const int32_t adjustments[], int count,
-                         const SkPaint& paint) {
-    SkScalar scale = paint.getTextSize() / paint.getTypeface()->getUnitsPerEm();
+                         const SkFont& font) {
+    SkScalar scale = font.getSize() / font.getTypeface()->getUnitsPerEm();
 
     SkScalar globalAdj = 0;
     for (int i = 0; i < count - 1; ++i) {
@@ -43,16 +42,16 @@
 }
 
 static void drawKernText(SkCanvas* canvas, const void* text, size_t len,
-                         SkScalar x, SkScalar y, const SkPaint& paint) {
-    SkTypeface* face = paint.getTypeface();
+                         SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint) {
+    SkTypeface* face = font.getTypeface();
     if (!face) {
-        canvas->drawText(text, len, x, y, paint);
+        canvas->drawSimpleText(text, len, kUTF8_SkTextEncoding, x, y, font, paint);
         return;
     }
 
     SkAutoSTMalloc<128, uint16_t> glyphStorage(len);
     uint16_t* glyphs = glyphStorage.get();
-    int glyphCount = paint.textToGlyphs(text, len, glyphs);
+    int glyphCount = font.textToGlyphs(text, len, kUTF8_SkTextEncoding, glyphs, len);
     if (glyphCount < 1) {
         return;
     }
@@ -60,19 +59,18 @@
     SkAutoSTMalloc<128, int32_t> adjustmentStorage(glyphCount - 1);
     int32_t* adjustments = adjustmentStorage.get();
     if (!face->getKerningPairAdjustments(glyphs, glyphCount, adjustments)) {
-        canvas->drawText(text, len, x, y, paint);
+        canvas->drawSimpleText(text, len, kUTF8_SkTextEncoding, x, y, font, paint);
         return;
     }
 
-    SkPaint glyphPaint(paint);
-    glyphPaint.setTextEncoding(kGlyphID_SkTextEncoding);
 
-    SkAutoSTMalloc<128, SkPoint> posStorage(glyphCount);
-    SkPoint* pos = posStorage.get();
-    getGlyphPositions(glyphPaint, glyphs, glyphCount, x, y, pos);
+    SkTextBlobBuilder builder;
+    auto rec = builder.allocRunPos(font, glyphCount);
+    memcpy(rec.glyphs, glyphs, glyphCount * sizeof(SkGlyphID));
+    getGlyphPositions(font, glyphs, glyphCount, x, y, rec.points());
+    applyKerning(rec.points(), adjustments, glyphCount, font);
 
-    applyKerning(pos, adjustments, glyphCount, glyphPaint);
-    canvas->drawPosText(glyphs, glyphCount * sizeof(uint16_t), pos, glyphPaint);
+    canvas->drawTextBlob(builder.make(), 0, 0, paint);
 }
 
 static constexpr SkFontStyle gStyles[] = {
@@ -112,27 +110,28 @@
     }
 
     void onDraw(SkCanvas* canvas) override {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setTextSize(SkIntToScalar(30));
+        SkFont font;
+        font.setSize(30);
 
         const char* text = fApplyKerning ? "Type AWAY" : "Hamburgefons";
         const size_t textLen = strlen(text);
 
         SkScalar x = SkIntToScalar(10);
-        SkScalar dy = paint.getFontMetrics(nullptr);
+        SkScalar dy = font.getMetrics(nullptr);
         SkScalar y = dy;
 
         if (fApplyKerning) {
-            paint.setSubpixelText(true);
+            font.setSubpixel(true);
         } else {
-            paint.setLinearText(true);
+            font.setLinearMetrics(true);
         }
+
+        SkPaint paint;
         for (int i = 0; i < gStylesCount; i++) {
-            paint.setTypeface(fFaces[i]);
-            canvas->drawText(text, textLen, x, y, paint);
+            font.setTypeface(fFaces[i]);
+            canvas->drawSimpleText(text, textLen, kUTF8_SkTextEncoding, x, y, font, paint);
             if (fApplyKerning) {
-                drawKernText(canvas, text, textLen, x + 240, y, paint);
+                drawKernText(canvas, text, textLen, x + 240, y, font, paint);
             }
             y += dy;
         }
@@ -173,6 +172,18 @@
         { true,  true , true  },  // subpixel anti-aliased in layer (flat pixel geometry)
     };
 
+    auto compute_edging = [](AliasType at) {
+        if (at.antiAlias) {
+            if (at.subpixelAntitalias) {
+                return SkFont::Edging::kSubpixelAntiAlias;
+            } else {
+                return SkFont::Edging::kAntiAlias;
+            }
+        } else {
+            return SkFont::Edging::kAlias;
+        }
+    };
+
     // The hintgasp.ttf is designed for the following sizes to be different.
     // GASP_DOGRAY                                      0x0002   0<=ppem<=10
     // GASP_SYMMETRIC_SMOOTHING                         0x0008   0<=ppem<=10
@@ -206,19 +217,19 @@
     SkScalar y = 0;  // The baseline of the previous output
     {
         SkPaint paint;
-        paint.setTypeface(face);
-        paint.setEmbeddedBitmapText(true);
+
+        SkFont font(face);
+        font.setEmbeddedBitmaps(true);
 
         SkScalar x = 0;
         SkScalar xMax = x;
         SkScalar xBase = 0;
         for (const SubpixelType subpixel : subpixelTypes) {
             y = 0;
-            paint.setSubpixelText(subpixel.requested);
+            font.setSubpixel(subpixel.requested);
 
             for (const AliasType& alias : aliasTypes) {
-                paint.setAntiAlias(alias.antiAlias);
-                paint.setLCDRenderText(alias.subpixelAntitalias);
+                font.setEdging(compute_edging(alias));
                 SkAutoCanvasRestore acr(canvas, false);
                 if (alias.inLayer) {
                     canvas->saveLayer(nullptr, &paint);
@@ -226,12 +237,12 @@
 
                 for (const SkScalar& textSize : textSizes) {
                     x = xBase + 5;
-                    paint.setTextSize(textSize);
+                    font.setSize(textSize);
 
-                    SkScalar dy = SkScalarCeilToScalar(paint.getFontMetrics(nullptr));
+                    SkScalar dy = SkScalarCeilToScalar(font.getMetrics(nullptr));
                     y += dy;
                     for (const SkFontHinting& hinting : hintingTypes) {
-                        paint.setHinting(hinting);
+                        font.setHinting(hinting);
 
                         for (const bool& rotateABit : rotateABitTypes) {
                             SkAutoCanvasRestore acr(canvas, true);
@@ -239,11 +250,12 @@
                                 canvas->rotate(2, x + subpixel.offset.x(),
                                                   y + subpixel.offset.y());
                             }
-                            canvas->drawText(&character, 1, x + subpixel.offset.x(),
-                                                            y + subpixel.offset.y(), paint);
+                            canvas->drawSimpleText(&character, 1, kUTF8_SkTextEncoding,
+                                                   x + subpixel.offset.x(),
+                                                   y + subpixel.offset.y(), font, paint);
 
                             SkScalar dx = SkScalarCeilToScalar(
-                                    paint.measureText(&character, 1)) + 5;
+                                    font.measureText(&character, 1, kUTF8_SkTextEncoding)) + 5;
                             x += dx;
                             xMax = SkTMax(x, xMax);
                         }
@@ -269,19 +281,18 @@
 
     {
         SkPaint paint;
-        paint.setTypeface(face);
-        paint.setTextSize(16);
+
+        SkFont font(face, 16);
 
         SkScalar x = 0;
         for (const bool& fakeBold : fakeBoldTypes) {
-            SkScalar dy = SkScalarCeilToScalar(paint.getFontMetrics(nullptr));
+            SkScalar dy = SkScalarCeilToScalar(font.getMetrics(nullptr));
             y += dy;
             x = 5;
 
-            paint.setFakeBoldText(fakeBold);
+            font.setEmbolden(fakeBold);
             for (const AliasType& alias : aliasTypes) {
-                paint.setAntiAlias(alias.antiAlias);
-                paint.setLCDRenderText(alias.subpixelAntitalias);
+                font.setEdging(compute_edging(alias));
                 SkAutoCanvasRestore acr(canvas, false);
                 if (alias.inLayer) {
                     canvas->saveLayer(nullptr, &paint);
@@ -289,9 +300,10 @@
                 for (const StyleTests& style : styleTypes) {
                     paint.setStyle(style.style);
                     paint.setStrokeWidth(style.strokeWidth);
-                    canvas->drawText(&character, 1, x, y, paint);
+                    canvas->drawSimpleText(&character, 1, kUTF8_SkTextEncoding, x, y, font, paint);
 
-                    SkScalar dx = SkScalarCeilToScalar(paint.measureText(&character, 1)) + 5;
+                    SkScalar dx = SkScalarCeilToScalar(font.measureText(&character, 1,
+                                                                        kUTF8_SkTextEncoding)) + 5;
                     x += dx;
                 }
             }
@@ -321,27 +333,27 @@
 
     {
         SkPaint paint;
-        paint.setTypeface(face);
-        paint.setTextSize(16);
+
+        SkFont font(face, 16);
 
         SkScalar x = 0;
         {
             for (const AliasType& alias : aliasTypes) {
-                SkScalar dy = SkScalarCeilToScalar(paint.getFontMetrics(nullptr));
+                SkScalar dy = SkScalarCeilToScalar(font.getMetrics(nullptr));
                 y += dy;
                 x = 5;
 
-                paint.setAntiAlias(alias.antiAlias);
-                paint.setLCDRenderText(alias.subpixelAntitalias);
+                font.setEdging(compute_edging(alias));
                 SkAutoCanvasRestore acr(canvas, false);
                 if (alias.inLayer) {
                     canvas->saveLayer(nullptr, &paint);
                 }
                 for (const MaskTests& mask : maskTypes) {
                     paint.setMaskFilter(SkMaskFilter::MakeBlur(mask.style, mask.sigma));
-                    canvas->drawText(&character, 1, x, y, paint);
+                    canvas->drawSimpleText(&character, 1, kUTF8_SkTextEncoding, x, y, font, paint);
 
-                    SkScalar dx = SkScalarCeilToScalar(paint.measureText(&character, 1)) + 5;
+                    SkScalar dx = SkScalarCeilToScalar(font.measureText(&character, 1,
+                                                                        kUTF8_SkTextEncoding)) + 5;
                     x += dx;
                 }
                 paint.setMaskFilter(nullptr);
diff --git a/gm/variedtext.cpp b/gm/variedtext.cpp
index e8e1c29..c11d00e 100644
--- a/gm/variedtext.cpp
+++ b/gm/variedtext.cpp
@@ -46,7 +46,7 @@
 
     void onOnceBeforeDraw() override {
         fPaint.setAntiAlias(true);
-        fPaint.setLCDRenderText(fLCD);
+        fFont.setEdging(fLCD ? SkFont::Edging::kSubpixelAntiAlias : SkFont::Edging::kAntiAlias);
 
         SkISize size = this->getISize();
         SkScalar w = SkIntToScalar(size.fWidth);
@@ -80,10 +80,10 @@
 
             SkRect r;
             fPaint.setColor(fColors[i]);
-            fPaint.setTypeface(fTypefaces[fTypefaceIndices[i]]);
-            fPaint.setTextSize(fPtSizes[i]);
+            fFont.setTypeface(fTypefaces[fTypefaceIndices[i]]);
+            fFont.setSize(fPtSizes[i]);
 
-            fPaint.measureText(fStrings[i].c_str(), fStrings[i].size(), &r);
+            fFont.measureText(fStrings[i].c_str(), fStrings[i].size(), kUTF8_SkTextEncoding, &r);
             // safeRect is set of x,y positions where we can draw the string without hitting
             // the GM's border.
             SkRect safeRect = SkRect::MakeLTRB(-r.fLeft, -r.fTop, w - r.fRight, h - r.fBottom);
@@ -108,13 +108,14 @@
     void onDraw(SkCanvas* canvas) override {
         for (int i = 0; i < kCnt; ++i) {
             fPaint.setColor(fColors[i]);
-            fPaint.setTextSize(fPtSizes[i]);
-            fPaint.setTypeface(fTypefaces[fTypefaceIndices[i]]);
+            fFont.setSize(fPtSizes[i]);
+            fFont.setTypeface(fTypefaces[fTypefaceIndices[i]]);
 
             canvas->save();
                 canvas->clipRect(fClipRects[i]);
                 canvas->translate(fPositions[i].fX, fPositions[i].fY);
-                canvas->drawString(fStrings[i], 0, 0, fPaint);
+                canvas->drawSimpleText(fStrings[i].c_str(), fStrings[i].size(), kUTF8_SkTextEncoding,
+                                       0, 0, fFont, fPaint);
             canvas->restore();
         }
 
@@ -141,6 +142,7 @@
     bool        fLCD;
     sk_sp<SkTypeface> fTypefaces[4];
     SkPaint     fPaint;
+    SkFont      fFont;
 
     // precomputed for each text draw
     SkString        fStrings[kCnt];
diff --git a/gm/verylargebitmap.cpp b/gm/verylargebitmap.cpp
index 0d7cab1..158a103 100644
--- a/gm/verylargebitmap.cpp
+++ b/gm/verylargebitmap.cpp
@@ -112,8 +112,7 @@
 
         colors[0] = SK_ColorGREEN;
         colors[1] = SK_ColorYELLOW;
-        // as of this writing, the raster code will fail to draw the scaled version
-        // since it has a 64K limit on x,y coordinates... (but gpu should succeed)
+        // This used to be big enough that we didn't draw on CPU, but now we do.
         show_image(canvas, veryBig, small, colors, fProc);
     }
 
diff --git a/gm/wacky_yuv_formats.cpp b/gm/wacky_yuv_formats.cpp
index 00c2fe1..0e4abc7 100644
--- a/gm/wacky_yuv_formats.cpp
+++ b/gm/wacky_yuv_formats.cpp
@@ -622,43 +622,87 @@
     static const char* kYUVColorSpaceNames[] = { "JPEG", "601", "709" };
     GR_STATIC_ASSERT(SK_ARRAY_COUNT(kYUVColorSpaceNames) == kLastEnum_SkYUVColorSpace+1);
 
-    SkPaint textPaint;
-    sk_tool_utils::set_portable_typeface(&textPaint, nullptr, SkFontStyle::Bold());
-    textPaint.setTextSize(16);
+    SkPaint paint;
+    SkFont font(sk_tool_utils::create_portable_typeface(nullptr, SkFontStyle::Bold()), 16);
+    font.setEdging(SkFont::Edging::kAlias);
 
     SkRect textRect;
     SkString colLabel;
 
     colLabel.printf("%s", kYUVColorSpaceNames[yuvColorSpace]);
-    textPaint.measureText(colLabel.c_str(), colLabel.size(), &textRect);
+    font.measureText(colLabel.c_str(), colLabel.size(), kUTF8_SkTextEncoding, &textRect);
     int y = textRect.height();
 
-    SkTextUtils::DrawString(canvas, colLabel, x, y, textPaint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, colLabel.c_str(), x, y, font, paint, SkTextUtils::kCenter_Align);
 
     colLabel.printf("%s", opaque ? "Opaque" : "Transparent");
 
-    textPaint.measureText(colLabel.c_str(), colLabel.size(), &textRect);
+    font.measureText(colLabel.c_str(), colLabel.size(), kUTF8_SkTextEncoding, &textRect);
     y += textRect.height();
 
-    SkTextUtils::DrawString(canvas, colLabel, x, y, textPaint, SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, colLabel.c_str(), x, y, font, paint, SkTextUtils::kCenter_Align);
 }
 
 static void draw_row_label(SkCanvas* canvas, int y, int yuvFormat) {
     static const char* kYUVFormatNames[] = { "AYUV", "NV12", "NV21", "I420", "YV12" };
     GR_STATIC_ASSERT(SK_ARRAY_COUNT(kYUVFormatNames) == kLast_YUVFormat+1);
 
-    SkPaint textPaint;
-    sk_tool_utils::set_portable_typeface(&textPaint, nullptr, SkFontStyle::Bold());
-    textPaint.setTextSize(16);
+    SkPaint paint;
+    SkFont font(sk_tool_utils::create_portable_typeface(nullptr, SkFontStyle::Bold()), 16);
+    font.setEdging(SkFont::Edging::kAlias);
 
     SkRect textRect;
     SkString rowLabel;
 
     rowLabel.printf("%s", kYUVFormatNames[yuvFormat]);
-    textPaint.measureText(rowLabel.c_str(), rowLabel.size(), &textRect);
+    font.measureText(rowLabel.c_str(), rowLabel.size(), kUTF8_SkTextEncoding, &textRect);
     y += kTileWidthHeight/2 + textRect.height()/2;
 
-    canvas->drawText(rowLabel.c_str(), rowLabel.size(), 0, y, textPaint);
+    canvas->drawString(rowLabel, 0, y, font, paint);
+}
+
+static GrBackendTexture create_yuva_texture(GrGpu* gpu, const SkBitmap& bm,
+                                            SkYUVAIndex yuvaIndices[4], int texIndex) {
+    SkASSERT(texIndex >= 0 && texIndex <= 3);
+    int channelCount = 0;
+    for (int i = 0; i < SkYUVAIndex::kIndexCount; ++i) {
+        if (yuvaIndices[i].fIndex == texIndex) {
+            ++channelCount;
+        }
+    }
+    // Need to create an RG texture for two-channel planes
+    GrBackendTexture tex;
+    if (2 == channelCount) {
+        SkASSERT(kRGBA_8888_SkColorType == bm.colorType());
+        SkAutoTMalloc<char> pixels(2 * bm.width()*bm.height());
+        char* currPixel = pixels;
+        for (int y = 0; y < bm.height(); ++y) {
+            for (int x = 0; x < bm.width(); ++x) {
+                SkColor color = bm.getColor(x, y);
+                currPixel[0] = SkColorGetR(color);
+                currPixel[1] = SkColorGetG(color);
+                currPixel += 2;
+            }
+        }
+        tex = gpu->createTestingOnlyBackendTexture(
+            pixels,
+            bm.width(),
+            bm.height(),
+            GrColorType::kRG_88,
+            false,
+            GrMipMapped::kNo,
+            2*bm.width());
+    } else {
+        tex = gpu->createTestingOnlyBackendTexture(
+            bm.getPixels(),
+            bm.width(),
+            bm.height(),
+            bm.colorType(),
+            false,
+            GrMipMapped::kNo,
+            bm.rowBytes());
+    }
+    return tex;
 }
 
 namespace skiagm {
@@ -723,6 +767,10 @@
                     SkBitmap resultBMs[4];
                     SkYUVAIndex yuvaIndices[4];
                     create_YUV(planes, (YUVFormat) format, resultBMs, yuvaIndices, opaque);
+                    int numTextures;
+                    if (!SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures)) {
+                        continue;
+                    }
 
                     if (context) {
                         if (context->abandoned()) {
@@ -734,30 +782,12 @@
                             return;
                         }
 
-                        bool used[4] = { false, false, false, false };
-                        for (int i = 0; i < 4; ++i) {
-                            if (yuvaIndices[i].fIndex >= 0) {
-                                SkASSERT(yuvaIndices[i].fIndex < 4);
-                                used[yuvaIndices[i].fIndex] = true;
-                            }
-                        }
-
                         GrBackendTexture yuvaTextures[4];
                         SkPixmap yuvaPixmaps[4];
 
-                        for (int i = 0; i < 4; ++i) {
-                            if (!used[i]) {
-                                continue;
-                            }
-
-                            yuvaTextures[i] = gpu->createTestingOnlyBackendTexture(
-                                resultBMs[i].getPixels(),
-                                resultBMs[i].width(),
-                                resultBMs[i].height(),
-                                resultBMs[i].colorType(),
-                                false,
-                                GrMipMapped::kNo,
-                                resultBMs[i].rowBytes());
+                        for (int i = 0; i < numTextures; ++i) {
+                            yuvaTextures[i] = create_yuva_texture(gpu, resultBMs[i],
+                                                                  yuvaIndices, i);
                             yuvaPixmaps[i] = resultBMs[i].pixmap();
                         }
 
diff --git a/gm/windowrectangles.cpp b/gm/windowrectangles.cpp
index 1abdf08..dc12d1d 100644
--- a/gm/windowrectangles.cpp
+++ b/gm/windowrectangles.cpp
@@ -266,10 +266,7 @@
 }
 
 void WindowRectanglesMaskGM::fail(SkCanvas* canvas) {
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(20);
-    sk_tool_utils::set_portable_typeface(&paint);
+    SkFont font(sk_tool_utils::create_portable_typeface(), 20);
 
     SkString errorMsg;
     errorMsg.printf("Requires GPU with %i window rectangles", kNumWindows);
@@ -277,9 +274,9 @@
     canvas->clipRect(SkRect::Make(kCoverRect));
     canvas->clear(SK_ColorWHITE);
 
-    SkTextUtils::DrawString(canvas, errorMsg, SkIntToScalar((kCoverRect.left() + kCoverRect.right())/2),
-                     SkIntToScalar((kCoverRect.top() + kCoverRect.bottom())/2 - 10), paint,
-                            SkTextUtils::kCenter_Align);
+    SkTextUtils::DrawString(canvas, errorMsg.c_str(), SkIntToScalar((kCoverRect.left() + kCoverRect.right())/2),
+                     SkIntToScalar((kCoverRect.top() + kCoverRect.bottom())/2 - 10),
+                            font, SkPaint(), SkTextUtils::kCenter_Align);
 }
 
 DEF_GM( return new WindowRectanglesMaskGM(); )
diff --git a/gm/xfermodes.cpp b/gm/xfermodes.cpp
index c8b1824..3af85d2 100644
--- a/gm/xfermodes.cpp
+++ b/gm/xfermodes.cpp
@@ -227,7 +227,8 @@
 
         SkPaint labelP;
         labelP.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&labelP);
+
+        SkFont font(sk_tool_utils::create_portable_typeface());
 
         const int W = 5;
 
@@ -258,8 +259,8 @@
 
 #if 1
                 const char* label = SkBlendMode_Name(gModes[i].fMode);
-                SkTextUtils::DrawString(canvas, label, x + w/2, y - labelP.getTextSize()/2,
-                                        labelP, SkTextUtils::kCenter_Align);
+                SkTextUtils::DrawString(canvas, label, x + w/2, y - font.getSize()/2,
+                                        font, labelP, SkTextUtils::kCenter_Align);
 #endif
                 x += w + SkIntToScalar(10);
                 if ((i % W) == W - 1) {
diff --git a/gm/xfermodes2.cpp b/gm/xfermodes2.cpp
index 21e6f2e..27f3dbc 100644
--- a/gm/xfermodes2.cpp
+++ b/gm/xfermodes2.cpp
@@ -33,9 +33,7 @@
         const SkScalar w = SkIntToScalar(kSize);
         const SkScalar h = SkIntToScalar(kSize);
 
-        SkPaint labelP;
-        labelP.setAntiAlias(true);
-        sk_tool_utils::set_portable_typeface(&labelP);
+        SkFont font(sk_tool_utils::create_portable_typeface());
 
         const int W = 6;
 
@@ -72,8 +70,7 @@
             canvas->restore();
 
 #if 1
-            SkTextUtils::DrawString(canvas, SkBlendMode_Name(mode),
-                                    x + w/2, y - labelP.getTextSize()/2, labelP,
+            SkTextUtils::DrawString(canvas, SkBlendMode_Name(mode), x + w/2, y - font.getSize()/2, font, SkPaint(),
                                     SkTextUtils::kCenter_Align);
 #endif
             x += w + SkIntToScalar(10);
diff --git a/gm/yuvtorgbeffect.cpp b/gm/yuvtorgbeffect.cpp
index 7982b48..18c7b77 100644
--- a/gm/yuvtorgbeffect.cpp
+++ b/gm/yuvtorgbeffect.cpp
@@ -19,7 +19,7 @@
 #include "SkGradientShader.h"
 #include "effects/GrYUVtoRGBEffect.h"
 #include "ops/GrDrawOp.h"
-#include "ops/GrRectOpFactory.h"
+#include "ops/GrFillRectOp.h"
 
 #define YSIZE 8
 #define USIZE 4
@@ -130,8 +130,8 @@
                     SkMatrix viewMatrix;
                     viewMatrix.setTranslate(x, y);
                     renderTargetContext->priv().testingOnly_addDrawOp(
-                            GrRectOpFactory::MakeNonAAFill(context, std::move(grPaint), viewMatrix,
-                                                           renderRect, GrAAType::kNone));
+                            GrFillRectOp::Make(context, std::move(grPaint), GrAAType::kNone,
+                                               viewMatrix, renderRect));
                 }
                 x += renderRect.width() + kTestPad;
             }
@@ -251,8 +251,8 @@
                 SkMatrix viewMatrix;
                 viewMatrix.setTranslate(x, y);
                 grPaint.addColorFragmentProcessor(std::move(fp));
-                std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(
-                          context, std::move(grPaint), viewMatrix, renderRect, GrAAType::kNone));
+                std::unique_ptr<GrDrawOp> op(GrFillRectOp::Make(context, std::move(grPaint),
+                        GrAAType::kNone, viewMatrix, renderRect));
                 renderTargetContext->priv().testingOnly_addDrawOp(std::move(op));
             }
         }
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
index 6201792a..55c344c 100644
--- a/gn/BUILD.gn
+++ b/gn/BUILD.gn
@@ -427,10 +427,11 @@
     ]
   } else if (is_win) {
     cflags = [ "/Z7" ]
-    ldflags = [ "/DEBUG:FASTLINK" ]
     if (is_clang) {
-      # /DEBUG:FASTLINK requires every object file to have standalone debug information.
-      cflags += [ "-fstandalone-debug" ]
+      cflags += [ "-mllvm", "-emit-codeview-ghash-section" ]
+      ldflags = [ "/DEBUG:GHASH" ]
+    } else {
+      ldflags = [ "/DEBUG:FASTLINK" ]
     }
   } else {
     cflags = [ "-g" ]
diff --git a/gn/BUILDCONFIG.gn b/gn/BUILDCONFIG.gn
index 031839d..9f1d0be 100644
--- a/gn/BUILDCONFIG.gn
+++ b/gn/BUILDCONFIG.gn
@@ -134,20 +134,27 @@
 
 msvc = ""
 if (target_os == "win") {
-  # By default we look for 2017 (Pro & Community), then 2015. If MSVC is installed in a
+  # By default we look for 2017 (Enterprise, Pro, and Community), then 2015. If MSVC is installed in a
   # non-default location, you can set win_vc to inform us where it is.
+  vc_2017_ent_default =
+      "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\VC"
   vc_2017_pro_default =
-      "C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/VC"
+      "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Professional\\VC"
   vc_2017_com_default =
-      "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC"
+      "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC"
   vc_2017_bt_default =
-      "C:/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/VC"
-  vc_2015_default = "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC"
+      "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\VC"
+  vc_2015_default = "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC"
 
   if (win_vc == "") {
     if ("True" == exec_script("//gn/checkdir.py",
-                              [ "$vc_2017_pro_default" ],
+                              [ "$vc_2017_ent_default" ],
                               "trim string")) {
+      win_vc = vc_2017_ent_default
+      msvc = 2017
+    } else if ("True" == exec_script("//gn/checkdir.py",
+                                     [ "$vc_2017_pro_default" ],
+                                     "trim string")) {
       win_vc = vc_2017_pro_default
       msvc = 2017
     } else if ("True" == exec_script("//gn/checkdir.py",
diff --git a/gn/bench.gni b/gn/bench.gni
index 40abf9b..91391f8 100644
--- a/gn/bench.gni
+++ b/gn/bench.gni
@@ -46,7 +46,6 @@
   "$_bench/DrawLatticeBench.cpp",
   "$_bench/EncodeBench.cpp",
   "$_bench/FontCacheBench.cpp",
-  "$_bench/FontScalerBench.cpp",
   "$_bench/FSRectBench.cpp",
   "$_bench/GameBench.cpp",
   "$_bench/GeometryBench.cpp",
@@ -105,7 +104,6 @@
   "$_bench/RotatedRectBench.cpp",
   "$_bench/RTreeBench.cpp",
   "$_bench/ScalarBench.cpp",
-  "$_bench/ShaderMaskBench.cpp",
   "$_bench/ShaderMaskFilterBench.cpp",
   "$_bench/ShadowBench.cpp",
   "$_bench/ShapesBench.cpp",
@@ -118,7 +116,6 @@
   "$_bench/StrokeBench.cpp",
   "$_bench/SwizzleBench.cpp",
   "$_bench/TableBench.cpp",
-  "$_bench/TextBench.cpp",
   "$_bench/TextBlobBench.cpp",
   "$_bench/TileBench.cpp",
   "$_bench/TileImageFilterBench.cpp",
diff --git a/gn/core.gni b/gn/core.gni
index e51d9c5..c0249ec 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -32,9 +32,7 @@
   "$_src/core/SkBitmapDevice.h",
   "$_src/core/SkBitmapProcState.cpp",
   "$_src/core/SkBitmapProcState.h",
-  "$_src/core/SkBitmapProcState_matrix.h",
   "$_src/core/SkBitmapProcState_matrixProcs.cpp",
-  "$_src/core/SkBitmapProcState_utils.h",
   "$_src/core/SkBitmapProvider.cpp",
   "$_src/core/SkBitmapProvider.h",
   "$_src/core/SkBlendMode.cpp",
@@ -86,6 +84,7 @@
   "$_src/core/SkDeferredDisplayList.cpp",
   "$_src/core/SkDeferredDisplayListRecorder.cpp",
   "$_src/core/SkDeque.cpp",
+  "$_src/core/SkDescriptor.cpp",
   "$_src/core/SkDescriptor.h",
   "$_src/core/SkDevice.cpp",
   "$_src/core/SkDevice.h",
@@ -422,7 +421,6 @@
 
   # private
   "$_include/private/SkArenaAlloc.h",
-  "$_include/private/SkAtomics.h",
   "$_include/private/SkChecksum.h",
   "$_include/private/SkDeferredDisplayList.h",
   "$_include/private/SkFixed.h",
diff --git a/gn/gm.gni b/gn/gm.gni
index 6528256..9a2313d 100644
--- a/gn/gm.gni
+++ b/gn/gm.gni
@@ -26,6 +26,7 @@
   "$_gm/arcto.cpp",
   "$_gm/arithmode.cpp",
   "$_gm/atlastext.cpp",
+  "$_gm/b_119394958.cpp",
   "$_gm/badpaint.cpp",
   "$_gm/beziereffects.cpp",
   "$_gm/beziers.cpp",
@@ -103,9 +104,11 @@
   "$_gm/crbug_892988.cpp",
   "$_gm/crbug_899512.cpp",
   "$_gm/crbug_905548.cpp",
+  "$_gm/crbug_918512.cpp",
   "$_gm/croppedrects.cpp",
   "$_gm/crosscontextimage.cpp",
   "$_gm/cubicpaths.cpp",
+  "$_gm/daa.cpp",
   "$_gm/dashcircle.cpp",
   "$_gm/dashcubics.cpp",
   "$_gm/dashing.cpp",
@@ -149,6 +152,7 @@
   "$_gm/flippity.cpp",
   "$_gm/fontcache.cpp",
   "$_gm/fontmgr.cpp",
+  "$_gm/fontregen.cpp",
   "$_gm/fontscaler.cpp",
   "$_gm/fontscalerdistortable.cpp",
   "$_gm/fwidth_squircle.cpp",
diff --git a/gn/gn_to_bp.py b/gn/gn_to_bp.py
index 2d5929d..8174a32 100644
--- a/gn/gn_to_bp.py
+++ b/gn/gn_to_bp.py
@@ -164,11 +164,10 @@
 cc_defaults {
     name: "skia_deps",
     shared_libs: [
+        "libandroidicu",
         "libdng_sdk",
         "libexpat",
         "libft2",
-        "libicui18n",
-        "libicuuc",
         "libjpeg",
         "liblog",
         "libpiex",
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 25404e5..b8ce2ef 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -18,7 +18,6 @@
   "$_include/gpu/GrDriverBugWorkarounds.h",
   "$_include/gpu/GrGpuResource.h",
   "$_include/gpu/GrRenderTarget.h",
-  "$_include/gpu/GrResourceKey.h",
   "$_include/gpu/GrSurface.h",
   "$_include/gpu/GrTexture.h",
   "$_include/gpu/GrSamplerState.h",
@@ -40,6 +39,7 @@
   "$_include/private/GrProxyRef.h",
   "$_include/private/GrSingleOwner.h",
   "$_include/private/GrRenderTargetProxy.h",
+  "$_include/private/GrResourceKey.h",
   "$_include/private/GrSurfaceProxy.h",
   "$_include/private/GrTextureProxy.h",
   "$_include/private/GrTypesPriv.h",
@@ -78,6 +78,8 @@
   "$_src/gpu/GrDefaultGeoProcFactory.h",
   "$_src/gpu/GrDeferredProxyUploader.h",
   "$_src/gpu/GrDeferredUpload.h",
+  "$_src/gpu/GrDeinstantiateProxyTracker.cpp",
+  "$_src/gpu/GrDeinstantiateProxyTracker.h",
   "$_src/gpu/GrDirectContext.cpp",
   "$_src/gpu/GrDistanceFieldGenFromVector.cpp",
   "$_src/gpu/GrDistanceFieldGenFromVector.h",
@@ -216,8 +218,6 @@
   "$_src/gpu/GrTextureRenderTargetProxy.cpp",
   "$_src/gpu/GrTextureRenderTargetProxy.h",
   "$_src/gpu/GrTRecorder.h",
-  "$_src/gpu/GrUninstantiateProxyTracker.cpp",
-  "$_src/gpu/GrUninstantiateProxyTracker.h",
   "$_src/gpu/GrUserStencilSettings.h",
   "$_src/gpu/GrWindowRectangles.h",
   "$_src/gpu/GrWindowRectsState.h",
@@ -231,14 +231,12 @@
   "$_src/gpu/ops/GrAAConvexTessellator.h",
   "$_src/gpu/ops/GrAAConvexPathRenderer.cpp",
   "$_src/gpu/ops/GrAAConvexPathRenderer.h",
-  "$_src/gpu/ops/GrAAFillRectOp.cpp",
   "$_src/gpu/ops/GrAAFillRRectOp.cpp",
   "$_src/gpu/ops/GrAAFillRRectOp.h",
   "$_src/gpu/ops/GrAAHairLinePathRenderer.cpp",
   "$_src/gpu/ops/GrAAHairLinePathRenderer.h",
   "$_src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp",
   "$_src/gpu/ops/GrAALinearizingConvexPathRenderer.h",
-  "$_src/gpu/ops/GrAAStrokeRectOp.cpp",
   "$_src/gpu/ops/GrAtlasTextOp.cpp",
   "$_src/gpu/ops/GrAtlasTextOp.h",
   "$_src/gpu/ops/GrClearOp.cpp",
@@ -266,8 +264,6 @@
   "$_src/gpu/ops/GrFillRectOp.h",
   "$_src/gpu/ops/GrMeshDrawOp.cpp",
   "$_src/gpu/ops/GrMeshDrawOp.h",
-  "$_src/gpu/ops/GrNonAAFillRectOp.cpp",
-  "$_src/gpu/ops/GrNonAAStrokeRectOp.cpp",
   "$_src/gpu/ops/GrLatticeOp.cpp",
   "$_src/gpu/ops/GrLatticeOp.h",
   "$_src/gpu/ops/GrOp.cpp",
@@ -276,7 +272,6 @@
   "$_src/gpu/ops/GrOvalOpFactory.h",
   "$_src/gpu/ops/GrQuadPerEdgeAA.cpp",
   "$_src/gpu/ops/GrQuadPerEdgeAA.h",
-  "$_src/gpu/ops/GrRectOpFactory.h",
   "$_src/gpu/ops/GrRegionOp.cpp",
   "$_src/gpu/ops/GrRegionOp.h",
   "$_src/gpu/ops/GrSemaphoreOp.cpp",
@@ -287,6 +282,8 @@
   "$_src/gpu/ops/GrSimpleMeshDrawOpHelper.h",
   "$_src/gpu/ops/GrSmallPathRenderer.cpp",
   "$_src/gpu/ops/GrSmallPathRenderer.h",
+  "$_src/gpu/ops/GrStrokeRectOp.cpp",
+  "$_src/gpu/ops/GrStrokeRectOp.h",
   "$_src/gpu/ops/GrTessellatingPathRenderer.cpp",
   "$_src/gpu/ops/GrTessellatingPathRenderer.h",
   "$_src/gpu/ops/GrTextureOp.cpp",
@@ -576,6 +573,7 @@
   "$_include/gpu/vk/GrVkExtensions.h",
   "$_include/gpu/vk/GrVkMemoryAllocator.h",
   "$_include/gpu/vk/GrVkTypes.h",
+  "$_include/gpu/vk/GrVkVulkan.h",
   "$_include/private/GrVkTypesPriv.h",
   "$_src/gpu/vk/GrVkAMDMemoryAllocator.cpp",
   "$_src/gpu/vk/GrVkAMDMemoryAllocator.h",
@@ -587,6 +585,8 @@
   "$_src/gpu/vk/GrVkCaps.h",
   "$_src/gpu/vk/GrVkCommandBuffer.cpp",
   "$_src/gpu/vk/GrVkCommandBuffer.h",
+  "$_src/gpu/vk/GrVkCommandPool.cpp",
+  "$_src/gpu/vk/GrVkCommandPool.h",
   "$_src/gpu/vk/GrVkCopyManager.cpp",
   "$_src/gpu/vk/GrVkCopyManager.h",
   "$_src/gpu/vk/GrVkCopyPipeline.cpp",
@@ -637,6 +637,8 @@
   "$_src/gpu/vk/GrVkSampler.h",
   "$_src/gpu/vk/GrVkSamplerYcbcrConversion.cpp",
   "$_src/gpu/vk/GrVkSamplerYcbcrConversion.h",
+  "$_src/gpu/vk/GrVkSecondaryCBDrawContext.cpp",
+  "$_src/gpu/vk/GrVkSecondaryCBDrawContext.h",
   "$_src/gpu/vk/GrVkSemaphore.cpp",
   "$_src/gpu/vk/GrVkSemaphore.h",
   "$_src/gpu/vk/GrVkStencilAttachment.cpp",
@@ -658,7 +660,6 @@
   "$_src/gpu/vk/GrVkVaryingHandler.h",
   "$_src/gpu/vk/GrVkVertexBuffer.cpp",
   "$_src/gpu/vk/GrVkVertexBuffer.h",
-  "$_src/gpu/vk/GrVkVulkan.h",
 ]
 
 skia_metal_sources = [
@@ -669,6 +670,7 @@
   "$_src/gpu/mtl/GrMtlCaps.mm",
   "$_src/gpu/mtl/GrMtlCopyManager.h",
   "$_src/gpu/mtl/GrMtlCopyManager.mm",
+  "$_src/gpu/mtl/GrMtlCppUtil.h",
   "$_src/gpu/mtl/GrMtlCopyPipelineState.h",
   "$_src/gpu/mtl/GrMtlCopyPipelineState.mm",
   "$_src/gpu/mtl/GrMtlGpu.h",
diff --git a/gn/samples.gni b/gn/samples.gni
index 3e43886..d098dcd 100644
--- a/gn/samples.gni
+++ b/gn/samples.gni
@@ -15,7 +15,6 @@
   "$_samplecode/SampleAAGeometry.cpp",
   "$_samplecode/SampleAARectModes.cpp",
   "$_samplecode/SampleAARects.cpp",
-  "$_samplecode/SampleAll.cpp",
   "$_samplecode/SampleAndroidShadows.cpp",
   "$_samplecode/SampleAnimatedImage.cpp",
   "$_samplecode/SampleAnimatedText.cpp",
@@ -25,7 +24,6 @@
   "$_samplecode/SampleBigBlur.cpp",
   "$_samplecode/SampleBigGradient.cpp",
   "$_samplecode/SampleBitmapRect.cpp",
-  "$_samplecode/SampleBlur.cpp",
   "$_samplecode/SampleCCPRGeometry.cpp",
   "$_samplecode/SampleCamera.cpp",
   "$_samplecode/SampleChart.cpp",
@@ -45,7 +43,6 @@
   "$_samplecode/SampleFatBits.cpp",
   "$_samplecode/SampleFillType.cpp",
   "$_samplecode/SampleFilter2.cpp",
-  "$_samplecode/SampleFilterFuzz.cpp",
   "$_samplecode/SampleFilterQuality.cpp",
   "$_samplecode/SampleFlutterAnimate.cpp",
   "$_samplecode/SampleFuzz.cpp",
@@ -63,7 +60,6 @@
   "$_samplecode/SampleLines.cpp",
   "$_samplecode/SampleLitAtlas.cpp",
   "$_samplecode/SampleManyRects.cpp",
-  "$_samplecode/SampleMeasure.cpp",
   "$_samplecode/SampleMegaStroke.cpp",
   "$_samplecode/SampleNima.cpp",
   "$_samplecode/SamplePatch.cpp",
@@ -91,8 +87,6 @@
   "$_samplecode/SampleStrokeRect.cpp",
   "$_samplecode/SampleSubpixelTranslate.cpp",
   "$_samplecode/SampleSVGFile.cpp",
-  "$_samplecode/SampleText.cpp",
-  "$_samplecode/SampleTextAlpha.cpp",
   "$_samplecode/SampleTextBox.cpp",
   "$_samplecode/SampleTextEffects.cpp",
   "$_samplecode/SampleTextureDomain.cpp",
diff --git a/gn/tests.gni b/gn/tests.gni
index 66fc2b8..27a4448 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -98,8 +98,10 @@
   "$_tests/GrMemoryPoolTest.cpp",
   "$_tests/GrMeshTest.cpp",
   "$_tests/GrMipMappedTest.cpp",
+  "$_tests/GrOpListFlushTest.cpp",
   "$_tests/GrPipelineDynamicStateTest.cpp",
   "$_tests/GrPorterDuffTest.cpp",
+  "$_tests/GrQuadListTest.cpp",
   "$_tests/GrShapeTest.cpp",
   "$_tests/GrSKSLPrettyPrintTest.cpp",
   "$_tests/GrSurfaceTest.cpp",
diff --git a/gn/toolchain/BUILD.gn b/gn/toolchain/BUILD.gn
index e0b5454..cf6437b 100644
--- a/gn/toolchain/BUILD.gn
+++ b/gn/toolchain/BUILD.gn
@@ -46,9 +46,13 @@
     # Toolchain asset includes a script that configures for x86 building.
     # We don't support x86 builds with local MSVC installations.
     env_setup = "cmd /c $win_sdk/bin/SetEnv.cmd /x86 && "
+  } else if (target_cpu == "arm64") {
+    # ARM64 compiler is incomplete - it relies on DLLs located in the host toolchain directory.
+    env_setup = "cmd /C set \"PATH=%PATH%;$win_vc\\Tools\\MSVC\\$win_toolchain_version\\bin\\HostX64\\x64\" && "
   }
 
   cl_m32_flag = ""
+
   if (clang_win != "") {
     if (target_cpu == "x86") {
       # cl.exe knows implicitly by the choice of executable that it's targeting
@@ -56,9 +60,19 @@
       # platforms. (All our builders are x86-64, so x86 is always non-host.)
       cl_m32_flag = "-m32"
     }
-    cl = "$clang_win/bin/clang-cl.exe"
+    if (host_os == "win") {
+      cl = "\"$clang_win/bin/clang-cl.exe\""
+      lib = "\"$clang_win/bin/lld-link.exe\" /lib"
+      link = "\"$clang_win/bin/lld-link.exe\""
+    } else {
+      cl = "\"$clang_win/bin/clang-cl\""
+      lib = "\"$clang_win/bin/lld-link\" /lib"
+      link = "\"$clang_win/bin/lld-link\""
+    }
   } else {
-    cl = "$bin/cl.exe"
+    cl = "\"$bin/cl.exe\""
+    lib = "\"$bin/lib.exe\""
+    link = "\"$bin/link.exe\""
   }
 
   tool("asm") {
@@ -66,7 +80,7 @@
     if (target_cpu == "x64") {
       _ml += "64"
     }
-    command = "$env_setup $bin/$_ml.exe {{asmflags}} /nologo /c /Fo {{output}} {{source}}"
+    command = "$env_setup \"$bin/$_ml.exe\" {{asmflags}} /nologo /c /Fo {{output}} {{source}}"
     outputs = [
       "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj",
     ]
@@ -78,7 +92,7 @@
     pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
 
     # Label names may have spaces so pdbname must be quoted.
-    command = "$env_setup $cc_wrapper \"$cl\" /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} $cl_m32_flag {{cflags_c}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
+    command = "$env_setup $cc_wrapper $cl /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} $cl_m32_flag {{cflags_c}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
     depsformat = "msvc"
     outputs = [
       "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj",
@@ -91,7 +105,7 @@
     pdbname = "{{target_out_dir}}/{{label_name}}_c.pdb"
 
     # Label names may have spaces so pdbname must be quoted.
-    command = "$env_setup $cc_wrapper \"$cl\" /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} $cl_m32_flag {{cflags_cc}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
+    command = "$env_setup $cc_wrapper $cl /nologo /showIncludes /FC {{defines}} {{include_dirs}} {{cflags}} $cl_m32_flag {{cflags_cc}} /c {{source}} /Fo{{output}} /Fd\"$pdbname\""
     depsformat = "msvc"
     outputs = [
       "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.obj",
@@ -102,7 +116,7 @@
   tool("alink") {
     rspfile = "{{output}}.rsp"
 
-    command = "$env_setup $bin/lib.exe /nologo /ignore:4221 {{arflags}} /OUT:{{output}} @$rspfile"
+    command = "$env_setup $lib /nologo /ignore:4221 {{arflags}} /OUT:{{output}} @$rspfile"
     outputs = [
       # Ignore {{output_extension}} and always use .lib, there's no reason to
       # allow targets to override this extension on Windows.
@@ -122,7 +136,7 @@
     pdbname = "${dllname}.pdb"
     rspfile = "${dllname}.rsp"
 
-    command = "$env_setup $bin/link.exe /nologo /IMPLIB:$libname /DLL /OUT:$dllname /PDB:$pdbname @$rspfile"
+    command = "$env_setup $link /nologo /IMPLIB:$libname /DLL /OUT:$dllname /PDB:$pdbname @$rspfile"
     outputs = [
       dllname,
       libname,
@@ -151,9 +165,7 @@
     pdbname = "$exename.pdb"
     rspfile = "$exename.rsp"
 
-    command =
-        "$env_setup $bin/link.exe /nologo /OUT:$exename /PDB:$pdbname @$rspfile"
-
+    command = "$env_setup $link /nologo /OUT:$exename /PDB:$pdbname @$rspfile"
     default_output_extension = ".exe"
     default_output_dir = "{{root_out_dir}}"
     outputs = [
diff --git a/include/android/SkAndroidFrameworkUtils.h b/include/android/SkAndroidFrameworkUtils.h
index 04a1c39..67b8492 100644
--- a/include/android/SkAndroidFrameworkUtils.h
+++ b/include/android/SkAndroidFrameworkUtils.h
@@ -9,10 +9,13 @@
 #define SkAndroidFrameworkUtils_DEFINED
 
 #include "SkTypes.h"
+#include "SkRefCnt.h"
 
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
 
 class SkCanvas;
+struct SkRect;
+class SkSurface;
 
 /**
  *  SkAndroidFrameworkUtils expose private APIs used only by Android framework.
@@ -33,6 +36,10 @@
 #endif //SK_SUPPORT_GPU
 
     static void SafetyNetLog(const char*);
+
+    static sk_sp<SkSurface> getSurfaceFromCanvas(SkCanvas* canvas);
+
+    static int SaveBehind(SkCanvas* canvas, const SkRect* subset);
 };
 
 #endif // SK_BUILD_FOR_ANDROID_ANDROID
diff --git a/include/atlastext/SkAtlasTextFont.h b/include/atlastext/SkAtlasTextFont.h
index a9e641f..ce9c605 100644
--- a/include/atlastext/SkAtlasTextFont.h
+++ b/include/atlastext/SkAtlasTextFont.h
@@ -8,6 +8,7 @@
 #ifndef SkAtlasTextFont_DEFINED
 #define SkAtlasTextFont_DEFINED
 
+#include "SkFont.h"
 #include "SkRefCnt.h"
 #include "SkTypeface.h"
 
@@ -24,6 +25,8 @@
 
     SkScalar size() const { return fSize; }
 
+    SkFont makeFont() const { return SkFont(fTypeface, fSize); }
+
 private:
     SkAtlasTextFont(sk_sp<SkTypeface> typeface, SkScalar size)
             : fTypeface(std::move(typeface)), fSize(size) {}
diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h
index 201cd74..6ce7b99 100644
--- a/include/codec/SkCodec.h
+++ b/include/codec/SkCodec.h
@@ -403,6 +403,9 @@
      *
      *  This may require a rewind.
      *
+     *  If kIncompleteInput is returned, may be called again after more data has
+     *  been provided to the source SkStream.
+     *
      *  @param dstInfo Info of the destination. If the dimensions do not match
      *      those of getInfo, this implies a scale.
      *  @param dst Memory to write to. Needs to be large enough to hold the subset,
@@ -421,10 +424,11 @@
     /**
      *  Start/continue the incremental decode.
      *
-     *  Not valid to call before calling startIncrementalDecode().
+     *  Not valid to call before a call to startIncrementalDecode() returns
+     *  kSuccess.
      *
-     *  After the first call, should only be called again if more data has been
-     *  provided to the source SkStream.
+     *  If kIncompleteInput is returned, may be called again after more data has
+     *  been provided to the source SkStream.
      *
      *  Unlike getPixels and getScanlines, this does not do any filling. This is
      *  left up to the caller, since they may be skipping lines or continuing the
diff --git a/include/core/SkCanvas.h b/include/core/SkCanvas.h
index f2cee44..ee79142 100644
--- a/include/core/SkCanvas.h
+++ b/include/core/SkCanvas.h
@@ -27,6 +27,10 @@
 #include "SkSurfaceProps.h"
 #include "SkVertices.h"
 
+#ifndef SK_SUPPORT_LEGACY_DRAWSTRING
+#define SK_SUPPORT_LEGACY_DRAWSTRING
+#endif
+
 class GrContext;
 class GrRenderTargetContext;
 class SkAndroidFrameworkUtils;
@@ -551,33 +555,6 @@
         return this->saveLayer(&bounds, paint);
     }
 
-    /** Saves SkMatrix and clip, and allocates a SkBitmap for subsequent drawing.
-        LCD text is preserved when the layer is drawn to the prior layer.
-
-        Calling restore() discards changes to SkMatrix and clip, and draws layer.
-
-        SkMatrix may be changed by translate(), scale(), rotate(), skew(), concat(),
-        setMatrix(), and resetMatrix(). Clip may be changed by clipRect(), clipRRect(),
-        clipPath(), clipRegion().
-
-        SkRect bounds suggests but does not define the layer size. To clip drawing to
-        a specific rectangle, use clipRect().
-
-        Optional SkPaint paint applies alpha, SkColorFilter, SkImageFilter, and
-        SkBlendMode when restore() is called.
-
-        Call restoreToCount() with returned value to restore this and subsequent saves.
-
-        Draw text on an opaque background so that LCD text blends correctly with the
-        prior layer. LCD text drawn on a background with transparency may result in
-        incorrect blending.
-
-        @param bounds  hint to limit the size of layer; may be nullptr
-        @param paint   graphics state for layer; may be nullptr
-        @return        depth of saved stack
-    */
-    int saveLayerPreserveLCDTextRequests(const SkRect* bounds, const SkPaint* paint);
-
     /** Saves SkMatrix and clip, and allocates SkBitmap for subsequent drawing.
 
         Calling restore() discards changes to SkMatrix and clip,
@@ -606,7 +583,7 @@
         kPreserveLCDText_SaveLayerFlag, kInitWithPrevious_SaveLayerFlag, or both flags.
     */
     enum SaveLayerFlagsSet {
-        kPreserveLCDText_SaveLayerFlag  = 1 << 1, //!< creates layer for LCD text
+        // kPreserveLCDText_SaveLayerFlag  = 1 << 1, (no longer used)
         kInitWithPrevious_SaveLayerFlag = 1 << 2, //!< initializes with previous contents
         kMaskAgainstCoverage_EXPERIMENTAL_DONT_USE_SaveLayerFlag =
                                           1 << 3, //!< experimental: do not use
@@ -722,7 +699,7 @@
         Call restoreToCount() with returned value to restore this and subsequent saves.
 
         @param layerRec  layer state
-        @return          depth of save state stack
+        @return          depth of save state stack before this call was made.
     */
     int saveLayer(const SaveLayerRec& layerRec);
 
@@ -1844,7 +1821,8 @@
     void experimental_DrawImageSetV1(const ImageSetEntry imageSet[], int cnt,
                                      SkFilterQuality quality, SkBlendMode mode);
 
-    /** Draws text, with origin at (x, y), using clip, SkMatrix, and SkPaint paint.
+    /** DEPRECATED. Use drawSimpleText or drawTextBlob.
+        Draws text, with origin at (x, y), using clip, SkMatrix, and SkPaint paint.
 
         text meaning depends on SkTextEncoding; by default, text is encoded as
         UTF-8.
@@ -1866,10 +1844,31 @@
     void drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
                   const SkPaint& paint);
 
-    // Experimental
+    /** Draws text, with origin at (x, y), using clip, SkMatrix, and SkPaint paint.
+
+        text meaning depends on SkTextEncoding; by default, text is encoded as
+        UTF-8.
+
+        x and y meaning depends on SkPaint::Align and SkPaint vertical text; by default
+        text draws left to right, positioning the first glyph left side bearing at x
+        and its baseline at y. Text size is affected by SkMatrix and SkPaint text size.
+
+        All elements of paint: SkPathEffect, SkMaskFilter, SkShader,
+        SkColorFilter, SkImageFilter, and SkDrawLooper; apply to text. By default, draws
+        filled 12 point black glyphs.
+
+        @param text        character code points or glyphs drawn
+        @param byteLength  byte length of text array
+        @param encoding    text encoding used in the text array
+        @param x           start of text on x-axis
+        @param y           start of text on y-axis
+        @param font        typeface, text size and so, used to describe the text
+        @param paint       blend, color, and so on, used to draw
+     */
     void drawSimpleText(const void* text, size_t byteLength, SkTextEncoding encoding,
                         SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint);
 
+#ifdef SK_SUPPORT_LEGACY_DRAWSTRING
     /** Draws null terminated string, with origin at (x, y), using clip, SkMatrix, and
         SkPaint paint.
 
@@ -1920,79 +1919,18 @@
         @param paint   text size, blend, color, and so on, used to draw
     */
     void drawString(const SkString& string, SkScalar x, SkScalar y, const SkPaint& paint);
+#endif
 
-    /** Draws each glyph in text with the origin in pos array, using clip, SkMatrix, and
-        SkPaint paint. The number of entries in pos array must match the number of glyphs
-        described by byteLength of text.
-
-        text meaning depends on SkTextEncoding; by default, text is encoded as
-        UTF-8. pos elements meaning depends on SkPaint vertical text; by default
-        glyph left side bearing and baseline are relative to SkPoint in pos array.
-        Text size is affected by SkMatrix and SkPaint text size.
-
-        All elements of paint: SkPathEffect, SkMaskFilter, SkShader,
-        SkColorFilter, SkImageFilter, and SkDrawLooper; apply to text. By default, draws
-        filled 12 point black glyphs.
-
-        Layout engines such as Harfbuzz typically position each glyph
-        rather than using the font advance widths.
-
-        @param text        character code points or glyphs drawn
-        @param byteLength  byte length of text array
-        @param pos         array of glyph origins
-        @param paint       text size, blend, color, and so on, used to draw
-    */
-    void drawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                     const SkPaint& paint);
-
-    /** Draws each glyph in text with its origin composed from xpos array and
-        constY, using clip, SkMatrix, and SkPaint paint. The number of entries in xpos array
-        must match the number of glyphs described by byteLength of text.
-
-        text meaning depends on SkTextEncoding; by default, text is encoded as
-        UTF-8. xpos elements meaning depends on SkPaint vertical text;
-        by default each glyph left side bearing is positioned at an xpos element and
-        its baseline is positioned at constY. Text size is affected by SkMatrix and
-        SkPaint text size.
-
-        All elements of paint: SkPathEffect, SkMaskFilter, SkShader,
-        SkColorFilter, SkImageFilter, and SkDrawLooper; apply to text. By default, draws
-        filled 12 point black glyphs.
-
-        Layout engines such as Harfbuzz typically position each glyph
-        rather than using the font advance widths if all glyphs share the same
-        baseline.
-
-        @param text        character code points or glyphs drawn
-        @param byteLength  byte length of text array
-        @param xpos        array of x-axis positions, used to position each glyph
-        @param constY      shared y-axis value for all of x-axis positions
-        @param paint       text size, blend, color, and so on, used to draw
-    */
-    void drawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], SkScalar constY,
-                      const SkPaint& paint);
-
-    /** Draws text, transforming each glyph by the corresponding SkRSXform,
-        using clip, SkMatrix, and SkPaint paint.
-
-        SkRSXform xform array specifies a separate square scale, rotation, and translation
-        for each glyph. xform does not affect paint SkShader.
-
-        Optional SkRect cullRect is a conservative bounds of text, taking into account
-        SkRSXform and paint. If cullRect is outside of clip, canvas can skip drawing.
-
-        All elements of paint: SkPathEffect, SkMaskFilter, SkShader,
-        SkColorFilter, SkImageFilter, and SkDrawLooper; apply to text. By default, draws
-        filled 12 point black glyphs.
-
-        @param text        character code points or glyphs drawn
-        @param byteLength  byte length of text array
-        @param xform       SkRSXform rotates, scales, and translates each glyph individually
-        @param cullRect    SkRect bounds of text for efficient clipping; or nullptr
-        @param paint       text size, blend, color, and so on, used to draw
-    */
-    void drawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                         const SkRect* cullRect, const SkPaint& paint);
+    // Experimental
+    void drawString(const char str[], SkScalar x, SkScalar y, const SkFont& font,
+                    const SkPaint& paint) {
+        this->drawSimpleText(str, strlen(str), kUTF8_SkTextEncoding, x, y, font, paint);
+    }
+    // Experimental
+    void drawString(const SkString& str, SkScalar x, SkScalar y, const SkFont& font,
+                    const SkPaint& paint) {
+        this->drawSimpleText(str.c_str(), str.size(), kUTF8_SkTextEncoding, x, y, font, paint);
+    }
 
     /** Draws SkTextBlob blob at (x, y), using clip, SkMatrix, and SkPaint paint.
 
@@ -2420,6 +2358,8 @@
     virtual SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& ) {
         return kFullLayer_SaveLayerStrategy;
     }
+    // returns true if we should actually perform the saveBehind, or false if we should just save.
+    virtual bool onDoSaveBehind(const SkRect*) { return true; }
     virtual void willRestore() {}
     virtual void didRestore() {}
     virtual void didConcat(const SkMatrix& ) {}
@@ -2441,15 +2381,6 @@
     virtual void onDrawPath(const SkPath& path, const SkPaint& paint);
     virtual void onDrawRegion(const SkRegion& region, const SkPaint& paint);
 
-    virtual void onDrawText(const void* text, size_t byteLength, SkScalar x,
-                            SkScalar y, const SkPaint& paint);
-    virtual void onDrawPosText(const void* text, size_t byteLength,
-                               const SkPoint pos[], const SkPaint& paint);
-    virtual void onDrawPosTextH(const void* text, size_t byteLength,
-                                const SkScalar xpos[], SkScalar constY,
-                                const SkPaint& paint);
-    virtual void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                                   const SkRect* cullRect, const SkPaint& paint);
     virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                 const SkPaint& paint);
 
@@ -2638,6 +2569,16 @@
     SkCanvas& operator=(SkCanvas&&) = delete;
     SkCanvas& operator=(const SkCanvas&) = delete;
 
+    /** Experimental
+     *  Saves the specified subset of the current pixels in the current layer,
+     *  and then clears those pixels to transparent black.
+     *  Restores the pixels on restore() by drawing them in SkBlendMode::kDstOver.
+     *
+     *  @param subset   conservative bounds of the area to be saved / restored.
+     *  @return depth of save state stack before this call was made.
+     */
+    int only_axis_aligned_saveBehind(const SkRect* subset);
+
     void resetForNextPicture(const SkIRect& bounds);
 
     // needs gettotalclip()
@@ -2661,6 +2602,7 @@
                                 SrcRectConstraint);
     void internalDrawPaint(const SkPaint& paint);
     void internalSaveLayer(const SaveLayerRec&, SaveLayerStrategy);
+    void internalSaveBehind(const SkRect*);
     void internalDrawDevice(SkBaseDevice*, int x, int y, const SkPaint*, SkImage* clipImage,
                             const SkMatrix& clipMatrix);
 
diff --git a/include/core/SkCanvasVirtualEnforcer.h b/include/core/SkCanvasVirtualEnforcer.h
index d451d60..6664b0a 100644
--- a/include/core/SkCanvasVirtualEnforcer.h
+++ b/include/core/SkCanvasVirtualEnforcer.h
@@ -30,14 +30,6 @@
     void onDrawPath(const SkPath& path, const SkPaint& paint) override = 0;
     void onDrawRegion(const SkRegion& region, const SkPaint& paint) override = 0;
 
-    void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                    const SkPaint& paint) override = 0;
-    void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                       const SkPaint& paint) override = 0;
-    void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                        SkScalar constY, const SkPaint& paint) override = 0;
-    void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                           const SkRect* cullRect, const SkPaint& paint) override = 0;
     void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                         const SkPaint& paint) override = 0;
 
diff --git a/include/core/SkColorSpace.h b/include/core/SkColorSpace.h
index 26771f3..50db248 100644
--- a/include/core/SkColorSpace.h
+++ b/include/core/SkColorSpace.h
@@ -8,13 +8,14 @@
 #ifndef SkColorSpace_DEFINED
 #define SkColorSpace_DEFINED
 
+#include "../private/SkFixed.h"
 #include "../private/SkOnce.h"
+#include "../../third_party/skcms/skcms.h"
 #include "SkMatrix44.h"
 #include "SkRefCnt.h"
 #include <memory>
 
 class SkData;
-struct skcms_ICCProfile;
 
 enum SkGammaNamed {
     kLinear_SkGammaNamed,
@@ -41,6 +42,8 @@
      *  representation of SkColorSpace.
      */
     bool toXYZD50(SkMatrix44* toXYZD50) const;
+
+    bool toXYZD50(skcms_Matrix3x3* toXYZD50) const;
 };
 
 /**
@@ -62,6 +65,62 @@
     float fF;
 };
 
+namespace SkNamedTransferFn {
+
+// Like SkNamedGamut::kSRGB, keeping this bitwise exactly the same as skcms makes things fastest.
+static constexpr skcms_TransferFunction kSRGB =
+    { 2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0.0f, 0.0f };
+
+static constexpr skcms_TransferFunction k2Dot2 =
+    { 2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+
+static constexpr skcms_TransferFunction kLinear =
+    { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+
+}
+
+namespace SkNamedGamut {
+
+static constexpr skcms_Matrix3x3 kSRGB = {{
+    // ICC fixed-point (16.16) representation, taken from skcms. Please keep them exactly in sync.
+    // 0.436065674f, 0.385147095f, 0.143066406f,
+    // 0.222488403f, 0.716873169f, 0.060607910f,
+    // 0.013916016f, 0.097076416f, 0.714096069f,
+    { SkFixedToFloat(0x6FA2), SkFixedToFloat(0x6299), SkFixedToFloat(0x24A0) },
+    { SkFixedToFloat(0x38F5), SkFixedToFloat(0xB785), SkFixedToFloat(0x0F84) },
+    { SkFixedToFloat(0x0390), SkFixedToFloat(0x18DA), SkFixedToFloat(0xB6CF) },
+}};
+
+static constexpr skcms_Matrix3x3 kAdobeRGB = {{
+    // ICC fixed-point (16.16) repesentation of:
+    // 0.60974, 0.20528, 0.14919,
+    // 0.31111, 0.62567, 0.06322,
+    // 0.01947, 0.06087, 0.74457,
+    { SkFixedToFloat(0x9c18), SkFixedToFloat(0x348d), SkFixedToFloat(0x2631) },
+    { SkFixedToFloat(0x4fa5), SkFixedToFloat(0xa02c), SkFixedToFloat(0x102f) },
+    { SkFixedToFloat(0x04fc), SkFixedToFloat(0x0f95), SkFixedToFloat(0xbe9c) },
+}};
+
+static constexpr skcms_Matrix3x3 kDCIP3 = {{
+    {  0.515102f,   0.291965f,  0.157153f  },
+    {  0.241182f,   0.692236f,  0.0665819f },
+    { -0.00104941f, 0.0418818f, 0.784378f  },
+}};
+
+static constexpr skcms_Matrix3x3 kRec2020 = {{
+    {  0.673459f,   0.165661f,  0.125100f  },
+    {  0.279033f,   0.675338f,  0.0456288f },
+    { -0.00193139f, 0.0299794f, 0.797162f  },
+}};
+
+static constexpr skcms_Matrix3x3 kXYZ = {{
+    { 1.0f, 0.0f, 0.0f },
+    { 0.0f, 1.0f, 0.0f },
+    { 0.0f, 0.0f, 1.0f },
+}};
+
+}
+
 class SK_API SkColorSpace : public SkNVRefCnt<SkColorSpace> {
 public:
     /**
@@ -107,6 +166,12 @@
     static sk_sp<SkColorSpace> MakeRGB(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50);
 
     /**
+     *  Create an SkColorSpace from a transfer function and a row-major 3x3 transformation to XYZ.
+     */
+    static sk_sp<SkColorSpace> MakeRGB(const skcms_TransferFunction& transferFn,
+                                       const skcms_Matrix3x3& toXYZ);
+
+    /**
      *  Create an SkColorSpace from a parsed (skcms) ICC profile.
      */
     static sk_sp<SkColorSpace> Make(const skcms_ICCProfile&);
@@ -136,12 +201,16 @@
      */
     bool isNumericalTransferFn(SkColorSpaceTransferFn* fn) const;
 
+    bool isNumericalTransferFn(skcms_TransferFunction* fn) const;
+
     /**
      *  Returns true and sets |toXYZD50| if the color gamut can be described as a matrix.
      *  Returns false otherwise.
      */
     bool toXYZD50(SkMatrix44* toXYZD50) const;
 
+    bool toXYZD50(skcms_Matrix3x3* toXYZD50) const;
+
     /**
      *  Returns a hash of the gamut transformation to XYZ D50. Allows for fast equality checking
      *  of gamuts, at the (very small) risk of collision.
diff --git a/include/core/SkDeferredDisplayListRecorder.h b/include/core/SkDeferredDisplayListRecorder.h
index b389281..300687a 100644
--- a/include/core/SkDeferredDisplayListRecorder.h
+++ b/include/core/SkDeferredDisplayListRecorder.h
@@ -137,20 +137,7 @@
                                           PromiseDoneProc promiseDoneProc,
                                           TextureContext textureContexts[]);
 
-    // deprecated version that doesn't take yuvaSizeInfo
-    sk_sp<SkImage> makeYUVAPromiseTexture(SkYUVColorSpace yuvColorSpace,
-                                          const GrBackendFormat yuvaFormats[],
-                                          const SkYUVAIndex yuvaIndices[4],
-                                          int imageWidth,
-                                          int imageHeight,
-                                          GrSurfaceOrigin imageOrigin,
-                                          sk_sp<SkColorSpace> imageColorSpace,
-                                          TextureFulfillProc textureFulfillProc,
-                                          TextureReleaseProc textureReleaseProc,
-                                          PromiseDoneProc promiseDoneProc,
-                                          TextureContext textureContexts[]);
-
-private:
+ private:
     bool init();
 
     const SkSurfaceCharacterization             fCharacterization;
diff --git a/include/core/SkDrawable.h b/include/core/SkDrawable.h
index 48cca6d..3210ef7 100644
--- a/include/core/SkDrawable.h
+++ b/include/core/SkDrawable.h
@@ -74,12 +74,13 @@
     /**
      * Snaps off a GpuDrawHandler to represent the state of the SkDrawable at the time the snap is
      * called. This is used for executing GPU backend specific draws intermixed with normal Skia GPU
-     * draws. The GPU API, which will be used for the draw, as well as the full matrix are passed in
-     * as inputs.
+     * draws. The GPU API, which will be used for the draw, as well as the full matrix and device
+     * clip bounds are passed in as inputs.
      */
     std::unique_ptr<GpuDrawHandler> snapGpuDrawHandler(GrBackendApi backendApi,
-                                                       const SkMatrix& matrix) {
-        return this->onSnapGpuDrawHandler(backendApi, matrix);
+                                                       const SkMatrix& matrix,
+                                                       const SkIRect& clipBounds) {
+        return this->onSnapGpuDrawHandler(backendApi, matrix, clipBounds);
     }
 
     SkPicture* newPictureSnapshot();
@@ -131,6 +132,12 @@
     virtual SkRect onGetBounds() = 0;
     virtual void onDraw(SkCanvas*) = 0;
 
+    virtual std::unique_ptr<GpuDrawHandler> onSnapGpuDrawHandler(GrBackendApi, const SkMatrix&,
+                                                                 const SkIRect& /*clipBounds*/) {
+        return nullptr;
+    }
+
+    // TODO: Delete this once Android gets updated to take the clipBounds version above.
     virtual std::unique_ptr<GpuDrawHandler> onSnapGpuDrawHandler(GrBackendApi, const SkMatrix&) {
         return nullptr;
     }
diff --git a/include/core/SkFont.h b/include/core/SkFont.h
index 781508a..009174c 100644
--- a/include/core/SkFont.h
+++ b/include/core/SkFont.h
@@ -44,6 +44,13 @@
     */
     SkFont(sk_sp<SkTypeface> typeface, SkScalar size);
 
+    /** Constructs SkFont with default values with SkTypeface.
+
+        @param typeface  font and style used to draw and measure text
+        @return          initialized SkFont
+    */
+    explicit SkFont(sk_sp<SkTypeface> typeface);
+
 
     /** Constructs SkFont with default values with SkTypeface and size in points,
         horizontal scale, and horizontal skew. Horizontal scale emulates condensed
@@ -65,7 +72,13 @@
         @return      true if SkFont pair are equivalent
     */
     bool operator==(const SkFont& font) const;
-    // Experimental
+
+    /** Compares SkFont and font, and returns true if they are not equivalent.
+        May return true if SkTypeface has identical contents but different pointers.
+
+        @param font  font to compare
+        @return      true if SkFont pair are not equivalent
+    */
     bool operator!=(const SkFont& font) const { return !(*this == font); }
 
     /** If true, instructs the font manager to always hint glyphs.
@@ -327,7 +340,25 @@
         @return            number of glyphs represented by text of length byteLength
     */
     SkScalar measureText(const void* text, size_t byteLength, SkTextEncoding encoding,
-                         SkRect* bounds = nullptr) const;
+                         SkRect* bounds = nullptr) const {
+        return this->measureText(text, byteLength, encoding, bounds, nullptr);
+    }
+
+    /** Returns the advance width of text.
+        The advance is the normal distance to move before drawing additional text.
+        Returns the bounding box of text if bounds is not nullptr. paint
+        stroke width or SkPathEffect may modify the advance with.
+
+        @param text        character storage encoded with SkTextEncoding
+        @param byteLength  length of character storage in bytes
+        @param encoding    one of: kUTF8_SkTextEncoding, kUTF16_SkTextEncoding,
+                           kUTF32_SkTextEncoding, kGlyphID_SkTextEncoding
+        @param bounds      returns bounding box relative to (0, 0) if not nullptr
+        @param paint       optional; may be nullptr
+        @return            number of glyphs represented by text of length byteLength
+    */
+    SkScalar measureText(const void* text, size_t byteLength, SkTextEncoding encoding,
+                         SkRect* bounds, const SkPaint* paint) const;
 
     /** DEPRECATED
         Retrieves the advance and bounds for each glyph in glyphs.
@@ -349,8 +380,7 @@
         this->getWidths(glyphs, count, widths);
     }
 
-    /** Experimental
-        Retrieves the advance and bounds for each glyph in glyphs.
+    /** Retrieves the advance and bounds for each glyph in glyphs.
         Both widths and bounds may be nullptr.
         If widths is not nullptr, widths must be an array of count entries.
         if bounds is not nullptr, bounds must be an array of count entries.
@@ -363,8 +393,7 @@
         this->getWidthsBounds(glyphs, count, widths, nullptr, nullptr);
     }
 
-    /** Experimental.
-        Retrieves the advance and bounds for each glyph in glyphs.
+    /** Retrieves the advance and bounds for each glyph in glyphs.
         Both widths and bounds may be nullptr.
         If widths is not nullptr, widths must be an array of count entries.
         if bounds is not nullptr, bounds must be an array of count entries.
@@ -373,29 +402,27 @@
         @param count       number of glyphs
         @param widths      returns text advances for each glyph; may be nullptr
         @param bounds      returns bounds for each glyph relative to (0, 0); may be nullptr
-        @param paint       optional, specifies stroking, patheffect and maskfilter
+        @param paint       optional, specifies stroking, SkPathEffect and SkMaskFilter
      */
     void getWidthsBounds(const uint16_t glyphs[], int count, SkScalar widths[], SkRect bounds[],
                          const SkPaint* paint) const;
 
 
-    /** Experimental.
-        Retrieves the bounds for each glyph in glyphs.
+    /** Retrieves the bounds for each glyph in glyphs.
         bounds must be an array of count entries.
-        If paint is not nullptr, its stroking, patheffect and maskfilter fields will be respected.
+        If paint is not nullptr, its stroking, SkPathEffect, and SkMaskFilter fields are respected.
 
         @param glyphs      array of glyph indices to be measured
         @param count       number of glyphs
         @param bounds      returns bounds for each glyph relative to (0, 0); may be nullptr
-        @param paint       optional, specifies stroking, patheffect and maskfilter
+        @param paint       optional, specifies stroking, SkPathEffect, and SkMaskFilter
      */
     void getBounds(const uint16_t glyphs[], int count, SkRect bounds[],
                    const SkPaint* paint) const {
         this->getWidthsBounds(glyphs, count, nullptr, bounds, paint);
     }
 
-    /** Experimental
-        Retrieves the positions for each glyph, beginning at the specified origin. The caller
+    /** Retrieves the positions for each glyph, beginning at the specified origin. The caller
         must allocated at least count number of elements in the pos[] array.
 
         @param glyphs   array of glyph indices to be positioned
@@ -405,8 +432,7 @@
      */
     void getPos(const uint16_t glyphs[], int count, SkPoint pos[], SkPoint origin = {0, 0}) const;
 
-    /** Experimental
-        Retrieves the x-positions for each glyph, beginning at the specified origin. The caller
+    /** Retrieves the x-positions for each glyph, beginning at the specified origin. The caller
         must allocated at least count number of elements in the xpos[] array.
 
         @param glyphs   array of glyph indices to be positioned
@@ -471,6 +497,12 @@
     */
     static SkFont LEGACY_ExtractFromPaint(const SkPaint& paint);
 
+    /** Experimental.
+     *  Dumps fields of the font to SkDebugf. May change its output over time, so clients should
+     *  not rely on this for anything specific. Used to aid in debugging.
+     */
+    void dump() const;
+
 private:
     enum PrivFlags {
         kForceAutoHinting_PrivFlag      = 1 << 0,
@@ -480,7 +512,7 @@
         kEmbolden_PrivFlag              = 1 << 4,
     };
 
-    static constexpr unsigned kAllFlags = 0x07F;
+    static constexpr unsigned kAllFlags = 0x1F;
 
     sk_sp<SkTypeface> fTypeface;
     SkScalar    fSize;
@@ -491,8 +523,17 @@
     uint8_t     fHinting;
 
     SkScalar setupForAsPaths(SkPaint*);
+    bool hasSomeAntiAliasing() const;
 
+    void glyphsToUnichars(const SkGlyphID glyphs[], int count, SkUnichar text[]) const;
+
+    friend class GrTextBlob;
     friend class SkCanonicalizeFont;
+    friend class SkFontPriv;
+    friend class SkGlyphRunListPainter;
+    friend class SkPaint;
+    friend class SkTextBlobCacheDiffCanvas;
+    friend class SVGTextBuilder;
 };
 
 #endif
diff --git a/include/core/SkFontTypes.h b/include/core/SkFontTypes.h
index 912e7de..bb1b03f 100644
--- a/include/core/SkFontTypes.h
+++ b/include/core/SkFontTypes.h
@@ -8,28 +8,21 @@
 #ifndef SkFontTypes_DEFINED
 #define SkFontTypes_DEFINED
 
-#include "SkScalar.h"
+#include "SkTypes.h"
+// remove me once google3 uses IWYU
 #include "SkTypeface.h"
 
-#ifdef SK_SUPPORT_LEGACY_TEXTENCODINGENUM
-enum SkTextEncoding : uint8_t {
-    kUTF8_SkTextEncoding,
-    kUTF16_SkTextEncoding,
-    kUTF32_SkTextEncoding,
-    kGlyphID_SkTextEncoding,
-};
-#else
 enum class SkTextEncoding {
-    kUTF8,
-    kUTF16,
-    kUTF32,
-    kGlyphID,
+    kUTF8,      //!< uses bytes to represent UTF-8 or ASCII
+    kUTF16,     //!< uses two byte words to represent most of Unicode
+    kUTF32,     //!< uses four byte words to represent all of Unicode
+    kGlyphID,   //!< uses two byte words to represent glyph indices
 };
+
 #define kUTF8_SkTextEncoding    SkTextEncoding::kUTF8
 #define kUTF16_SkTextEncoding   SkTextEncoding::kUTF16
 #define kUTF32_SkTextEncoding   SkTextEncoding::kUTF32
 #define kGlyphID_SkTextEncoding SkTextEncoding::kGlyphID
-#endif
 
 enum class SkFontHinting {
     kNone,      //!< glyph outlines unchanged
diff --git a/include/core/SkOverdrawCanvas.h b/include/core/SkOverdrawCanvas.h
index ffc049e..8c377db 100644
--- a/include/core/SkOverdrawCanvas.h
+++ b/include/core/SkOverdrawCanvas.h
@@ -21,11 +21,6 @@
     /* Does not take ownership of canvas */
     SkOverdrawCanvas(SkCanvas*);
 
-    void onDrawText(const void*, size_t, SkScalar, SkScalar, const SkPaint&) override;
-    void onDrawPosText(const void*, size_t, const SkPoint[], const SkPaint&) override;
-    void onDrawPosTextH(const void*, size_t, const SkScalar[], SkScalar, const SkPaint&) override;
-    void onDrawTextRSXform(const void*, size_t, const SkRSXform[], const SkRect*,
-                           const SkPaint&) override;
     void onDrawTextBlob(const SkTextBlob*, SkScalar, SkScalar, const SkPaint&) override;
     void onDrawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4], SkBlendMode,
                      const SkPaint&) override;
@@ -61,8 +56,8 @@
     void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override;
 
 private:
-    void drawPosTextCommon(const void*, size_t, const SkScalar[], int, const SkPoint&,
-                           const SkPaint&);
+    void drawPosTextCommon(const SkGlyphID[], int, const SkScalar[], int, const SkPoint&,
+                           const SkFont&, const SkPaint&);
 
     inline SkPaint overdrawPaint(const SkPaint& paint);
 
diff --git a/include/core/SkPaint.h b/include/core/SkPaint.h
index 7ebb85e..9e587f3 100644
--- a/include/core/SkPaint.h
+++ b/include/core/SkPaint.h
@@ -27,7 +27,9 @@
 #include "SkMatrix.h"
 #include "SkRefCnt.h"
 
-#define SK_SUPPORT_LEGACY_FONTMETRICS_IN_PAINT
+#ifndef SK_SUPPORT_LEGACY_PAINT_FONT_FIELDS
+#define SK_SUPPORT_LEGACY_PAINT_FONT_FIELDS
+#endif
 
 class GrTextBlob;
 class SkAutoDescriptor;
@@ -47,17 +49,13 @@
 class SkPath;
 class SkPathEffect;
 struct SkPoint;
-class SkRunFont;
+class SkFont;
 class SkShader;
 class SkSurfaceProps;
 class SkTextBlob;
 class SkTextBlobRunIterator;
 class SkTypeface;
 
-#ifndef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
-#define SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
-#endif
-
 /** \class SkPaint
     SkPaint controls options applied when drawing and measuring. SkPaint collects all
     options outside of the SkCanvas clip and SkCanvas matrix.
@@ -219,7 +217,7 @@
         kSubpixelText_Flag       = 0x80,   //!< mask for setting subpixel text
         kLCDRenderText_Flag      = 0x200,  //!< mask for setting LCD text
         kEmbeddedBitmapText_Flag = 0x400,  //!< mask for setting font embedded bitmaps
-        kAutoHinting_Flag        = 0x800,  //!< mask for setting auto-hinting
+        kAutoHinting_Flag        = 0x800,  //!< mask for setting force hinting
                                            // 0x1000 used to be kVertical
         kAllFlags                = 0xFFFF, //!< mask of all Flags
     };
@@ -379,7 +377,7 @@
     /** Sets whether to always hint glyphs.
         If SkPaint::Hinting is set to SkFontHinting::kNormal or SkFontHinting::kFull
         and useAutohinter is set, instructs the font manager to always hint glyphs.
-        auto-hinting has no effect if SkPaint::Hinting is set to SkFontHinting::kNone or
+        useAutohinter has no effect if SkPaint::Hinting is set to SkFontHinting::kNone or
         SkFontHinting::kSlight.
 
         Only affects platforms that use FreeType as the font manager.
@@ -755,6 +753,9 @@
     */
     void setMaskFilter(sk_sp<SkMaskFilter> maskFilter);
 
+#ifndef SK_SUPPORT_LEGACY_PAINT_FONT_FIELDS
+private:
+#endif
     /** Returns SkTypeface if set, or nullptr.
         Does not alter SkTypeface SkRefCnt.
 
@@ -775,6 +776,9 @@
         @param typeface  font and style used to draw text
     */
     void setTypeface(sk_sp<SkTypeface> typeface);
+#ifndef SK_SUPPORT_LEGACY_PAINT_FONT_FIELDS
+public:
+#endif
 
     /** Returns SkImageFilter if set, or nullptr.
         Does not alter SkImageFilter SkRefCnt.
@@ -834,6 +838,9 @@
     */
     void setLooper(sk_sp<SkDrawLooper> drawLooper);
 
+#ifndef SK_SUPPORT_LEGACY_PAINT_FONT_FIELDS
+private:
+#endif
     /** Returns text size in points.
 
         @return  typographic height of text
@@ -875,152 +882,43 @@
     */
     void setTextSkewX(SkScalar skewX);
 
-#ifdef SK_SUPPORT_LEGACY_TEXTENCODINGENUM
-    /** \enum SkPaint::TextEncoding
-        TextEncoding determines whether text specifies character codes and their encoded
-        size, or glyph indices. Characters are encoded as specified by the Unicode standard.
-
-        Character codes encoded size are specified by UTF-8, UTF-16, or UTF-32.
-        All character code formats are able to represent all of Unicode, differing only
-        in the total storage required.
-
-        UTF-8 (RFC 3629) encodes each character as one or more 8-bit bytes.
-
-        UTF-16 (RFC 2781) encodes each character as one or two 16-bit words.
-
-        UTF-32 encodes each character as one 32-bit word.
-
-        font manager uses font data to convert character code points into glyph indices.
-        A glyph index is a 16-bit word.
-
-        TextEncoding is set to kUTF8_TextEncoding by default.
-    */
-    enum TextEncoding : uint8_t {
-        kUTF8_TextEncoding,    //!< uses bytes to represent UTF-8 or ASCII
-        kUTF16_TextEncoding,   //!< uses two byte words to represent most of Unicode
-        kUTF32_TextEncoding,   //!< uses four byte words to represent all of Unicode
-        kGlyphID_TextEncoding, //!< uses two byte words to represent glyph indices
-    };
-
-    /** Returns SkPaint::TextEncoding.
-        SkPaint::TextEncoding determines how character code points are mapped to font glyph indices.
-
-        @return  one of: kUTF8_TextEncoding, kUTF16_TextEncoding, kUTF32_TextEncoding, or
-                 kGlyphID_TextEncoding
-    */
-    TextEncoding getTextEncoding() const {
-      return (TextEncoding)fBitfields.fTextEncoding;
-    }
-
-    /** Sets SkPaint::TextEncoding to encoding.
-        SkPaint::TextEncoding determines how character code points are mapped to font glyph indices.
-        Invalid values for encoding are ignored.
-
-        @param encoding  one of: kUTF8_TextEncoding, kUTF16_TextEncoding, kUTF32_TextEncoding, or
-                         kGlyphID_TextEncoding
-    */
-    void setTextEncoding(TextEncoding encoding) {
-        this->setTextEncoding((SkTextEncoding)encoding);
-    }
-#else
-    // Experimental
+#ifdef SK_SUPPORT_LEGACY_PAINTTEXTENCODING
+    /**
+     *  Returns the text encoding. Text encoding describes how to interpret the text bytes pass
+     *  to methods like SkFont::measureText() and SkCanvas::drawText().
+     *  @return the text encoding
+     */
     SkTextEncoding getTextEncoding() const {
         return (SkTextEncoding)fBitfields.fTextEncoding;
     }
-#endif
-    // Experimental
+
+    /**
+     *  Sets the text encoding. Text encoding describes how to interpret the text bytes pass
+     *  to methods like SkFont::measureText() and SkCanvas::drawText().
+     *  @param encoding  the new text encoding
+     */
     void setTextEncoding(SkTextEncoding encoding);
+#endif
+#ifndef SK_SUPPORT_LEGACY_PAINT_FONT_FIELDS
+public:
+#endif
 
 #ifdef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
 
-#ifdef SK_SUPPORT_LEGACY_FONTMETRICS_IN_PAINT
-    /**
-        SkFontMetrics is filled out by getFontMetrics(). SkFontMetrics contents reflect the values
-        computed by font manager using SkTypeface. Values are set to zero if they are
-        not available.
-
-        All vertical values are relative to the baseline, on a y-axis pointing down.
-        Zero is on the baseline, negative values are above the baseline, and positive
-        values are below the baseline.
-
-        fUnderlineThickness and fUnderlinePosition have a bit set in fFlags if their values
-        are valid, since their value may be zero.
-
-        fStrikeoutThickness and fStrikeoutPosition have a bit set in fFlags if their values
-        are valid, since their value may be zero.
-    */
-    typedef SkFontMetrics FontMetrics;
-#endif
-
-    /** Returns SkFontMetrics associated with SkTypeface.
-        The return value is the recommended spacing between lines: the sum of metrics
-        descent, ascent, and leading.
-        If metrics is not nullptr, SkFontMetrics is copied to metrics.
-        Results are scaled by text size but does not take into account
-        dimensions required by text scale x, text skew x, fake bold,
-        style stroke, and SkPathEffect.
-
-        @param metrics  storage for SkFontMetrics; may be nullptr
-        @return         recommended spacing between lines
+    /** Deprecated; use SkFont::getMetrics instead
     */
     SkScalar getFontMetrics(SkFontMetrics* metrics) const;
 
-    /** Returns the recommended spacing between lines: the sum of metrics
-        descent, ascent, and leading.
-        Result is scaled by text size but does not take into account
-        dimensions required by stroking and SkPathEffect.
-        Returns the same result as getFontMetrics().
-
-        @return  recommended spacing between lines
+    /** Deprecated; use SkFont::getSpacing instead
     */
     SkScalar getFontSpacing() const { return this->getFontMetrics(nullptr); }
 
-    /** Returns the union of bounds of all glyphs.
-     Returned dimensions are computed by font manager from font data,
-     ignoring SkPaint::Hinting. Includes font metrics, but not fake bold or SkPathEffect.
-
-     If text size is large, text scale is one, and text skew is zero,
-     returns the bounds as:
-     { SkFontMetrics::fXMin, SkFontMetrics::fTop, SkFontMetrics::fXMax, SkFontMetrics::fBottom }.
-
-     @return  union of bounds of all glyphs
-     */
-    SkRect getFontBounds() const;
-
-    /** Converts text into glyph indices.
-        Returns the number of glyph indices represented by text.
-        SkTextEncoding specifies how text represents characters or glyphs.
-        glyphs may be nullptr, to compute the glyph count.
-
-        Does not check text for valid character codes or valid glyph indices.
-
-        If byteLength equals zero, returns zero.
-        If byteLength includes a partial character, the partial character is ignored.
-
-        If SkTextEncoding is kUTF8_SkTextEncoding and
-        text contains an invalid UTF-8 sequence, zero is returned.
-
-        @param text        character storage encoded with SkTextEncoding
-        @param byteLength  length of character storage in bytes
-        @param glyphs      storage for glyph indices; may be nullptr
-        @return            number of glyphs represented by text of length byteLength
+    /** Deprecated; use SkFont::textToGlyphs instead
     */
     int textToGlyphs(const void* text, size_t byteLength,
                      SkGlyphID glyphs[]) const;
 
-    /** Returns true if all text corresponds to a non-zero glyph index.
-        Returns false if any characters in text are not supported in
-        SkTypeface.
-
-        If SkTextEncoding is kGlyphID_SkTextEncoding,
-        returns true if all glyph indices in text are non-zero;
-        does not check to see if text contains valid glyph indices for SkTypeface.
-
-        Returns true if byteLength is zero.
-
-        @param text        array of characters or glyphs
-        @param byteLength  number of bytes in text array
-        @return            true if all text corresponds to a non-zero glyph index
+    /** Deprecated; use SkFont::containsText instead
     */
     bool containsText(const void* text, size_t byteLength) const;
 #endif
@@ -1039,189 +937,38 @@
     void glyphsToUnichars(const SkGlyphID glyphs[], int count, SkUnichar text[]) const;
 
 #ifdef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
-    /** Returns the number of glyphs in text.
-        Uses SkTextEncoding to count the glyphs.
-        Returns the same result as textToGlyphs().
-
-        @param text        character storage encoded with SkTextEncoding
-        @param byteLength  length of character storage in bytes
-        @return            number of glyphs represented by text of length byteLength
+    /** Deprecated; use SkFont::countText instead
     */
     int countText(const void* text, size_t byteLength) const;
 
-    /** Returns the advance width of text.
-        The advance is the normal distance to move before drawing additional text.
-        Uses SkTextEncoding to decode text, SkTypeface to get the font metrics,
-        and text size, text scale x, text skew x, stroke width, and
-        SkPathEffect to scale the metrics and bounds.
-        Returns the bounding box of text if bounds is not nullptr.
-        The bounding box is computed as if the text was drawn at the origin.
-
-        @param text    character codes or glyph indices to be measured
-        @param length  number of bytes of text to measure
-        @param bounds  returns bounding box relative to (0, 0) if not nullptr
-        @return        advance width or height
+    /** Deprecated; use SkFont::measureText instead
     */
     SkScalar measureText(const void* text, size_t length, SkRect* bounds) const;
 
-    /** Returns the advance width of text.
-        The advance is the normal distance to move before drawing additional text.
-        Uses SkTextEncoding to decode text, SkTypeface to get the font metrics,
-        and text size to scale the metrics.
-        Does not scale the advance or bounds by fake bold or SkPathEffect.
-
-        @param text    character codes or glyph indices to be measured
-        @param length  number of bytes of text to measure
-        @return        advance width or height
+    /** Deprecated; use SkFont::measureText instead
     */
     SkScalar measureText(const void* text, size_t length) const {
         return this->measureText(text, length, nullptr);
     }
-#endif
 
-    /** Returns the bytes of text that fit within maxWidth.
-        The text fragment fits if its advance width is less than or equal to maxWidth.
-        Measures only while the advance is less than or equal to maxWidth.
-        Returns the advance or the text fragment in measuredWidth if it not nullptr.
-        Uses SkTextEncoding to decode text, SkTypeface to get the font metrics,
-        and text size to scale the metrics.
-        Does not scale the advance or bounds by fake bold or SkPathEffect.
-
-        @param text           character codes or glyph indices to be measured
-        @param length         number of bytes of text to measure
-        @param maxWidth       advance limit; text is measured while advance is less than maxWidth
-        @param measuredWidth  returns the width of the text less than or equal to maxWidth
-        @return               bytes of text that fit, always less than or equal to length
-    */
-    size_t  breakText(const void* text, size_t length, SkScalar maxWidth,
-                      SkScalar* measuredWidth = nullptr) const;
-
-#ifdef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
-    /** Retrieves the advance and bounds for each glyph in text, and returns
-        the glyph count in text.
-        Both widths and bounds may be nullptr.
-        If widths is not nullptr, widths must be an array of glyph count entries.
-        if bounds is not nullptr, bounds must be an array of glyph count entries.
-        Uses SkTextEncoding to decode text, SkTypeface to get the font metrics,
-        and text size to scale the widths and bounds.
-        Does not scale the advance by fake bold or SkPathEffect.
-        Does include fake bold and SkPathEffect in the bounds.
-
-        @param text        character codes or glyph indices to be measured
-        @param byteLength  number of bytes of text to measure
-        @param widths      returns text advances for each glyph; may be nullptr
-        @param bounds      returns bounds for each glyph relative to (0, 0); may be nullptr
-        @return            glyph count in text
+    /** Deprecated; use SkFont::getWidthsBounds instead
     */
     int getTextWidths(const void* text, size_t byteLength, SkScalar widths[],
                       SkRect bounds[] = nullptr) const;
 
-    /** Returns the geometry as SkPath equivalent to the drawn text.
-        Uses SkTextEncoding to decode text, SkTypeface to get the glyph paths,
-        and text size, fake bold, and SkPathEffect to scale and modify the glyph paths.
-        All of the glyph paths are stored in path.
-        Uses x, y, to position path.
-
-        @param text    character codes or glyph indices
-        @param length  number of bytes of text
-        @param x       x-axis value of the origin of the text
-        @param y       y-axis value of the origin of the text
-        @param path    geometry of the glyphs
+    /** Deprecated; use SkFont::getPath instead
     */
     void getTextPath(const void* text, size_t length, SkScalar x, SkScalar y,
                      SkPath* path) const;
 
-    /** Returns the geometry as SkPath equivalent to the drawn text.
-        Uses SkTextEncoding to decode text, SkTypeface to get the glyph paths,
-        and text size, fake bold, and SkPathEffect to scale and modify the glyph paths.
-        All of the glyph paths are stored in path.
-        Uses pos array to position path.
-        pos contains a position for each glyph.
-
-        @param text    character codes or glyph indices
-        @param length  number of bytes of text
-        @param pos     positions of each glyph
-        @param path    geometry of the glyphs
+    /** Deprecated; use SkFont::getPath instead
     */
     void getPosTextPath(const void* text, size_t length,
                         const SkPoint pos[], SkPath* path) const;
 #endif
 
-#ifdef SK_SUPPORT_LEGACY_TEXTINTERCEPTS
-public:
-#else
-private:
-#endif
-    /** Returns the number of intervals that intersect bounds.
-        bounds describes a pair of lines parallel to the text advance.
-        The return count is zero or a multiple of two, and is at most twice the number of glyphs in
-        the string.
-        Uses SkTextEncoding to decode text, SkTypeface to get the glyph paths,
-        and text size, fake bold, and SkPathEffect to scale and modify the glyph paths.
-        Uses x, y to position intervals.
-
-        Pass nullptr for intervals to determine the size of the interval array.
-
-        intervals are cached to improve performance for multiple calls.
-
-        @param text       character codes or glyph indices
-        @param length     number of bytes of text
-        @param x          x-axis value of the origin of the text
-        @param y          y-axis value of the origin of the text
-        @param bounds     lower and upper line parallel to the advance
-        @param intervals  returned intersections; may be nullptr
-        @return           number of intersections; may be zero
-    */
-    int getTextIntercepts(const void* text, size_t length, SkScalar x, SkScalar y,
-                          const SkScalar bounds[2], SkScalar* intervals) const;
-
-    /** Returns the number of intervals that intersect bounds.
-        bounds describes a pair of lines parallel to the text advance.
-        The return count is zero or a multiple of two, and is at most twice the number of glyphs in
-        the string.
-        Uses SkTextEncoding to decode text, SkTypeface to get the glyph paths,
-        and text size, fake bold, and SkPathEffect to scale and modify the glyph paths.
-        Uses pos array to position intervals.
-
-        Pass nullptr for intervals to determine the size of the interval array.
-
-        intervals are cached to improve performance for multiple calls.
-
-        @param text       character codes or glyph indices
-        @param length     number of bytes of text
-        @param pos        positions of each glyph
-        @param bounds     lower and upper line parallel to the advance
-        @param intervals  returned intersections; may be nullptr
-        @return           number of intersections; may be zero
-    */
-    int getPosTextIntercepts(const void* text, size_t length, const SkPoint pos[],
-                             const SkScalar bounds[2], SkScalar* intervals) const;
-
-    /** Returns the number of intervals that intersect bounds.
-        bounds describes a pair of lines parallel to the text advance.
-        The return count is zero or a multiple of two, and is at most twice the number of glyphs in
-        the string.
-        Uses SkTextEncoding to decode text, SkTypeface to get the glyph paths,
-        and text size, fake bold, and SkPathEffect to scale and modify the glyph paths.
-        Uses xpos array, constY to position intervals.
-
-        Pass nullptr for intervals to determine the size of the interval array.
-
-        intervals are cached to improve performance for multiple calls.
-
-        @param text       character codes or glyph indices
-        @param length     number of bytes of text
-        @param xpos       positions of each glyph on x-axis
-        @param constY     position of each glyph on y-axis
-        @param bounds     lower and upper line parallel to the advance
-        @param intervals  returned intersections; may be nullptr
-        @return           number of intersections; may be zero
-    */
-    int getPosTextHIntercepts(const void* text, size_t length, const SkScalar xpos[],
-                              SkScalar constY, const SkScalar bounds[2], SkScalar* intervals) const;
-public:
-
-    /** Returns the number of intervals that intersect bounds.
+    /** DEPRECATED -- call method on SkTextBlob
+        Returns the number of intervals that intersect bounds.
         bounds describes a pair of lines parallel to the text advance.
         The return count is zero or a multiple of two, and is at most twice the number of glyphs in
         the string.
@@ -1229,7 +976,7 @@
         and text size, fake bold, and SkPathEffect to scale and modify the glyph paths.
         Uses run array to position intervals.
 
-        SkTextEncoding must be set to SkPaint::kGlyphID_TextEncoding.
+        SkTextEncoding must be set to kGlyphID_SkTextEncoding.
 
         Pass nullptr for intervals to determine the size of the interval array.
 
@@ -1329,10 +1076,6 @@
                                       Style style) const;
 
 private:
-    friend class SkGlyphRun;
-    friend class SkGlyphRunBuilder;
-    SkPaint(const SkPaint&, const SkRunFont&);
-
     sk_sp<SkTypeface>     fTypeface;
     sk_sp<SkPathEffect>   fPathEffect;
     sk_sp<SkShader>       fShader;
@@ -1363,6 +1106,13 @@
         uint32_t fBitfieldsUInt;
     };
 
+    SkTextEncoding private_internal_getTextEncoding() const {
+        return (SkTextEncoding)fBitfields.fTextEncoding;
+    }
+    void private_internal_setTextEncoding(SkTextEncoding e) {
+        fBitfields.fTextEncoding = (unsigned)e;
+    }
+
     SkScalar measure_text(SkGlyphCache*, const char* text, size_t length,
                           int* count, SkRect* bounds) const;
 
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index d463956..183f1c3 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -1670,7 +1670,6 @@
     */
     uint32_t getGenerationID() const;
 
-#ifdef SK_SUPPORT_DIRECT_PATHREF_VALIDATION
     /** Returns if SkPath data is consistent. Corrupt SkPath data is detected if
         internal values are out of range or internal storage does not match
         array dimensions.
@@ -1678,14 +1677,6 @@
         @return  true if SkPath data is consistent
     */
     bool isValid() const { return this->isValidImpl() && fPathRef->isValid(); }
-#else
-    /** Deprecated.
-     */
-    bool isValid() const { return this->isValidImpl(); }
-    /** Deprecated.
-     */
-    bool pathRefIsValid() const { return fPathRef->isValid(); }
-#endif
 
 private:
     sk_sp<SkPathRef>               fPathRef;
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index fd055fe..1e60cb6 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -263,10 +263,14 @@
     // V62: Don't negate size of custom encoded images (don't write origin x,y either)
     // V63: Store image bounds (including origin) instead of just width/height to support subsets
     // V64: Remove occluder feature from blur maskFilter
+    // V65: Float4 paint color
+    // V66: Add saveBehind
+    // V67: Blobs serialize fonts instead of paints
+    // V68: Paint doesn't serialize font-related stuff
 
     // Only SKPs within the min/current picture version range (inclusive) can be read.
     static const uint32_t     MIN_PICTURE_VERSION = 56;     // august 2017
-    static const uint32_t CURRENT_PICTURE_VERSION = 65;
+    static const uint32_t CURRENT_PICTURE_VERSION = 68;
 
     static_assert(MIN_PICTURE_VERSION <= 62, "Remove kFontAxes_bad from SkFontDescriptor.cpp");
 
diff --git a/include/core/SkRect.h b/include/core/SkRect.h
index 1748955..863a5fb8 100644
--- a/include/core/SkRect.h
+++ b/include/core/SkRect.h
@@ -142,6 +142,9 @@
     */
     int32_t y() const { return fTop; }
 
+    // Experimental
+    SkIPoint topLeft() const { return {fLeft, fTop}; }
+
     /** Returns span on the x-axis. This does not check if SkIRect is sorted, or if
         result fits in 32-bit signed integer; result may be negative.
 
diff --git a/include/core/SkShader.h b/include/core/SkShader.h
index 2761daa..a8e75a1 100644
--- a/include/core/SkShader.h
+++ b/include/core/SkShader.h
@@ -40,22 +40,25 @@
 class SK_API SkShader : public SkFlattenable {
 public:
     enum TileMode {
-        /** replicate the edge color if the shader draws outside of its
-         *  original bounds
+        /**
+         *  Replicate the edge color if the shader draws outside of its
+         *  original bounds.
          */
         kClamp_TileMode,
 
-        /** repeat the shader's image horizontally and vertically */
+        /**
+         *  Repeat the shader's image horizontally and vertically.
+         */
         kRepeat_TileMode,
 
-        /** repeat the shader's image horizontally and vertically, alternating
-         *  mirror images so that adjacent images always seam
+        /**
+         *  Repeat the shader's image horizontally and vertically, alternating
+         *  mirror images so that adjacent images always seam.
          */
         kMirror_TileMode,
 
         /**
          *  Only draw within the original domain, return transparent-black everywhere else.
-         *  EXPERIMENTAL -- DO NOT USE YET
          */
         kDecal_TileMode,
 
diff --git a/include/core/SkSurfaceCharacterization.h b/include/core/SkSurfaceCharacterization.h
index 2abd5b6..8f4c0a3 100644
--- a/include/core/SkSurfaceCharacterization.h
+++ b/include/core/SkSurfaceCharacterization.h
@@ -31,6 +31,8 @@
     enum class Textureable : bool { kNo = false, kYes = true };
     enum class MipMapped : bool { kNo = false, kYes = true };
     enum class UsesGLFBO0 : bool { kNo = false, kYes = true };
+    // This flag indicates if the surface is wrapping a raw Vulkan secondary command buffer.
+    enum class VulkanSecondaryCBCompatible : bool { kNo = false, kYes = true };
 
     SkSurfaceCharacterization()
             : fCacheMaxResourceBytes(0)
@@ -41,6 +43,7 @@
             , fIsTextureable(Textureable::kYes)
             , fIsMipMapped(MipMapped::kYes)
             , fUsesGLFBO0(UsesGLFBO0::kNo)
+            , fVulkanSecondaryCBCompatible(VulkanSecondaryCBCompatible::kNo)
             , fSurfaceProps(0, kUnknown_SkPixelGeometry) {
     }
 
@@ -72,6 +75,9 @@
     bool isTextureable() const { return Textureable::kYes == fIsTextureable; }
     bool isMipMapped() const { return MipMapped::kYes == fIsMipMapped; }
     bool usesGLFBO0() const { return UsesGLFBO0::kYes == fUsesGLFBO0; }
+    bool vulkanSecondaryCBCompatible() const {
+        return VulkanSecondaryCBCompatible::kYes == fVulkanSecondaryCBCompatible;
+    }
     SkColorSpace* colorSpace() const { return fImageInfo.colorSpace(); }
     sk_sp<SkColorSpace> refColorSpace() const { return fImageInfo.refColorSpace(); }
     const SkSurfaceProps& surfaceProps()const { return fSurfaceProps; }
@@ -92,6 +98,7 @@
                               GrFSAAType FSAAType, int stencilCnt,
                               Textureable isTextureable, MipMapped isMipMapped,
                               UsesGLFBO0 usesGLFBO0,
+                              VulkanSecondaryCBCompatible vulkanSecondaryCBCompatible,
                               const SkSurfaceProps& surfaceProps)
             : fContextInfo(std::move(contextInfo))
             , fCacheMaxResourceBytes(cacheMaxResourceBytes)
@@ -103,6 +110,7 @@
             , fIsTextureable(isTextureable)
             , fIsMipMapped(isMipMapped)
             , fUsesGLFBO0(usesGLFBO0)
+            , fVulkanSecondaryCBCompatible(vulkanSecondaryCBCompatible)
             , fSurfaceProps(surfaceProps) {
     }
 
@@ -116,10 +124,16 @@
              Textureable isTextureable,
              MipMapped isMipMapped,
              UsesGLFBO0 usesGLFBO0,
+             VulkanSecondaryCBCompatible vulkanSecondaryCBCompatible,
              const SkSurfaceProps& surfaceProps) {
         SkASSERT(MipMapped::kNo == isMipMapped || Textureable::kYes == isTextureable);
         SkASSERT(Textureable::kNo == isTextureable || UsesGLFBO0::kNo == usesGLFBO0);
 
+        SkASSERT(VulkanSecondaryCBCompatible::kNo == vulkanSecondaryCBCompatible ||
+                 UsesGLFBO0::kNo == usesGLFBO0);
+        SkASSERT(Textureable::kNo == isTextureable ||
+                 VulkanSecondaryCBCompatible::kNo == vulkanSecondaryCBCompatible);
+
         fContextInfo = contextInfo;
         fCacheMaxResourceBytes = cacheMaxResourceBytes;
 
@@ -131,6 +145,7 @@
         fIsTextureable = isTextureable;
         fIsMipMapped = isMipMapped;
         fUsesGLFBO0 = usesGLFBO0;
+        fVulkanSecondaryCBCompatible = vulkanSecondaryCBCompatible;
         fSurfaceProps = surfaceProps;
     }
 
@@ -145,6 +160,7 @@
     Textureable                     fIsTextureable;
     MipMapped                       fIsMipMapped;
     UsesGLFBO0                      fUsesGLFBO0;
+    VulkanSecondaryCBCompatible     fVulkanSecondaryCBCompatible;
     SkSurfaceProps                  fSurfaceProps;
 };
 
@@ -173,6 +189,7 @@
     bool isTextureable() const { return false; }
     bool isMipMapped() const { return false; }
     bool usesGLFBO0() const { return false; }
+    bool vulkanSecondaryCBCompatible() const { return false; }
     SkColorSpace* colorSpace() const { return nullptr; }
     sk_sp<SkColorSpace> refColorSpace() const { return nullptr; }
     const SkSurfaceProps& surfaceProps()const { return fSurfaceProps; }
diff --git a/include/core/SkTextBlob.h b/include/core/SkTextBlob.h
index 31d3f17..3b5910c 100644
--- a/include/core/SkTextBlob.h
+++ b/include/core/SkTextBlob.h
@@ -26,6 +26,7 @@
 
 #include <atomic>
 
+struct SkRSXform;
 struct SkSerialProcs;
 struct SkDeserialProcs;
 
@@ -51,6 +52,23 @@
     */
     uint32_t uniqueID() const { return fUniqueID; }
 
+    /** Returns the number of intervals that intersect bounds.
+        bounds describes a pair of lines parallel to the text advance.
+        The return count is zero or a multiple of two, and is at most twice the number of glyphs in
+        the the blob.
+
+        Pass nullptr for intervals to determine the size of the interval array.
+
+        Runs within the blob that contain SkRSXform are ignored when computing intercepts.
+
+        @param bounds     lower and upper line parallel to the advance
+        @param intervals  returned intersections; may be nullptr
+        @param paint      specifies stroking, SkPathEffect that affects the result; may be nullptr
+        @return           number of intersections; may be zero
+     */
+    int getIntercepts(const SkScalar bounds[2], SkScalar intervals[],
+                      const SkPaint* paint = nullptr) const;
+
     /** Creates SkTextBlob with a single run.
 
         font contains attributes used to define the run text.
@@ -80,6 +98,44 @@
         return MakeFromText(string, strlen(string), font, encoding);
     }
 
+    /** Experimental.
+        Returns a textblob built from a single run of text with x-positions and a single y value.
+        This is equivalent to using SkTextBlobBuilder and calling allocRunPosH().
+        Returns nullptr if byteLength is zero.
+
+        @param text        character code points or glyphs drawn (based on encoding)
+        @param byteLength  byte length of text array
+        @param xpos    array of x-positions, must contain values for all of the character points.
+        @param constY  shared y-position for each character point, to be paired with each xpos.
+        @param font    SkFont used for this run
+        @param encoding specifies the encoding of the text array.
+        @return        new textblob or nullptr
+     */
+    static sk_sp<SkTextBlob> MakeFromPosTextH(const void* text, size_t byteLength,
+                                      const SkScalar xpos[], SkScalar constY, const SkFont& font,
+                                      SkTextEncoding encoding = kUTF8_SkTextEncoding);
+
+    /** Experimental.
+        Returns a textblob built from a single run of text with positions.
+        This is equivalent to using SkTextBlobBuilder and calling allocRunPos().
+        Returns nullptr if byteLength is zero.
+
+        @param text        character code points or glyphs drawn (based on encoding)
+        @param byteLength  byte length of text array
+        @param pos     array of positions, must contain values for all of the character points.
+        @param font    SkFont used for this run
+        @param encoding specifies the encoding of the text array.
+        @return        new textblob or nullptr
+     */
+    static sk_sp<SkTextBlob> MakeFromPosText(const void* text, size_t byteLength,
+                                             const SkPoint pos[], const SkFont& font,
+                                             SkTextEncoding encoding = kUTF8_SkTextEncoding);
+
+    // Experimental
+    static sk_sp<SkTextBlob> MakeFromRSXform(const void* text, size_t byteLength,
+                                             const SkRSXform xform[], const SkFont& font,
+                                             SkTextEncoding encoding = kUTF8_SkTextEncoding);
+
     /** Writes data to allow later reconstruction of SkTextBlob. memory points to storage
         to receive the encoded data, and memory_size describes the size of storage.
         Returns bytes used if provided storage is large enough to hold all data;
@@ -211,6 +267,11 @@
         SkScalar*  pos;      //!< storage for positions in run
         char*      utf8text; //!< reserved for future use
         uint32_t*  clusters; //!< reserved for future use
+
+        // experimental
+        SkPoint*    points() const { return reinterpret_cast<SkPoint*>(pos); }
+        // experimental
+        SkRSXform*  xforms() const { return reinterpret_cast<SkRSXform*>(pos); }
     };
 
     /** Returns run with storage for glyphs. Caller must write count glyphs to
@@ -284,24 +345,31 @@
     const RunBuffer& allocRunPos(const SkFont& font, int count,
                                  const SkRect* bounds = nullptr);
 
+    // Experimental, RunBuffer.pos points to SkRSXform array
+    const RunBuffer& allocRunRSXform(const SkFont& font, int count);
+
 private:
-    const RunBuffer& allocRunText(const SkPaint& font,
+    const RunBuffer& allocRunText(const SkFont& font,
                                   int count,
                                   SkScalar x,
                                   SkScalar y,
                                   int textByteCount,
                                   SkString lang,
                                   const SkRect* bounds = nullptr);
-    const RunBuffer& allocRunTextPosH(const SkPaint& font, int count, SkScalar y,
+    const RunBuffer& allocRunTextPosH(const SkFont& font, int count, SkScalar y,
                                       int textByteCount, SkString lang,
                                       const SkRect* bounds = nullptr);
-    const RunBuffer& allocRunTextPos(const SkPaint& font, int count,
+    const RunBuffer& allocRunTextPos(const SkFont& font, int count,
                                      int textByteCount, SkString lang,
                                      const SkRect* bounds = nullptr);
+    const RunBuffer& allocRunRSXform(const SkFont& font, int count,
+                                     int textByteCount, SkString lang,
+                                     const SkRect* bounds = nullptr);
+
     void reserve(size_t size);
-    void allocInternal(const SkPaint& font, SkTextBlob::GlyphPositioning positioning,
+    void allocInternal(const SkFont& font, SkTextBlob::GlyphPositioning positioning,
                        int count, int textBytes, SkPoint offset, const SkRect* bounds);
-    bool mergeRun(const SkPaint& font, SkTextBlob::GlyphPositioning positioning,
+    bool mergeRun(const SkFont& font, SkTextBlob::GlyphPositioning positioning,
                   uint32_t count, SkPoint offset);
     void updateDeferredBounds();
 
diff --git a/include/core/SkTypeface.h b/include/core/SkTypeface.h
index 3d52e7a..ac6914d 100644
--- a/include/core/SkTypeface.h
+++ b/include/core/SkTypeface.h
@@ -437,6 +437,7 @@
 
     friend class SkFontPriv;       // GetDefaultTypeface
     friend class SkPaintPriv;      // GetDefaultTypeface
+    friend class SkFont;           // getGlyphToUnicodeMap
 
 private:
     SkFontID            fUniqueID;
diff --git a/include/docs/SkPDFDocument.h b/include/docs/SkPDFDocument.h
index 4623c7a..3d2dd29 100644
--- a/include/docs/SkPDFDocument.h
+++ b/include/docs/SkPDFDocument.h
@@ -9,62 +9,69 @@
 #include "SkString.h"
 #include "SkTime.h"
 
+class SkExecutor;
+
 namespace SkPDF {
 
-/** Table 333 in PDF 32000-1:2008
+/** Table 333 in PDF 32000-1:2008 §14.8.4.2
 */
 enum class DocumentStructureType {
-    kDocument,
-    kPart,
-    kArt,         // Article
-    kSect,        // Section
-    kDiv,
-    kBlockQuote,
-    kCaption,
-    kTOC,         // Table of Contents
-    kTOCI,        // Table of Contents Item
-    kIndex,
-    kNonStruct,
-    kPrivate,
-    kH,           // Heading
-    kH1,          // Heading level 1
-    kH2,
-    kH3,
-    kH4,
-    kH5,
-    kH6,          // Heading level 6
-    kP,           // Paragraph
-    kL,           // List
-    kLI,          // List item
-    kLbl,         // List item label
-    kLBody,       // List item body
-    kTable,
-    kTR,
-    kTH,
-    kTD,
-    kTHead,
-    kTBody,
-    kTFoot,
-    kSpan,
-    kQuote,
-    kNote,
-    kReference,
-    kBibEntry,
-    kCode,
-    kLink,
-    kAnnot,
-    kRuby,
-    kWarichu,
-    kFigure,
-    kFormula,
-    kForm,        // Form control (not like an HTML FORM element)
+    kDocument,    //!< Document
+    kPart,        //!< Part
+    kArt,         //!< Article
+    kSect,        //!< Section
+    kDiv,         //!< Division
+    kBlockQuote,  //!< Block quotation
+    kCaption,     //!< Caption
+    kTOC,         //!< Table of Contents
+    kTOCI,        //!< Table of Contents Item
+    kIndex,       //!< Index
+    kNonStruct,   //!< Nonstructural element
+    kPrivate,     //!< Private element
+    kH,           //!< Heading
+    kH1,          //!< Heading level 1
+    kH2,          //!< Heading level 2
+    kH3,          //!< Heading level 3
+    kH4,          //!< Heading level 4
+    kH5,          //!< Heading level 5
+    kH6,          //!< Heading level 6
+    kP,           //!< Paragraph
+    kL,           //!< List
+    kLI,          //!< List item
+    kLbl,         //!< List item label
+    kLBody,       //!< List item body
+    kTable,       //!< Table
+    kTR,          //!< Table row
+    kTH,          //!< Table header cell
+    kTD,          //!< Table data cell
+    kTHead,       //!< Table header row group
+    kTBody,       //!< Table body row group
+    kTFoot,       //!< table footer row group
+    kSpan,        //!< Span
+    kQuote,       //!< Quotation
+    kNote,        //!< Note
+    kReference,   //!< Reference
+    kBibEntry,    //!< Bibliography entry
+    kCode,        //!< Code
+    kLink,        //!< Link
+    kAnnot,       //!< Annotation
+    kRuby,        //!< Ruby annotation
+    kRB,          //!< Ruby base text
+    kRT,          //!< Ruby annotation text
+    kRP,          //!< Ruby punctuation
+    kWarichu,     //!< Warichu annotation
+    kWT,          //!< Warichu text
+    kWP,          //!< Warichu punctuation
+    kFigure,      //!< Figure
+    kFormula,     //!< Formula
+    kForm,        //!< Form control (not like an HTML FORM element)
 };
 
-/**
- *  A node in a PDF structure tree, giving a semantic representation
- *  of the content.  Each node ID is associated with content
- *  by passing the SkCanvas and node ID to SkPDF::SetNodeId() when drawing.
- */
+/** A node in a PDF structure tree, giving a semantic representation
+    of the content.  Each node ID is associated with content
+    by passing the SkCanvas and node ID to SkPDF::SetNodeId() when drawing.
+    NodeIDs should be unique within each tree.
+*/
 struct StructureElementNode {
     const StructureElementNode* fChildren = nullptr;
     size_t fChildCount;
@@ -135,12 +142,23 @@
     */
     int fEncodingQuality = 101;
 
-    /**
-     *  An optional tree of structured document tags that provide
-     *  a semantic representation of the content. The caller
-     *  should retain ownership.
-     */
+    /** An optional tree of structured document tags that provide
+        a semantic representation of the content. The caller
+        should retain ownership.
+    */
     const StructureElementNode* fStructureElementTreeRoot = nullptr;
+
+    /** Executor to handle threaded work within PDF Backend. If this is nullptr,
+        then all work will be done serially on the main thread. To have worker
+        threads assist with various tasks, set this to a valid SkExecutor
+        instance. Currently used for executing Deflate algorithm in parallel.
+
+        If set, the PDF output will be non-reproducible in the order and
+        internal numbering of objects, but should render the same.
+
+        Experimental.
+    */
+    SkExecutor* fExecutor = nullptr;
 };
 
 /** Associate a node ID with subsequent drawing commands in an
diff --git a/include/effects/SkLayerDrawLooper.h b/include/effects/SkLayerDrawLooper.h
index b18b32c..43eeeca 100644
--- a/include/effects/SkLayerDrawLooper.h
+++ b/include/effects/SkLayerDrawLooper.h
@@ -26,13 +26,14 @@
      */
     enum Bits {
         kStyle_Bit      = 1 << 0,   //!< use this layer's Style/stroke settings
-        kTextSkewX_Bit  = 1 << 1,   //!< use this layer's textskewx
         kPathEffect_Bit = 1 << 2,   //!< use this layer's patheffect
         kMaskFilter_Bit = 1 << 3,   //!< use this layer's maskfilter
         kShader_Bit     = 1 << 4,   //!< use this layer's shader
         kColorFilter_Bit = 1 << 5,  //!< use this layer's colorfilter
         kXfermode_Bit   = 1 << 6,   //!< use this layer's xfermode
 
+        // unsupported kTextSkewX_Bit  = 1 << 1,
+
         /**
          *  Use the layer's paint entirely, with these exceptions:
          *  - We never override the draw's paint's text_encoding, since that is
diff --git a/include/gpu/GrBackendSurface.h b/include/gpu/GrBackendSurface.h
index c72283e..28543c3 100644
--- a/include/gpu/GrBackendSurface.h
+++ b/include/gpu/GrBackendSurface.h
@@ -184,6 +184,9 @@
     bool getMtlTextureInfo(GrMtlTextureInfo*) const;
 #endif
 
+    // Get the GrBackendFormat for this texture (or an invalid format if this is not valid).
+    GrBackendFormat getBackendFormat() const;
+
     // If the backend API is Mock, copies a snapshot of the GrMockTextureInfo struct into the passed
     // in pointer and returns true. Otherwise returns false if the backend API is not Mock.
     bool getMockTextureInfo(GrMockTextureInfo*) const;
diff --git a/include/gpu/GrConfig.h b/include/gpu/GrConfig.h
index e034968..1c07dc4 100644
--- a/include/gpu/GrConfig.h
+++ b/include/gpu/GrConfig.h
@@ -41,7 +41,7 @@
 #endif
 
 #if !defined(GR_GPU_STATS)
-  #if defined(SK_DEBUG) || defined(SK_DUMP_STATS)
+  #if defined(SK_DEBUG) || defined(SK_DUMP_STATS) || defined(GR_TEST_UTILS)
       #define GR_GPU_STATS    1
   #else
       #define GR_GPU_STATS    0
diff --git a/include/gpu/GrContext.h b/include/gpu/GrContext.h
index 84101a8..5c3781f 100644
--- a/include/gpu/GrContext.h
+++ b/include/gpu/GrContext.h
@@ -283,6 +283,8 @@
 
     bool supportsDistanceFieldText() const;
 
+    void storeVkPipelineCacheData();
+
 protected:
     GrContext(GrBackendApi, int32_t id = SK_InvalidGenID);
 
@@ -297,6 +299,11 @@
     sk_sp<GrSkSLFPFactoryCache>             fFPFactoryCache;
 
 private:
+    // fTaskGroup must appear before anything that uses it (e.g. fGpu), so that it is destroyed
+    // after all of its users. Clients of fTaskGroup will generally want to ensure that they call
+    // wait() on it as they are being destroyed, to avoid the possibility of pending tasks being
+    // invoked after objects they depend upon have already been destroyed.
+    std::unique_ptr<SkTaskGroup>            fTaskGroup;
     sk_sp<GrGpu>                            fGpu;
     GrResourceCache*                        fResourceCache;
     GrResourceProvider*                     fResourceProvider;
@@ -319,8 +326,6 @@
     // GrRenderTargetContexts.  It is also passed to the GrResourceProvider and SkGpuDevice.
     mutable GrSingleOwner                   fSingleOwner;
 
-    std::unique_ptr<SkTaskGroup>            fTaskGroup;
-
     const uint32_t                          fUniqueID;
 
     std::unique_ptr<GrDrawingManager>       fDrawingManager;
diff --git a/include/gpu/GrContextOptions.h b/include/gpu/GrContextOptions.h
index e134bd7..c5f9b80 100644
--- a/include/gpu/GrContextOptions.h
+++ b/include/gpu/GrContextOptions.h
@@ -133,19 +133,6 @@
     bool fAvoidStencilBuffers = false;
 
     /**
-     * When specifing new data for a vertex/index buffer that replaces old data Ganesh can give
-     * a hint to the driver that the previous data will not be used in future draws like this:
-     *  glBufferData(GL_..._BUFFER, size, NULL, usage);       //<--hint, NULL means
-     *  glBufferSubData(GL_..._BUFFER, 0, lessThanSize, data) //   old data can't be
-     *                                                        //   used again.
-     * However, this can be an unoptimization on some platforms, esp. Chrome.
-     * Chrome's cmd buffer will create a new allocation and memset the whole thing
-     * to zero (for security reasons).
-     * Defaults to the value of GR_GL_USE_BUFFER_DATA_NULL_HINT #define (which is, by default, 1).
-     */
-    Enable fUseGLBufferDataNullHint = Enable::kDefault;
-
-    /**
      * If true, texture fetches from mip-mapped textures will be biased to read larger MIP levels.
      * This has the effect of sharpening those textures, at the cost of some aliasing, and possible
      * performance impact.
diff --git a/include/gpu/GrGpuResource.h b/include/gpu/GrGpuResource.h
index 7f57cdd..6271db8 100644
--- a/include/gpu/GrGpuResource.h
+++ b/include/gpu/GrGpuResource.h
@@ -8,9 +8,9 @@
 #ifndef GrGpuResource_DEFINED
 #define GrGpuResource_DEFINED
 
+#include "../private/GrResourceKey.h"
 #include "../private/GrTypesPriv.h"
 #include "../private/SkNoncopyable.h"
-#include "GrResourceKey.h"
 
 class GrContext;
 class GrGpu;
@@ -85,8 +85,6 @@
         kPendingWrite_CntType,
     };
 
-    bool isPurgeable() const { return !this->internalHasRef() && !this->internalHasPendingIO(); }
-
     bool internalHasPendingRead() const { return SkToBool(fPendingReads); }
     bool internalHasPendingWrite() const { return SkToBool(fPendingWrites); }
     bool internalHasPendingIO() const { return SkToBool(fPendingWrites | fPendingReads); }
@@ -301,6 +299,8 @@
 
 
 private:
+    bool isPurgeable() const { return !this->internalHasRef() && !this->internalHasPendingIO(); }
+
     /**
      * Called by the registerWithCache if the resource is available to be used as scratch.
      * Resource subclasses should override this if the instances should be recycled as scratch
@@ -316,6 +316,11 @@
 
     virtual size_t onGpuMemorySize() const = 0;
 
+    /**
+     * Called by GrResourceCache when a resource transitions from being unpurgeable to purgeable.
+     */
+    virtual void becamePurgeable() {}
+
     // See comments in CacheAccess and ResourcePriv.
     void setUniqueKey(const GrUniqueKey&);
     void removeUniqueKey();
diff --git a/include/gpu/GrSamplerState.h b/include/gpu/GrSamplerState.h
index 75f6891..63197bc 100644
--- a/include/gpu/GrSamplerState.h
+++ b/include/gpu/GrSamplerState.h
@@ -16,7 +16,7 @@
 class GrSamplerState {
 public:
     enum class Filter : uint8_t { kNearest, kBilerp, kMipMap };
-    enum class WrapMode : uint8_t { kClamp, kRepeat, kMirrorRepeat };
+    enum class WrapMode : uint8_t { kClamp, kRepeat, kMirrorRepeat, kClampToBorder };
 
     static constexpr GrSamplerState ClampNearest() { return GrSamplerState(); }
     static constexpr GrSamplerState ClampBilerp() {
@@ -51,7 +51,8 @@
     WrapMode wrapModeY() const { return fWrapModes[1]; }
 
     bool isRepeated() const {
-        return WrapMode::kClamp != fWrapModes[0] || WrapMode::kClamp != fWrapModes[1];
+        return (WrapMode::kClamp != fWrapModes[0] && WrapMode::kClampToBorder != fWrapModes[0]) ||
+               (WrapMode::kClamp != fWrapModes[1] && WrapMode::kClampToBorder != fWrapModes[1]);
     }
 
     bool operator==(const GrSamplerState& that) const {
diff --git a/include/gpu/GrSurface.h b/include/gpu/GrSurface.h
index bcd854f..021ace5 100644
--- a/include/gpu/GrSurface.h
+++ b/include/gpu/GrSurface.h
@@ -65,6 +65,12 @@
     static size_t ComputeSize(GrPixelConfig config, int width, int height, int colorSamplesPerPixel,
                               GrMipMapped, bool useNextPow2 = false);
 
+    /**
+     * The pixel values of this surface cannot be modified (e.g. doesn't support write pixels or
+     * MIP map level regen).
+     */
+    bool readOnly() const { return fSurfaceFlags & GrInternalSurfaceFlags::kReadOnly; }
+
 protected:
     void setHasMixedSamples() {
         SkASSERT(this->asRenderTarget());
@@ -72,14 +78,6 @@
     }
     bool hasMixedSamples() const { return fSurfaceFlags & GrInternalSurfaceFlags::kMixedSampled; }
 
-    void setSupportsWindowRects() {
-        SkASSERT(this->asRenderTarget());
-        fSurfaceFlags |= GrInternalSurfaceFlags::kWindowRectsSupport;
-    }
-    bool supportsWindowRects() const {
-        return fSurfaceFlags & GrInternalSurfaceFlags::kWindowRectsSupport;
-    }
-
     void setGLRTFBOIDIs0() {
         SkASSERT(this->asRenderTarget());
         fSurfaceFlags |= GrInternalSurfaceFlags::kGLRTFBOIDIs0;
@@ -88,6 +86,11 @@
         return fSurfaceFlags & GrInternalSurfaceFlags::kGLRTFBOIDIs0;
     }
 
+    void setReadOnly() {
+        SkASSERT(!this->asRenderTarget());
+        fSurfaceFlags |= GrInternalSurfaceFlags::kReadOnly;
+    }
+
     // Methods made available via GrSurfacePriv
     bool hasPendingRead() const;
     bool hasPendingWrite() const;
diff --git a/include/gpu/GrTexture.h b/include/gpu/GrTexture.h
index c1ed58b..286b7ce 100644
--- a/include/gpu/GrTexture.h
+++ b/include/gpu/GrTexture.h
@@ -19,7 +19,7 @@
 
 class GrTexturePriv;
 
-class GrTexture : virtual public GrSurface {
+class SK_API GrTexture : virtual public GrSurface {
 public:
     GrTexture* asTexture() override { return this; }
     const GrTexture* asTexture() const override { return this; }
@@ -60,6 +60,17 @@
         this->setRelease(std::move(helper));
     }
 
+    /**
+     * Installs a proc on this texture. It will be called when the texture becomes "idle". Idle is
+     * defined to mean that the texture has no refs or pending IOs and that GPU I/O operations on
+     * the texture are completed if the backend API disallows deletion of a texture before such
+     * operations occur (e.g. Vulkan). After the idle proc is called it is removed. The idle proc
+     * will always be called before the texture is destroyed, even in unusual shutdown scenarios
+     * (e.g. GrContext::abandonContext()).
+     */
+    using IdleProc = void(void*);
+    virtual void setIdleProc(IdleProc, void* context) = 0;
+
     /** Access methods that are only to be used within Skia code. */
     inline GrTexturePriv texturePriv();
     inline const GrTexturePriv texturePriv() const;
diff --git a/include/gpu/gl/GrGLConfig.h b/include/gpu/gl/GrGLConfig.h
index 1526f06..feb6ba0 100644
--- a/include/gpu/gl/GrGLConfig.h
+++ b/include/gpu/gl/GrGLConfig.h
@@ -50,16 +50,6 @@
  * GR_GL_CHECK_ERROR_START: controls the initial value of gCheckErrorGL
  * when GR_GL_CHECK_ERROR is 1.  Defaults to 1.
  *
- * GR_GL_USE_BUFFER_DATA_NULL_HINT: When specifing new data for a vertex/index
- * buffer that replaces old data Ganesh can give a hint to the driver that the
- * previous data will not be used in future draws like this:
- *  glBufferData(GL_..._BUFFER, size, NULL, usage);       //<--hint, NULL means
- *  glBufferSubData(GL_..._BUFFER, 0, lessThanSize, data) //   old data can't be
- *                                                        //   used again.
- * However, this can be an unoptimization on some platforms, esp. Chrome.
- * Chrome's cmd buffer will create a new allocation and memset the whole thing
- * to zero (for security reasons). Defaults to 1 (enabled).
- *
  * GR_GL_CHECK_ALLOC_WITH_GET_ERROR: If set to 1 this will then glTexImage,
  * glBufferData, glRenderbufferStorage, etc will be checked for errors. This
  * amounts to ensuring the error is GL_NO_ERROR, calling the allocating
@@ -76,10 +66,6 @@
  * GR_GL_MUST_USE_VBO: Indicates that all vertices and indices must be rendered
  * from VBOs. Chromium's command buffer doesn't allow glVertexAttribArray with
  * ARARY_BUFFER 0 bound or glDrawElements with ELEMENT_ARRAY_BUFFER 0 bound.
- *
- * GR_GL_USE_NEW_SHADER_SOURCE_SIGNATURE is for compatibility with the new version
- * of the OpenGLES2.0 headers from Khronos.  glShaderSource now takes a const char * const *,
- * instead of a const char
  */
 
 #if !defined(GR_GL_LOG_CALLS)
@@ -106,24 +92,8 @@
     #define GR_GL_CHECK_ERROR_START                     1
 #endif
 
-#if !defined(GR_GL_USE_BUFFER_DATA_NULL_HINT)
-    #define GR_GL_USE_BUFFER_DATA_NULL_HINT             1
-#endif
-
 #if !defined(GR_GL_CHECK_ALLOC_WITH_GET_ERROR)
     #define GR_GL_CHECK_ALLOC_WITH_GET_ERROR            1
 #endif
 
-#if !defined(GR_GL_CHECK_FBO_STATUS_ONCE_PER_FORMAT)
-    #define GR_GL_CHECK_FBO_STATUS_ONCE_PER_FORMAT      0
-#endif
-
-#if !defined(GR_GL_MUST_USE_VBO)
-    #define GR_GL_MUST_USE_VBO                          0
-#endif
-
-#if !defined(GR_GL_USE_NEW_SHADER_SOURCE_SIGNATURE)
-    #define GR_GL_USE_NEW_SHADER_SOURCE_SIGNATURE       0
-#endif
-
 #endif
diff --git a/include/gpu/gl/GrGLConfig_chrome.h b/include/gpu/gl/GrGLConfig_chrome.h
index 838e054..683cd97 100644
--- a/include/gpu/gl/GrGLConfig_chrome.h
+++ b/include/gpu/gl/GrGLConfig_chrome.h
@@ -11,24 +11,10 @@
 // glGetError() forces a sync with gpu process on chrome
 #define GR_GL_CHECK_ERROR_START                     0
 
-// cmd buffer allocates memory and memsets it to zero when it sees glBufferData
-// with NULL.
-#define GR_GL_USE_BUFFER_DATA_NULL_HINT             0
-
 // Check error is even more expensive in chrome (cmd buffer flush). The
 // compositor also doesn't check its allocations.
 #define GR_GL_CHECK_ALLOC_WITH_GET_ERROR            0
 
-// CheckFramebufferStatus in chrome synchronizes the gpu and renderer processes.
-#define GR_GL_CHECK_FBO_STATUS_ONCE_PER_FORMAT      1
-
-// Non-VBO vertices and indices are not allowed in Chromium.
-#define GR_GL_MUST_USE_VBO                          1
-
-// Use updated Khronos signature for glShaderSource
-// (const char* const instead of char**).
-#define GR_GL_USE_NEW_SHADER_SOURCE_SIGNATURE       1
-
 #if !defined(GR_GL_IGNORE_ES3_MSAA)
     #define GR_GL_IGNORE_ES3_MSAA 1
 #endif
diff --git a/include/gpu/gl/GrGLFunctions.h b/include/gpu/gl/GrGLFunctions.h
index a677efb..c0a681d 100644
--- a/include/gpu/gl/GrGLFunctions.h
+++ b/include/gpu/gl/GrGLFunctions.h
@@ -143,12 +143,7 @@
 using GrGLScissorFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height);
 // GL_CHROMIUM_bind_uniform_location
 using GrGLBindUniformLocationFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint program, GrGLint location, const char* name);
-
-#if GR_GL_USE_NEW_SHADER_SOURCE_SIGNATURE
 using GrGLShaderSourceFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint shader, GrGLsizei count, const char* const* str, const GrGLint* length);
-#else
-using GrGLShaderSourceFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint shader, GrGLsizei count, const char** str, const GrGLint* length);
-#endif
 using GrGLStencilFuncFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum func, GrGLint ref, GrGLuint mask);
 using GrGLStencilFuncSeparateFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum face, GrGLenum func, GrGLint ref, GrGLuint mask);
 using GrGLStencilMaskFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLuint mask);
@@ -158,6 +153,8 @@
 using GrGLTexBufferFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum internalformat, GrGLuint buffer);
 using GrGLTexBufferRangeFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum internalformat, GrGLuint buffer, GrGLintptr offset, GrGLsizeiptr size);
 using GrGLTexImage2DFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLint level, GrGLint internalformat, GrGLsizei width, GrGLsizei height, GrGLint border, GrGLenum format, GrGLenum type, const GrGLvoid* pixels);
+using GrGLTexParameterfFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum pname, GrGLfloat param);
+using GrGLTexParameterfvFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum pname, const GrGLfloat* params);
 using GrGLTexParameteriFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum pname, GrGLint param);
 using GrGLTexParameterivFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLenum pname, const GrGLint* params);
 using GrGLTexStorage2DFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum target, GrGLsizei levels, GrGLenum internalformat, GrGLsizei width, GrGLsizei height);
@@ -234,9 +231,6 @@
 using GrGLMultiDrawArraysIndirectFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, const GrGLvoid* indirect, GrGLsizei drawcount, GrGLsizei stride);
 using GrGLMultiDrawElementsIndirectFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLenum mode, GrGLenum type, const GrGLvoid* indirect, GrGLsizei drawcount, GrGLsizei stride);
 
-/* ARB_sample_shading */
-using GrGLMinSampleShadingFn = GrGLvoid GR_GL_FUNCTION_TYPE(GrGLfloat value);
-
 /* ARB_sync */
 using GrGLFenceSyncFn = GrGLsync GR_GL_FUNCTION_TYPE(GrGLenum condition, GrGLbitfield flags);
 using GrGLIsSyncFn = GrGLboolean GR_GL_FUNCTION_TYPE(GrGLsync sync);
diff --git a/include/gpu/gl/GrGLInterface.h b/include/gpu/gl/GrGLInterface.h
index b0a8d93..56683b6 100644
--- a/include/gpu/gl/GrGLInterface.h
+++ b/include/gpu/gl/GrGLInterface.h
@@ -240,6 +240,8 @@
         GrGLFunction<GrGLTexBufferFn> fTexBuffer;
         GrGLFunction<GrGLTexBufferRangeFn> fTexBufferRange;
         GrGLFunction<GrGLTexImage2DFn> fTexImage2D;
+        GrGLFunction<GrGLTexParameterfFn> fTexParameterf;
+        GrGLFunction<GrGLTexParameterfvFn> fTexParameterfv;
         GrGLFunction<GrGLTexParameteriFn> fTexParameteri;
         GrGLFunction<GrGLTexParameterivFn> fTexParameteriv;
         GrGLFunction<GrGLTexSubImage2DFn> fTexSubImage2D;
@@ -310,9 +312,6 @@
         /* NV_framebuffer_mixed_samples */
         GrGLFunction<GrGLCoverageModulationFn> fCoverageModulation;
 
-        /* ARB_sample_shading */
-        GrGLFunction<GrGLMinSampleShadingFn> fMinSampleShading;
-
         /* ARB_sync */
         GrGLFunction<GrGLFenceSyncFn> fFenceSync;
         GrGLFunction<GrGLIsSyncFn> fIsSync;
diff --git a/include/gpu/vk/GrVkTypes.h b/include/gpu/vk/GrVkTypes.h
index ec88a5f..9bd5d61 100644
--- a/include/gpu/vk/GrVkTypes.h
+++ b/include/gpu/vk/GrVkTypes.h
@@ -9,11 +9,9 @@
 #ifndef GrVkTypes_DEFINED
 #define GrVkTypes_DEFINED
 
-#ifdef SK_VULKAN
-#include <vulkan/vulkan_core.h>
-#else
-#include "../../../third_party/vulkan/vulkan/vulkan_core.h"
-#endif
+#include "SkTypes.h"
+#include "GrVkVulkan.h"
+
 #ifndef VK_VERSION_1_1
 #error Skia requires the use of Vulkan 1.1 headers
 #endif
@@ -217,7 +215,6 @@
     VkCommandBuffer fSecondaryCommandBuffer;
     uint32_t        fColorAttachmentIndex;
     VkRenderPass    fCompatibleRenderPass;
-    uint32_t        fImageAttachmentIndex;
     VkFormat        fFormat;
     VkRect2D*       fDrawBounds;
 };
diff --git a/include/gpu/vk/GrVkVulkan.h b/include/gpu/vk/GrVkVulkan.h
new file mode 100644
index 0000000..89f6437
--- /dev/null
+++ b/include/gpu/vk/GrVkVulkan.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrVkVulkan_DEFINED
+#define GrVkVulkan_DEFINED
+
+#include "SkTypes.h"
+
+#if SKIA_IMPLEMENTATION || !defined(SK_VULKAN)
+#include "../../include/third_party/vulkan/vulkan/vulkan_core.h"
+#else
+// For google3 builds we don't set SKIA_IMPLEMENTATION so we need to make sure that the vulkan
+// headers stay up to date for our needs
+#include <vulkan/vulkan_core.h>
+#endif
+
+#ifdef SK_BUILD_FOR_ANDROID
+// This is needed to get android extensions for external memory
+#if SKIA_IMPLEMENTATION || !defined(SK_VULKAN)
+#include "../../include/third_party/vulkan/vulkan/vulkan_android.h"
+#else
+// For google3 builds we don't set SKIA_IMPLEMENTATION so we need to make sure that the vulkan
+// headers stay up to date for our needs
+#include <vulkan/vulkan_android.h>
+#endif
+#endif
+
+#endif
diff --git a/include/ports/SkFontMgr_fuchsia.h b/include/ports/SkFontMgr_fuchsia.h
new file mode 100644
index 0000000..4bcff82
--- /dev/null
+++ b/include/ports/SkFontMgr_fuchsia.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkFontMgr_fuchsia_DEFINED
+#define SkFontMgr_fuchsia_DEFINED
+
+#include <fuchsia/fonts/cpp/fidl.h>
+
+#include "SkRefCnt.h"
+
+class SkFontMgr;
+
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_Fuchsia(fuchsia::fonts::ProviderSyncPtr provider);
+
+#endif  // SkFontMgr_fuchsia_DEFINED
diff --git a/include/private/GrColor.h b/include/private/GrColor.h
index 1d1f54b..098dda0 100644
--- a/include/private/GrColor.h
+++ b/include/private/GrColor.h
@@ -98,6 +98,8 @@
         }
     }
 
+    size_t size() const { return fWideColor ? 8 : 4; }
+
 private:
     friend struct GrVertexWriter;
 
diff --git a/include/private/GrOpList.h b/include/private/GrOpList.h
index e596ca9..4b1d0e3 100644
--- a/include/private/GrOpList.h
+++ b/include/private/GrOpList.h
@@ -121,7 +121,7 @@
     SkDEBUGCODE(void validate() const);
     void closeThoseWhoDependOnMe(const GrCaps&);
 
-    // Remove all Ops which reference proxies that have not been instantiated.
+    // Remove all Ops which reference proxies that are not instantiated.
     virtual void purgeOpsWithUninstantiatedProxies() = 0;
 
     // Feed proxy usage intervals to the GrResourceAllocator class
diff --git a/include/private/GrRenderTargetProxy.h b/include/private/GrRenderTargetProxy.h
index 1f5b79f..3cedf74 100644
--- a/include/private/GrRenderTargetProxy.h
+++ b/include/private/GrRenderTargetProxy.h
@@ -54,6 +54,8 @@
 
     int maxWindowRectangles(const GrCaps& caps) const;
 
+    bool wrapsVkSecondaryCB() const { return fWrapsVkSecondaryCB == WrapsVkSecondaryCB::kYes; }
+
     // TODO: move this to a priv class!
     bool refsWrappedObjects() const;
 
@@ -84,7 +86,9 @@
                         SkBackingFit, SkBudgeted, GrInternalSurfaceFlags);
 
     // Wrapped version
-    GrRenderTargetProxy(sk_sp<GrSurface>, GrSurfaceOrigin);
+    enum class WrapsVkSecondaryCB : bool { kNo = false, kYes = true };
+    GrRenderTargetProxy(sk_sp<GrSurface>, GrSurfaceOrigin,
+                        WrapsVkSecondaryCB wrapsVkSecondaryCB = WrapsVkSecondaryCB::kNo);
 
     sk_sp<GrSurface> createSurface(GrResourceProvider*) const override;
 
@@ -94,13 +98,6 @@
     }
     bool hasMixedSamples() const { return fSurfaceFlags & GrInternalSurfaceFlags::kMixedSampled; }
 
-    void setSupportsWindowRects() {
-        fSurfaceFlags |= GrInternalSurfaceFlags::kWindowRectsSupport;
-    }
-    bool supportsWindowRects() const {
-        return fSurfaceFlags & GrInternalSurfaceFlags::kWindowRectsSupport;
-    }
-
     void setGLRTFBOIDIs0() {
         fSurfaceFlags |= GrInternalSurfaceFlags::kGLRTFBOIDIs0;
     }
@@ -120,8 +117,9 @@
     // that particular class don't require it. Changing the size of this object can move the start
     // address of other types, leading to this problem.
 
-    int                 fSampleCnt;
-    bool                fNeedsStencil;
+    int                fSampleCnt;
+    bool               fNeedsStencil;
+    WrapsVkSecondaryCB fWrapsVkSecondaryCB;
 
     // For wrapped render targets the actual GrRenderTarget is stored in the GrIORefProxy class.
     // For deferred proxies that pointer is filled in when we need to instantiate the
diff --git a/include/gpu/GrResourceKey.h b/include/private/GrResourceKey.h
similarity index 91%
rename from include/gpu/GrResourceKey.h
rename to include/private/GrResourceKey.h
index 006fae8..8179b19 100644
--- a/include/gpu/GrResourceKey.h
+++ b/include/private/GrResourceKey.h
@@ -50,10 +50,9 @@
     }
 
     bool operator==(const GrResourceKey& that) const {
-        return this->hash() == that.hash() &&
-                0 == memcmp(&fKey[kHash_MetaDataIdx + 1],
-                            &that.fKey[kHash_MetaDataIdx + 1],
-                            this->internalSize() - sizeof(uint32_t));
+        return this->hash() == that.hash() && 0 == memcmp(&fKey[kHash_MetaDataIdx + 1],
+                                                          &that.fKey[kHash_MetaDataIdx + 1],
+                                                          this->internalSize() - sizeof(uint32_t));
     }
 
     GrResourceKey& operator=(const GrResourceKey& that) {
@@ -90,7 +89,7 @@
             SkDebugf("domain: %d ", this->domain());
             SkDebugf("size: %dB ", this->internalSize());
             for (size_t i = 0; i < this->internalSize(); ++i) {
-                SkDebugf("%d ", fKey[i]);
+                SkDebugf("%d ", fKey[SkTo<int>(i)]);
             }
             SkDebugf("\n");
         }
@@ -144,9 +143,7 @@
     };
     static const uint32_t kMetaDataCnt = kLastMetaDataIdx + 1;
 
-    size_t internalSize() const {
-        return fKey[kDomainAndSize_MetaDataIdx] >> 16;
-    }
+    size_t internalSize() const { return fKey[kDomainAndSize_MetaDataIdx] >> 16; }
 
     void validate() const {
         SkASSERT(fKey[kHash_MetaDataIdx] ==
@@ -155,7 +152,7 @@
         SkASSERT(SkIsAlign4(this->internalSize()));
     }
 
-    friend class TestResource; // For unit test to access kMetaDataCnt.
+    friend class TestResource;  // For unit test to access kMetaDataCnt.
 
     // bmp textures require 5 uint32_t values.
     SkAutoSTMalloc<kMetaDataCnt + 5, uint32_t> fKey;
@@ -210,15 +207,13 @@
         return *this;
     }
 
-    bool operator==(const GrScratchKey& that) const {
-        return this->INHERITED::operator==(that);
-    }
+    bool operator==(const GrScratchKey& that) const { return this->INHERITED::operator==(that); }
     bool operator!=(const GrScratchKey& that) const { return !(*this == that); }
 
     class Builder : public INHERITED::Builder {
     public:
         Builder(GrScratchKey* key, ResourceType type, int data32Count)
-            : INHERITED::Builder(key, type, data32Count) {}
+                : INHERITED::Builder(key, type, data32Count) {}
     };
 };
 
@@ -262,17 +257,11 @@
         return *this;
     }
 
-    bool operator==(const GrUniqueKey& that) const {
-        return this->INHERITED::operator==(that);
-    }
+    bool operator==(const GrUniqueKey& that) const { return this->INHERITED::operator==(that); }
     bool operator!=(const GrUniqueKey& that) const { return !(*this == that); }
 
-    void setCustomData(sk_sp<SkData> data) {
-        fData = std::move(data);
-    }
-    SkData* getCustomData() const {
-        return fData.get();
-    }
+    void setCustomData(sk_sp<SkData> data) { fData = std::move(data); }
+    SkData* getCustomData() const { return fData.get(); }
 
     const char* tag() const { return fTag; }
 
@@ -330,7 +319,7 @@
     name##_once(gr_init_static_unique_key_once, &name##_storage); \
     static const GrUniqueKey& name = *reinterpret_cast<GrUniqueKey*>(name##_storage.get())
 
-static inline void gr_init_static_unique_key_once(SkAlignedSTStorage<1,GrUniqueKey>* keyStorage) {
+static inline void gr_init_static_unique_key_once(SkAlignedSTStorage<1, GrUniqueKey>* keyStorage) {
     GrUniqueKey* key = new (keyStorage->get()) GrUniqueKey;
     GrUniqueKey::Builder builder(key, GrUniqueKey::GenerateDomain(), 0);
 }
@@ -355,8 +344,8 @@
     uint32_t fContextID;
 };
 
-static inline bool SkShouldPostMessageToBus(
-        const GrUniqueKeyInvalidatedMessage& msg, uint32_t msgBusUniqueID) {
+static inline bool SkShouldPostMessageToBus(const GrUniqueKeyInvalidatedMessage& msg,
+                                            uint32_t msgBusUniqueID) {
     return msg.contextID() == msgBusUniqueID;
 }
 
diff --git a/include/private/GrSurfaceProxy.h b/include/private/GrSurfaceProxy.h
index 887400a..5dd139a 100644
--- a/include/private/GrSurfaceProxy.h
+++ b/include/private/GrSurfaceProxy.h
@@ -205,10 +205,10 @@
 class GrSurfaceProxy : public GrIORefProxy {
 public:
     enum class LazyInstantiationType {
-        kSingleUse,         // Instantiation callback is allowed to be called only once
-        kMultipleUse,       // Instantiation callback can be called multiple times.
-        kUninstantiate,     // Instantiation callback can be called multiple times,
-                            // but we will uninstantiate the proxy after every flush
+        kSingleUse,      // Instantiation callback is allowed to be called only once.
+        kMultipleUse,    // Instantiation callback can be called multiple times.
+        kDeinstantiate,  // Instantiation callback can be called multiple times,
+                         // but we will deinstantiate the proxy after every flush.
     };
 
     enum class LazyState {
@@ -267,8 +267,6 @@
 
     const GrBackendFormat& backendFormat() const { return fFormat; }
 
-    GrTextureType textureType() const { return fFormat.textureType(); }
-
     class UniqueID {
     public:
         static UniqueID InvalidID() {
@@ -325,7 +323,7 @@
 
     virtual bool instantiate(GrResourceProvider* resourceProvider) = 0;
 
-    void deInstantiate();
+    void deinstantiate();
 
     /**
      * Proxies that are already instantiated and whose backing surface cannot be recycled to
@@ -365,6 +363,13 @@
      */
     SkBudgeted isBudgeted() const { return fBudgeted; }
 
+    /**
+     * The pixel values of this proxy's surface cannot be modified (e.g. doesn't support write
+     * pixels or MIP map level regen). Read-only proxies also bypass interval tracking and
+     * assignment in GrResourceAllocator.
+     */
+    bool readOnly() const { return fSurfaceFlags & GrInternalSurfaceFlags::kReadOnly; }
+
     void setLastOpList(GrOpList* opList);
     GrOpList* getLastOpList() { return fLastOpList; }
 
@@ -391,14 +396,12 @@
     }
 
     // Helper function that creates a temporary SurfaceContext to perform the copy
-    // It always returns a kExact-backed proxy bc it is used when converting an SkSpecialImage
-    // to an SkImage. The copy is is not a render target and not multisampled.
-    static sk_sp<GrTextureProxy> Copy(GrContext*, GrSurfaceProxy* src, GrMipMapped,
-                                      SkIRect srcRect, SkBudgeted);
+    // The copy is is not a render target and not multisampled.
+    static sk_sp<GrTextureProxy> Copy(GrContext*, GrSurfaceProxy* src, GrMipMapped, SkIRect srcRect,
+                                      SkBackingFit, SkBudgeted);
 
     // Copy the entire 'src'
-    // It always returns a kExact-backed proxy bc it is used in SkGpuDevice::snapSpecial
-    static sk_sp<GrTextureProxy> Copy(GrContext* context, GrSurfaceProxy* src, GrMipMapped,
+    static sk_sp<GrTextureProxy> Copy(GrContext*, GrSurfaceProxy* src, GrMipMapped, SkBackingFit,
                                       SkBudgeted budgeted);
 
     // Test-only entry point - should decrease in use as proxies propagate
@@ -432,7 +435,7 @@
                    const GrBackendFormat& format, const GrSurfaceDesc&, GrSurfaceOrigin,
                    SkBackingFit, SkBudgeted, GrInternalSurfaceFlags);
 
-    // Wrapped version
+    // Wrapped version.
     GrSurfaceProxy(sk_sp<GrSurface>, GrSurfaceOrigin, SkBackingFit);
 
     virtual ~GrSurfaceProxy();
diff --git a/include/private/GrTextureProxy.h b/include/private/GrTextureProxy.h
index 0926f16..71d581c 100644
--- a/include/private/GrTextureProxy.h
+++ b/include/private/GrTextureProxy.h
@@ -40,6 +40,8 @@
     // been instantiated or not.
     GrMipMapped proxyMipMapped() const { return fMipMapped; }
 
+    GrTextureType textureType() const { return this->backendFormat().textureType(); }
+
     /** If true then the texture does not support MIP maps and only supports clamp wrap mode. */
     bool hasRestrictedSampling() const {
         return GrTextureTypeHasRestrictedSampling(this->textureType());
diff --git a/include/private/GrTypesPriv.h b/include/private/GrTypesPriv.h
index 86cde4d..29ab814 100644
--- a/include/private/GrTypesPriv.h
+++ b/include/private/GrTypesPriv.h
@@ -42,6 +42,7 @@
     kRGBA_4444_GrPixelConfig,
     kRGBA_8888_GrPixelConfig,
     kRGB_888_GrPixelConfig,
+    kRG_88_GrPixelConfig,
     kBGRA_8888_GrPixelConfig,
     kSRGBA_8888_GrPixelConfig,
     kSBGRA_8888_GrPixelConfig,
@@ -349,7 +350,10 @@
     kTexture2DSampler_GrSLType,
     kTextureExternalSampler_GrSLType,
     kTexture2DRectSampler_GrSLType,
+
+    kLast_GrSLType = kTexture2DRectSampler_GrSLType
 };
+static const int kGrSLTypeCount = kLast_GrSLType + 1;
 
 /**
  * The type of texture. Backends other than GL currently only use the 2D value but the type must
@@ -358,6 +362,7 @@
  */
 enum class GrTextureType {
     k2D,
+    /* Rectangle uses unnormalized texture coordinates. */
     kRectangle,
     kExternal
 };
@@ -824,11 +829,19 @@
     kNone                           = 0,
 
     // Surface-level
+
     kNoPendingIO                    = 1 << 0,
 
     kSurfaceMask                    = kNoPendingIO,
 
-    // RT-only
+    // Texture-level
+
+    // Means the pixels in the texture are read-only. Cannot also be a GrRenderTarget[Proxy].
+    kReadOnly                       = 1 << 1,
+
+    kTextureMask                    = kReadOnly,
+
+    // RT-level
 
     // For internal resources:
     //    this is enabled whenever MSAA is enabled and GrCaps reports mixed samples are supported
@@ -838,17 +851,10 @@
     //        are supported
     kMixedSampled                   = 1 << 2,
 
-    // For internal resources:
-    //    this is enabled whenever GrCaps reports window rect support
-    // For wrapped resources1
-    //    this is disabled for FBO0
-    //    but, otherwise, is enabled whenever GrCaps reports window rect support
-    kWindowRectsSupport             = 1 << 3,
-
     // This flag is for use with GL only. It tells us that the internal render target wraps FBO 0.
-    kGLRTFBOIDIs0                   = 1 << 4,
+    kGLRTFBOIDIs0                   = 1 << 3,
 
-    kRenderTargetMask               = kMixedSampled | kWindowRectsSupport | kGLRTFBOIDIs0,
+    kRenderTargetMask               = kMixedSampled | kGLRTFBOIDIs0,
 };
 GR_MAKE_BITFIELD_CLASS_OPS(GrInternalSurfaceFlags)
 
@@ -953,6 +959,7 @@
         case kRGB_565_GrPixelConfig:
         case kRGBA_4444_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRG_88_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
         case kRGBA_1010102_GrPixelConfig:
@@ -982,6 +989,7 @@
             return 1;
         case kRGB_565_GrPixelConfig:
         case kRGBA_4444_GrPixelConfig:
+        case kRG_88_GrPixelConfig:
         case kAlpha_half_GrPixelConfig:
         case kAlpha_half_as_Red_GrPixelConfig:
             return 2;
@@ -1009,6 +1017,7 @@
     switch (config) {
         case kRGB_565_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRG_88_GrPixelConfig:
         case kGray_8_GrPixelConfig:
         case kGray_8_as_Lum_GrPixelConfig:
         case kGray_8_as_Red_GrPixelConfig:
@@ -1050,6 +1059,7 @@
         case kRGBA_4444_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRG_88_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
         case kSRGBA_8888_GrPixelConfig:
         case kSBGRA_8888_GrPixelConfig:
@@ -1075,6 +1085,7 @@
         case kRGB_565_GrPixelConfig:
         case kRGBA_4444_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRG_88_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
         case kSRGBA_8888_GrPixelConfig:
@@ -1108,6 +1119,7 @@
         case kRGBA_4444_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRG_88_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
         case kSRGBA_8888_GrPixelConfig:
         case kSBGRA_8888_GrPixelConfig:
@@ -1139,6 +1151,7 @@
     kABGR_4444,  // This name differs from SkColorType. kARGB_4444_SkColorType is misnamed.
     kRGBA_8888,
     kRGB_888x,
+    kRG_88,
     kBGRA_8888,
     kRGBA_1010102,
     kGray_8,
@@ -1156,6 +1169,7 @@
         case GrColorType::kABGR_4444:    return kARGB_4444_SkColorType;
         case GrColorType::kRGBA_8888:    return kRGBA_8888_SkColorType;
         case GrColorType::kRGB_888x:     return kRGB_888x_SkColorType;
+        case GrColorType::kRG_88:        return kUnknown_SkColorType;
         case GrColorType::kBGRA_8888:    return kBGRA_8888_SkColorType;
         case GrColorType::kRGBA_1010102: return kRGBA_1010102_SkColorType;
         case GrColorType::kGray_8:       return kGray_8_SkColorType;
@@ -1195,6 +1209,8 @@
         case GrColorType::kABGR_4444:    return kRGBA_SkColorTypeComponentFlags;
         case GrColorType::kRGBA_8888:    return kRGBA_SkColorTypeComponentFlags;
         case GrColorType::kRGB_888x:     return kRGB_SkColorTypeComponentFlags;
+        case GrColorType::kRG_88:        return kRed_SkColorTypeComponentFlag |
+                                                kGreen_SkColorTypeComponentFlag;
         case GrColorType::kBGRA_8888:    return kRGBA_SkColorTypeComponentFlags;
         case GrColorType::kRGBA_1010102: return kRGBA_SkColorTypeComponentFlags;
         case GrColorType::kGray_8:       return kGray_SkColorTypeComponentFlag;
@@ -1224,6 +1240,7 @@
         case GrColorType::kABGR_4444:    return 2;
         case GrColorType::kRGBA_8888:    return 4;
         case GrColorType::kRGB_888x:     return 4;
+        case GrColorType::kRG_88:        return 2;
         case GrColorType::kBGRA_8888:    return 4;
         case GrColorType::kRGBA_1010102: return 4;
         case GrColorType::kGray_8:       return 1;
@@ -1260,6 +1277,9 @@
         case kRGB_888_GrPixelConfig:
             *srgbEncoded = GrSRGBEncoded::kNo;
             return GrColorType::kRGB_888x;
+        case kRG_88_GrPixelConfig:
+            *srgbEncoded = GrSRGBEncoded::kNo;
+            return GrColorType::kRG_88;
         case kBGRA_8888_GrPixelConfig:
             *srgbEncoded = GrSRGBEncoded::kNo;
             return GrColorType::kBGRA_8888;
@@ -1337,6 +1357,9 @@
         case GrColorType::kRGB_888x:
             return (GrSRGBEncoded::kYes == srgbEncoded) ? kUnknown_GrPixelConfig
                                                         : kRGB_888_GrPixelConfig;
+        case GrColorType::kRG_88:
+            return (GrSRGBEncoded::kYes == srgbEncoded) ? kUnknown_GrPixelConfig
+                                                        : kRG_88_GrPixelConfig;
 
         case GrColorType::kBGRA_8888:
             return (GrSRGBEncoded::kYes == srgbEncoded) ? kSBGRA_8888_GrPixelConfig
diff --git a/include/private/SkAtomics.h b/include/private/SkAtomics.h
deleted file mode 100644
index 0a34429..0000000
--- a/include/private/SkAtomics.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkAtomics_DEFINED
-#define SkAtomics_DEFINED
-
-#include "SkTypes.h"
-#include <atomic>
-
-// ~~~~~~~~ Very Legacy APIs ~~~~~~~~~
-//
-// Here are shims for our very old atomics API, to be weaned off of.  They use
-// sequentially-consistent memory order to match historical behavior, but most
-// of the callers could perform better with explicit, weaker memory ordering.
-
-inline int32_t sk_atomic_inc(int32_t* ptr) {
-    return reinterpret_cast<std::atomic<int32_t>*>(ptr)->fetch_add(+1);
-}
-inline int32_t sk_atomic_dec(int32_t* ptr) {
-    return reinterpret_cast<std::atomic<int32_t>*>(ptr)->fetch_add(-1);
-}
-
-#endif//SkAtomics_DEFINED
diff --git a/include/private/SkNx.h b/include/private/SkNx.h
index 29c4d73..4123ba8 100644
--- a/include/private/SkNx.h
+++ b/include/private/SkNx.h
@@ -19,7 +19,7 @@
 // Every single SkNx method wants to be fully inlined.  (We know better than MSVC).
 #define AI SK_ALWAYS_INLINE
 
-namespace {
+namespace {  // NOLINT(google-build-namespaces)
 
 // The default SkNx<N,T> just proxies down to a pair of SkNx<N/2, T>.
 template <int N, typename T>
diff --git a/include/private/SkNx_neon.h b/include/private/SkNx_neon.h
index 98ef3fe..e702aef 100644
--- a/include/private/SkNx_neon.h
+++ b/include/private/SkNx_neon.h
@@ -10,7 +10,7 @@
 
 #include <arm_neon.h>
 
-namespace {
+namespace {  // NOLINT(google-build-namespaces)
 
 // ARMv8 has vrndm(q)_f32 to floor floats.  Here we emulate it:
 //   - roundtrip through integers via truncation
@@ -496,6 +496,7 @@
 
     AI SkNx operator + (const SkNx& o) const { return vaddq_u8(fVec, o.fVec); }
     AI SkNx operator - (const SkNx& o) const { return vsubq_u8(fVec, o.fVec); }
+    AI SkNx operator & (const SkNx& o) const { return vandq_u8(fVec, o.fVec); }
 
     AI static SkNx Min(const SkNx& a, const SkNx& b) { return vminq_u8(a.fVec, b.fVec); }
     AI SkNx operator < (const SkNx& o) const { return vcltq_u8(fVec, o.fVec); }
diff --git a/include/private/SkNx_sse.h b/include/private/SkNx_sse.h
index 335b70c..94b458e 100644
--- a/include/private/SkNx_sse.h
+++ b/include/private/SkNx_sse.h
@@ -21,7 +21,7 @@
 // This file may assume <= SSE2, but must check SK_CPU_SSE_LEVEL for anything more recent.
 // If you do, make sure this is in a static inline function... anywhere else risks violating ODR.
 
-namespace {
+namespace {  // NOLINT(google-build-namespaces)
 
 // Emulate _mm_floor_ps() with SSE2:
 //   - roundtrip through integers via truncation
@@ -668,6 +668,7 @@
 
     AI SkNx operator + (const SkNx& o) const { return _mm_add_epi8(fVec, o.fVec); }
     AI SkNx operator - (const SkNx& o) const { return _mm_sub_epi8(fVec, o.fVec); }
+    AI SkNx operator & (const SkNx& o) const { return _mm_and_si128(fVec, o.fVec); }
 
     AI static SkNx Min(const SkNx& a, const SkNx& b) { return _mm_min_epu8(a.fVec, b.fVec); }
     AI SkNx operator < (const SkNx& o) const {
diff --git a/include/private/SkPathRef.h b/include/private/SkPathRef.h
index 5678eb7..91c05ba 100644
--- a/include/private/SkPathRef.h
+++ b/include/private/SkPathRef.h
@@ -8,7 +8,6 @@
 #ifndef SkPathRef_DEFINED
 #define SkPathRef_DEFINED
 
-#include "SkAtomics.h"
 #include "SkMatrix.h"
 #include "SkMutex.h"
 #include "SkPoint.h"
@@ -18,7 +17,7 @@
 #include "SkTDArray.h"
 #include "SkTemplates.h"
 #include "SkTo.h"
-
+#include <atomic>
 #include <limits>
 
 class SkRBuffer;
@@ -47,7 +46,7 @@
                int incReserveVerbs = 0,
                int incReservePoints = 0);
 
-        ~Editor() { SkDEBUGCODE(sk_atomic_dec(&fPathRef->fEditorsAttached);) }
+        ~Editor() { SkDEBUGCODE(fPathRef->fEditorsAttached--;) }
 
         /**
          * Returns the array of points.
@@ -358,7 +357,7 @@
         // The next two values don't matter unless fIsOval or fIsRRect are true.
         fRRectOrOvalIsCCW = false;
         fRRectOrOvalStartIdx = 0xAC;
-        SkDEBUGCODE(fEditorsAttached = 0;)
+        SkDEBUGCODE(fEditorsAttached.store(0);)
         SkDEBUGCODE(this->validate();)
     }
 
@@ -558,7 +557,7 @@
         kEmptyGenID = 1, // GenID reserved for path ref with zero points and zero verbs.
     };
     mutable uint32_t    fGenerationID;
-    SkDEBUGCODE(int32_t fEditorsAttached;) // assert that only one editor in use at any time.
+    SkDEBUGCODE(std::atomic<int> fEditorsAttached;) // assert only one editor in use at any time.
 
     SkMutex                         fGenIDChangeListenersMutex;
     SkTDArray<GenIDChangeListener*> fGenIDChangeListeners;  // pointers are reffed
diff --git a/third_party/vulkan/LICENSE b/include/third_party/vulkan/LICENSE
similarity index 100%
rename from third_party/vulkan/LICENSE
rename to include/third_party/vulkan/LICENSE
diff --git a/third_party/vulkan/vulkan/vk_platform.h b/include/third_party/vulkan/vulkan/vk_platform.h
similarity index 100%
rename from third_party/vulkan/vulkan/vk_platform.h
rename to include/third_party/vulkan/vulkan/vk_platform.h
diff --git a/third_party/vulkan/vulkan/vulkan.h b/include/third_party/vulkan/vulkan/vulkan.h
similarity index 100%
rename from third_party/vulkan/vulkan/vulkan.h
rename to include/third_party/vulkan/vulkan/vulkan.h
diff --git a/third_party/vulkan/vulkan/vulkan_android.h b/include/third_party/vulkan/vulkan/vulkan_android.h
similarity index 100%
rename from third_party/vulkan/vulkan/vulkan_android.h
rename to include/third_party/vulkan/vulkan/vulkan_android.h
diff --git a/third_party/vulkan/vulkan/vulkan_core.h b/include/third_party/vulkan/vulkan/vulkan_core.h
similarity index 99%
rename from third_party/vulkan/vulkan/vulkan_core.h
rename to include/third_party/vulkan/vulkan/vulkan_core.h
index c27b84d..9965ed0 100644
--- a/third_party/vulkan/vulkan/vulkan_core.h
+++ b/include/third_party/vulkan/vulkan/vulkan_core.h
@@ -47,7 +47,7 @@
 
 
 #define VK_NULL_HANDLE 0
-        
+
 
 
 #define VK_DEFINE_HANDLE(object) typedef struct object##_T* object;
@@ -60,7 +60,7 @@
         #define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef uint64_t object;
 #endif
 #endif
-        
+
 
 
 typedef uint32_t VkFlags;
diff --git a/third_party/vulkan/vulkan/vulkan_ios.h b/include/third_party/vulkan/vulkan/vulkan_ios.h
similarity index 100%
rename from third_party/vulkan/vulkan/vulkan_ios.h
rename to include/third_party/vulkan/vulkan/vulkan_ios.h
diff --git a/third_party/vulkan/vulkan/vulkan_macos.h b/include/third_party/vulkan/vulkan/vulkan_macos.h
similarity index 100%
rename from third_party/vulkan/vulkan/vulkan_macos.h
rename to include/third_party/vulkan/vulkan/vulkan_macos.h
diff --git a/third_party/vulkan/vulkan/vulkan_win32.h b/include/third_party/vulkan/vulkan/vulkan_win32.h
similarity index 100%
rename from third_party/vulkan/vulkan/vulkan_win32.h
rename to include/third_party/vulkan/vulkan/vulkan_win32.h
diff --git a/third_party/vulkan/vulkan/vulkan_xcb.h b/include/third_party/vulkan/vulkan/vulkan_xcb.h
similarity index 100%
rename from third_party/vulkan/vulkan/vulkan_xcb.h
rename to include/third_party/vulkan/vulkan/vulkan_xcb.h
diff --git a/include/utils/SkLuaCanvas.h b/include/utils/SkLuaCanvas.h
index aa984c7..3feaa93 100644
--- a/include/utils/SkLuaCanvas.h
+++ b/include/utils/SkLuaCanvas.h
@@ -24,20 +24,13 @@
 protected:
     void willSave() override;
     SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override;
+    bool onDoSaveBehind(const SkRect*) override;
     void willRestore() override;
 
     void didConcat(const SkMatrix&) override;
     void didSetMatrix(const SkMatrix&) override;
 
     void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
-    virtual void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                            const SkPaint&) override;
-    virtual void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                               const SkPaint&) override;
-    virtual void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                                SkScalar constY, const SkPaint&) override;
-    void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                           const SkRect* cull, const SkPaint& paint) override;
     virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                 const SkPaint& paint) override;
 
diff --git a/include/utils/SkNWayCanvas.h b/include/utils/SkNWayCanvas.h
index 6d77ea6..20714a6 100644
--- a/include/utils/SkNWayCanvas.h
+++ b/include/utils/SkNWayCanvas.h
@@ -27,22 +27,15 @@
 
     void willSave() override;
     SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override;
+    bool onDoSaveBehind(const SkRect*) override;
     void willRestore() override;
 
     void didConcat(const SkMatrix&) override;
     void didSetMatrix(const SkMatrix&) override;
 
     void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
-    virtual void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                            const SkPaint&) override;
-    virtual void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                               const SkPaint&) override;
-    virtual void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                                SkScalar constY, const SkPaint&) override;
     virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                 const SkPaint& paint) override;
-    void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                           const SkRect* cull, const SkPaint& paint) override;
     virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4],
                              const SkPoint texCoords[4], SkBlendMode,
                              const SkPaint& paint) override;
diff --git a/include/utils/SkNoDrawCanvas.h b/include/utils/SkNoDrawCanvas.h
index f8c197b..234bd22 100644
--- a/include/utils/SkNoDrawCanvas.h
+++ b/include/utils/SkNoDrawCanvas.h
@@ -38,16 +38,12 @@
 
 protected:
     SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override;
+    bool onDoSaveBehind(const SkRect*) override;
 
     // No-op overrides for aborting rasterization earlier than SkNullBlitter.
     void onDrawAnnotation(const SkRect&, const char[], SkData*) override {}
     void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override {}
     void onDrawDrawable(SkDrawable*, const SkMatrix*) override {}
-    void onDrawText(const void*, size_t, SkScalar, SkScalar, const SkPaint&) override {}
-    void onDrawPosText(const void*, size_t, const SkPoint[], const SkPaint&) override {}
-    void onDrawPosTextH(const void*, size_t, const SkScalar[], SkScalar, const SkPaint&) override {}
-    void onDrawTextRSXform(const void*, size_t, const SkRSXform[], const SkRect*,
-                           const SkPaint&) override {}
     void onDrawTextBlob(const SkTextBlob*, SkScalar, SkScalar, const SkPaint&) override {}
     void onDrawPatch(const SkPoint[12], const SkColor[4], const SkPoint[4], SkBlendMode,
                      const SkPaint&) override {}
diff --git a/include/utils/SkPaintFilterCanvas.h b/include/utils/SkPaintFilterCanvas.h
index c57cffe..abbcbed 100644
--- a/include/utils/SkPaintFilterCanvas.h
+++ b/include/utils/SkPaintFilterCanvas.h
@@ -98,14 +98,6 @@
     void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override;
     void onDrawDrawable(SkDrawable*, const SkMatrix*) override;
 
-    void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                    const SkPaint&) override;
-    void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                       const SkPaint&) override;
-    void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                        SkScalar constY, const SkPaint&) override;
-    void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                           const SkRect* cull, const SkPaint& paint) override;
     void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                         const SkPaint& paint) override;
     void onDrawAtlas(const SkImage*, const SkRSXform[], const SkRect[], const SkColor[],
diff --git a/include/utils/SkTextUtils.h b/include/utils/SkTextUtils.h
index 30a66a7..9969032 100644
--- a/include/utils/SkTextUtils.h
+++ b/include/utils/SkTextUtils.h
@@ -9,10 +9,12 @@
 #define SkTextUtils_DEFINED
 
 #include "SkCanvas.h"
-#include "SkPaint.h"
 #include "SkFont.h"
+#include "SkPaint.h"
 #include "SkString.h"
 
+class SkPath;
+
 class SkTextUtils {
 public:
     enum Align {
@@ -21,17 +23,16 @@
         kRight_Align,
     };
 
-    static void DrawText(SkCanvas*, const void* text, size_t size, SkScalar x, SkScalar y,
-                          const SkPaint&, Align = kLeft_Align);
+    static void Draw(SkCanvas*, const void* text, size_t size, SkTextEncoding,
+                     SkScalar x, SkScalar y, const SkFont&, const SkPaint&, Align = kLeft_Align);
 
     static void DrawString(SkCanvas* canvas, const char text[], SkScalar x, SkScalar y,
-                           const SkPaint& paint, Align align = kLeft_Align) {
-        DrawText(canvas, text, strlen(text), x, y, paint, align);
+                           const SkFont& font, const SkPaint& paint, Align align = kLeft_Align) {
+        Draw(canvas, text, strlen(text), kUTF8_SkTextEncoding, x, y, font, paint, align);
     }
-    static void DrawString(SkCanvas* canvas, const SkString& str, SkScalar x, SkScalar y,
-                           const SkPaint& paint, Align align = kLeft_Align) {
-        DrawText(canvas, str.c_str(), str.size(), x, y, paint, align);
-    }
+
+    static void GetPath(const void* text, size_t length, SkTextEncoding, SkScalar x, SkScalar y,
+                        const SkFont&, SkPath*);
 };
 
 #endif
diff --git a/infra/bots/assets/android_ndk_darwin/VERSION b/infra/bots/assets/android_ndk_darwin/VERSION
index 7813681..c793025 100644
--- a/infra/bots/assets/android_ndk_darwin/VERSION
+++ b/infra/bots/assets/android_ndk_darwin/VERSION
@@ -1 +1 @@
-5
\ No newline at end of file
+7
\ No newline at end of file
diff --git a/infra/bots/assets/android_ndk_darwin/create.py b/infra/bots/assets/android_ndk_darwin/create.py
index 809f755..2b5faea 100755
--- a/infra/bots/assets/android_ndk_darwin/create.py
+++ b/infra/bots/assets/android_ndk_darwin/create.py
@@ -15,7 +15,7 @@
 import shutil
 import subprocess
 
-NDK_VER = "android-ndk-r17"
+NDK_VER = "android-ndk-r19-beta2"
 NDK_URL = \
     "https://dl.google.com/android/repository/%s-darwin-x86_64.zip" % NDK_VER
 
diff --git a/infra/bots/assets/android_ndk_linux/VERSION b/infra/bots/assets/android_ndk_linux/VERSION
index 9d60796..ca7bf83 100644
--- a/infra/bots/assets/android_ndk_linux/VERSION
+++ b/infra/bots/assets/android_ndk_linux/VERSION
@@ -1 +1 @@
-11
\ No newline at end of file
+13
\ No newline at end of file
diff --git a/infra/bots/assets/android_ndk_linux/create.py b/infra/bots/assets/android_ndk_linux/create.py
index 2af2905..50f118c 100755
--- a/infra/bots/assets/android_ndk_linux/create.py
+++ b/infra/bots/assets/android_ndk_linux/create.py
@@ -15,7 +15,7 @@
 import shutil
 import subprocess
 
-NDK_VER = "android-ndk-r17"
+NDK_VER = "android-ndk-r19-beta2"
 NDK_URL = \
     "https://dl.google.com/android/repository/%s-linux-x86_64.zip" % NDK_VER
 
diff --git a/infra/bots/assets/android_ndk_windows/VERSION b/infra/bots/assets/android_ndk_windows/VERSION
index 62f9457..301160a 100644
--- a/infra/bots/assets/android_ndk_windows/VERSION
+++ b/infra/bots/assets/android_ndk_windows/VERSION
@@ -1 +1 @@
-6
\ No newline at end of file
+8
\ No newline at end of file
diff --git a/infra/bots/assets/android_ndk_windows/create.py b/infra/bots/assets/android_ndk_windows/create.py
index 9336a5e..30ae2ba 100755
--- a/infra/bots/assets/android_ndk_windows/create.py
+++ b/infra/bots/assets/android_ndk_windows/create.py
@@ -15,7 +15,7 @@
 import shutil
 import subprocess
 
-NDK_VER = "android-ndk-r17"
+NDK_VER = "android-ndk-r19-beta2"
 NDK_URL = \
     "https://dl.google.com/android/repository/%s-windows-x86_64.zip" % NDK_VER
 
diff --git a/infra/bots/assets/clang_linux/VERSION b/infra/bots/assets/clang_linux/VERSION
index 3cacc0b..ca7bf83 100644
--- a/infra/bots/assets/clang_linux/VERSION
+++ b/infra/bots/assets/clang_linux/VERSION
@@ -1 +1 @@
-12
\ No newline at end of file
+13
\ No newline at end of file
diff --git a/infra/bots/assets/clang_linux/create.py b/infra/bots/assets/clang_linux/create.py
index 37a779b..887b3f0 100755
--- a/infra/bots/assets/clang_linux/create.py
+++ b/infra/bots/assets/clang_linux/create.py
@@ -30,7 +30,11 @@
                          BRANCH, REPO + "clang"])
   subprocess.check_call(["git", "clone", "--depth", "1", "-b",
                          BRANCH, REPO + "lld"])
-  os.chdir("../projects")
+  os.chdir("clang/tools")
+  subprocess.check_call(["git", "clone", "--depth", "1", "-b",
+                         BRANCH, REPO + "clang-tools-extra", "extra"])
+
+  os.chdir("../../../projects")
   subprocess.check_call(["git", "clone", "--depth", "1", "-b",
                          BRANCH, REPO + "compiler-rt"])
   subprocess.check_call(["git", "clone", "--depth", "1", "-b",
diff --git a/infra/bots/assets/clang_win/VERSION b/infra/bots/assets/clang_win/VERSION
index c793025..301160a 100644
--- a/infra/bots/assets/clang_win/VERSION
+++ b/infra/bots/assets/clang_win/VERSION
@@ -1 +1 @@
-7
\ No newline at end of file
+8
\ No newline at end of file
diff --git a/infra/bots/assets/clang_win/create.py b/infra/bots/assets/clang_win/create.py
index b5a44e3..570e77a 100755
--- a/infra/bots/assets/clang_win/create.py
+++ b/infra/bots/assets/clang_win/create.py
@@ -18,7 +18,7 @@
 
 # Copied from CLANG_REVISION here:
 # https://cs.chromium.org/chromium/src/tools/clang/scripts/update.py
-CLANG_REVISION = '331747'
+CLANG_REVISION = '346388'
 CLANG_SUB_REVISION = '1'
 CLANG_PKG_VERSION = '%s-%s' % (CLANG_REVISION, CLANG_SUB_REVISION)
 GS_URL = ('https://commondatastorage.googleapis.com/chromium-browser-clang'
diff --git a/infra/bots/assets/go_deps/VERSION b/infra/bots/assets/go_deps/VERSION
index c147342..176fdeb 100644
--- a/infra/bots/assets/go_deps/VERSION
+++ b/infra/bots/assets/go_deps/VERSION
@@ -1 +1 @@
-81
\ No newline at end of file
+119
\ No newline at end of file
diff --git a/infra/bots/assets/skp/VERSION b/infra/bots/assets/skp/VERSION
index dc9414b..9da06a1 100644
--- a/infra/bots/assets/skp/VERSION
+++ b/infra/bots/assets/skp/VERSION
@@ -1 +1 @@
-154
\ No newline at end of file
+160
\ No newline at end of file
diff --git a/infra/bots/assets/win_toolchain/VERSION b/infra/bots/assets/win_toolchain/VERSION
index 301160a..f11c82a 100644
--- a/infra/bots/assets/win_toolchain/VERSION
+++ b/infra/bots/assets/win_toolchain/VERSION
@@ -1 +1 @@
-8
\ No newline at end of file
+9
\ No newline at end of file
diff --git a/infra/bots/assets/win_toolchain/create.py b/infra/bots/assets/win_toolchain/create.py
index e96cde9..429ee80 100755
--- a/infra/bots/assets/win_toolchain/create.py
+++ b/infra/bots/assets/win_toolchain/create.py
@@ -6,12 +6,21 @@
 # found in the LICENSE file.
 
 
-"""Download an updated VS toolchain"""
+"""
+Create an updated VS toolchain
 
+Before you can run this script, you need a collated VC toolchain + Windows SDK.
+To generate that, run depot_tools/win_toolchain/package_from_installed.py
+That script pulls all of the compiler and SDK bits from your locally installed
+version of Visual Studio. The comments in that script include instructions on
+which components need to be installed (C++, ARM64, etc...)
+
+That script produces a .zip file with a SHA filename. Unzip that file, then
+pass the unzipped directory as the src_dir to this script.
+"""
 
 import argparse
 import common
-import json
 import os
 import shlex
 import shutil
@@ -19,8 +28,6 @@
 import sys
 import utils
 
-import win_toolchain_utils
-
 
 # By default the toolchain includes a bunch of unnecessary stuff with long path
 # names. Trim out directories with these names.
@@ -33,9 +40,6 @@
   'AccChecker',
 ]
 
-REPO_CHROME = 'https://chromium.googlesource.com/chromium/src.git'
-
-
 def filter_toolchain_files(dirname, files):
   """Callback for shutil.copytree. Return lists of files to skip."""
   split = dirname.split(os.path.sep)
@@ -45,83 +49,18 @@
        return files
   return []
 
-
-def get_toolchain_dir(toolchain_dir_output):
-  """Find the toolchain directory."""
-  prefix = 'vs_path = '
-  for line in toolchain_dir_output.splitlines():
-    if line.startswith(prefix):
-      return line[len(prefix):].strip('"')
-  raise Exception('Unable to find toolchain dir in output:\n%s' % (
-                  toolchain_dir_output))
-
-
-def gen_toolchain(chrome_path, msvs_version, target_dir):
-  """Update the VS toolchain and copy it to the target_dir."""
-  with utils.chdir(os.path.join(chrome_path, 'src')):
-    subprocess.check_call([utils.GCLIENT, 'sync'])
-    depot_tools = subprocess.check_output([
-        'python', os.path.join('build', 'find_depot_tools.py')]).rstrip()
-    with utils.git_branch():
-      vs_toolchain_py = os.path.join('build', 'vs_toolchain.py')
-      env = os.environ.copy()
-      env['GYP_MSVS_VERSION'] = msvs_version
-      subprocess.check_call(['python', vs_toolchain_py, 'update'], env=env)
-      output = subprocess.check_output(['python', vs_toolchain_py,
-                                        'get_toolchain_dir'], env=env).rstrip()
-      src_dir = get_toolchain_dir(output)
-      # Mock out absolute paths in win_toolchain.json.
-      win_toolchain_utils.abstract(os.path.join('build', 'win_toolchain.json'),
-                                   os.path.dirname(depot_tools))
-
-      # Copy the toolchain files to the target_dir.
-      build = os.path.join(os.getcwd(), 'build')
-      dst_build = os.path.join(target_dir, 'src', 'build')
-      os.makedirs(dst_build)
-      for f in ('find_depot_tools.py', 'vs_toolchain.py', 'win_toolchain.json'):
-        shutil.copyfile(os.path.join(build, f), os.path.join(dst_build, f))
-
-      shutil.copytree(os.path.join(os.getcwd(), 'tools', 'gyp', 'pylib'),
-                      os.path.join(target_dir, 'src', 'tools', 'gyp', 'pylib'))
-
-      dst_depot_tools = os.path.join(target_dir, 'depot_tools')
-      os.makedirs(dst_depot_tools)
-      for f in ('gclient.py', 'breakpad.py'):
-        shutil.copyfile(os.path.join(depot_tools, f),
-                        os.path.join(dst_depot_tools, f))
-      toolchain_dst = os.path.join(
-          target_dir, 'depot_tools', os.path.relpath(src_dir, depot_tools))
-      shutil.copytree(src_dir, toolchain_dst, ignore=filter_toolchain_files)
-
-
-def create_asset(target_dir, msvs_version, chrome_path=None):
-  """Create the asset."""
-  if not os.path.isdir(target_dir):
-    os.makedirs(target_dir)
-  with utils.tmp_dir() as tmp_dir:
-    if not chrome_path:
-      print ('Syncing Chrome from scratch. If you already have a checkout, '
-             'specify --chrome_path to save time.')
-      chrome_path = os.path.join(tmp_dir.name, 'src')
-    if not os.path.isdir(chrome_path):
-      subprocess.check_call([utils.GCLIENT, 'config', REPO_CHROME, '--managed'])
-      subprocess.check_call([utils.GCLIENT, 'sync'])
-
-    gen_toolchain(chrome_path, msvs_version, target_dir)
-
 def main():
   if sys.platform != 'win32':
     print >> sys.stderr, 'This script only runs on Windows.'
     sys.exit(1)
 
   parser = argparse.ArgumentParser()
-  parser.add_argument('--msvs_version', required=True)
-  parser.add_argument('--chrome_path')
+  parser.add_argument('--src_dir', '-s', required=True)
   parser.add_argument('--target_dir', '-t', required=True)
   args = parser.parse_args()
+  src_dir = os.path.abspath(args.src_dir)
   target_dir = os.path.abspath(args.target_dir)
-  create_asset(target_dir, args.msvs_version, args.chrome_path)
-
+  shutil.copytree(src_dir, target_dir, ignore=filter_toolchain_files)
 
 if __name__ == '__main__':
   main()
diff --git a/infra/bots/assets/win_toolchain/create_and_upload.py b/infra/bots/assets/win_toolchain/create_and_upload.py
index d428ca4..c914d41 100755
--- a/infra/bots/assets/win_toolchain/create_and_upload.py
+++ b/infra/bots/assets/win_toolchain/create_and_upload.py
@@ -20,23 +20,21 @@
 def main():
   parser = argparse.ArgumentParser()
   parser.add_argument('--gsutil')
-  parser.add_argument('--chrome_path')
-  parser.add_argument('--msvs_version', required=True)
+  parser.add_argument('--src_dir', '-s', required=True)
   args = parser.parse_args()
 
   with utils.tmp_dir():
     cwd = os.getcwd()
     create_script = os.path.join(common.FILE_DIR, 'create.py')
     upload_script = os.path.join(common.FILE_DIR, 'upload.py')
+    target_dir = os.path.join(cwd, 'win_toolchain')
 
     try:
       cmd = ['python', create_script,
-             '-t', cwd,
-             '--msvs_version', args.msvs_version]
-      if args.chrome_path:
-        cmd.extend(['--chrome_path', args.chrome_path])
+             '-t', target_dir,
+             '-s', args.src_dir]
       subprocess.check_call(cmd)
-      cmd = ['python', upload_script, '-t', cwd]
+      cmd = ['python', upload_script, '-t', target_dir]
       if args.gsutil:
         cmd.extend(['--gsutil', args.gsutil])
       subprocess.check_call(cmd)
diff --git a/infra/bots/assets/win_vulkan_sdk/README.md b/infra/bots/assets/win_vulkan_sdk/README.md
deleted file mode 100644
index 1175790..0000000
--- a/infra/bots/assets/win_vulkan_sdk/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-To create the vulkan sdk asset:
-
-Install the vulkan sdk from https://vulkan.lunarg.com/signin on a windows machine
-
-The default install dir is C:\VulkanSDK\VERSION
-
-When uploading the CIPD asset, use -s C:\VulkanSDK\VERSION
diff --git a/infra/bots/assets/win_vulkan_sdk/VERSION b/infra/bots/assets/win_vulkan_sdk/VERSION
deleted file mode 100644
index 7f8f011..0000000
--- a/infra/bots/assets/win_vulkan_sdk/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-7
diff --git a/infra/bots/assets/win_vulkan_sdk/common.py b/infra/bots/assets/win_vulkan_sdk/common.py
deleted file mode 100755
index 4920c9b..0000000
--- a/infra/bots/assets/win_vulkan_sdk/common.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2016 Google Inc.
-#
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-"""Common vars used by scripts in this directory."""
-
-
-import os
-import sys
-
-FILE_DIR = os.path.dirname(os.path.abspath(__file__))
-INFRA_BOTS_DIR = os.path.realpath(os.path.join(FILE_DIR, os.pardir, os.pardir))
-
-sys.path.insert(0, INFRA_BOTS_DIR)
-from assets import assets
-
-ASSET_NAME = os.path.basename(FILE_DIR)
-
-
-def run(cmd):
-  """Run a command, eg. "upload" or "download". """
-  assets.main([cmd, ASSET_NAME] + sys.argv[1:])
diff --git a/infra/bots/assets/win_vulkan_sdk/create.py b/infra/bots/assets/win_vulkan_sdk/create.py
deleted file mode 100644
index 0728f6d..0000000
--- a/infra/bots/assets/win_vulkan_sdk/create.py
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2016 Google Inc.
-#
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-"""Create the asset."""
-
-
-import argparse
-import shutil
-import sys
-import os
-
-
-def create_asset(target_dir, sdk_path):
-  """Create the asset."""
-  # The bots only need Include from the SDK.
-  target_include_dir = os.path.join(target_dir, "Include")
-  sdk_include_dir = os.path.join(sdk_path, "Include")
-  if not os.path.isdir(target_dir):
-    os.makedirs(target_dir)
-  shutil.copytree(sdk_include_dir, target_include_dir)
-
-def main():
-  if sys.platform != 'win32':
-    print >> sys.stderr, 'This script only runs on Windows.'
-    sys.exit(1)
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--target_dir', '-t', required=True)
-  parser.add_argument('--sdk_path', '-s', required=True)
-  args = parser.parse_args()
-  create_asset(args.target_dir, args.sdk_path)
-
-
-if __name__ == '__main__':
-  main()
diff --git a/infra/bots/assets/win_vulkan_sdk/create_and_upload.py b/infra/bots/assets/win_vulkan_sdk/create_and_upload.py
deleted file mode 100644
index 69dbec9..0000000
--- a/infra/bots/assets/win_vulkan_sdk/create_and_upload.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2016 Google Inc.
-#
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-"""Create the asset and upload it."""
-
-
-import argparse
-import common
-import os
-import subprocess
-import sys
-import utils
-
-
-def main():
-  if sys.platform != 'win32':
-    print >> sys.stderr, 'This script only runs on Windows.'
-    sys.exit(1)
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--gsutil')
-  parser.add_argument('--sdk_path', '-s', required=True)
-  args = parser.parse_args()
-
-  with utils.tmp_dir():
-    cwd = os.getcwd()
-    create_script = os.path.join(common.FILE_DIR, 'create.py')
-    upload_script = os.path.join(common.FILE_DIR, 'upload.py')
-
-    try:
-      cwd = os.path.join(cwd, 'sdk')
-      cmd = ['python', create_script,
-             '-t', cwd,
-             '--sdk_path', args.sdk_path]
-      subprocess.check_call(cmd)
-      cmd = ['python', upload_script, '-t', cwd]
-      if args.gsutil:
-        cmd.extend(['--gsutil', args.gsutil])
-      subprocess.check_call(cmd)
-    except subprocess.CalledProcessError:
-      # Trap exceptions to avoid printing two stacktraces.
-      sys.exit(1)
-
-
-if __name__ == '__main__':
-  main()
diff --git a/infra/bots/assets/win_vulkan_sdk/download.py b/infra/bots/assets/win_vulkan_sdk/download.py
deleted file mode 100755
index 96cc87d..0000000
--- a/infra/bots/assets/win_vulkan_sdk/download.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2016 Google Inc.
-#
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-"""Download the current version of the asset."""
-
-
-import common
-
-
-if __name__ == '__main__':
-  common.run('download')
diff --git a/infra/bots/assets/win_vulkan_sdk/upload.py b/infra/bots/assets/win_vulkan_sdk/upload.py
deleted file mode 100755
index ba7fc8b..0000000
--- a/infra/bots/assets/win_vulkan_sdk/upload.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2016 Google Inc.
-#
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-"""Upload a new version of the asset."""
-
-
-import common
-
-
-if __name__ == '__main__':
-  common.run('upload')
diff --git a/infra/bots/bootstrap_win_toolchain_json.py b/infra/bots/bootstrap_win_toolchain_json.py
deleted file mode 100644
index 199abe0..0000000
--- a/infra/bots/bootstrap_win_toolchain_json.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2016 Google Inc.
-#
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-"""Resolve the path placeholders in the win_toolchain.json file."""
-
-
-import argparse
-import win_toolchain_utils
-
-
-def main():
-  parser = argparse.ArgumentParser()
-  parser.add_argument('--win_toolchain_json', required=True)
-  parser.add_argument('--depot_tools_parent_dir', required=True)
-  args = parser.parse_args()
-  win_toolchain_utils.resolve(args.win_toolchain_json,
-                              args.depot_tools_parent_dir)
-
-
-if __name__ == '__main__':
-  main()
diff --git a/infra/bots/gen_tasks.go b/infra/bots/gen_tasks.go
index 86a4575..98deffc 100644
--- a/infra/bots/gen_tasks.go
+++ b/infra/bots/gen_tasks.go
@@ -30,17 +30,15 @@
 )
 
 const (
-	BUNDLE_RECIPES_NAME         = "Housekeeper-PerCommit-BundleRecipes"
-	ISOLATE_GCLOUD_LINUX_NAME   = "Housekeeper-PerCommit-IsolateGCloudLinux"
-	ISOLATE_GO_DEPS_NAME        = "Housekeeper-PerCommit-IsolateGoDeps"
-	ISOLATE_GO_LINUX_NAME       = "Housekeeper-PerCommit-IsolateGoLinux"
-	ISOLATE_SKIMAGE_NAME        = "Housekeeper-PerCommit-IsolateSkImage"
-	ISOLATE_SKP_NAME            = "Housekeeper-PerCommit-IsolateSKP"
-	ISOLATE_SVG_NAME            = "Housekeeper-PerCommit-IsolateSVG"
-	ISOLATE_NDK_LINUX_NAME      = "Housekeeper-PerCommit-IsolateAndroidNDKLinux"
-	ISOLATE_SDK_LINUX_NAME      = "Housekeeper-PerCommit-IsolateAndroidSDKLinux"
-	ISOLATE_WIN_TOOLCHAIN_NAME  = "Housekeeper-PerCommit-IsolateWinToolchain"
-	ISOLATE_WIN_VULKAN_SDK_NAME = "Housekeeper-PerCommit-IsolateWinVulkanSDK"
+	BUNDLE_RECIPES_NAME        = "Housekeeper-PerCommit-BundleRecipes"
+	ISOLATE_GCLOUD_LINUX_NAME  = "Housekeeper-PerCommit-IsolateGCloudLinux"
+	ISOLATE_GO_DEPS_NAME       = "Housekeeper-PerCommit-IsolateGoDeps"
+	ISOLATE_SKIMAGE_NAME       = "Housekeeper-PerCommit-IsolateSkImage"
+	ISOLATE_SKP_NAME           = "Housekeeper-PerCommit-IsolateSKP"
+	ISOLATE_SVG_NAME           = "Housekeeper-PerCommit-IsolateSVG"
+	ISOLATE_NDK_LINUX_NAME     = "Housekeeper-PerCommit-IsolateAndroidNDKLinux"
+	ISOLATE_SDK_LINUX_NAME     = "Housekeeper-PerCommit-IsolateAndroidSDKLinux"
+	ISOLATE_WIN_TOOLCHAIN_NAME = "Housekeeper-PerCommit-IsolateWinToolchain"
 
 	DEFAULT_OS_DEBIAN    = "Debian-9.4"
 	DEFAULT_OS_LINUX_GCE = DEFAULT_OS_DEBIAN
@@ -127,6 +125,12 @@
 			Path: "cache/git_cache",
 		},
 	}
+	CACHES_GO = []*specs.Cache{
+		&specs.Cache{
+			Name: "go_cache",
+			Path: "cache/go_cache",
+		},
+	}
 	CACHES_WORKDIR = []*specs.Cache{
 		&specs.Cache{
 			Name: "work",
@@ -318,6 +322,7 @@
 			"log_location": logdogAnnotationUrl(),
 		},
 		Isolate:        relpath(isolate),
+		MaxAttempts:    attempts(name),
 		Outputs:        outputs,
 		ServiceAccount: serviceAccount,
 	}
@@ -448,7 +453,7 @@
 			"Ubuntu17":   "Ubuntu-17.04",
 			"Ubuntu18":   "Ubuntu-18.04",
 			"Win":        DEFAULT_OS_WIN,
-			"Win10":      "Windows-10-17134.407",
+			"Win10":      "Windows-10-17763.195",
 			"Win2k8":     "Windows-2008ServerR2-SP1",
 			"Win2016":    DEFAULT_OS_WIN,
 			"Win7":       "Windows-7-SP1",
@@ -564,6 +569,7 @@
 					"IntelHD4400":   "8086:0a16-20.19.15.4963",
 					"IntelIris540":  "8086:1926-25.20.100.6444",
 					"IntelIris6100": "8086:162b-20.19.15.4963",
+					"IntelIris655":  "8086:3ea5-25.20.100.6444",
 					"RadeonHD7770":  "1002:683d-24.20.13001.1010",
 					"RadeonR9M470X": "1002:6646-24.20.13001.1010",
 					"QuadroP400":    "10de:1cb3-25.21.14.1678",
@@ -701,10 +707,6 @@
 		cipdPkg: "go_deps",
 		path:    "go_deps",
 	},
-	ISOLATE_GO_LINUX_NAME: {
-		cipdPkg: "go",
-		path:    "go",
-	},
 	ISOLATE_SKIMAGE_NAME: {
 		cipdPkg: "skimage",
 		path:    "skimage",
@@ -727,11 +729,7 @@
 	},
 	ISOLATE_WIN_TOOLCHAIN_NAME: {
 		cipdPkg: "win_toolchain",
-		path:    "t",
-	},
-	ISOLATE_WIN_VULKAN_SDK_NAME: {
-		cipdPkg: "win_vulkan_sdk",
-		path:    "win_vulkan_sdk",
+		path:    "win_toolchain",
 	},
 }
 
@@ -782,10 +780,18 @@
 	t.CipdPackages = append(t.CipdPackages, CIPD_PKGS_GIT...)
 }
 
+// usesGo adds attributes to tasks which use go. Recipes should use
+// "with api.context(env=api.infra.go_env)".
+// (Not needed for tasks that just want to run Go code from the infra repo -- instead use go_deps.)
+func usesGo(b *specs.TasksCfgBuilder, t *specs.TaskSpec) {
+	t.Caches = append(t.Caches, CACHES_GO...)
+	t.CipdPackages = append(t.CipdPackages, b.MustGetCipdPackageFromAsset("go"))
+	t.Dependencies = append(t.Dependencies, isolateCIPDAsset(b, ISOLATE_GO_DEPS_NAME))
+}
+
 // usesDocker adds attributes to tasks which use docker.
 func usesDocker(t *specs.TaskSpec, name string) {
-	// currently, just the WASM (using EMCC) builder uses Docker.
-	if strings.Contains(name, "EMCC") {
+	if strings.Contains(name, "EMCC") || strings.Contains(name, "SKQP") || strings.Contains(name, "LottieWeb") {
 		t.Caches = append(t.Caches, CACHES_DOCKER...)
 	}
 }
@@ -796,6 +802,24 @@
 	task.IoTimeout = timeout // With kitchen, step logs don't count toward IoTimeout.
 }
 
+// attempts returns the desired MaxAttempts for this task.
+func attempts(name string) int {
+	if strings.Contains(name, "Android_Framework") {
+		// The reason for this has been lost to time.
+		return 1
+	}
+	if !(strings.HasPrefix(name, "Build-") || strings.HasPrefix(name, "Upload-")) {
+		for _, extraConfig := range []string{"ASAN", "MSAN", "TSAN", "UBSAN", "Valgrind"} {
+			if strings.Contains(name, extraConfig) {
+				// Sanitizers often find non-deterministic issues that retries would hide.
+				return 1
+			}
+		}
+	}
+	// Retry by default to hide random bot/hardware failures.
+	return 2
+}
+
 // compile generates a compile task. Returns the name of the last task in the
 // generated chain of tasks, which the Job should add as a dependency.
 func compile(b *specs.TasksCfgBuilder, name string, parts map[string]string) string {
@@ -855,9 +879,6 @@
 		if strings.Contains(name, "Clang") {
 			task.CipdPackages = append(task.CipdPackages, b.MustGetCipdPackageFromAsset("clang_win"))
 		}
-		if strings.Contains(name, "Vulkan") {
-			task.Dependencies = append(task.Dependencies, isolateCIPDAsset(b, ISOLATE_WIN_VULKAN_SDK_NAME))
-		}
 		if strings.Contains(name, "OpenCL") {
 			task.CipdPackages = append(task.CipdPackages,
 				b.MustGetCipdPackageFromAsset("opencl_headers"),
@@ -877,8 +898,6 @@
 		}
 	}
 
-	task.MaxAttempts = 2
-
 	// Add the task.
 	b.MustAddTask(name, task)
 
@@ -914,8 +933,7 @@
 	}
 	task := kitchenTask(name, "recreate_skps", "swarm_recipe.isolate", SERVICE_ACCOUNT_RECREATE_SKPS, dims, nil, OUTPUT_NONE)
 	task.CipdPackages = append(task.CipdPackages, CIPD_PKGS_GIT...)
-	task.CipdPackages = append(task.CipdPackages, b.MustGetCipdPackageFromAsset("go"))
-	task.Dependencies = append(task.Dependencies, isolateCIPDAsset(b, ISOLATE_GO_DEPS_NAME))
+	usesGo(b, task)
 	timeout(task, 4*time.Hour)
 	b.MustAddTask(name, task)
 	return name
@@ -928,8 +946,7 @@
 	dims := linuxGceDimensions(MACHINE_TYPE_LARGE)
 	task := kitchenTask(name, "update_go_deps", "swarm_recipe.isolate", SERVICE_ACCOUNT_UPDATE_GO_DEPS, dims, nil, OUTPUT_NONE)
 	task.CipdPackages = append(task.CipdPackages, CIPD_PKGS_GIT...)
-	task.CipdPackages = append(task.CipdPackages, b.MustGetCipdPackageFromAsset("go"))
-	task.Dependencies = append(task.Dependencies, isolateCIPDAsset(b, ISOLATE_GO_DEPS_NAME))
+	usesGo(b, task)
 	b.MustAddTask(name, task)
 	return name
 }
@@ -948,7 +965,6 @@
 func housekeeper(b *specs.TasksCfgBuilder, name string) string {
 	task := kitchenTask(name, "housekeeper", "swarm_recipe.isolate", SERVICE_ACCOUNT_HOUSEKEEPER, linuxGceDimensions(MACHINE_TYPE_SMALL), nil, OUTPUT_NONE)
 	usesGit(task, name)
-	task.CipdPackages = append(task.CipdPackages, b.MustGetCipdPackageFromAsset("go"))
 	b.MustAddTask(name, task)
 	return name
 }
@@ -959,8 +975,7 @@
 	task := kitchenTask(name, "bookmaker", "swarm_recipe.isolate", SERVICE_ACCOUNT_BOOKMAKER, linuxGceDimensions(MACHINE_TYPE_SMALL), nil, OUTPUT_NONE)
 	task.Caches = append(task.Caches, CACHES_WORKDIR...)
 	task.CipdPackages = append(task.CipdPackages, CIPD_PKGS_GIT...)
-	task.CipdPackages = append(task.CipdPackages, b.MustGetCipdPackageFromAsset("go"))
-	task.Dependencies = append(task.Dependencies, compileTaskName)
+	task.Dependencies = append(task.Dependencies, compileTaskName, isolateCIPDAsset(b, ISOLATE_GO_DEPS_NAME))
 	timeout(task, 2*time.Hour)
 	b.MustAddTask(name, task)
 	return name
@@ -971,7 +986,6 @@
 // should add as a dependency.
 func androidFrameworkCompile(b *specs.TasksCfgBuilder, name string) string {
 	task := kitchenTask(name, "android_compile", "swarm_recipe.isolate", SERVICE_ACCOUNT_COMPILE, linuxGceDimensions(MACHINE_TYPE_SMALL), nil, OUTPUT_NONE)
-	task.MaxAttempts = 1
 	timeout(task, time.Hour)
 	b.MustAddTask(name, task)
 	return name
@@ -982,8 +996,7 @@
 func infra(b *specs.TasksCfgBuilder, name string) string {
 	task := kitchenTask(name, "infra", "swarm_recipe.isolate", SERVICE_ACCOUNT_COMPILE, linuxGceDimensions(MACHINE_TYPE_SMALL), nil, OUTPUT_NONE)
 	usesGit(task, name)
-	task.CipdPackages = append(task.CipdPackages, b.MustGetCipdPackageFromAsset("go"))
-	task.Dependencies = append(task.Dependencies, isolateCIPDAsset(b, ISOLATE_GO_DEPS_NAME))
+	usesGo(b, task)
 	b.MustAddTask(name, task)
 	return name
 }
@@ -1024,9 +1037,7 @@
 func calmbench(b *specs.TasksCfgBuilder, name string, parts map[string]string, compileTaskName, compileParentName string) string {
 	task := kitchenTask(name, "calmbench", "calmbench.isolate", "", swarmDimensions(parts), nil, OUTPUT_PERF)
 	usesGit(task, name)
-	task.CipdPackages = append(task.CipdPackages, b.MustGetCipdPackageFromAsset("go"))
 	task.Dependencies = append(task.Dependencies, compileTaskName, compileParentName, ISOLATE_SKP_NAME, ISOLATE_SVG_NAME)
-	task.MaxAttempts = 2
 	if parts["cpu_or_gpu_value"] == "QuadroP400" {
 		// Specify "rack" dimension for consistent test results.
 		// See https://bugs.chromium.org/p/chromium/issues/detail?id=784662&desc=2#c34
@@ -1122,7 +1133,7 @@
 		task.Dependencies = append(task.Dependencies, deps...)
 	}
 	task.Expiration = 20 * time.Hour
-	task.MaxAttempts = 2
+
 	timeout(task, 4*time.Hour)
 	if strings.Contains(parts["extra_config"], "Valgrind") {
 		timeout(task, 9*time.Hour)
@@ -1171,7 +1182,6 @@
 	task.CipdPackages = append(task.CipdPackages, pkgs...)
 	task.Dependencies = append(task.Dependencies, compileTaskName)
 	task.Expiration = 20 * time.Hour
-	task.MaxAttempts = 2
 	timeout(task, 4*time.Hour)
 	if deps := getIsolatedCIPDDeps(parts); len(deps) > 0 {
 		task.Dependencies = append(task.Dependencies, deps...)
diff --git a/infra/bots/jobs.json b/infra/bots/jobs.json
index c50369c..dbdbc7f 100644
--- a/infra/bots/jobs.json
+++ b/infra/bots/jobs.json
@@ -37,6 +37,7 @@
   "Build-Debian9-Clang-x86_64-Debug-SafeStack",
   "Build-Debian9-Clang-x86_64-Debug-Static",
   "Build-Debian9-Clang-x86_64-Debug-SwiftShader",
+  "Build-Debian9-Clang-x86_64-Debug-Tidy",
   "Build-Debian9-Clang-x86_64-Debug-Vulkan",
   "Build-Debian9-Clang-x86_64-Release",
   "Build-Debian9-Clang-x86_64-Release-ANGLE",
@@ -105,6 +106,8 @@
   "Build-Win-Clang-x86_64-Release-ANGLE",
   "Build-Win-Clang-x86_64-Release-UBSAN",
   "Build-Win-Clang-x86_64-Release-Vulkan",
+  "Build-Win-MSVC-arm64-Debug",
+  "Build-Win-MSVC-arm64-Release",
   "Build-Win-MSVC-x86-Debug",
   "Build-Win-MSVC-x86-Release",
   "Build-Win-MSVC-x86_64-Debug",
@@ -260,6 +263,9 @@
   "Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan",
   "Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41",
   "Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41",
+  "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All",
+  "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN",
+  "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN",
   "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan",
@@ -292,6 +298,12 @@
   "Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All",
   "Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE",
   "Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan",
+  "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All",
+  "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE",
+  "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan",
+  "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All",
+  "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE",
+  "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan",
   "Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All",
   "Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All-ANGLE",
   "Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All",
@@ -508,6 +520,15 @@
   "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41",
   "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41",
   "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41",
+  "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All",
+  "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN",
+  "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1",
+  "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan",
+  "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3",
+  "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN",
+  "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan",
+  "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan",
+  "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ASAN",
   "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-DDL3_ASAN",
@@ -550,6 +571,13 @@
   "Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE",
   "Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-NativeFonts",
   "Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan",
+  "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All",
+  "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE",
+  "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-OpenCL",
+  "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan",
+  "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All",
+  "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE",
+  "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan",
   "Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All",
   "Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All-ANGLE",
   "Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All",
diff --git a/infra/bots/recipe_modules/build/default.py b/infra/bots/recipe_modules/build/default.py
index 74acc32..8d0244c 100644
--- a/infra/bots/recipe_modules/build/default.py
+++ b/infra/bots/recipe_modules/build/default.py
@@ -56,13 +56,10 @@
   os            = api.vars.builder_cfg.get('os',            '')
   target_arch   = api.vars.builder_cfg.get('target_arch',   '')
 
-  clang_linux        = str(api.vars.slave_dir.join('clang_linux'))
-  linux_vulkan_sdk   = str(api.vars.slave_dir.join('linux_vulkan_sdk'))
-  win_toolchain = str(api.vars.slave_dir.join(
-    't', 'depot_tools', 'win_toolchain', 'vs_files',
-    '5454e45bf3764c03d3fc1024b3bf5bc41e3ab62c'))
-  win_vulkan_sdk = str(api.vars.slave_dir.join('win_vulkan_sdk'))
-  moltenvk = str(api.vars.slave_dir.join('moltenvk'))
+  clang_linux      = str(api.vars.slave_dir.join('clang_linux'))
+  linux_vulkan_sdk = str(api.vars.slave_dir.join('linux_vulkan_sdk'))
+  win_toolchain    = str(api.vars.slave_dir.join('win_toolchain'))
+  moltenvk         = str(api.vars.slave_dir.join('moltenvk'))
 
   cc, cxx = None, None
   extra_cflags = []
@@ -134,6 +131,11 @@
     else:
       cc, cxx = 'gcc', 'g++'
 
+  if 'Tidy' in extra_tokens:
+    # Swap in clang-tidy.sh for clang++, but update PATH so it can find clang++.
+    cxx = skia_dir.join("tools/clang-tidy.sh")
+    env['PATH'] = '%s:%%(PATH)s' % (clang_linux + '/bin')
+
   if 'Coverage' in extra_tokens:
     # See https://clang.llvm.org/docs/SourceBasedCodeCoverage.html for
     # more info on using llvm to gather coverage information.
@@ -207,11 +209,10 @@
   if 'Shared' in extra_tokens:
     args['is_component_build'] = 'true'
   if 'Vulkan' in extra_tokens and not 'Android' in extra_tokens:
+    args['skia_use_vulkan'] = 'true'
     args['skia_enable_vulkan_debug_layers'] = 'false'
     if api.vars.is_linux:
       args['skia_vulkan_sdk'] = '"%s"' % linux_vulkan_sdk
-    if 'Win' in os:
-      args['skia_vulkan_sdk'] = '"%s"' % win_vulkan_sdk
     if 'MoltenVK' in extra_tokens:
       args['skia_moltenvk_path'] = '"%s"' % moltenvk
   if 'Metal' in extra_tokens:
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Tidy.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Tidy.json
new file mode 100644
index 0000000..e42e703
--- /dev/null
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Tidy.json
@@ -0,0 +1,99 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/cache/work/skia/infra/bots/assets/clang_linux/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get clang_linux VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "[START_DIR]/cache/work/skia/bin/fetch-gn"
+    ],
+    "cwd": "[START_DIR]/cache/work/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "fetch-gn"
+  },
+  {
+    "cmd": [
+      "[START_DIR]/cache/work/skia/bin/gn",
+      "gen",
+      "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-Tidy/Debug",
+      "--args=cc=\"[START_DIR]/clang_linux/bin/clang\" cxx=\"[START_DIR]/cache/work/skia/tools/clang-tidy.sh\" extra_cflags=[\"-B[START_DIR]/clang_linux/bin\", \"-DDUMMY_clang_linux_version=42\", \"-O1\"] extra_ldflags=[\"-B[START_DIR]/clang_linux/bin\", \"-fuse-ld=lld\"] target_cpu=\"x86_64\""
+    ],
+    "cwd": "[START_DIR]/cache/work/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "[START_DIR]/clang_linux/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "name": "gn gen"
+  },
+  {
+    "cmd": [
+      "ninja",
+      "-C",
+      "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-Tidy/Debug"
+    ],
+    "cwd": "[START_DIR]/cache/work/skia",
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "[START_DIR]/clang_linux/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "name": "ninja"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import errno\nimport glob\nimport os\nimport shutil\nimport sys\n\nsrc = sys.argv[1]\ndst = sys.argv[2]\nbuild_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']\n\ntry:\n  os.makedirs(dst)\nexcept OSError as e:\n  if e.errno != errno.EEXIST:\n    raise\n\nfor pattern in build_products_whitelist:\n  path = os.path.join(src, pattern)\n  for f in glob.glob(path):\n    dst_path = os.path.join(dst, os.path.relpath(f, src))\n    if not os.path.isdir(os.path.dirname(dst_path)):\n      os.makedirs(os.path.dirname(dst_path))\n    print 'Copying build product %s to %s' % (f, dst_path)\n    shutil.move(f, dst_path)\n",
+      "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Debug-Tidy/Debug",
+      "[START_DIR]/[SWARM_OUT_DIR]/out/Debug"
+    ],
+    "infra_step": true,
+    "name": "copy build products",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import errno@@@",
+      "@@@STEP_LOG_LINE@python.inline@import glob@@@",
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@import shutil@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@src = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@dst = sys.argv[2]@@@",
+      "@@@STEP_LOG_LINE@python.inline@build_products_whitelist = ['bookmaker', 'dm', 'dm.exe', 'dm.app', 'nanobench.app', 'get_images_from_skps', 'get_images_from_skps.exe', 'hello-opencl', 'hello-opencl.exe', 'nanobench', 'nanobench.exe', 'skpbench', 'skpbench.exe', '*.so', '*.dll', '*.dylib', 'skia_launcher', 'skiaserve', 'lib/*.so', 'run_testlab', 'skqp-universal-debug.apk', 'whitelist_devices.json']@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  os.makedirs(dst)@@@",
+      "@@@STEP_LOG_LINE@python.inline@except OSError as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  if e.errno != errno.EEXIST:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@for pattern in build_products_whitelist:@@@",
+      "@@@STEP_LOG_LINE@python.inline@  path = os.path.join(src, pattern)@@@",
+      "@@@STEP_LOG_LINE@python.inline@  for f in glob.glob(path):@@@",
+      "@@@STEP_LOG_LINE@python.inline@    dst_path = os.path.join(dst, os.path.relpath(f, src))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if not os.path.isdir(os.path.dirname(dst_path)):@@@",
+      "@@@STEP_LOG_LINE@python.inline@      os.makedirs(os.path.dirname(dst_path))@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Copying build product %s to %s' % (f, dst_path)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    shutil.move(f, dst_path)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Vulkan.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Vulkan.json
index 321e0c2f..c9c7d61 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Vulkan.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Vulkan.json
@@ -32,7 +32,7 @@
       "[START_DIR]/cache/work/skia/bin/gn",
       "gen",
       "[START_DIR]/cache/work/skia/out/Build-Debian9-Clang-x86_64-Release-Vulkan/Release",
-      "--args=cc=\"[START_DIR]/clang_linux/bin/clang\" cxx=\"[START_DIR]/clang_linux/bin/clang++\" extra_cflags=[\"-B[START_DIR]/clang_linux/bin\", \"-DDUMMY_clang_linux_version=42\"] extra_ldflags=[\"-B[START_DIR]/clang_linux/bin\", \"-fuse-ld=lld\"] is_debug=false skia_enable_vulkan_debug_layers=false skia_vulkan_sdk=\"[START_DIR]/linux_vulkan_sdk\" target_cpu=\"x86_64\""
+      "--args=cc=\"[START_DIR]/clang_linux/bin/clang\" cxx=\"[START_DIR]/clang_linux/bin/clang++\" extra_cflags=[\"-B[START_DIR]/clang_linux/bin\", \"-DDUMMY_clang_linux_version=42\"] extra_ldflags=[\"-B[START_DIR]/clang_linux/bin\", \"-fuse-ld=lld\"] is_debug=false skia_enable_vulkan_debug_layers=false skia_use_vulkan=true skia_vulkan_sdk=\"[START_DIR]/linux_vulkan_sdk\" target_cpu=\"x86_64\""
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan.json
index 5c851ec..2f3af16 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan.json
@@ -53,7 +53,7 @@
       "[START_DIR]/cache/work/skia/bin/gn",
       "gen",
       "[START_DIR]/cache/work/skia/out/Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan/Release",
-      "--args=cc=\"clang\" cxx=\"clang++\" extra_cflags=[\"-DDUMMY_xcode_build_version=9c40b\"] is_debug=false skia_enable_vulkan_debug_layers=false skia_moltenvk_path=\"[START_DIR]/moltenvk\" target_cpu=\"x86_64\""
+      "--args=cc=\"clang\" cxx=\"clang++\" extra_cflags=[\"-DDUMMY_xcode_build_version=9c40b\"] is_debug=false skia_enable_vulkan_debug_layers=false skia_moltenvk_path=\"[START_DIR]/moltenvk\" skia_use_vulkan=true target_cpu=\"x86_64\""
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86-Debug-Exceptions.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86-Debug-Exceptions.json
index b8fff7e..d2508e0 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86-Debug-Exceptions.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86-Debug-Exceptions.json
@@ -32,7 +32,7 @@
       "[START_DIR]/cache/work/skia/bin/gn",
       "gen",
       "[START_DIR]/cache/work/skia/out/Build-Win-Clang-x86-Debug-Exceptions/Debug",
-      "--args=cc=\"clang\" clang_win=\"[START_DIR]/clang_win\" cxx=\"clang++\" extra_cflags=[\"-O1\", \"/EHsc\", \"-DDUMMY_clang_win_version=42\"] target_cpu=\"x86\" win_sdk=\"[START_DIR]/t/depot_tools/win_toolchain/vs_files/5454e45bf3764c03d3fc1024b3bf5bc41e3ab62c/win_sdk\" win_vc=\"[START_DIR]/t/depot_tools/win_toolchain/vs_files/5454e45bf3764c03d3fc1024b3bf5bc41e3ab62c/VC\""
+      "--args=cc=\"clang\" clang_win=\"[START_DIR]/clang_win\" cxx=\"clang++\" extra_cflags=[\"-O1\", \"/EHsc\", \"-DDUMMY_clang_win_version=42\"] target_cpu=\"x86\" win_sdk=\"[START_DIR]/win_toolchain/win_sdk\" win_vc=\"[START_DIR]/win_toolchain/VC\""
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Debug-OpenCL.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Debug-OpenCL.json
index 058f010..465f3b8 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Debug-OpenCL.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Debug-OpenCL.json
@@ -32,7 +32,7 @@
       "[START_DIR]/cache/work/skia/bin/gn",
       "gen",
       "[START_DIR]/cache/work/skia/out/Build-Win-Clang-x86_64-Debug-OpenCL/Debug_x64",
-      "--args=cc=\"clang\" clang_win=\"[START_DIR]/clang_win\" cxx=\"clang++\" extra_cflags=[\"-O1\", \"-imsvc[START_DIR]/opencl_headers\", \"-DDUMMY_clang_win_version=42\"] extra_ldflags=[\"/LIBPATH:[START_DIR]/cache/work/skia/third_party/externals/opencl-lib/3-0/lib/x86_64\"] skia_use_opencl=true target_cpu=\"x86_64\" win_sdk=\"[START_DIR]/t/depot_tools/win_toolchain/vs_files/5454e45bf3764c03d3fc1024b3bf5bc41e3ab62c/win_sdk\" win_vc=\"[START_DIR]/t/depot_tools/win_toolchain/vs_files/5454e45bf3764c03d3fc1024b3bf5bc41e3ab62c/VC\""
+      "--args=cc=\"clang\" clang_win=\"[START_DIR]/clang_win\" cxx=\"clang++\" extra_cflags=[\"-O1\", \"-imsvc[START_DIR]/opencl_headers\", \"-DDUMMY_clang_win_version=42\"] extra_ldflags=[\"/LIBPATH:[START_DIR]/cache/work/skia/third_party/externals/opencl-lib/3-0/lib/x86_64\"] skia_use_opencl=true target_cpu=\"x86_64\" win_sdk=\"[START_DIR]/win_toolchain/win_sdk\" win_vc=\"[START_DIR]/win_toolchain/VC\""
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Release-Vulkan.json b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Release-Vulkan.json
index 04a86b9..af18c44 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Release-Vulkan.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Release-Vulkan.json
@@ -32,7 +32,7 @@
       "[START_DIR]/cache/work/skia/bin/gn",
       "gen",
       "[START_DIR]/cache/work/skia/out/Build-Win-Clang-x86_64-Release-Vulkan/Release_x64",
-      "--args=cc=\"clang\" clang_win=\"[START_DIR]/clang_win\" cxx=\"clang++\" extra_cflags=[\"-DDUMMY_clang_win_version=42\"] is_debug=false skia_enable_vulkan_debug_layers=false skia_vulkan_sdk=\"[START_DIR]/win_vulkan_sdk\" target_cpu=\"x86_64\" win_sdk=\"[START_DIR]/t/depot_tools/win_toolchain/vs_files/5454e45bf3764c03d3fc1024b3bf5bc41e3ab62c/win_sdk\" win_vc=\"[START_DIR]/t/depot_tools/win_toolchain/vs_files/5454e45bf3764c03d3fc1024b3bf5bc41e3ab62c/VC\""
+      "--args=cc=\"clang\" clang_win=\"[START_DIR]/clang_win\" cxx=\"clang++\" extra_cflags=[\"-DDUMMY_clang_win_version=42\"] is_debug=false skia_enable_vulkan_debug_layers=false skia_use_vulkan=true target_cpu=\"x86_64\" win_sdk=\"[START_DIR]/win_toolchain/win_sdk\" win_vc=\"[START_DIR]/win_toolchain/VC\""
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
diff --git a/infra/bots/recipe_modules/build/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json b/infra/bots/recipe_modules/build/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json
index 33680b7..af18cbd 100644
--- a/infra/bots/recipe_modules/build/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json
+++ b/infra/bots/recipe_modules/build/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json
@@ -38,9 +38,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "build firebase runner"
   },
diff --git a/infra/bots/recipe_modules/build/examples/full.py b/infra/bots/recipe_modules/build/examples/full.py
index 1a6aff4..1bf15b9 100644
--- a/infra/bots/recipe_modules/build/examples/full.py
+++ b/infra/bots/recipe_modules/build/examples/full.py
@@ -36,6 +36,7 @@
   'Build-Debian9-Clang-x86_64-Debug-OpenCL',
   'Build-Debian9-Clang-x86_64-Debug-SK_CPU_LIMIT_SSE41',
   'Build-Debian9-Clang-x86_64-Debug-SafeStack',
+  'Build-Debian9-Clang-x86_64-Debug-Tidy',
   'Build-Debian9-Clang-x86_64-Release-ASAN',
   'Build-Debian9-Clang-x86_64-Release-Fast',
   'Build-Debian9-Clang-x86_64-Release-NoDEPS',
diff --git a/infra/bots/recipe_modules/checkout/api.py b/infra/bots/recipe_modules/checkout/api.py
index 3c4ed76..cc91492 100644
--- a/infra/bots/recipe_modules/checkout/api.py
+++ b/infra/bots/recipe_modules/checkout/api.py
@@ -94,7 +94,7 @@
     if checkout_flutter:
       # Skia is a DEP of Flutter; the 'revision' property is a Skia revision,
       # and any patch should be applied to Skia, not Flutter.
-      main.revision = 'origin/skia-master'
+      main.revision = 'origin/master'
       main.managed = True
       m[main_name] = 'got_flutter_revision'
       if flutter_android:
diff --git a/infra/bots/recipe_modules/checkout/examples/full.expected/flutter_trybot.json b/infra/bots/recipe_modules/checkout/examples/full.expected/flutter_trybot.json
index 1263543..da67b7a 100644
--- a/infra/bots/recipe_modules/checkout/examples/full.expected/flutter_trybot.json
+++ b/infra/bots/recipe_modules/checkout/examples/full.expected/flutter_trybot.json
@@ -47,7 +47,7 @@
       "--patch_ref",
       "https://skia.googlesource.com/skia.git@refs/changes/89/456789/12",
       "--revision",
-      "src/flutter@origin/skia-master",
+      "src/flutter@origin/master",
       "--revision",
       "src/third_party/skia@abc123"
     ],
@@ -64,7 +64,7 @@
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
       "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"src/flutter\": \"origin/skia-master\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"src/flutter\": \"origin/master\", @@@",
       "@@@STEP_LOG_LINE@json.output@    \"src/third_party/skia\": \"abc123\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }, @@@",
       "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
diff --git a/infra/bots/recipe_modules/flavor/android.py b/infra/bots/recipe_modules/flavor/android.py
index 1fd07e1..8a4745e 100644
--- a/infra/bots/recipe_modules/flavor/android.py
+++ b/infra/bots/recipe_modules/flavor/android.py
@@ -344,13 +344,15 @@
         infra_step=True,
         timeout=30)
 
+
   def install(self):
     self._adb('mkdir ' + self.device_dirs.resource_dir,
               'shell', 'mkdir', '-p', self.device_dirs.resource_dir)
     if 'ASAN' in self.m.vars.extra_tokens:
+      self._ever_ran_adb = True
       asan_setup = self.m.vars.slave_dir.join(
             'android_ndk_linux', 'toolchains', 'llvm', 'prebuilt',
-            'linux-x86_64', 'lib64', 'clang', '6.0.2', 'bin',
+            'linux-x86_64', 'lib64', 'clang', '8.0.2', 'bin',
             'asan_device_setup')
       self.m.run(self.m.python.inline, 'Setting up device to run ASAN',
         program="""
@@ -428,6 +430,22 @@
           abort_on_failure=True)
 
   def cleanup_steps(self):
+    if 'ASAN' in self.m.vars.extra_tokens:
+      self._ever_ran_adb = True
+      # Remove ASAN.
+      asan_setup = self.m.vars.slave_dir.join(
+            'android_ndk_linux', 'toolchains', 'llvm', 'prebuilt',
+            'linux-x86_64', 'lib64', 'clang', '8.0.2', 'bin',
+            'asan_device_setup')
+      self.m.run(self.m.step,
+                 'wait for device before uninstalling ASAN',
+                 cmd=[self.ADB_BINARY, 'wait-for-device'], infra_step=True,
+                 timeout=180, abort_on_failure=False,
+                 fail_build_on_failure=False)
+      self.m.run(self.m.step, 'uninstall ASAN',
+                 cmd=[asan_setup, '--revert'], infra_step=True, timeout=300,
+                 abort_on_failure=False, fail_build_on_failure=False)
+
     if self._ever_ran_adb:
       self.m.run(self.m.python.inline, 'dump log', program="""
           import os
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
index 3ef73fc..763f06c 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
@@ -671,7 +671,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/perf",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -693,7 +693,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "perf/*"
     ],
     "infra_step": true,
@@ -701,8 +701,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -714,7 +714,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -731,7 +731,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -748,10 +748,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
index 75c5dde..d11b550 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
@@ -1049,7 +1049,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/perf",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -1071,7 +1071,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "perf/*"
     ],
     "infra_step": true,
@@ -1079,8 +1079,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -1092,7 +1092,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1109,7 +1109,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1126,10 +1126,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
index 4d4b721..02245f4 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
@@ -660,7 +660,7 @@
       "adb",
       "pull",
       "/cache/skia/perf",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -681,7 +681,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "perf/*"
     ],
     "infra_step": true,
@@ -689,8 +689,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -702,7 +702,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -719,7 +719,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -736,10 +736,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
index 4000647..6d6207e 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
@@ -713,7 +713,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -735,7 +735,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -743,8 +743,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -756,7 +756,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -773,7 +773,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -790,10 +790,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
index df3ef21..4c9b41f 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json
@@ -671,7 +671,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -693,7 +693,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -701,8 +701,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -714,7 +714,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -731,7 +731,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -748,10 +748,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
index 85fcd2e..1b40c0f 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json
@@ -955,7 +955,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -977,7 +977,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -985,8 +985,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -998,7 +998,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1015,7 +1015,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1032,10 +1032,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_ASAN.json b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_ASAN.json
index dc91992..0c4a134 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_ASAN.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_ASAN.json
@@ -114,7 +114,7 @@
       "-u",
       "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\nASAN_SETUP = sys.argv[2]\n\ndef wait_for_device():\n  while True:\n    time.sleep(5)\n    print 'Waiting for device'\n    subprocess.check_output([ADB, 'wait-for-device'])\n    bit1 = subprocess.check_output([ADB, 'shell', 'getprop',\n                                   'dev.bootcomplete'])\n    bit2 = subprocess.check_output([ADB, 'shell', 'getprop',\n                                   'sys.boot_completed'])\n    if '1' in bit1 and '1' in bit2:\n      print 'Device detected'\n      break\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\noutput = subprocess.check_output([ADB, 'disable-verity'])\nprint output\n\nif 'already disabled' not in output:\n  print 'Rebooting device'\n  subprocess.check_output([ADB, 'reboot'])\n  wait_for_device()\n\ndef installASAN(revert=False):\n  # ASAN setup script is idempotent, either it installs it or\n  # says it's installed.  Returns True on success, false otherwise.\n  out = subprocess.check_output([ADB, 'wait-for-device'])\n  print out\n  cmd = [ASAN_SETUP]\n  if revert:\n    cmd = [ASAN_SETUP, '--revert']\n  process = subprocess.Popen(cmd, env={'ADB': ADB},\n                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n\n  # this also blocks until command finishes\n  (stdout, stderr) = process.communicate()\n  print stdout\n  print 'Stderr: %s' % stderr\n  return process.returncode == 0\n\nif not installASAN():\n  print 'Trying to revert the ASAN install and then re-install'\n  # ASAN script sometimes has issues if it was interrupted or partially applied\n  # Try reverting it, then re-enabling it\n  if not installASAN(revert=True):\n    raise Exception('reverting ASAN install failed')\n\n  # Sleep because device does not reboot instantly\n  time.sleep(10)\n\n  if not installASAN():\n    raise Exception('Tried twice to setup ASAN and failed.')\n\n# Sleep because device does not reboot instantly\ntime.sleep(10)\nwait_for_device()\n",
       "/opt/infra-android/tools/adb",
-      "[START_DIR]/android_ndk_linux/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/6.0.2/bin/asan_device_setup"
+      "[START_DIR]/android_ndk_linux/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/8.0.2/bin/asan_device_setup"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
@@ -1042,7 +1042,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -1064,7 +1064,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -1072,8 +1072,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -1085,7 +1085,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1102,7 +1102,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1119,16 +1119,42 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
   },
   {
     "cmd": [
+      "/opt/infra-android/tools/adb",
+      "wait-for-device"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "wait for device before uninstalling ASAN",
+    "timeout": 180
+  },
+  {
+    "cmd": [
+      "[START_DIR]/android_ndk_linux/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/8.0.2/bin/asan_device_setup",
+      "--revert"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "uninstall ASAN",
+    "timeout": 300
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "\nimport os\nimport subprocess\nimport sys\nout = sys.argv[1]\nlog = subprocess.check_output(['/opt/infra-android/tools/adb', 'logcat', '-d'])\nfor line in log.split('\\n'):\n  tokens = line.split()\n  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':\n    addr, path = tokens[-2:]\n    local = os.path.join(out, os.path.basename(path))\n    if os.path.exists(local):\n      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])\n      line = line.replace(addr, addr + ' ' + sym.strip())\n  print line\n",
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed.json b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed.json
index 0d69fff..44680fd 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed.json
@@ -909,8 +909,10 @@
   },
   {
     "failure": {
-      "failure": {
-        "step": "Scale CPU 0 to 0.600000 (attempt 3)"
+      "exception": {
+        "traceback": [
+          "<omitted by recipe engine>"
+        ]
       },
       "humanReason": "Infra Failure: Step('Scale CPU 0 to 0.600000 (attempt 3)') returned 1"
     },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_golo.json b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_golo.json
index e2730d1..df7ff62 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_golo.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_golo.json
@@ -909,8 +909,10 @@
   },
   {
     "failure": {
-      "failure": {
-        "step": "Scale CPU 4 to 0.600000 (attempt 3)"
+      "exception": {
+        "traceback": [
+          "<omitted by recipe engine>"
+        ]
       },
       "humanReason": "Infra Failure: Step('Scale CPU 4 to 0.600000 (attempt 3)') returned 1"
     },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_once.json b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_once.json
index c91d67d..36c4c17 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_once.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed_once.json
@@ -979,7 +979,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/perf",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -1001,7 +1001,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "perf/*"
     ],
     "infra_step": true,
@@ -1009,8 +1009,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -1022,7 +1022,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1039,7 +1039,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1056,10 +1056,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/failed_infra_step.json b/infra/bots/recipe_modules/flavor/examples/full.expected/failed_infra_step.json
index efd0f24..b9fbf9c 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/failed_infra_step.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/failed_infra_step.json
@@ -903,7 +903,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/perf",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -925,7 +925,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "perf/*"
     ],
     "infra_step": true,
@@ -933,8 +933,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -946,7 +946,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -963,7 +963,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -980,10 +980,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/failed_read_version.json b/infra/bots/recipe_modules/flavor/examples/full.expected/failed_read_version.json
index 2c8fe78..d9da730 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/failed_read_version.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/failed_read_version.json
@@ -954,7 +954,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/perf",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -976,7 +976,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "perf/*"
     ],
     "infra_step": true,
@@ -984,8 +984,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -997,7 +997,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1014,7 +1014,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1031,10 +1031,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command.json b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command.json
index 496b621..52cd188 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command.json
@@ -954,7 +954,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/perf",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -976,7 +976,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "perf/*"
     ],
     "infra_step": true,
@@ -984,8 +984,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -997,7 +997,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1014,7 +1014,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1031,10 +1031,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command_retries_exhausted.json b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command_retries_exhausted.json
index f2f452a..11888e9 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command_retries_exhausted.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_adb_command_retries_exhausted.json
@@ -277,8 +277,10 @@
   },
   {
     "failure": {
-      "failure": {
-        "step": "mkdir /sdcard/revenge_of_the_skiabot/resources (attempt 3)"
+      "exception": {
+        "traceback": [
+          "<omitted by recipe engine>"
+        ]
       },
       "humanReason": "Infra Failure: Step('mkdir /sdcard/revenge_of_the_skiabot/resources (attempt 3)') returned 1"
     },
diff --git a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install_retries_exhausted.json b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install_retries_exhausted.json
index 5a8fec3..2a417b6 100644
--- a/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install_retries_exhausted.json
+++ b/infra/bots/recipe_modules/flavor/examples/full.expected/retry_ios_install_retries_exhausted.json
@@ -124,8 +124,10 @@
   },
   {
     "failure": {
-      "failure": {
-        "step": "install_dm (attempt 2)"
+      "exception": {
+        "traceback": [
+          "<omitted by recipe engine>"
+        ]
       },
       "humanReason": "Infra Failure: Step('install_dm (attempt 2)') returned 1"
     },
diff --git a/infra/bots/recipe_modules/infra/api.py b/infra/bots/recipe_modules/infra/api.py
index 98ca232..f886cdf 100644
--- a/infra/bots/recipe_modules/infra/api.py
+++ b/infra/bots/recipe_modules/infra/api.py
@@ -27,9 +27,10 @@
   @property
   def go_env(self):
     return {
+        'GOCACHE': self.m.vars.cache_dir.join('go_cache'),
         'GOPATH': self.gopath,
         'GOROOT': self.goroot,
-        'PATH': '%s:%s:%%(PATH)s' % (self.go_bin, self.gopath),
+        'PATH': '%s:%s:%%(PATH)s' % (self.go_bin, self.gopath.join('bin')),
     }
 
   @property
diff --git a/infra/bots/recipe_modules/infra/examples/full.expected/infra_tests.json b/infra/bots/recipe_modules/infra/examples/full.expected/infra_tests.json
index 29dff8a..9f5a978 100644
--- a/infra/bots/recipe_modules/infra/examples/full.expected/infra_tests.json
+++ b/infra/bots/recipe_modules/infra/examples/full.expected/infra_tests.json
@@ -6,9 +6,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "go version"
   },
@@ -19,9 +20,10 @@
     ],
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "env go version"
   },
diff --git a/infra/bots/recipes.py b/infra/bots/recipes.py
index d726c68..04fb9c9 100755
--- a/infra/bots/recipes.py
+++ b/infra/bots/recipes.py
@@ -10,7 +10,7 @@
 ** DO NOT MODIFY **
 *******************
 
-This is a copy of https://chromium.googlesource.com/infra/luci/recipes-py/+/master/doc/recipes.py.
+This is a copy of https://chromium.googlesource.com/infra/luci/recipes-py/+/master/recipes.py.
 To fix bugs, fix in the googlesource repo then run the autoroller.
 """
 
@@ -73,7 +73,7 @@
       raise MalformedRecipesCfg('unknown version %d' % pb['api_version'],
                                 recipes_cfg_path)
 
-    # If we're running ./doc/recipes.py from the recipe_engine repo itself, then
+    # If we're running ./recipes.py from the recipe_engine repo itself, then
     # return None to signal that there's no EngineDep.
     if pb['project_id'] == 'recipe_engine':
       return None, pb.get('recipes_path', '')
@@ -132,7 +132,7 @@
 def parse_args(argv):
   """This extracts a subset of the arguments that this bootstrap script cares
   about. Currently this consists of:
-    * an override for the recipe engine in the form of `-O recipe_engin=/path`
+    * an override for the recipe engine in the form of `-O recipe_engine=/path`
     * the --package option.
   """
   PREFIX = 'recipe_engine='
@@ -211,7 +211,7 @@
 
   return _subprocess_call([
       VPYTHON, '-u',
-      os.path.join(engine_path, 'recipes.py')] + args)
+      os.path.join(engine_path, 'recipe_engine', 'main.py')] + args)
 
 
 if __name__ == '__main__':
diff --git a/infra/bots/recipes/android_compile.expected/android_compile_trybot_failure.json b/infra/bots/recipes/android_compile.expected/android_compile_trybot_failure.json
index 66ebda6..af1f121 100644
--- a/infra/bots/recipes/android_compile.expected/android_compile_trybot_failure.json
+++ b/infra/bots/recipes/android_compile.expected/android_compile_trybot_failure.json
@@ -22,7 +22,7 @@
     "cmd": [
       "gsutil",
       "cat",
-      "gs://android-compile-tasks/1234-1.json"
+      "gs://android-compile-tasks/cf_x86_phone-eng-1234-1.json"
     ],
     "name": "Get task log links",
     "stdout": "/path/to/tmp/json",
diff --git a/infra/bots/recipes/android_compile.py b/infra/bots/recipes/android_compile.py
index 6939d6b..80dfff8 100644
--- a/infra/bots/recipes/android_compile.py
+++ b/infra/bots/recipes/android_compile.py
@@ -60,7 +60,8 @@
     api.step('Trigger and wait for task on android compile server', cmd=cmd)
   except api.step.StepFailure as e:
     # Add withpatch and nopatch logs as links (if they exist).
-    gs_file = 'gs://android-compile-tasks/%s-%s.json' % (issue, patchset)
+    gs_file = 'gs://android-compile-tasks/%s-%s-%s.json' % (
+        lunch_target, issue, patchset)
     step_result = api.step('Get task log links',
                            ['gsutil', 'cat', gs_file],
                            stdout=api.json.output())
diff --git a/infra/bots/recipes/bookmaker.expected/nightly_bookmaker.json b/infra/bots/recipes/bookmaker.expected/nightly_bookmaker.json
index 291a4dd..7295fac 100644
--- a/infra/bots/recipes/bookmaker.expected/nightly_bookmaker.json
+++ b/infra/bots/recipes/bookmaker.expected/nightly_bookmaker.json
@@ -95,46 +95,6 @@
   },
   {
     "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go version"
-  },
-  {
-    "cmd": [
-      "go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "env go version"
-  },
-  {
-    "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "get",
-      "go.skia.org/infra/fiddlek/go/fiddlecli"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go get fiddlecli"
-  },
-  {
-    "cmd": [
       "[START_DIR]/build/bookmaker",
       "-a",
       "docs/status.json",
@@ -144,9 +104,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Extract all fiddles out of md files"
   },
@@ -158,15 +119,16 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Output fiddle.json"
   },
   {
     "cmd": [
-      "[START_DIR]/go_deps/bin/fiddlecli",
+      "fiddlecli",
       "--input",
       "[START_DIR]/fiddle.json",
       "--output",
@@ -179,9 +141,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Force fiddle to compile all examples"
   },
@@ -198,9 +161,10 @@
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>"
     },
     "infra_step": true,
     "name": "Read fiddleout.json"
@@ -213,9 +177,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Output fiddleout.json"
   },
@@ -231,9 +196,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Generate and Upload Markdown files"
   },
diff --git a/infra/bots/recipes/bookmaker.expected/nightly_failed_extract_fiddles.json b/infra/bots/recipes/bookmaker.expected/nightly_failed_extract_fiddles.json
index 076e17e..e68670e 100644
--- a/infra/bots/recipes/bookmaker.expected/nightly_failed_extract_fiddles.json
+++ b/infra/bots/recipes/bookmaker.expected/nightly_failed_extract_fiddles.json
@@ -95,46 +95,6 @@
   },
   {
     "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go version"
-  },
-  {
-    "cmd": [
-      "go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "env go version"
-  },
-  {
-    "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "get",
-      "go.skia.org/infra/fiddlek/go/fiddlecli"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go get fiddlecli"
-  },
-  {
-    "cmd": [
       "[START_DIR]/build/bookmaker",
       "-a",
       "docs/status.json",
@@ -144,9 +104,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Extract all fiddles out of md files",
     "~followup_annotations": [
diff --git a/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddlecli.json b/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddlecli.json
index f7b1baa..607b793 100644
--- a/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddlecli.json
+++ b/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddlecli.json
@@ -95,46 +95,6 @@
   },
   {
     "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go version"
-  },
-  {
-    "cmd": [
-      "go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "env go version"
-  },
-  {
-    "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "get",
-      "go.skia.org/infra/fiddlek/go/fiddlecli"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go get fiddlecli"
-  },
-  {
-    "cmd": [
       "[START_DIR]/build/bookmaker",
       "-a",
       "docs/status.json",
@@ -144,9 +104,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Extract all fiddles out of md files"
   },
@@ -158,15 +119,16 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Output fiddle.json"
   },
   {
     "cmd": [
-      "[START_DIR]/go_deps/bin/fiddlecli",
+      "fiddlecli",
       "--input",
       "[START_DIR]/fiddle.json",
       "--output",
@@ -179,9 +141,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Force fiddle to compile all examples",
     "~followup_annotations": [
diff --git a/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddles.json b/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddles.json
index 436bada..8eb10c5 100644
--- a/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddles.json
+++ b/infra/bots/recipes/bookmaker.expected/nightly_failed_fiddles.json
@@ -95,46 +95,6 @@
   },
   {
     "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go version"
-  },
-  {
-    "cmd": [
-      "go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "env go version"
-  },
-  {
-    "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "get",
-      "go.skia.org/infra/fiddlek/go/fiddlecli"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go get fiddlecli"
-  },
-  {
-    "cmd": [
       "[START_DIR]/build/bookmaker",
       "-a",
       "docs/status.json",
@@ -144,9 +104,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Extract all fiddles out of md files"
   },
@@ -158,15 +119,16 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Output fiddle.json"
   },
   {
     "cmd": [
-      "[START_DIR]/go_deps/bin/fiddlecli",
+      "fiddlecli",
       "--input",
       "[START_DIR]/fiddle.json",
       "--output",
@@ -179,9 +141,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Force fiddle to compile all examples"
   },
@@ -198,9 +161,10 @@
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>"
     },
     "infra_step": true,
     "name": "Read fiddleout.json"
@@ -213,9 +177,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Output fiddleout.json"
   },
diff --git a/infra/bots/recipes/bookmaker.expected/nightly_failed_upload.json b/infra/bots/recipes/bookmaker.expected/nightly_failed_upload.json
index ed3b6ae..90a5ed4 100644
--- a/infra/bots/recipes/bookmaker.expected/nightly_failed_upload.json
+++ b/infra/bots/recipes/bookmaker.expected/nightly_failed_upload.json
@@ -95,46 +95,6 @@
   },
   {
     "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go version"
-  },
-  {
-    "cmd": [
-      "go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "env go version"
-  },
-  {
-    "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "get",
-      "go.skia.org/infra/fiddlek/go/fiddlecli"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go get fiddlecli"
-  },
-  {
-    "cmd": [
       "[START_DIR]/build/bookmaker",
       "-a",
       "docs/status.json",
@@ -144,9 +104,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Extract all fiddles out of md files"
   },
@@ -158,15 +119,16 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Output fiddle.json"
   },
   {
     "cmd": [
-      "[START_DIR]/go_deps/bin/fiddlecli",
+      "fiddlecli",
       "--input",
       "[START_DIR]/fiddle.json",
       "--output",
@@ -179,9 +141,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Force fiddle to compile all examples"
   },
@@ -197,9 +160,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Generate and Upload Markdown files",
     "~followup_annotations": [
diff --git a/infra/bots/recipes/bookmaker.expected/percommit_bookmaker.json b/infra/bots/recipes/bookmaker.expected/percommit_bookmaker.json
index 59eb232..0230b60 100644
--- a/infra/bots/recipes/bookmaker.expected/percommit_bookmaker.json
+++ b/infra/bots/recipes/bookmaker.expected/percommit_bookmaker.json
@@ -95,46 +95,6 @@
   },
   {
     "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go version"
-  },
-  {
-    "cmd": [
-      "go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "env go version"
-  },
-  {
-    "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "get",
-      "go.skia.org/infra/fiddlek/go/fiddlecli"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go get fiddlecli"
-  },
-  {
-    "cmd": [
       "[START_DIR]/build/bookmaker",
       "-a",
       "docs/status.json",
@@ -143,9 +103,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Validate docs match include/core/*.h"
   },
diff --git a/infra/bots/recipes/bookmaker.expected/percommit_failed_validation.json b/infra/bots/recipes/bookmaker.expected/percommit_failed_validation.json
index faf4089..a1f1aaf 100644
--- a/infra/bots/recipes/bookmaker.expected/percommit_failed_validation.json
+++ b/infra/bots/recipes/bookmaker.expected/percommit_failed_validation.json
@@ -95,46 +95,6 @@
   },
   {
     "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go version"
-  },
-  {
-    "cmd": [
-      "go",
-      "version"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "env go version"
-  },
-  {
-    "cmd": [
-      "[START_DIR]/go/go/bin/go",
-      "get",
-      "go.skia.org/infra/fiddlek/go/fiddlecli"
-    ],
-    "env": {
-      "CHROME_HEADLESS": "1",
-      "GOPATH": "[START_DIR]/go_deps",
-      "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
-    },
-    "name": "go get fiddlecli"
-  },
-  {
-    "cmd": [
       "[START_DIR]/build/bookmaker",
       "-a",
       "docs/status.json",
@@ -143,9 +103,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Validate docs match include/core/*.h",
     "~followup_annotations": [
diff --git a/infra/bots/recipes/bookmaker.py b/infra/bots/recipes/bookmaker.py
index 94d4503..eaa0a80 100644
--- a/infra/bots/recipes/bookmaker.py
+++ b/infra/bots/recipes/bookmaker.py
@@ -25,23 +25,10 @@
 ]
 
 
-def go_get_fiddlecli(api):
-  env = api.context.env
-  env.update(api.infra.go_env)
-  with api.context(env=env):
-    api.run.with_retry(
-        api.step,
-        'go get fiddlecli',
-        5,  # Update attempts.
-        cmd=[api.infra.go_exe, 'get', 'go.skia.org/infra/fiddlek/go/fiddlecli'])
-
-
 def RunSteps(api):
   api.vars.setup()
   checkout_root = api.checkout.default_checkout_root
   api.checkout.bot_update(checkout_root=checkout_root)
-  api.infra.go_version()
-  go_get_fiddlecli(api)
 
   skia_dir = checkout_root.join('skia')
   with api.context(cwd=skia_dir, env=api.infra.go_env):
@@ -66,7 +53,9 @@
         raise e
 
     elif 'Nightly' in buildername:
-      fiddlecli_binary = api.path.join(api.infra.gopath, 'bin', 'fiddlecli')
+      # fiddlecli is compiled and bundled into the go_deps asset. With
+      # api.infra.go_env, it is on PATH.
+      fiddlecli_binary = 'fiddlecli'
       fiddlecli_input = api.path.join(api.path['start_dir'], 'fiddle.json')
       fiddlecli_output = api.path.join(api.path['start_dir'], 'fiddleout.json')
 
@@ -133,7 +122,7 @@
              skia_dir.join('infra', 'bots', 'upload_md.py'),
             '--bookmaker_binary', bookmaker_binary,
              '--fiddlecli_output', fiddlecli_output]
-      with api.context(cwd=skia_dir, env=api.infra.go_env):
+      with api.context(cwd=skia_dir):
         api.run(api.step, 'Generate and Upload Markdown files', cmd=cmd)
 
 
diff --git a/infra/bots/recipes/compile.expected/Build-Debian9-GCC-x86_64-Release-Flutter_Android.json b/infra/bots/recipes/compile.expected/Build-Debian9-GCC-x86_64-Release-Flutter_Android.json
index befcc93..1046f27 100644
--- a/infra/bots/recipes/compile.expected/Build-Debian9-GCC-x86_64-Release-Flutter_Android.json
+++ b/infra/bots/recipes/compile.expected/Build-Debian9-GCC-x86_64-Release-Flutter_Android.json
@@ -45,7 +45,7 @@
       "--output_json",
       "/path/to/tmp/json",
       "--revision",
-      "src/flutter@origin/skia-master",
+      "src/flutter@origin/master",
       "--revision",
       "src/third_party/skia@abc123"
     ],
@@ -62,7 +62,7 @@
       "@@@STEP_LOG_LINE@json.output@{@@@",
       "@@@STEP_LOG_LINE@json.output@  \"did_run\": true, @@@",
       "@@@STEP_LOG_LINE@json.output@  \"fixed_revisions\": {@@@",
-      "@@@STEP_LOG_LINE@json.output@    \"src/flutter\": \"origin/skia-master\", @@@",
+      "@@@STEP_LOG_LINE@json.output@    \"src/flutter\": \"origin/master\", @@@",
       "@@@STEP_LOG_LINE@json.output@    \"src/third_party/skia\": \"abc123\"@@@",
       "@@@STEP_LOG_LINE@json.output@  }, @@@",
       "@@@STEP_LOG_LINE@json.output@  \"manifest\": {@@@",
diff --git a/infra/bots/recipes/compile.expected/Build-Win-Clang-x86-Debug.json b/infra/bots/recipes/compile.expected/Build-Win-Clang-x86-Debug.json
index 5695cb0..81b8cc4 100644
--- a/infra/bots/recipes/compile.expected/Build-Win-Clang-x86-Debug.json
+++ b/infra/bots/recipes/compile.expected/Build-Win-Clang-x86-Debug.json
@@ -141,7 +141,7 @@
       "[START_DIR]/cache/work/skia/bin/gn",
       "gen",
       "[START_DIR]/cache/work/skia/out/Build-Win-Clang-x86-Debug/Debug",
-      "--args=cc=\"clang\" clang_win=\"[START_DIR]/clang_win\" cxx=\"clang++\" extra_cflags=[\"-O1\", \"-DDUMMY_clang_win_version=42\"] target_cpu=\"x86\" win_sdk=\"[START_DIR]/t/depot_tools/win_toolchain/vs_files/5454e45bf3764c03d3fc1024b3bf5bc41e3ab62c/win_sdk\" win_vc=\"[START_DIR]/t/depot_tools/win_toolchain/vs_files/5454e45bf3764c03d3fc1024b3bf5bc41e3ab62c/VC\""
+      "--args=cc=\"clang\" clang_win=\"[START_DIR]/clang_win\" cxx=\"clang++\" extra_cflags=[\"-O1\", \"-DDUMMY_clang_win_version=42\"] target_cpu=\"x86\" win_sdk=\"[START_DIR]/win_toolchain/win_sdk\" win_vc=\"[START_DIR]/win_toolchain/VC\""
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
diff --git a/infra/bots/recipes/infra.expected/infra_tests.json b/infra/bots/recipes/infra.expected/infra_tests.json
index 3631e0f..8770e30 100644
--- a/infra/bots/recipes/infra.expected/infra_tests.json
+++ b/infra/bots/recipes/infra.expected/infra_tests.json
@@ -102,9 +102,10 @@
     ],
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>"
     },
     "name": "infra_tests"
   },
diff --git a/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_NoGPUThreads.json b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_NoGPUThreads.json
index d4fe082..907c57d 100644
--- a/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_NoGPUThreads.json
+++ b/infra/bots/recipes/perf.expected/Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_NoGPUThreads.json
@@ -1057,7 +1057,7 @@
       "/usr/bin/adb.1.0.35",
       "pull",
       "/sdcard/revenge_of_the_skiabot/perf",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -1079,7 +1079,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "perf/*"
     ],
     "infra_step": true,
@@ -1087,8 +1087,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -1100,7 +1100,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1117,7 +1117,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1134,10 +1134,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json b/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
index 0303e6a..9b8f44b 100644
--- a/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
+++ b/infra/bots/recipes/perf.expected/Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
@@ -416,7 +416,7 @@
       "adb",
       "pull",
       "/cache/skia/perf",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -437,7 +437,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "perf/*"
     ],
     "infra_step": true,
@@ -445,8 +445,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -458,7 +458,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -475,7 +475,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -492,10 +492,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan.json b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan.json
index 934c31a..8982756 100644
--- a/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan.json
+++ b/infra/bots/recipes/perf.expected/Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan.json
@@ -155,9 +155,11 @@
       "--reduceOpListSplitting",
       "--match",
       "~desk_carsvg.skp_1",
+      "~desk_googlehome.skp",
       "~desk_tiger8svg.skp_1",
       "~desk_wowwiki.skp",
       "~desk_ynevsvg.skp_1.1",
+      "~desk_nostroke_tiger8svg.skp",
       "~keymobi_booking_com.skp_1",
       "~keymobi_booking_com.skp_1_mpd",
       "~keymobi_cnn_article.skp_1",
@@ -168,6 +170,7 @@
       "~keymobi_techcrunch_com.skp_1.1",
       "~keymobi_techcrunch.skp_1.1",
       "~keymobi_techcrunch.skp_1.1_mpd",
+      "~svgparse_Seal_of_California.svg_1.1",
       "~svgparse_NewYork-StateSeal.svg_1.1",
       "~svgparse_Vermont_state_seal.svg_1",
       "~tabl_gamedeksiam.skp_1.1",
diff --git a/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer.json b/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer.json
new file mode 100644
index 0000000..3964129
--- /dev/null
+++ b/infra/bots/recipes/perf.expected/Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer.json
@@ -0,0 +1,249 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skp/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/skimage/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]/skia/infra/bots/assets/svg/VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]/tmp/SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "rmtree [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]/build/nanobench",
+      "-i",
+      "[START_DIR]/skia/resources",
+      "--skps",
+      "[START_DIR]/skp",
+      "--images",
+      "[START_DIR]/skimage/nanobench",
+      "--svgs",
+      "[START_DIR]/svg",
+      "--pre_log",
+      "--images",
+      "--gpuStatsDump",
+      "true",
+      "--scales",
+      "1.0",
+      "1.1",
+      "--nocpu",
+      "--config",
+      "commandbuffer",
+      "--match",
+      "~^desk_micrographygirlsvg.skp_1.1$",
+      "~inc0.gif",
+      "~inc1.gif",
+      "~incInterlaced.gif",
+      "~inc0.jpg",
+      "~incGray.jpg",
+      "~inc0.wbmp",
+      "~inc1.wbmp",
+      "~inc0.webp",
+      "~inc1.webp",
+      "~inc0.ico",
+      "~inc1.ico",
+      "~inc0.png",
+      "~inc1.png",
+      "~inc2.png",
+      "~inc12.png",
+      "~inc13.png",
+      "~inc14.png",
+      "~inc0.webp",
+      "~inc1.webp",
+      "--outResultsFile",
+      "[START_DIR]/[SWARM_OUT_DIR]/nanobench_abc123_1337000001.json",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "IntelHD615",
+      "extra_config",
+      "CommandBuffer",
+      "model",
+      "MacBook10.1",
+      "os",
+      "Mac"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "name": "nanobench"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]/resources/fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]/[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs perf_dir"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.expected/Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan.json b/infra/bots/recipes/perf.expected/Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan.json
new file mode 100644
index 0000000..59da027
--- /dev/null
+++ b/infra/bots/recipes/perf.expected/Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan.json
@@ -0,0 +1,277 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]\\tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skp\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]\\tmp\\SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skimage\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]\\tmp\\SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\svg\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]\\tmp\\SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]\\[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "rmtree [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]\\[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs [SWARM_OUT_DIR]"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\build\\nanobench",
+      "-i",
+      "[START_DIR]\\skia\\resources",
+      "--skps",
+      "[START_DIR]\\skp",
+      "--images",
+      "[START_DIR]\\skimage\\nanobench",
+      "--svgs",
+      "[START_DIR]\\svg",
+      "--pre_log",
+      "--images",
+      "--gpuStatsDump",
+      "true",
+      "--scales",
+      "1.0",
+      "1.1",
+      "--nocpu",
+      "--config",
+      "vk",
+      "--reduceOpListSplitting",
+      "--match",
+      "~^GM_varied_text_clipped_lcd$",
+      "~^GM_varied_text_ignorable_clip_lcd$",
+      "~^fontscaler_lcd$",
+      "~^rotated_rects_aa_changing_transparent_src$",
+      "~^rotated_rects_aa_same_transparent_src$",
+      "~^srcmode_rects_1_aa$",
+      "~^desk_skbug6850overlay2.skp_1$",
+      "~^desk_skbug6850overlay2.skp_1.1$",
+      "~^desk_skbug6850overlay2.skp_1.1_mpd$",
+      "~^desk_skbug6850overlay2.skp_1_mpd$",
+      "~^blendmode_mask_DstATop$",
+      "~^blendmode_mask_Src$",
+      "~^blendmode_mask_SrcIn$",
+      "~^blendmode_mask_SrcOut$",
+      "~^desk_carsvg.skp_1$",
+      "~^desk_carsvg.skp_1.1$",
+      "~^desk_carsvg.skp_1.1_mpd$",
+      "~^desk_carsvg.skp_1_mpd$",
+      "~^desk_googlespreadsheet.skp_1$",
+      "~^desk_googlespreadsheet.skp_1.1$",
+      "~^desk_googlespreadsheet.skp_1.1_mpd$",
+      "~^desk_googlespreadsheet.skp_1_mpd$",
+      "~^rotated_rects_aa_alternating_transparent_and_opaque_src$",
+      "~^shadermask_LCD_FF$",
+      "~^text_16_LCD_88$",
+      "~^text_16_LCD_BK$",
+      "~^text_16_LCD_FF$",
+      "~^text_16_LCD_WT$",
+      "~inc0.gif",
+      "~inc1.gif",
+      "~incInterlaced.gif",
+      "~inc0.jpg",
+      "~incGray.jpg",
+      "~inc0.wbmp",
+      "~inc1.wbmp",
+      "~inc0.webp",
+      "~inc1.webp",
+      "~inc0.ico",
+      "~inc1.ico",
+      "~inc0.png",
+      "~inc1.png",
+      "~inc2.png",
+      "~inc12.png",
+      "~inc13.png",
+      "~inc14.png",
+      "~inc0.webp",
+      "~inc1.webp",
+      "--outResultsFile",
+      "[START_DIR]\\[SWARM_OUT_DIR]\\nanobench_abc123_1337000001.json",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "IntelIris655",
+      "extra_config",
+      "Vulkan",
+      "model",
+      "NUC8i5BEK",
+      "os",
+      "Win10"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "name": "nanobench"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]\\[SWARM_OUT_DIR]"
+    ],
+    "infra_step": true,
+    "name": "makedirs perf_dir"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/perf.py b/infra/bots/recipes/perf.py
index e56d882..be7b5f0 100644
--- a/infra/bots/recipes/perf.py
+++ b/infra/bots/recipes/perf.py
@@ -149,6 +149,7 @@
       'IntelIris6100' in bot or # gen 8 - broadwell
       'IntelIris540' in bot or  # gen 9 - skylake
       'IntelIris640' in bot or  # gen 9 - kaby lake
+      'IntelIris655' in bot or  # gen 9 - coffee lake
       'MaliT760' in bot or
       'MaliT860' in bot or
       'MaliT880' in bot):
@@ -182,9 +183,11 @@
   if 'IntelHD405' in bot and api.vars.is_linux and 'Vulkan' in bot:
     # skia:7322
     match.append('~desk_carsvg.skp_1')
+    match.append('~desk_googlehome.skp')
     match.append('~desk_tiger8svg.skp_1')
     match.append('~desk_wowwiki.skp')
     match.append('~desk_ynevsvg.skp_1.1')
+    match.append('~desk_nostroke_tiger8svg.skp')
     match.append('~keymobi_booking_com.skp_1')
     match.append('~keymobi_booking_com.skp_1_mpd')
     match.append('~keymobi_cnn_article.skp_1')
@@ -195,6 +198,7 @@
     match.append('~keymobi_techcrunch_com.skp_1.1')
     match.append('~keymobi_techcrunch.skp_1.1')
     match.append('~keymobi_techcrunch.skp_1.1_mpd')
+    match.append('~svgparse_Seal_of_California.svg_1.1')
     match.append('~svgparse_NewYork-StateSeal.svg_1.1')
     match.append('~svgparse_Vermont_state_seal.svg_1')
     match.append('~tabl_gamedeksiam.skp_1.1')
@@ -202,6 +206,40 @@
     match.append('~top25desk_ebay_com.skp_1.1')
     match.append('~top25desk_ebay.skp_1.1')
     match.append('~top25desk_ebay.skp_1.1_mpd')
+  if 'MacBook10.1' in bot and 'CommandBuffer' in bot:
+    match.append('~^desk_micrographygirlsvg.skp_1.1$')
+  if 'IntelIris655' in bot and 'Win10' in bot and 'Vulkan' in bot:
+    # skia:8587
+    match.append('~^GM_varied_text_clipped_lcd$')
+    match.append('~^GM_varied_text_ignorable_clip_lcd$')
+    match.append('~^fontscaler_lcd$')
+    match.append('~^rotated_rects_aa_changing_transparent_src$')
+    match.append('~^rotated_rects_aa_same_transparent_src$')
+    match.append('~^srcmode_rects_1_aa$')
+    match.append('~^desk_skbug6850overlay2.skp_1$')
+    match.append('~^desk_skbug6850overlay2.skp_1.1$')
+    match.append('~^desk_skbug6850overlay2.skp_1.1_mpd$')
+    match.append('~^desk_skbug6850overlay2.skp_1_mpd$')
+    # skia:8659
+    match.append('~^blendmode_mask_DstATop$')
+    match.append('~^blendmode_mask_Src$')
+    match.append('~^blendmode_mask_SrcIn$')
+    match.append('~^blendmode_mask_SrcOut$')
+    match.append('~^desk_carsvg.skp_1$')
+    match.append('~^desk_carsvg.skp_1.1$')
+    match.append('~^desk_carsvg.skp_1.1_mpd$')
+    match.append('~^desk_carsvg.skp_1_mpd$')
+    match.append('~^desk_googlespreadsheet.skp_1$')
+    match.append('~^desk_googlespreadsheet.skp_1.1$')
+    match.append('~^desk_googlespreadsheet.skp_1.1_mpd$')
+    match.append('~^desk_googlespreadsheet.skp_1_mpd$')
+    if 'Release' in bot:
+      match.append('~^rotated_rects_aa_alternating_transparent_and_opaque_src$')
+      match.append('~^shadermask_LCD_FF$')
+      match.append('~^text_16_LCD_88$')
+      match.append('~^text_16_LCD_BK$')
+      match.append('~^text_16_LCD_FF$')
+      match.append('~^text_16_LCD_WT$')
   if ('ASAN' in bot or 'UBSAN' in bot) and 'CPU' in bot:
     # floor2int_undef benches undefined behavior, so ASAN correctly complains.
     match.append('~^floor2int_undef$')
@@ -365,12 +403,14 @@
   'Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan',
   'Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All',
   ('Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-'
-    'CommandBuffer'),
+   'CommandBuffer'),
+  'Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer',
   ('Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-'
    'MoltenVK_Vulkan'),
   ('Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-'
     'Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41'),
   'Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE',
+  'Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan',
   'Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All',
 ]
 
diff --git a/infra/bots/recipes/recreate_skps.expected/Housekeeper-Weekly-RecreateSKPs.json b/infra/bots/recipes/recreate_skps.expected/Housekeeper-Weekly-RecreateSKPs.json
index 5445b6c..d8ad681 100644
--- a/infra/bots/recipes/recreate_skps.expected/Housekeeper-Weekly-RecreateSKPs.json
+++ b/infra/bots/recipes/recreate_skps.expected/Housekeeper-Weekly-RecreateSKPs.json
@@ -213,9 +213,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Upload SKPs"
   },
diff --git a/infra/bots/recipes/recreate_skps.expected/failed_upload.json b/infra/bots/recipes/recreate_skps.expected/failed_upload.json
index 3591b7c..11d4b5b 100644
--- a/infra/bots/recipes/recreate_skps.expected/failed_upload.json
+++ b/infra/bots/recipes/recreate_skps.expected/failed_upload.json
@@ -213,9 +213,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Upload SKPs",
     "~followup_annotations": [
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
index 1bc01e1..a3a553c 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json
@@ -798,7 +798,7 @@
       "/usr/bin/adb.1.0.35",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -820,7 +820,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -828,8 +828,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -841,7 +841,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -858,7 +858,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -875,10 +875,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android.json
index 96326ae..b101e578 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android.json
@@ -756,7 +756,7 @@
       "/usr/bin/adb.1.0.35",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -778,7 +778,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -786,8 +786,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -799,7 +799,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -816,7 +816,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -833,10 +833,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads.json b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads.json
index da43eac..b09c6e5 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads.json
@@ -756,7 +756,7 @@
       "/usr/bin/adb.1.0.35",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -778,7 +778,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -786,8 +786,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -799,7 +799,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -816,7 +816,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -833,10 +833,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan.json b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan.json
index 7593389..bc1a833 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan.json
@@ -756,7 +756,7 @@
       "/usr/bin/adb.1.0.35",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -778,7 +778,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -786,8 +786,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -799,7 +799,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -816,7 +816,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -833,10 +833,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android.json
index 798a4ff..03ce407 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android.json
@@ -756,7 +756,7 @@
       "/usr/bin/adb.1.0.35",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -778,7 +778,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -786,8 +786,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -799,7 +799,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -816,7 +816,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -833,10 +833,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR.json b/infra/bots/recipes/test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR.json
index cb54317..b683676 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR.json
@@ -756,7 +756,7 @@
       "/usr/bin/adb.1.0.35",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -778,7 +778,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -786,8 +786,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -799,7 +799,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -816,7 +816,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -833,10 +833,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android.json
index c515017..b25230d 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android.json
@@ -798,7 +798,7 @@
       "/usr/bin/adb.1.0.35",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -820,7 +820,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -828,8 +828,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -841,7 +841,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -858,7 +858,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -875,10 +875,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android.json
index be19a7e..b242535 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android.json
@@ -798,7 +798,7 @@
       "/usr/bin/adb.1.0.35",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -820,7 +820,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -828,8 +828,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -841,7 +841,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -858,7 +858,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -875,10 +875,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android.json b/infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android.json
index 006a6b8..1419c09 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android.json
@@ -840,7 +840,7 @@
       "/usr/bin/adb.1.0.35",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -862,7 +862,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -870,8 +870,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -883,7 +883,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -900,7 +900,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -917,10 +917,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm-Debug-All-Android_ASAN.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm-Debug-All-Android_ASAN.json
index d550f8f..97c914e 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm-Debug-All-Android_ASAN.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm-Debug-All-Android_ASAN.json
@@ -51,7 +51,7 @@
       "-u",
       "\nimport os\nimport subprocess\nimport sys\nimport time\nADB = sys.argv[1]\nASAN_SETUP = sys.argv[2]\n\ndef wait_for_device():\n  while True:\n    time.sleep(5)\n    print 'Waiting for device'\n    subprocess.check_output([ADB, 'wait-for-device'])\n    bit1 = subprocess.check_output([ADB, 'shell', 'getprop',\n                                   'dev.bootcomplete'])\n    bit2 = subprocess.check_output([ADB, 'shell', 'getprop',\n                                   'sys.boot_completed'])\n    if '1' in bit1 and '1' in bit2:\n      print 'Device detected'\n      break\n\nlog = subprocess.check_output([ADB, 'root'])\n# check for message like 'adbd cannot run as root in production builds'\nprint log\nif 'cannot' in log:\n  raise Exception('adb root failed')\n\noutput = subprocess.check_output([ADB, 'disable-verity'])\nprint output\n\nif 'already disabled' not in output:\n  print 'Rebooting device'\n  subprocess.check_output([ADB, 'reboot'])\n  wait_for_device()\n\ndef installASAN(revert=False):\n  # ASAN setup script is idempotent, either it installs it or\n  # says it's installed.  Returns True on success, false otherwise.\n  out = subprocess.check_output([ADB, 'wait-for-device'])\n  print out\n  cmd = [ASAN_SETUP]\n  if revert:\n    cmd = [ASAN_SETUP, '--revert']\n  process = subprocess.Popen(cmd, env={'ADB': ADB},\n                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n\n  # this also blocks until command finishes\n  (stdout, stderr) = process.communicate()\n  print stdout\n  print 'Stderr: %s' % stderr\n  return process.returncode == 0\n\nif not installASAN():\n  print 'Trying to revert the ASAN install and then re-install'\n  # ASAN script sometimes has issues if it was interrupted or partially applied\n  # Try reverting it, then re-enabling it\n  if not installASAN(revert=True):\n    raise Exception('reverting ASAN install failed')\n\n  # Sleep because device does not reboot instantly\n  time.sleep(10)\n\n  if not installASAN():\n    raise Exception('Tried twice to setup ASAN and failed.')\n\n# Sleep because device does not reboot instantly\ntime.sleep(10)\nwait_for_device()\n",
       "/usr/bin/adb.1.0.35",
-      "[START_DIR]/android_ndk_linux/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/6.0.2/bin/asan_device_setup"
+      "[START_DIR]/android_ndk_linux/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/8.0.2/bin/asan_device_setup"
     ],
     "env": {
       "CHROME_HEADLESS": "1",
@@ -894,6 +894,32 @@
   },
   {
     "cmd": [
+      "/usr/bin/adb.1.0.35",
+      "wait-for-device"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "wait for device before uninstalling ASAN",
+    "timeout": 180
+  },
+  {
+    "cmd": [
+      "[START_DIR]/android_ndk_linux/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/8.0.2/bin/asan_device_setup",
+      "--revert"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "uninstall ASAN",
+    "timeout": 300
+  },
+  {
+    "cmd": [
       "python",
       "-u",
       "\nimport os\nimport subprocess\nimport sys\nout = sys.argv[1]\nlog = subprocess.check_output(['/usr/bin/adb.1.0.35', 'logcat', '-d'])\nfor line in log.split('\\n'):\n  tokens = line.split()\n  if len(tokens) == 11 and tokens[-7] == 'F' and tokens[-3] == 'pc':\n    addr, path = tokens[-2:]\n    local = os.path.join(out, os.path.basename(path))\n    if os.path.exists(local):\n      sym = subprocess.check_output(['addr2line', '-Cfpe', local, addr])\n      line = line.replace(addr, addr + ' ' + sym.strip())\n  print line\n",
diff --git a/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan.json b/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan.json
index da1a4f3..f216477 100644
--- a/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan.json
+++ b/infra/bots/recipes/test.expected/Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan.json
@@ -940,7 +940,7 @@
       "/usr/bin/adb.1.0.35",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -962,7 +962,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -970,8 +970,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -983,7 +983,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1000,7 +1000,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -1017,10 +1017,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json b/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
index 9c34771..c224a72 100644
--- a/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
+++ b/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json
@@ -521,7 +521,7 @@
       "adb",
       "pull",
       "/dev/shm/skia/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -542,7 +542,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -550,8 +550,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -563,7 +563,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -580,7 +580,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -597,10 +597,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json b/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
index 74f7732..f1e29e8 100644
--- a/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
+++ b/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json
@@ -628,7 +628,7 @@
       "adb",
       "pull",
       "/dev/shm/skia/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -649,7 +649,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -657,8 +657,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -670,7 +670,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -687,7 +687,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -704,10 +704,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3.json b/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3.json
index 67a83dd..b389be9 100644
--- a/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3.json
+++ b/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3.json
@@ -263,6 +263,7 @@
       "0",
       "--config",
       "ddl-gl",
+      "ddl2-gl",
       "--src",
       "gm",
       "skp",
diff --git a/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs.json b/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs.json
index 9121903..e4a0872 100644
--- a/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs.json
+++ b/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs.json
@@ -256,6 +256,7 @@
       "gbr-gl",
       "glbetex",
       "glbert",
+      "glenarrow",
       "--src",
       "tests",
       "gm",
diff --git a/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan.json b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan.json
new file mode 100644
index 0000000..2a1848b
--- /dev/null
+++ b/infra/bots/recipes/test.expected/Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan.json
@@ -0,0 +1,598 @@
+[
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]\\tmp"
+    ],
+    "infra_step": true,
+    "name": "makedirs tmp_dir"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skp\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skp VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]\\tmp\\SKP_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SKP_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\skimage\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get skimage VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]\\tmp\\SK_IMAGE_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SK_IMAGE_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "[START_DIR]\\skia\\infra\\bots\\assets\\svg\\VERSION",
+      "/path/to/tmp/"
+    ],
+    "infra_step": true,
+    "name": "Get svg VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "copy",
+      "42",
+      "[START_DIR]\\tmp\\SVG_VERSION"
+    ],
+    "infra_step": true,
+    "name": "write SVG_VERSION"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "rmtree",
+      "[START_DIR]\\test"
+    ],
+    "infra_step": true,
+    "name": "rmtree test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "RECIPE_MODULE[recipe_engine::file]\\resources\\fileutil.py",
+      "--json-output",
+      "/path/to/tmp/json",
+      "ensure-directory",
+      "--mode",
+      "0777",
+      "[START_DIR]\\test"
+    ],
+    "infra_step": true,
+    "name": "makedirs test"
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "\nimport contextlib\nimport math\nimport socket\nimport sys\nimport time\nimport urllib2\n\nHASHES_URL = sys.argv[1]\nRETRIES = 5\nTIMEOUT = 60\nWAIT_BASE = 15\n\nsocket.setdefaulttimeout(TIMEOUT)\nfor retry in range(RETRIES):\n  try:\n    with contextlib.closing(\n        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:\n      hashes = w.read()\n      with open(sys.argv[2], 'w') as f:\n        f.write(hashes)\n        break\n  except Exception as e:\n    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL\n    print e\n    if retry == RETRIES:\n      raise\n    waittime = WAIT_BASE * math.pow(2, retry)\n    print 'Retry in %d seconds.' % waittime\n    time.sleep(waittime)\n",
+      "https://example.com/hashes.txt",
+      "[START_DIR]\\tmp\\uninteresting_hashes.txt"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "infra_step": true,
+    "name": "get uninteresting hashes",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@import contextlib@@@",
+      "@@@STEP_LOG_LINE@python.inline@import math@@@",
+      "@@@STEP_LOG_LINE@python.inline@import socket@@@",
+      "@@@STEP_LOG_LINE@python.inline@import sys@@@",
+      "@@@STEP_LOG_LINE@python.inline@import time@@@",
+      "@@@STEP_LOG_LINE@python.inline@import urllib2@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@HASHES_URL = sys.argv[1]@@@",
+      "@@@STEP_LOG_LINE@python.inline@RETRIES = 5@@@",
+      "@@@STEP_LOG_LINE@python.inline@TIMEOUT = 60@@@",
+      "@@@STEP_LOG_LINE@python.inline@WAIT_BASE = 15@@@",
+      "@@@STEP_LOG_LINE@python.inline@@@@",
+      "@@@STEP_LOG_LINE@python.inline@socket.setdefaulttimeout(TIMEOUT)@@@",
+      "@@@STEP_LOG_LINE@python.inline@for retry in range(RETRIES):@@@",
+      "@@@STEP_LOG_LINE@python.inline@  try:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    with contextlib.closing(@@@",
+      "@@@STEP_LOG_LINE@python.inline@        urllib2.urlopen(HASHES_URL, timeout=TIMEOUT)) as w:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      hashes = w.read()@@@",
+      "@@@STEP_LOG_LINE@python.inline@      with open(sys.argv[2], 'w') as f:@@@",
+      "@@@STEP_LOG_LINE@python.inline@        f.write(hashes)@@@",
+      "@@@STEP_LOG_LINE@python.inline@        break@@@",
+      "@@@STEP_LOG_LINE@python.inline@  except Exception as e:@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Failed to get uninteresting hashes from %s:' % HASHES_URL@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print e@@@",
+      "@@@STEP_LOG_LINE@python.inline@    if retry == RETRIES:@@@",
+      "@@@STEP_LOG_LINE@python.inline@      raise@@@",
+      "@@@STEP_LOG_LINE@python.inline@    waittime = WAIT_BASE * math.pow(2, retry)@@@",
+      "@@@STEP_LOG_LINE@python.inline@    print 'Retry in %d seconds.' % waittime@@@",
+      "@@@STEP_LOG_LINE@python.inline@    time.sleep(waittime)@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_BOT_ID', '')\n"
+    ],
+    "name": "get swarming bot id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_BOT_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "python",
+      "-u",
+      "import os\nprint os.environ.get('SWARMING_TASK_ID', '')\n"
+    ],
+    "name": "get swarming task id",
+    "stdout": "/path/to/tmp/",
+    "~followup_annotations": [
+      "@@@STEP_LOG_LINE@python.inline@import os@@@",
+      "@@@STEP_LOG_LINE@python.inline@print os.environ.get('SWARMING_TASK_ID', '')@@@",
+      "@@@STEP_LOG_END@python.inline@@@"
+    ]
+  },
+  {
+    "cmd": [
+      "[START_DIR]\\build\\dm",
+      "--resourcePath",
+      "[START_DIR]\\skia\\resources",
+      "--skps",
+      "[START_DIR]\\skp",
+      "--images",
+      "[START_DIR]\\skimage\\dm",
+      "--colorImages",
+      "[START_DIR]\\skimage\\colorspace",
+      "--nameByHash",
+      "--properties",
+      "gitHash",
+      "abc123",
+      "builder",
+      "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan",
+      "buildbucket_build_id",
+      "123454321",
+      "swarming_bot_id",
+      "skia-bot-123",
+      "swarming_task_id",
+      "123456",
+      "--svgs",
+      "[START_DIR]\\svg",
+      "--key",
+      "arch",
+      "x86_64",
+      "compiler",
+      "Clang",
+      "configuration",
+      "Debug",
+      "cpu_or_gpu",
+      "GPU",
+      "cpu_or_gpu_value",
+      "IntelIris655",
+      "extra_config",
+      "Vulkan",
+      "model",
+      "NUC8i5BEK",
+      "os",
+      "Win10",
+      "--uninterestingHashesFile",
+      "[START_DIR]\\tmp\\uninteresting_hashes.txt",
+      "--writePath",
+      "[START_DIR]\\[SWARM_OUT_DIR]",
+      "--dont_write",
+      "pdf",
+      "--randomProcessorTest",
+      "--nocpu",
+      "--config",
+      "vk",
+      "--src",
+      "tests",
+      "gm",
+      "image",
+      "colorImage",
+      "svg",
+      "--blacklist",
+      "_",
+      "svg",
+      "_",
+      "svgparse_",
+      "_",
+      "image",
+      "gen_platf",
+      "pal8os2v2.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "pal8os2v2-16.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgba32abf.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24prof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rgb24lprof.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "8bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "4bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "32bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "24bpp-pixeldata-cropped.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "testimgari.jpg",
+      "_",
+      "image",
+      "gen_platf",
+      "rle8-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "rle4-height-negative.bmp",
+      "_",
+      "image",
+      "gen_platf",
+      "error",
+      "_",
+      "image",
+      "_",
+      "interlaced1.png",
+      "_",
+      "image",
+      "_",
+      "interlaced2.png",
+      "_",
+      "image",
+      "_",
+      "interlaced3.png",
+      "_",
+      "image",
+      "_",
+      ".arw",
+      "_",
+      "image",
+      "_",
+      ".cr2",
+      "_",
+      "image",
+      "_",
+      ".dng",
+      "_",
+      "image",
+      "_",
+      ".nef",
+      "_",
+      "image",
+      "_",
+      ".nrw",
+      "_",
+      "image",
+      "_",
+      ".orf",
+      "_",
+      "image",
+      "_",
+      ".raf",
+      "_",
+      "image",
+      "_",
+      ".rw2",
+      "_",
+      "image",
+      "_",
+      ".pef",
+      "_",
+      "image",
+      "_",
+      ".srw",
+      "_",
+      "image",
+      "_",
+      ".ARW",
+      "_",
+      "image",
+      "_",
+      ".CR2",
+      "_",
+      "image",
+      "_",
+      ".DNG",
+      "_",
+      "image",
+      "_",
+      ".NEF",
+      "_",
+      "image",
+      "_",
+      ".NRW",
+      "_",
+      "image",
+      "_",
+      ".ORF",
+      "_",
+      "image",
+      "_",
+      ".RAF",
+      "_",
+      "image",
+      "_",
+      ".RW2",
+      "_",
+      "image",
+      "_",
+      ".PEF",
+      "_",
+      "image",
+      "_",
+      ".SRW",
+      "vk",
+      "gm",
+      "_",
+      "aarectmodes",
+      "vk",
+      "gm",
+      "_",
+      "aaxfermodes",
+      "vk",
+      "gm",
+      "_",
+      "crbug_892988",
+      "vk",
+      "gm",
+      "_",
+      "dftext",
+      "vk",
+      "gm",
+      "_",
+      "dftext_blob_persp",
+      "vk",
+      "gm",
+      "_",
+      "dont_clip_to_layer",
+      "vk",
+      "gm",
+      "_",
+      "drawregionmodes",
+      "vk",
+      "gm",
+      "_",
+      "filterfastbounds",
+      "vk",
+      "gm",
+      "_",
+      "fontmgr_iter",
+      "vk",
+      "gm",
+      "_",
+      "fontmgr_match",
+      "vk",
+      "gm",
+      "_",
+      "fontscalerdistortable",
+      "vk",
+      "gm",
+      "_",
+      "gammagradienttext",
+      "vk",
+      "gm",
+      "_",
+      "gammatext",
+      "vk",
+      "gm",
+      "_",
+      "gradtext",
+      "vk",
+      "gm",
+      "_",
+      "hairmodes",
+      "vk",
+      "gm",
+      "_",
+      "imagefilters_xfermodes",
+      "vk",
+      "gm",
+      "_",
+      "imagefiltersclipped",
+      "vk",
+      "gm",
+      "_",
+      "imagefiltersscaled",
+      "vk",
+      "gm",
+      "_",
+      "imagefiltersstroked",
+      "vk",
+      "gm",
+      "_",
+      "imagefilterstransformed",
+      "vk",
+      "gm",
+      "_",
+      "imageresizetiled",
+      "vk",
+      "gm",
+      "_",
+      "lcdblendmodes",
+      "vk",
+      "gm",
+      "_",
+      "lcdoverlap",
+      "vk",
+      "gm",
+      "_",
+      "lcdtext",
+      "vk",
+      "gm",
+      "_",
+      "lcdtextsize",
+      "vk",
+      "gm",
+      "_",
+      "matriximagefilter",
+      "vk",
+      "gm",
+      "_",
+      "resizeimagefilter",
+      "vk",
+      "gm",
+      "_",
+      "rotate_imagefilter",
+      "vk",
+      "gm",
+      "_",
+      "savelayer_lcdtext",
+      "vk",
+      "gm",
+      "_",
+      "shadermaskfilter_image",
+      "vk",
+      "gm",
+      "_",
+      "srcmode",
+      "vk",
+      "gm",
+      "_",
+      "surfaceprops",
+      "vk",
+      "gm",
+      "_",
+      "textblobgeometrychange",
+      "vk",
+      "gm",
+      "_",
+      "textbloblooper",
+      "vk",
+      "gm",
+      "_",
+      "textblobrandomfont",
+      "vk",
+      "gm",
+      "_",
+      "textfilter_color",
+      "vk",
+      "gm",
+      "_",
+      "textfilter_image",
+      "vk",
+      "gm",
+      "_",
+      "varied_text_clipped_lcd",
+      "vk",
+      "gm",
+      "_",
+      "varied_text_ignorable_clip_lcd",
+      "vk",
+      "gm",
+      "_",
+      "fontscaler",
+      "vk",
+      "gm",
+      "_",
+      "mixedtextblobs",
+      "vk",
+      "gm",
+      "_",
+      "textblobmixedsizes",
+      "vk",
+      "gm",
+      "_",
+      "textblobmixedsizes_df",
+      "--nonativeFonts",
+      "--reduceOpListSplitting",
+      "--verbose"
+    ],
+    "env": {
+      "CHROME_HEADLESS": "1",
+      "PATH": "<PATH>;RECIPE_PACKAGE_REPO[depot_tools]"
+    },
+    "name": "dm"
+  },
+  {
+    "jsonResult": null,
+    "name": "$result"
+  }
+]
\ No newline at end of file
diff --git a/infra/bots/recipes/test.expected/failed_get_hashes.json b/infra/bots/recipes/test.expected/failed_get_hashes.json
index 02333a9..f72fee1 100644
--- a/infra/bots/recipes/test.expected/failed_get_hashes.json
+++ b/infra/bots/recipes/test.expected/failed_get_hashes.json
@@ -814,7 +814,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -836,7 +836,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -844,8 +844,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -857,7 +857,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -874,7 +874,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -891,10 +891,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/failed_pull.json b/infra/bots/recipes/test.expected/failed_pull.json
index 23d7a55..35eec3d 100644
--- a/infra/bots/recipes/test.expected/failed_pull.json
+++ b/infra/bots/recipes/test.expected/failed_pull.json
@@ -814,7 +814,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -871,7 +871,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -928,7 +928,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -952,10 +952,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
@@ -1009,8 +1009,10 @@
   },
   {
     "failure": {
-      "failure": {
-        "step": "adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out (attempt 3)"
+      "exception": {
+        "traceback": [
+          "<omitted by recipe engine>"
+        ]
       },
       "humanReason": "Infra Failure: Step('adb pull.pull /sdcard/revenge_of_the_skiabot/dm_out (attempt 3)') returned 1"
     },
diff --git a/infra/bots/recipes/test.expected/failed_push.json b/infra/bots/recipes/test.expected/failed_push.json
index 1ae862a..e126813 100644
--- a/infra/bots/recipes/test.expected/failed_push.json
+++ b/infra/bots/recipes/test.expected/failed_push.json
@@ -143,8 +143,10 @@
   },
   {
     "failure": {
-      "failure": {
-        "step": "push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources"
+      "exception": {
+        "traceback": [
+          "<omitted by recipe engine>"
+        ]
       },
       "humanReason": "Infra Failure: Step('push [START_DIR]/skia/resources/* /sdcard/revenge_of_the_skiabot/resources') returned 1"
     },
diff --git a/infra/bots/recipes/test.expected/internal_bot_2.json b/infra/bots/recipes/test.expected/internal_bot_2.json
index 290ecdb..bdfb91c 100644
--- a/infra/bots/recipes/test.expected/internal_bot_2.json
+++ b/infra/bots/recipes/test.expected/internal_bot_2.json
@@ -770,7 +770,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -792,7 +792,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -800,8 +800,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -813,7 +813,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -830,7 +830,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -847,10 +847,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.expected/internal_bot_5.json b/infra/bots/recipes/test.expected/internal_bot_5.json
index b9bde87..30d3522 100644
--- a/infra/bots/recipes/test.expected/internal_bot_5.json
+++ b/infra/bots/recipes/test.expected/internal_bot_5.json
@@ -691,7 +691,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android buildbucket_build_id 123454321 swarming_bot_id \"\" swarming_task_id \"\" --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ test _ SRGBReadWritePixels _ test _ ES2BlendWithNoTexture _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
+      "set -x; /data/local/tmp/dm --resourcePath /sdcard/revenge_of_the_skiabot/resources --skps /sdcard/revenge_of_the_skiabot/skps --images /sdcard/revenge_of_the_skiabot/images/dm --colorImages /sdcard/revenge_of_the_skiabot/images/colorspace --nameByHash --properties gitHash abc123 builder Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android buildbucket_build_id 123454321 swarming_bot_id \"\" swarming_task_id \"\" --svgs /sdcard/revenge_of_the_skiabot/svgs --key arch arm compiler Clang configuration Debug cpu_or_gpu GPU cpu_or_gpu_value Tegra3 extra_config Android model Nexus7 os Android --uninterestingHashesFile /sdcard/revenge_of_the_skiabot/uninteresting_hashes.txt --writePath /sdcard/revenge_of_the_skiabot/dm_out --dont_write pdf --nocpu --config gles glesdft glessrgb --src tests gm image colorImage svg --blacklist _ svg _ svgparse_ glessrgb image _ _ _ image gen_platf error _ test _ GrShape _ test _ SRGBReadWritePixels _ image _ interlaced1.png _ image _ interlaced2.png _ image _ interlaced3.png _ image _ .arw _ image _ .cr2 _ image _ .dng _ image _ .nef _ image _ .nrw _ image _ .orf _ image _ .raf _ image _ .rw2 _ image _ .pef _ image _ .srw _ image _ .ARW _ image _ .CR2 _ image _ .DNG _ image _ .NEF _ image _ .NRW _ image _ .ORF _ image _ .RAF _ image _ .RW2 _ image _ .PEF _ image _ .SRW --nonativeFonts --verbose; echo $? >/data/local/tmp/rc",
       "[START_DIR]/tmp/dm.sh"
     ],
     "env": {
@@ -770,7 +770,7 @@
       "/opt/infra-android/tools/adb",
       "pull",
       "/sdcard/revenge_of_the_skiabot/dm_out",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "cwd": "[START_DIR]/skia",
     "env": {
@@ -792,7 +792,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "glob",
-      "[TMP_BASE]/adb_pull_tmp_1",
+      "[CLEANUP]/adb_pull_tmp_1",
       "dm_out/*"
     ],
     "infra_step": true,
@@ -800,8 +800,8 @@
     "stdout": "/path/to/tmp/",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/1.png@@@",
-      "@@@STEP_LOG_LINE@glob@[TMP_BASE]/adb_pull_tmp_1/2.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/1.png@@@",
+      "@@@STEP_LOG_LINE@glob@[CLEANUP]/adb_pull_tmp_1/2.png@@@",
       "@@@STEP_LOG_END@glob@@@"
     ]
   },
@@ -813,7 +813,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/1.png",
+      "[CLEANUP]/adb_pull_tmp_1/1.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -830,7 +830,7 @@
       "--json-output",
       "/path/to/tmp/json",
       "copy",
-      "[TMP_BASE]/adb_pull_tmp_1/2.png",
+      "[CLEANUP]/adb_pull_tmp_1/2.png",
       "[START_DIR]/[SWARM_OUT_DIR]"
     ],
     "infra_step": true,
@@ -847,10 +847,10 @@
       "--json-output",
       "/path/to/tmp/json",
       "rmtree",
-      "[TMP_BASE]/adb_pull_tmp_1"
+      "[CLEANUP]/adb_pull_tmp_1"
     ],
     "infra_step": true,
-    "name": "adb pull.rmtree [TMP_BASE]/adb_pull_tmp_1",
+    "name": "adb pull.rmtree [CLEANUP]/adb_pull_tmp_1",
     "~followup_annotations": [
       "@@@STEP_NEST_LEVEL@1@@@"
     ]
diff --git a/infra/bots/recipes/test.py b/infra/bots/recipes/test.py
index e510ee5..655372d 100644
--- a/infra/bots/recipes/test.py
+++ b/infra/bots/recipes/test.py
@@ -234,8 +234,9 @@
         blacklist('gltestpersistentcache gm _ glyph_pos_h_b')
 
     # Test SkColorSpaceXformCanvas and rendering to wrapped dsts on a few bots
+    # Also test 'glenarrow', which hits F16 surfaces and F16 vertex colors.
     if 'BonusConfigs' in api.vars.extra_tokens:
-      configs = ['gbr-gl', 'glbetex', 'glbert']
+      configs = ['gbr-gl', 'glbetex', 'glbert', 'glenarrow']
 
 
     if 'ChromeOS' in bot:
@@ -259,7 +260,9 @@
     if 'DDL3' in bot:
       # This bot generates the ddl-gl and ddl-vk images for the
       # large skps and the gms
-      configs = ['ddl-' + c for c in configs if c == 'gl' or c == 'vk']
+      ddl_configs = ['ddl-' + c for c in configs if c == 'gl' or c == 'vk']
+      ddl2_configs = ['ddl2-' + c for c in configs if c == 'gl' or c == 'vk']
+      configs = ddl_configs + ddl2_configs
       args.extend(['--skpViewportSize', "2048"])
       args.extend(['--gpuThreads', "0"])
 
@@ -427,9 +430,8 @@
     blacklist('_ test _ SRGBMipMap')
 
   if api.vars.internal_hardware_label == '5':
-    # skia:8470
+    # http://b/118312149#comment9
     blacklist('_ test _ SRGBReadWritePixels')
-    blacklist('_ test _ ES2BlendWithNoTexture')
 
   # skia:4095
   bad_serialize_gms = ['bleed_image',
@@ -665,6 +667,53 @@
     # skbug.com/8047
     match.append('~FloatingPointTextureTest$')
 
+  if 'Vulkan' in bot and 'Win10' in bot and 'IntelIris655' in bot:
+    # skia:8659
+    blacklist(['vk', 'gm', '_', 'aarectmodes'])
+    blacklist(['vk', 'gm', '_', 'aaxfermodes'])
+    blacklist(['vk', 'gm', '_', 'crbug_892988'])
+    blacklist(['vk', 'gm', '_', 'dftext'])
+    blacklist(['vk', 'gm', '_', 'dftext_blob_persp'])
+    blacklist(['vk', 'gm', '_', 'dont_clip_to_layer'])
+    blacklist(['vk', 'gm', '_', 'drawregionmodes'])
+    blacklist(['vk', 'gm', '_', 'filterfastbounds'])
+    blacklist(['vk', 'gm', '_', 'fontmgr_iter'])
+    blacklist(['vk', 'gm', '_', 'fontmgr_match'])
+    blacklist(['vk', 'gm', '_', 'fontscalerdistortable'])
+    blacklist(['vk', 'gm', '_', 'gammagradienttext'])
+    blacklist(['vk', 'gm', '_', 'gammatext'])
+    blacklist(['vk', 'gm', '_', 'gradtext'])
+    blacklist(['vk', 'gm', '_', 'hairmodes'])
+    blacklist(['vk', 'gm', '_', 'imagefilters_xfermodes'])
+    blacklist(['vk', 'gm', '_', 'imagefiltersclipped'])
+    blacklist(['vk', 'gm', '_', 'imagefiltersscaled'])
+    blacklist(['vk', 'gm', '_', 'imagefiltersstroked'])
+    blacklist(['vk', 'gm', '_', 'imagefilterstransformed'])
+    blacklist(['vk', 'gm', '_', 'imageresizetiled'])
+    blacklist(['vk', 'gm', '_', 'lcdblendmodes'])
+    blacklist(['vk', 'gm', '_', 'lcdoverlap'])
+    blacklist(['vk', 'gm', '_', 'lcdtext'])
+    blacklist(['vk', 'gm', '_', 'lcdtextsize'])
+    blacklist(['vk', 'gm', '_', 'matriximagefilter'])
+    blacklist(['vk', 'gm', '_', 'resizeimagefilter'])
+    blacklist(['vk', 'gm', '_', 'rotate_imagefilter'])
+    blacklist(['vk', 'gm', '_', 'savelayer_lcdtext'])
+    blacklist(['vk', 'gm', '_', 'shadermaskfilter_image'])
+    blacklist(['vk', 'gm', '_', 'srcmode'])
+    blacklist(['vk', 'gm', '_', 'surfaceprops'])
+    blacklist(['vk', 'gm', '_', 'textblobgeometrychange'])
+    blacklist(['vk', 'gm', '_', 'textbloblooper'])
+    blacklist(['vk', 'gm', '_', 'textblobrandomfont'])
+    blacklist(['vk', 'gm', '_', 'textfilter_color'])
+    blacklist(['vk', 'gm', '_', 'textfilter_image'])
+    blacklist(['vk', 'gm', '_', 'varied_text_clipped_lcd'])
+    blacklist(['vk', 'gm', '_', 'varied_text_ignorable_clip_lcd'])
+    if 'Debug' in bot:
+      blacklist(['vk', 'gm', '_', 'fontscaler'])
+      blacklist(['vk', 'gm', '_', 'mixedtextblobs'])
+      blacklist(['vk', 'gm', '_', 'textblobmixedsizes'])
+      blacklist(['vk', 'gm', '_', 'textblobmixedsizes_df'])
+
   if 'MoltenVK' in bot:
     # skbug.com/7959
     blacklist(['_', 'gm', '_', 'vertices_scaled_shader'])
@@ -758,6 +807,7 @@
       'IntelIris6100' in bot or # gen 8 - broadwell
       'IntelIris540' in bot or  # gen 9 - skylake
       'IntelIris640' in bot or  # gen 9 - kaby lake
+      'IntelIris655' in bot or  # gen 9 - coffee lake
       'MaliT760' in bot or
       'MaliT860' in bot or
       'MaliT880' in bot):
@@ -994,6 +1044,7 @@
    '-ReleaseAndAbandonGpuContext'),
   'Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts_GDI',
   'Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE',
+  'Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan',
   'Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE',
   'Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan',
   'Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE',
diff --git a/infra/bots/recipes/update_go_deps.expected/Housekeeper-Nightly-UpdateGoDEPS.json b/infra/bots/recipes/update_go_deps.expected/Housekeeper-Nightly-UpdateGoDEPS.json
index 8319f2a..82f5f35 100644
--- a/infra/bots/recipes/update_go_deps.expected/Housekeeper-Nightly-UpdateGoDEPS.json
+++ b/infra/bots/recipes/update_go_deps.expected/Housekeeper-Nightly-UpdateGoDEPS.json
@@ -102,9 +102,10 @@
     "cwd": "[START_DIR]/cache/work/skia",
     "env": {
       "CHROME_HEADLESS": "1",
+      "GOCACHE": "[START_DIR]/cache/go_cache",
       "GOPATH": "[START_DIR]/go_deps",
       "GOROOT": "[START_DIR]/go/go",
-      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
+      "PATH": "[START_DIR]/go/go/bin:[START_DIR]/go_deps/bin:<PATH>:RECIPE_PACKAGE_REPO[depot_tools]"
     },
     "name": "Update Asset"
   },
diff --git a/infra/bots/tasks.json b/infra/bots/tasks.json
index c1910a2..73e2730 100755
--- a/infra/bots/tasks.json
+++ b/infra/bots/tasks.json
@@ -193,6 +193,11 @@
         "Build-Debian9-Clang-x86_64-Debug-SwiftShader"
       ]
     },
+    "Build-Debian9-Clang-x86_64-Debug-Tidy": {
+      "tasks": [
+        "Build-Debian9-Clang-x86_64-Debug-Tidy"
+      ]
+    },
     "Build-Debian9-Clang-x86_64-Debug-Vulkan": {
       "tasks": [
         "Build-Debian9-Clang-x86_64-Debug-Vulkan"
@@ -535,6 +540,16 @@
         "Build-Win-Clang-x86_64-Release-Vulkan"
       ]
     },
+    "Build-Win-MSVC-arm64-Debug": {
+      "tasks": [
+        "Build-Win-MSVC-arm64-Debug"
+      ]
+    },
+    "Build-Win-MSVC-arm64-Release": {
+      "tasks": [
+        "Build-Win-MSVC-arm64-Release"
+      ]
+    },
     "Build-Win-MSVC-x86-Debug": {
       "tasks": [
         "Build-Win-MSVC-x86-Debug"
@@ -1321,6 +1336,21 @@
         "Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41"
       ]
     },
+    "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
+      "tasks": [
+        "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All"
+      ]
+    },
+    "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN": {
+      "tasks": [
+        "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN"
+      ]
+    },
+    "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
+      "tasks": [
+        "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan"
+      ]
+    },
     "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
       "tasks": [
         "Upload-Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All"
@@ -1481,6 +1511,36 @@
         "Upload-Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan"
       ]
     },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All": {
+      "tasks": [
+        "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All"
+      ]
+    },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE": {
+      "tasks": [
+        "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE"
+      ]
+    },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan": {
+      "tasks": [
+        "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan"
+      ]
+    },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All": {
+      "tasks": [
+        "Upload-Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All"
+      ]
+    },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE": {
+      "tasks": [
+        "Upload-Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE"
+      ]
+    },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan": {
+      "tasks": [
+        "Upload-Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan"
+      ]
+    },
     "Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All": {
       "tasks": [
         "Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All"
@@ -2565,6 +2625,51 @@
         "Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41"
       ]
     },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
+      "tasks": [
+        "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN": {
+      "tasks": [
+        "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1": {
+      "tasks": [
+        "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan": {
+      "tasks": [
+        "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3": {
+      "tasks": [
+        "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN": {
+      "tasks": [
+        "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan": {
+      "tasks": [
+        "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan": {
+      "tasks": [
+        "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
+      "tasks": [
+        "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan"
+      ]
+    },
     "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
       "tasks": [
         "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All"
@@ -2775,6 +2880,41 @@
         "Upload-Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan"
       ]
     },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All": {
+      "tasks": [
+        "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE": {
+      "tasks": [
+        "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-OpenCL": {
+      "tasks": [
+        "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-OpenCL"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan": {
+      "tasks": [
+        "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All": {
+      "tasks": [
+        "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE": {
+      "tasks": [
+        "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan": {
+      "tasks": [
+        "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan"
+      ]
+    },
     "Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All": {
       "tasks": [
         "Upload-Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All"
@@ -3375,7 +3515,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/armhf_sysroot",
@@ -4273,7 +4413,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/armhf_sysroot",
@@ -5859,7 +5999,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -6452,6 +6592,10 @@
         {
           "name": "work",
           "path": "cache/work"
+        },
+        {
+          "name": "docker",
+          "path": "cache/docker"
         }
       ],
       "cipd_packages": [
@@ -6612,7 +6756,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -6741,7 +6885,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -6870,7 +7014,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -7004,7 +7148,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/chromebook_x86_64_gles",
@@ -7138,7 +7282,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -7267,7 +7411,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/opencl_headers",
@@ -7406,7 +7550,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -7535,7 +7679,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -7664,7 +7808,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -7793,7 +7937,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/cmake_linux",
@@ -7874,6 +8018,135 @@
       ],
       "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
     },
+    "Build-Debian9-Clang-x86_64-Debug-Tidy": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:0ae21738597e5601ba90372315145fec18582fc4"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "skia/bots/clang_linux",
+          "path": "clang_linux",
+          "version": "version:13"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Debian9-Clang-x86_64-Debug-Tidy\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highcpu-64",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
     "Build-Debian9-Clang-x86_64-Debug-Vulkan": {
       "caches": [
         {
@@ -7927,7 +8200,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -8061,7 +8334,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -8190,7 +8463,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -8319,7 +8592,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -8448,7 +8721,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -8582,7 +8855,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/chromebook_x86_64_gles",
@@ -8716,7 +8989,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -8841,7 +9114,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -8970,7 +9243,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -9099,7 +9372,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -9228,7 +9501,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -9357,7 +9630,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -9486,7 +9759,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -9615,7 +9888,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -9744,7 +10017,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/cmake_linux",
@@ -9878,7 +10151,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -10007,7 +10280,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -10141,7 +10414,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -13079,7 +13352,7 @@
         {
           "name": "skia/bots/android_ndk_darwin",
           "path": "android_ndk_darwin",
-          "version": "version:5"
+          "version": "version:7"
         }
       ],
       "command": [
@@ -15070,7 +15343,7 @@
         {
           "name": "skia/bots/android_ndk_windows",
           "path": "n",
-          "version": "version:6"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -15205,7 +15478,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -15341,7 +15614,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -15477,7 +15750,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -15613,7 +15886,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -15749,7 +16022,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -15885,7 +16158,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         },
         {
           "name": "skia/bots/opencl_headers",
@@ -16026,7 +16299,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -16162,7 +16435,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -16209,8 +16482,7 @@
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Housekeeper-PerCommit-IsolateWinToolchain",
-        "Housekeeper-PerCommit-IsolateWinVulkanSDK"
+        "Housekeeper-PerCommit-IsolateWinToolchain"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
@@ -16299,7 +16571,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -16435,7 +16707,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -16571,7 +16843,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -16707,7 +16979,7 @@
         {
           "name": "skia/bots/clang_win",
           "path": "clang_win",
-          "version": "version:7"
+          "version": "version:8"
         }
       ],
       "command": [
@@ -16754,8 +17026,269 @@
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Housekeeper-PerCommit-IsolateWinToolchain",
-        "Housekeeper-PerCommit-IsolateWinVulkanSDK"
+        "Housekeeper-PerCommit-IsolateWinToolchain"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "image:windows-server-2016-dc-v20180710",
+        "machine_type:n1-highcpu-64",
+        "os:Windows-2016Server-14393",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Build-Win-MSVC-arm64-Debug": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:0ae21738597e5601ba90372315145fec18582fc4"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-arm64-Debug\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Housekeeper-PerCommit-IsolateWinToolchain"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "image:windows-server-2016-dc-v20180710",
+        "machine_type:n1-highcpu-64",
+        "os:Windows-2016Server-14393",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "build"
+      ],
+      "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Build-Win-MSVC-arm64-Release": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        },
+        {
+          "name": "git",
+          "path": "cache/git"
+        },
+        {
+          "name": "git_cache",
+          "path": "cache/git_cache"
+        },
+        {
+          "name": "work",
+          "path": "cache/work"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.17.1.chromium15"
+        },
+        {
+          "name": "infra/tools/git/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:0ae21738597e5601ba90372315145fec18582fc4"
+        },
+        {
+          "name": "infra/tools/luci/git-credential-luci/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compile",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Build-Win-MSVC-arm64-Release\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"build\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Housekeeper-PerCommit-IsolateWinToolchain"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
@@ -17279,8 +17812,7 @@
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Housekeeper-PerCommit-IsolateWinToolchain",
-        "Housekeeper-PerCommit-IsolateWinVulkanSDK"
+        "Housekeeper-PerCommit-IsolateWinToolchain"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
@@ -17542,8 +18074,7 @@
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Housekeeper-PerCommit-IsolateWinToolchain",
-        "Housekeeper-PerCommit-IsolateWinVulkanSDK"
+        "Housekeeper-PerCommit-IsolateWinToolchain"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
@@ -17671,6 +18202,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "outputs": [
         "perf"
       ]
@@ -17773,6 +18305,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "outputs": [
         "perf"
       ]
@@ -17875,6 +18408,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "outputs": [
         "perf"
       ]
@@ -17977,6 +18511,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "outputs": [
         "perf"
       ]
@@ -18079,6 +18614,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "outputs": [
         "perf"
       ]
@@ -18181,6 +18717,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "outputs": [
         "perf"
       ]
@@ -18283,6 +18820,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "outputs": [
         "perf"
       ]
@@ -18336,11 +18874,6 @@
           "name": "infra/tools/luci/git-credential-luci/${platform}",
           "path": "cipd_bin_packages",
           "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
-        },
-        {
-          "name": "skia/bots/go",
-          "path": "go",
-          "version": "version:6"
         }
       ],
       "command": [
@@ -18467,11 +19000,6 @@
           "name": "infra/tools/luci/git-credential-luci/${platform}",
           "path": "cipd_bin_packages",
           "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
-        },
-        {
-          "name": "skia/bots/go",
-          "path": "go",
-          "version": "version:6"
         }
       ],
       "command": [
@@ -18590,11 +19118,6 @@
           "name": "infra/tools/luci/git-credential-luci/${platform}",
           "path": "cipd_bin_packages",
           "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
-        },
-        {
-          "name": "skia/bots/go",
-          "path": "go",
-          "version": "version:6"
         }
       ],
       "command": [
@@ -18641,7 +19164,8 @@
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-GCC-x86_64-Release"
+        "Build-Debian9-GCC-x86_64-Release",
+        "Housekeeper-PerCommit-IsolateGoDeps"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
@@ -18665,6 +19189,7 @@
       },
       "io_timeout_ns": 7200000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-bookmaker@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Housekeeper-Nightly-RecreateSKPs_Canary": {
@@ -18672,6 +19197,10 @@
         {
           "name": "vpython",
           "path": "cache/vpython"
+        },
+        {
+          "name": "go_cache",
+          "path": "cache/go_cache"
         }
       ],
       "cipd_packages": [
@@ -18776,6 +19305,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-recreate-skps@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Housekeeper-Nightly-UpdateGoDEPS": {
@@ -18783,6 +19313,10 @@
         {
           "name": "vpython",
           "path": "cache/vpython"
+        },
+        {
+          "name": "go_cache",
+          "path": "cache/go_cache"
         }
       ],
       "cipd_packages": [
@@ -18890,6 +19424,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-recreate-skps@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Housekeeper-OnDemand-Presubmit": {
@@ -19011,6 +19546,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "empty.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Housekeeper-PerCommit": {
@@ -19062,11 +19598,6 @@
           "name": "infra/tools/luci/git-credential-luci/${platform}",
           "path": "cipd_bin_packages",
           "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
-        },
-        {
-          "name": "skia/bots/go",
-          "path": "go",
-          "version": "version:6"
         }
       ],
       "command": [
@@ -19136,6 +19667,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-housekeeper@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Housekeeper-PerCommit-Bookmaker": {
@@ -19179,11 +19711,6 @@
           "name": "infra/tools/luci/git-credential-luci/${platform}",
           "path": "cipd_bin_packages",
           "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
-        },
-        {
-          "name": "skia/bots/go",
-          "path": "go",
-          "version": "version:6"
         }
       ],
       "command": [
@@ -19230,7 +19757,8 @@
       ],
       "dependencies": [
         "Housekeeper-PerCommit-BundleRecipes",
-        "Build-Debian9-GCC-x86_64-Release"
+        "Build-Debian9-GCC-x86_64-Release",
+        "Housekeeper-PerCommit-IsolateGoDeps"
       ],
       "dimensions": [
         "cpu:x86-64-Haswell_GCE",
@@ -19254,6 +19782,7 @@
       },
       "io_timeout_ns": 7200000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-bookmaker@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Housekeeper-PerCommit-BundleRecipes": {
@@ -19394,6 +19923,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Housekeeper-PerCommit-InfraTests": {
@@ -19413,6 +19943,10 @@
         {
           "name": "work",
           "path": "cache/work"
+        },
+        {
+          "name": "go_cache",
+          "path": "cache/go_cache"
         }
       ],
       "cipd_packages": [
@@ -19520,6 +20054,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-compile-tasks@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Housekeeper-PerCommit-IsolateAndroidNDKLinux": {
@@ -19527,7 +20062,7 @@
         {
           "name": "skia/bots/android_ndk_linux",
           "path": "android_ndk_linux",
-          "version": "version:11"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -19550,7 +20085,7 @@
         {
           "name": "skia/bots/go_deps",
           "path": "go_deps",
-          "version": "version:81"
+          "version": "version:119"
         }
       ],
       "command": [
@@ -19573,7 +20108,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         }
       ],
       "command": [
@@ -19641,37 +20176,14 @@
       "cipd_packages": [
         {
           "name": "skia/bots/win_toolchain",
-          "path": "t",
-          "version": "version:8"
+          "path": "win_toolchain",
+          "version": "version:9"
         }
       ],
       "command": [
         "/bin/cp",
         "-rL",
-        "t",
-        "${ISOLATED_OUTDIR}"
-      ],
-      "dimensions": [
-        "cpu:x86-64-Haswell_GCE",
-        "gpu:none",
-        "machine_type:n1-highmem-2",
-        "os:Debian-9.4",
-        "pool:Skia"
-      ],
-      "isolate": "empty.isolate"
-    },
-    "Housekeeper-PerCommit-IsolateWinVulkanSDK": {
-      "cipd_packages": [
-        {
-          "name": "skia/bots/win_vulkan_sdk",
-          "path": "win_vulkan_sdk",
-          "version": "version:7"
-        }
-      ],
-      "command": [
-        "/bin/cp",
-        "-rL",
-        "win_vulkan_sdk",
+        "win_toolchain",
         "${ISOLATED_OUTDIR}"
       ],
       "dimensions": [
@@ -19688,6 +20200,10 @@
         {
           "name": "vpython",
           "path": "cache/vpython"
+        },
+        {
+          "name": "go_cache",
+          "path": "cache/go_cache"
         }
       ],
       "cipd_packages": [
@@ -19792,6 +20308,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-recreate-skps@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Debug-All-Android": {
@@ -26905,7 +27422,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -27017,7 +27534,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -27129,7 +27646,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -27139,7 +27656,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -27210,7 +27727,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "perf"
       ]
@@ -27246,7 +27763,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -27358,7 +27875,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -27368,7 +27885,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -27439,7 +27956,7 @@
       },
       "io_timeout_ns": 32400000000000,
       "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "perf"
       ]
@@ -27475,7 +27992,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -27587,7 +28104,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -27699,7 +28216,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -27811,7 +28328,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -27821,7 +28338,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -27892,7 +28409,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "perf"
       ]
@@ -27928,7 +28445,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -28040,7 +28557,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -28152,7 +28669,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -28264,7 +28781,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -28375,7 +28892,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -28486,7 +29003,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -28602,7 +29119,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -28723,7 +29240,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -28839,7 +29356,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -28960,7 +29477,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -29076,7 +29593,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -29197,7 +29714,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -29313,7 +29830,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -29429,7 +29946,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -29545,7 +30062,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -30051,7 +30568,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -30163,7 +30680,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -30275,7 +30792,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -30386,7 +30903,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -30497,7 +31014,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -30608,7 +31125,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -30719,7 +31236,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -30830,7 +31347,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -30941,7 +31458,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -31052,7 +31569,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -31127,7 +31644,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "perf"
       ]
@@ -31163,7 +31680,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -31274,7 +31791,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -31385,7 +31902,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -31496,7 +32013,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -31607,7 +32124,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -31718,7 +32235,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -31829,7 +32346,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -31941,7 +32458,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -32053,7 +32570,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -32165,7 +32682,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -32277,7 +32794,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -32287,7 +32804,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -32358,7 +32875,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "perf"
       ]
@@ -32394,7 +32911,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -32511,7 +33028,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -32623,7 +33140,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -32633,7 +33150,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -32704,7 +33221,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "perf"
       ]
@@ -32740,7 +33257,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -32857,7 +33374,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -32939,7 +33456,7 @@
       },
       "io_timeout_ns": 32400000000000,
       "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "perf"
       ]
@@ -32975,7 +33492,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -33057,6 +33574,352 @@
       },
       "io_timeout_ns": 32400000000000,
       "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia",
+        "rack:2"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/clang_linux",
+          "path": "clang_linux",
+          "version": "version:13"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug-ASAN"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia",
+        "rack:2"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/linux_vulkan_sdk",
+          "path": "linux_vulkan_sdk",
+          "version": "version:1"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia",
+        "rack:2"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
       "max_attempts": 2,
       "outputs": [
         "perf"
@@ -33093,7 +33956,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -33205,7 +34068,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -33215,7 +34078,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -33286,7 +34149,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "perf"
       ]
@@ -33322,7 +34185,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -33444,7 +34307,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -33500,7 +34363,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -33560,7 +34423,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -33616,7 +34479,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -33676,7 +34539,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -33732,7 +34595,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -33792,7 +34655,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -33848,7 +34711,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -33908,7 +34771,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -33964,7 +34827,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -34024,7 +34887,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -34080,7 +34943,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -34140,7 +35003,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -34256,7 +35119,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -34372,7 +35235,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -34488,7 +35351,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -34604,7 +35467,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -34721,7 +35584,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -34838,7 +35701,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -34955,7 +35818,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -35072,7 +35935,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -35189,7 +36052,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -35615,7 +36478,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -35671,7 +36534,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -35731,7 +36594,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -35787,7 +36650,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -35847,7 +36710,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -35903,7 +36766,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -35963,7 +36826,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -36019,7 +36882,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -36079,7 +36942,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -36135,7 +36998,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -36195,7 +37058,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -36251,7 +37114,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -36311,7 +37174,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -36367,7 +37230,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -36427,7 +37290,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -36483,7 +37346,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -36543,7 +37406,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -36599,7 +37462,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -36659,7 +37522,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -36715,7 +37578,703 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Debug-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Release"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Release-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "perf_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "perf"
+      ]
+    },
+    "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "perf",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"perf\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Release-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -36775,7 +38334,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -36831,7 +38390,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -36891,7 +38450,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -36947,7 +38506,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37007,7 +38566,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -37063,7 +38622,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37123,7 +38682,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -37179,7 +38738,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37239,7 +38798,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -37295,7 +38854,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37355,7 +38914,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -37411,7 +38970,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37471,7 +39030,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -37527,7 +39086,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37587,7 +39146,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -37643,7 +39202,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37703,7 +39262,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -37759,7 +39318,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37819,7 +39378,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -37875,7 +39434,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -37935,7 +39494,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -37991,7 +39550,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38051,7 +39610,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -38107,7 +39666,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38167,7 +39726,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -38223,7 +39782,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38283,7 +39842,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -38339,7 +39898,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38399,7 +39958,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -38455,7 +40014,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38515,7 +40074,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -38571,7 +40130,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38631,7 +40190,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -38687,7 +40246,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38747,7 +40306,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -38803,7 +40362,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38863,7 +40422,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -38919,7 +40478,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -38979,7 +40538,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -39035,7 +40594,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39095,7 +40654,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -39151,7 +40710,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39211,7 +40770,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -39267,7 +40826,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -39327,7 +40886,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -39444,7 +41003,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -39561,7 +41120,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -39678,7 +41237,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -39795,7 +41354,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -39913,7 +41472,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -40031,7 +41590,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -40108,7 +41667,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "perf"
       ]
@@ -40149,7 +41708,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -40267,7 +41826,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -40344,7 +41903,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "perf_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "perf"
       ]
@@ -40385,7 +41944,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -40503,7 +42062,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -40621,7 +42180,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -40737,7 +42296,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -40853,7 +42412,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -40969,7 +42528,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -46639,7 +48198,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -46740,7 +48299,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -46841,7 +48400,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -46942,7 +48501,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -47443,7 +49002,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -47544,7 +49103,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -49872,7 +51431,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -49984,7 +51543,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -50096,7 +51655,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -50106,7 +51665,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -50177,7 +51736,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -50213,7 +51772,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -50325,7 +51884,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -50335,7 +51894,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -50406,7 +51965,7 @@
       },
       "io_timeout_ns": 32400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -50442,7 +52001,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -50554,7 +52113,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -50666,7 +52225,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -50778,7 +52337,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -50890,7 +52449,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -50900,7 +52459,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -50971,7 +52530,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -51007,7 +52566,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -51119,7 +52678,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -51231,7 +52790,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -51343,7 +52902,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -51455,7 +53014,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -51567,7 +53126,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -51679,7 +53238,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -51689,7 +53248,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -51760,7 +53319,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -51796,7 +53355,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -51907,7 +53466,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -52018,7 +53577,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -52130,7 +53689,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -52242,7 +53801,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -52358,7 +53917,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -52479,7 +54038,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -52595,7 +54154,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -52716,7 +54275,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -52829,7 +54388,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -52945,7 +54504,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -53071,7 +54630,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -53192,7 +54751,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -53308,7 +54867,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -53318,7 +54877,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/mesa_intel_driver_linux",
@@ -53393,7 +54952,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -53429,7 +54988,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -53550,7 +55109,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -53666,7 +55225,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -53782,7 +55341,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -53898,7 +55457,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -54794,7 +56353,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -54906,7 +56465,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -55018,7 +56577,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -55130,7 +56689,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -55242,7 +56801,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -55353,7 +56912,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -55464,7 +57023,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -55575,7 +57134,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -55686,7 +57245,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -55797,7 +57356,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -55908,7 +57467,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -56019,7 +57578,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -56130,7 +57689,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -56205,7 +57764,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -56241,7 +57800,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -56352,7 +57911,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -56463,7 +58022,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -56538,7 +58097,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -56574,7 +58133,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -56685,7 +58244,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -56796,7 +58355,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -56907,7 +58466,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -57018,7 +58577,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -57129,7 +58688,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -57240,7 +58799,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -57351,7 +58910,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -57426,7 +58985,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -57462,7 +59021,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -57574,7 +59133,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -57686,7 +59245,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -57798,7 +59357,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -57909,7 +59468,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -57919,7 +59478,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -57989,7 +59548,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -58025,7 +59584,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -58136,7 +59695,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -58252,7 +59811,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -58363,7 +59922,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -58373,7 +59932,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -58443,7 +60002,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -58479,7 +60038,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -58489,7 +60048,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -58564,7 +60123,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -58600,7 +60159,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -58716,7 +60275,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -58832,7 +60391,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -58943,7 +60502,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -58953,7 +60512,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -59023,7 +60582,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -59059,7 +60618,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -59069,7 +60628,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -59139,7 +60698,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -59175,7 +60734,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -59185,7 +60744,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -59260,7 +60819,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -59296,7 +60855,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -59306,7 +60865,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -59376,7 +60935,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -59412,7 +60971,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -59422,7 +60981,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -59497,7 +61056,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -59533,7 +61092,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -59644,7 +61203,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -59760,7 +61319,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -59841,7 +61400,7 @@
       },
       "io_timeout_ns": 32400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -59877,7 +61436,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -59958,7 +61517,7 @@
       },
       "io_timeout_ns": 32400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -59994,7 +61553,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -60075,6 +61634,1040 @@
       },
       "io_timeout_ns": 32400000000000,
       "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/clang_linux",
+          "path": "clang_linux",
+          "version": "version:13"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug-ASAN"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/linux_vulkan_sdk",
+          "path": "linux_vulkan_sdk",
+          "version": "version:1"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/clang_linux",
+          "path": "clang_linux",
+          "version": "version:13"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug-ASAN"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/clang_linux",
+          "path": "clang_linux",
+          "version": "version:13"
+        },
+        {
+          "name": "skia/bots/linux_vulkan_sdk",
+          "path": "linux_vulkan_sdk",
+          "version": "version:1"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_ASAN_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug-ASAN_Vulkan"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 1,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/linux_vulkan_sdk",
+          "path": "linux_vulkan_sdk",
+          "version": "version:1"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        },
+        {
+          "name": "skia/bots/linux_vulkan_sdk",
+          "path": "linux_vulkan_sdk",
+          "version": "version:1"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Debian9-Clang-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:10de:1cb3-390.87",
+        "os:Ubuntu-18.04",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
       "max_attempts": 2,
       "outputs": [
         "test"
@@ -60111,7 +62704,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -60222,7 +62815,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -60232,7 +62825,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -60302,7 +62895,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -60338,7 +62931,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -60348,7 +62941,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -60418,7 +63011,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -60454,7 +63047,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -60464,7 +63057,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -60539,7 +63132,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -60575,7 +63168,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -60585,7 +63178,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         }
       ],
       "command": [
@@ -60655,7 +63248,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -60691,7 +63284,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -60701,7 +63294,7 @@
         {
           "name": "skia/bots/clang_linux",
           "path": "clang_linux",
-          "version": "version:12"
+          "version": "version:13"
         },
         {
           "name": "skia/bots/linux_vulkan_sdk",
@@ -60776,7 +63369,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -60812,7 +63405,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -60923,7 +63516,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -61044,7 +63637,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -61100,7 +63693,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -61160,7 +63753,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -61216,7 +63809,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -61276,7 +63869,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -61332,7 +63925,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -61392,7 +63985,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -61448,7 +64041,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -61508,7 +64101,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -61564,7 +64157,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -61624,7 +64217,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -61680,7 +64273,7 @@
       ],
       "dimensions": [
         "gpu:1002:6646-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -61740,7 +64333,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -61856,7 +64449,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -61972,7 +64565,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -62088,7 +64681,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -62204,7 +64797,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -62320,7 +64913,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -62436,7 +65029,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -62552,7 +65145,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -62668,7 +65261,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -62789,7 +65382,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -62905,7 +65498,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -63021,7 +65614,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -63137,7 +65730,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -63253,7 +65846,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -63369,7 +65962,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -63490,7 +66083,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -63546,7 +66139,7 @@
       ],
       "dimensions": [
         "cpu:x86-64-i7-5557U",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -63606,7 +66199,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -63662,7 +66255,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -63722,7 +66315,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -63778,7 +66371,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -63838,7 +66431,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -63894,7 +66487,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -63954,7 +66547,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -64010,7 +66603,7 @@
       ],
       "dimensions": [
         "gpu:8086:162b-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -64070,7 +66663,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -64126,7 +66719,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -64186,7 +66779,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -64242,7 +66835,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -64302,7 +66895,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -64358,7 +66951,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -64418,7 +67011,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -64474,7 +67067,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -64534,7 +67127,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -64590,7 +67183,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -64650,7 +67243,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -64706,7 +67299,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -64766,7 +67359,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -64822,7 +67415,7 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -64882,7 +67475,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -64938,7 +67531,819 @@
       ],
       "dimensions": [
         "gpu:8086:1926-25.20.100.6444",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Debug"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Debug-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-OpenCL": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "compute_test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-OpenCL\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Debug-OpenCL"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Debug-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Release"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Release-ANGLE"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 14400000000000,
+      "expiration_ns": 72000000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 14400000000000,
+      "isolate": "test_skia_bundled.isolate",
+      "max_attempts": 2,
+      "outputs": [
+        "test"
+      ]
+    },
+    "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "skia/bots/skimage",
+          "path": "skimage",
+          "version": "version:39"
+        },
+        {
+          "name": "skia/bots/skp",
+          "path": "skp",
+          "version": "version:160"
+        },
+        {
+          "name": "skia/bots/svg",
+          "path": "svg",
+          "version": "version:9"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "test",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"gold_hashes_url\":\"https://storage.googleapis.com/skia-infra-gm/hash_files/gold-prod-hashes.txt\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"test\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Build-Win-Clang-x86_64-Release-Vulkan"
+      ],
+      "dimensions": [
+        "gpu:8086:3ea5-25.20.100.6444",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -64998,7 +68403,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -65054,7 +68459,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65114,7 +68519,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -65170,7 +68575,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65230,7 +68635,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -65286,7 +68691,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65346,7 +68751,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -65402,7 +68807,7 @@
       ],
       "dimensions": [
         "gpu:8086:0a16-20.19.15.4963",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65462,7 +68867,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -65518,7 +68923,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65578,7 +68983,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -65634,7 +69039,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65694,7 +69099,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -65750,7 +69155,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65810,7 +69215,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -65866,7 +69271,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -65926,7 +69331,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -65982,7 +69387,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -66042,7 +69447,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -66098,7 +69503,7 @@
       ],
       "dimensions": [
         "gpu:10de:11c0-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -66158,7 +69563,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -66214,7 +69619,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -66274,7 +69679,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -66330,7 +69735,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -66390,7 +69795,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -66446,7 +69851,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -66506,7 +69911,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -66562,7 +69967,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -66622,7 +70027,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -66678,7 +70083,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -66738,7 +70143,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -66794,7 +70199,7 @@
       ],
       "dimensions": [
         "gpu:1002:683d-24.20.13001.1010",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -66854,7 +70259,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -66910,7 +70315,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -66970,7 +70375,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -67026,7 +70431,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -67086,7 +70491,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -67142,7 +70547,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -67202,7 +70607,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -67258,7 +70663,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -67318,7 +70723,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -67374,7 +70779,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -67434,7 +70839,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -67490,7 +70895,7 @@
       ],
       "dimensions": [
         "gpu:10de:1401-25.21.14.1634",
-        "os:Windows-10-17134.407",
+        "os:Windows-10-17763.195",
         "pool:Skia"
       ],
       "env_prefixes": {
@@ -67550,7 +70955,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -67666,7 +71071,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -67782,7 +71187,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -67898,7 +71303,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -68014,7 +71419,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -68132,7 +71537,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -68250,7 +71655,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -68368,7 +71773,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -68486,7 +71891,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -68604,7 +72009,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -68722,7 +72127,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -68799,7 +72204,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -68840,7 +72245,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -68958,7 +72363,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -69076,7 +72481,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -69194,7 +72599,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -69312,7 +72717,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -69389,7 +72794,7 @@
       },
       "io_timeout_ns": 14400000000000,
       "isolate": "test_skia_bundled.isolate",
-      "max_attempts": 2,
+      "max_attempts": 1,
       "outputs": [
         "test"
       ]
@@ -69430,7 +72835,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -69548,7 +72953,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -69666,7 +73071,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -69784,7 +73189,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -69902,7 +73307,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -70018,7 +73423,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -70134,7 +73539,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -70250,7 +73655,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -70366,7 +73771,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -70482,7 +73887,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -70598,7 +74003,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -70714,7 +74119,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -70830,7 +74235,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -70946,7 +74351,7 @@
         {
           "name": "skia/bots/skp",
           "path": "skp",
-          "version": "version:154"
+          "version": "version:160"
         },
         {
           "name": "skia/bots/svg",
@@ -71811,6 +75216,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-binary-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Build-Debian9-Clang-arm64-Release-Android": {
@@ -71905,6 +75311,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-binary-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Build-Debian9-Clang-x64-Release-Android": {
@@ -71999,6 +75406,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-binary-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Build-Debian9-Clang-x86-Release-Android": {
@@ -72093,6 +75501,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-binary-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-BuildStats-Debian9-Clang-arm-Release-Flutter_Android": {
@@ -72192,6 +75601,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-BuildStats-Debian9-EMCC-asmjs-Release-PathKit": {
@@ -72291,6 +75701,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-BuildStats-Debian9-EMCC-wasm-Release-CanvasKit": {
@@ -72390,6 +75801,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-BuildStats-Debian9-EMCC-wasm-Release-CanvasKit_CPU": {
@@ -72489,6 +75901,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-BuildStats-Debian9-EMCC-wasm-Release-PathKit": {
@@ -72588,6 +76001,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All": {
@@ -72687,6 +76101,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-calmbench-upload@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
@@ -72786,6 +76201,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-calmbench-upload@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android": {
@@ -72885,6 +76301,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android": {
@@ -72984,6 +76401,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NoGPUThreads": {
@@ -73083,6 +76501,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android": {
@@ -73182,6 +76601,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan": {
@@ -73281,6 +76701,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android": {
@@ -73380,6 +76801,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android_Vulkan": {
@@ -73479,6 +76901,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android": {
@@ -73578,6 +77001,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android": {
@@ -73677,6 +77101,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android_NoGPUThreads": {
@@ -73776,6 +77201,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android": {
@@ -73875,6 +77301,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan": {
@@ -73974,6 +77401,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan_NoGPUThreads": {
@@ -74073,6 +77501,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Release-All-Android": {
@@ -74172,6 +77601,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android": {
@@ -74271,6 +77701,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Release-All-Android": {
@@ -74370,6 +77801,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan": {
@@ -74469,6 +77901,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android": {
@@ -74568,6 +78001,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan": {
@@ -74667,6 +78101,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android": {
@@ -74766,6 +78201,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android": {
@@ -74865,6 +78301,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android": {
@@ -74964,6 +78401,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android": {
@@ -75063,6 +78501,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_CCPR_Skpbench": {
@@ -75162,6 +78601,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Skpbench": {
@@ -75261,6 +78701,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan": {
@@ -75360,6 +78801,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan_Skpbench": {
@@ -75459,6 +78901,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android": {
@@ -75558,6 +79001,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_CCPR_Skpbench": {
@@ -75657,6 +79101,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Skpbench": {
@@ -75756,6 +79201,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan": {
@@ -75855,6 +79301,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan_Skpbench": {
@@ -75954,6 +79401,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All": {
@@ -76053,6 +79501,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Release-All": {
@@ -76152,6 +79601,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Release-All": {
@@ -76251,6 +79701,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Release-All": {
@@ -76350,6 +79801,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Release-All": {
@@ -76449,6 +79901,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All": {
@@ -76548,6 +80001,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All": {
@@ -76647,6 +80101,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All": {
@@ -76746,6 +80201,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All": {
@@ -76845,6 +80301,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs": {
@@ -76944,6 +80401,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Fast": {
@@ -77043,6 +80501,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER": {
@@ -77142,6 +80601,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-Clang-GCE-CPU-AVX512-x86_64-Release-All": {
@@ -77241,6 +80701,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All": {
@@ -77340,6 +80801,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan": {
@@ -77439,6 +80901,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All": {
@@ -77538,6 +81001,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-Vulkan": {
@@ -77637,6 +81101,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Release-All": {
@@ -77736,6 +81201,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All": {
@@ -77835,6 +81301,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit": {
@@ -77934,6 +81401,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit": {
@@ -78033,6 +81501,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit": {
@@ -78132,6 +81601,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit": {
@@ -78231,6 +81701,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
@@ -78330,6 +81801,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer": {
@@ -78429,6 +81901,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
@@ -78528,6 +82001,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All-CommandBuffer": {
@@ -78627,6 +82101,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
@@ -78726,6 +82201,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
@@ -78825,6 +82301,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-CommandBuffer": {
@@ -78924,6 +82401,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
@@ -79023,6 +82501,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
@@ -79122,6 +82601,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer": {
@@ -79221,6 +82701,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
@@ -79320,6 +82801,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
@@ -79419,6 +82901,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
@@ -79518,6 +83001,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
@@ -79617,6 +83101,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All": {
@@ -79721,6 +83206,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-ANGLE": {
@@ -79825,6 +83311,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-Vulkan": {
@@ -79929,6 +83416,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All": {
@@ -80033,6 +83521,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All-ANGLE": {
@@ -80137,6 +83626,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
@@ -80241,6 +83731,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE": {
@@ -80345,6 +83836,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
@@ -80449,6 +83941,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench": {
@@ -80553,6 +84046,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLRecord_9x9": {
@@ -80657,6 +84151,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLTotal_9x9": {
@@ -80761,6 +84256,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All": {
@@ -80865,6 +84361,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE": {
@@ -80969,6 +84466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All": {
@@ -81073,6 +84571,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE": {
@@ -81177,6 +84676,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan": {
@@ -81281,6 +84781,322 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_nano_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_nano_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_nano_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-perf\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Perf-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All": {
@@ -81385,6 +85201,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE": {
@@ -81489,6 +85306,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All": {
@@ -81593,6 +85411,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-ANGLE": {
@@ -81697,6 +85516,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan": {
@@ -81801,6 +85621,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All": {
@@ -81905,6 +85726,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-ANGLE": {
@@ -82009,6 +85831,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Vulkan": {
@@ -82113,6 +85936,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All": {
@@ -82217,6 +86041,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-ANGLE": {
@@ -82321,6 +86146,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-Vulkan": {
@@ -82425,6 +86251,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All": {
@@ -82529,6 +86356,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
@@ -82633,6 +86461,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All": {
@@ -82737,6 +86566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All": {
@@ -82836,6 +86666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Release-All": {
@@ -82935,6 +86766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Perf-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Release-All": {
@@ -83034,6 +86866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-nano-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Debug-All-Android": {
@@ -83133,6 +86966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android": {
@@ -83232,6 +87066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android": {
@@ -83331,6 +87166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Debug-All-Android_NoGPUThreads": {
@@ -83430,6 +87266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android": {
@@ -83529,6 +87366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NativeFonts": {
@@ -83628,6 +87466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS6-GPU-MaliT760-arm64-Release-All-Android_NoGPUThreads": {
@@ -83727,6 +87566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android": {
@@ -83826,6 +87666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android_Vulkan": {
@@ -83925,6 +87766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android": {
@@ -84024,6 +87866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Release-All-Android_Vulkan": {
@@ -84123,6 +87966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android": {
@@ -84222,6 +88066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Debug-All-Android_Vulkan": {
@@ -84321,6 +88166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android": {
@@ -84420,6 +88266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-GalaxyS9-GPU-MaliG72-arm64-Release-All-Android_Vulkan": {
@@ -84519,6 +88366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android": {
@@ -84618,6 +88466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Debug-All-Android_NativeFonts": {
@@ -84717,6 +88566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-MotoG4-CPU-Snapdragon617-arm-Release-All-Android": {
@@ -84816,6 +88666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android": {
@@ -84915,6 +88766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Debug-All-Android_NoGPUThreads": {
@@ -85014,6 +88866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android": {
@@ -85113,6 +88966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-MotoG4-GPU-Adreno405-arm-Release-All-Android_NoGPUThreads": {
@@ -85212,6 +89066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android": {
@@ -85311,6 +89166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_CCPR": {
@@ -85410,6 +89266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan": {
@@ -85509,6 +89366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Debug-All-Android_Vulkan_NoGPUThreads": {
@@ -85608,6 +89466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android": {
@@ -85707,6 +89566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan": {
@@ -85806,6 +89666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-NVIDIA_Shield-GPU-TegraX1-arm64-Release-All-Android_Vulkan_NoGPUThreads": {
@@ -85905,6 +89766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Debug-All-Android": {
@@ -86004,6 +89866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5-CPU-Snapdragon800-arm-Release-All-Android": {
@@ -86103,6 +89966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Debug-All-Android": {
@@ -86202,6 +90066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5-GPU-Adreno330-arm-Release-All-Android": {
@@ -86301,6 +90166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Debug-All-Android": {
@@ -86400,6 +90266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5x-CPU-Snapdragon808-arm64-Release-All-Android": {
@@ -86499,6 +90366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Debug-All-Android_Vulkan": {
@@ -86598,6 +90466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5x-GPU-Adreno418-arm-Release-All-Android_Vulkan": {
@@ -86697,6 +90566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android": {
@@ -86796,6 +90666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android_Vulkan": {
@@ -86895,6 +90766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android": {
@@ -86994,6 +90866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_Vulkan": {
@@ -87093,6 +90966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Debug-All-Android": {
@@ -87192,6 +91066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus7-CPU-Tegra3-arm-Release-All-Android": {
@@ -87291,6 +91166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Debug-All-Android": {
@@ -87390,6 +91266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Nexus7-GPU-Tegra3-arm-Release-All-Android": {
@@ -87489,6 +91366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Debug-All-Android": {
@@ -87588,6 +91466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-NexusPlayer-CPU-Moorefield-x86-Release-All-Android": {
@@ -87687,6 +91566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Debug-All-Android": {
@@ -87786,6 +91666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-NexusPlayer-GPU-PowerVRG6430-x86-Release-All-Android": {
@@ -87885,6 +91766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android": {
@@ -87984,6 +91866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_CCPR": {
@@ -88083,6 +91966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Debug-All-Android_Vulkan": {
@@ -88182,6 +92066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android": {
@@ -88281,6 +92166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Pixel-GPU-Adreno530-arm64-Release-All-Android_Vulkan": {
@@ -88380,6 +92266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android": {
@@ -88479,6 +92366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_DDL1_Vulkan": {
@@ -88578,6 +92466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_DDL3_Vulkan": {
@@ -88677,6 +92566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Debug-All-Android_Vulkan": {
@@ -88776,6 +92666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android": {
@@ -88875,6 +92766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Android-Clang-Pixel2XL-GPU-Adreno540-arm64-Release-All-Android_Vulkan": {
@@ -88974,6 +92866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Debug-All": {
@@ -89073,6 +92966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-ASUSChromebookFlipC100-GPU-MaliT764-arm-Release-All": {
@@ -89172,6 +93066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Debug-All": {
@@ -89271,6 +93166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-AcerChromebook13_CB5_311-GPU-TegraK1-arm-Release-All": {
@@ -89370,6 +93266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All": {
@@ -89469,6 +93366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Release-All": {
@@ -89568,6 +93466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Debug-All": {
@@ -89667,6 +93566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-Pixelbook-GPU-IntelHDGraphics615-x86_64-Release-All": {
@@ -89766,6 +93666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Debug-All": {
@@ -89865,6 +93766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-SamsungChromebook2012-GPU-MaliT604-arm-Release-All": {
@@ -89964,6 +93866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Debug-All": {
@@ -90063,6 +93966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All": {
@@ -90162,6 +94066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Debug-All": {
@@ -90261,6 +94166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All": {
@@ -90360,6 +94266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Debug-All": {
@@ -90459,6 +94366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All": {
@@ -90558,6 +94466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86-Debug-All": {
@@ -90657,6 +94566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All": {
@@ -90756,6 +94666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs": {
@@ -90855,6 +94766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
@@ -90954,6 +94866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SK_USE_DISCARDABLE_SCALEDIMAGECACHE": {
@@ -91053,6 +94966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-SafeStack": {
@@ -91152,6 +95066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All": {
@@ -91251,6 +95166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-BonusConfigs": {
@@ -91350,6 +95266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Fast": {
@@ -91449,6 +95366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SKNX_NO_SIMD": {
@@ -91548,6 +95466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_CPU_LIMIT_SSE2": {
@@ -91647,6 +95566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_CPU_LIMIT_SSE41": {
@@ -91746,6 +95666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SK_FORCE_RASTER_PIPELINE_BLITTER": {
@@ -91845,6 +95766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX512-x86_64-Debug-All": {
@@ -91944,6 +95866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-CPU-AVX512-x86_64-Release-All": {
@@ -92043,6 +95966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Debug-All-SwiftShader": {
@@ -92142,6 +96066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-GCE-GPU-SwiftShader-x86_64-Release-All-SwiftShader": {
@@ -92241,6 +96166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All": {
@@ -92340,6 +96266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Debug-All-Vulkan": {
@@ -92439,6 +96366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All": {
@@ -92538,6 +96466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-NUC5PPYH-GPU-IntelHD405-x86_64-Release-All-Vulkan": {
@@ -92637,6 +96566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All": {
@@ -92736,6 +96666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan": {
@@ -92835,6 +96766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All": {
@@ -92934,6 +96866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Release-All-Vulkan": {
@@ -93033,6 +96966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Debug-All": {
@@ -93132,6 +97066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-NUCDE3815TYKHE-GPU-IntelBayTrail-x86_64-Release-All": {
@@ -93231,6 +97166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Debug-All": {
@@ -93330,6 +97266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-Clang-ShuttleA-GPU-IntelHD2000-x86_64-Release-All": {
@@ -93429,6 +97366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Debug-All-PathKit": {
@@ -93528,6 +97466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit": {
@@ -93627,6 +97566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-CanvasKit": {
@@ -93726,6 +97666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Debug-All-PathKit": {
@@ -93825,6 +97766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-CanvasKit": {
@@ -93924,6 +97866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit": {
@@ -94023,6 +97966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Debug-All-CanvasKit": {
@@ -94122,6 +98066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit": {
@@ -94221,6 +98166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-GCC-GCE-CPU-AVX2-x86-Debug-All": {
@@ -94320,6 +98266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-GCC-GCE-CPU-AVX2-x86-Release-All": {
@@ -94419,6 +98366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Debug-All": {
@@ -94518,6 +98466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Debian9-GCC-GCE-CPU-AVX2-x86_64-Release-All": {
@@ -94617,6 +98566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All": {
@@ -94716,6 +98666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Debug-All-CommandBuffer": {
@@ -94815,6 +98766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All": {
@@ -94914,6 +98866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-NativeFonts": {
@@ -95013,6 +98966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All": {
@@ -95112,6 +99066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All-CommandBuffer": {
@@ -95211,6 +99166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Release-All": {
@@ -95310,6 +99266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All": {
@@ -95409,6 +99366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
@@ -95508,6 +99466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Release-All": {
@@ -95607,6 +99566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All": {
@@ -95706,6 +99666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-CommandBuffer": {
@@ -95805,6 +99766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal": {
@@ -95904,6 +99866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-MoltenVK_Vulkan": {
@@ -96003,6 +99966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All": {
@@ -96102,6 +100066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal": {
@@ -96201,6 +100166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-MoltenVK_Vulkan": {
@@ -96300,6 +100266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All": {
@@ -96399,6 +100366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer": {
@@ -96498,6 +100466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Mac-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All": {
@@ -96597,6 +100566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
@@ -96696,6 +100666,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1": {
@@ -96795,6 +100766,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan": {
@@ -96894,6 +100866,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3": {
@@ -96993,6 +100966,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan": {
@@ -97092,6 +101066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
@@ -97191,6 +101166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
@@ -97290,6 +101266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
@@ -97389,6 +101366,607 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1_Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3_Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
@@ -97488,6 +102066,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
@@ -97587,6 +102166,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All": {
@@ -97691,6 +102271,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-ANGLE": {
@@ -97795,6 +102376,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Debug-All-Vulkan": {
@@ -97899,6 +102481,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All": {
@@ -98003,6 +102586,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-ANGLE": {
@@ -98107,6 +102691,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-AlphaR2-GPU-RadeonR9M470X-x86_64-Release-All-Vulkan": {
@@ -98211,6 +102796,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All": {
@@ -98315,6 +102901,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-GT610-x86_64-Debug-All-ANGLE": {
@@ -98419,6 +103006,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All": {
@@ -98523,6 +103111,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-GT610-x86_64-Release-All-ANGLE": {
@@ -98627,6 +103216,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All": {
@@ -98731,6 +103321,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ANGLE": {
@@ -98835,6 +103426,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-BonusConfigs": {
@@ -98939,6 +103531,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
@@ -99043,6 +103636,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_ProcDump": {
@@ -99147,6 +103741,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All": {
@@ -99251,6 +103846,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE": {
@@ -99355,6 +103951,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs": {
@@ -99459,6 +104056,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
@@ -99563,6 +104161,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_ProcDump": {
@@ -99667,6 +104266,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts": {
@@ -99771,6 +104371,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All": {
@@ -99875,6 +104476,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Debug-All-ANGLE": {
@@ -99979,6 +104581,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All": {
@@ -100083,6 +104686,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC5i7RYH-GPU-IntelIris6100-x86_64-Release-All-ANGLE": {
@@ -100187,6 +104791,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All": {
@@ -100291,6 +104896,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-ANGLE": {
@@ -100395,6 +105001,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Debug-All-Vulkan": {
@@ -100499,6 +105106,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All": {
@@ -100603,6 +105211,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-ANGLE": {
@@ -100707,6 +105316,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-NativeFonts": {
@@ -100811,6 +105421,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-Vulkan": {
@@ -100915,6 +105526,637 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-ANGLE"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Debug-All-Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-ANGLE"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
+      "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
+    },
+    "Upload-Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan": {
+      "caches": [
+        {
+          "name": "vpython",
+          "path": "cache/vpython"
+        }
+      ],
+      "cipd_packages": [
+        {
+          "name": "infra/tools/luci/kitchen/${platform}",
+          "path": ".",
+          "version": "git_revision:546aae39f1fb9dce9add528e2011afa574535ecd"
+        },
+        {
+          "name": "infra/tools/luci-auth/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c"
+        },
+        {
+          "name": "infra/tools/luci/vpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc"
+        },
+        {
+          "name": "infra/python/cpython/${platform}",
+          "path": "cipd_bin_packages",
+          "version": "version:2.7.14.chromium14"
+        },
+        {
+          "name": "infra/gsutil",
+          "path": "cipd_bin_packages",
+          "version": "version:4.28"
+        }
+      ],
+      "command": [
+        "./kitchen${EXECUTABLE_SUFFIX}",
+        "cook",
+        "-checkout-dir",
+        "recipe_bundle",
+        "-mode",
+        "swarming",
+        "-luci-system-account",
+        "system",
+        "-cache-dir",
+        "cache",
+        "-temp-dir",
+        "tmp",
+        "-known-gerrit-host",
+        "android.googlesource.com",
+        "-known-gerrit-host",
+        "boringssl.googlesource.com",
+        "-known-gerrit-host",
+        "chromium.googlesource.com",
+        "-known-gerrit-host",
+        "dart.googlesource.com",
+        "-known-gerrit-host",
+        "fuchsia.googlesource.com",
+        "-known-gerrit-host",
+        "go.googlesource.com",
+        "-known-gerrit-host",
+        "llvm.googlesource.com",
+        "-known-gerrit-host",
+        "skia.googlesource.com",
+        "-known-gerrit-host",
+        "webrtc.googlesource.com",
+        "-output-result-json",
+        "${ISOLATED_OUTDIR}/build_result_filename",
+        "-workdir",
+        ".",
+        "-recipe",
+        "upload_dm_results",
+        "-properties",
+        "{\"$kitchen\":{\"devshell\":true,\"git_auth\":true},\"buildbucket_build_id\":\"<(BUILDBUCKET_BUILD_ID)\",\"buildername\":\"Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan\",\"gs_bucket\":\"skia-infra-gm\",\"patch_issue\":\"<(ISSUE)\",\"patch_ref\":\"<(PATCH_REF)\",\"patch_repo\":\"<(PATCH_REPO)\",\"patch_set\":\"<(PATCHSET)\",\"patch_storage\":\"<(PATCH_STORAGE)\",\"repository\":\"<(REPO)\",\"revision\":\"<(REVISION)\",\"swarm_out_dir\":\"output_ignored\"}",
+        "-logdog-annotation-url",
+        "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      ],
+      "dependencies": [
+        "Housekeeper-PerCommit-BundleRecipes",
+        "Test-Win10-Clang-NUC8i5BEK-GPU-IntelIris655-x86_64-Release-All-Vulkan"
+      ],
+      "dimensions": [
+        "cpu:x86-64-Haswell_GCE",
+        "gpu:none",
+        "machine_type:n1-highmem-2",
+        "os:Debian-9.4",
+        "pool:Skia"
+      ],
+      "env_prefixes": {
+        "PATH": [
+          "cipd_bin_packages",
+          "cipd_bin_packages/bin"
+        ],
+        "VPYTHON_VIRTUALENV_ROOT": [
+          "cache/vpython"
+        ]
+      },
+      "execution_timeout_ns": 3600000000000,
+      "extra_tags": {
+        "log_location": "logdog://logs.chromium.org/skia/<(TASK_ID)/+/annotations"
+      },
+      "io_timeout_ns": 3600000000000,
+      "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All": {
@@ -101019,6 +106261,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Debug-All-ANGLE": {
@@ -101123,6 +106366,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All": {
@@ -101227,6 +106471,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-NUCD34010WYKH-GPU-IntelHD4400-x86_64-Release-All-ANGLE": {
@@ -101331,6 +106576,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All": {
@@ -101435,6 +106681,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-ANGLE": {
@@ -101539,6 +106786,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Debug-All-Vulkan": {
@@ -101643,6 +106891,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All": {
@@ -101747,6 +106996,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-ANGLE": {
@@ -101851,6 +107101,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan": {
@@ -101955,6 +107206,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All": {
@@ -102059,6 +107311,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-ANGLE": {
@@ -102163,6 +107416,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Debug-All-Vulkan": {
@@ -102267,6 +107521,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All": {
@@ -102371,6 +107626,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-ANGLE": {
@@ -102475,6 +107731,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Vulkan": {
@@ -102579,6 +107836,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All": {
@@ -102683,6 +107941,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE": {
@@ -102787,6 +108046,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-Vulkan": {
@@ -102891,6 +108151,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All": {
@@ -102995,6 +108256,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-ANGLE": {
@@ -103099,6 +108361,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Release-All-Vulkan": {
@@ -103203,6 +108466,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All": {
@@ -103307,6 +108571,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan": {
@@ -103411,6 +108676,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All": {
@@ -103515,6 +108781,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win10-MSVC-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan": {
@@ -103619,6 +108886,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86-Debug-All": {
@@ -103723,6 +108991,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86-Release-All": {
@@ -103827,6 +109096,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All": {
@@ -103931,6 +109201,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA": {
@@ -104035,6 +109306,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FDAA": {
@@ -104139,6 +109411,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA": {
@@ -104243,6 +109516,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All": {
@@ -104347,6 +109621,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FAAA": {
@@ -104451,6 +109726,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FDAA": {
@@ -104555,6 +109831,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Release-All-FSAA": {
@@ -104659,6 +109936,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-MSVC-GCE-CPU-AVX2-x86-Debug-All": {
@@ -104763,6 +110041,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-MSVC-GCE-CPU-AVX2-x86-Release-All": {
@@ -104867,6 +110146,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Debug-All": {
@@ -104971,6 +110251,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win2016-MSVC-GCE-CPU-AVX2-x86_64-Release-All": {
@@ -105075,6 +110356,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win7-Clang-Golo-CPU-AVX-x86-Debug-All": {
@@ -105179,6 +110461,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win7-Clang-Golo-CPU-AVX-x86-Release-All": {
@@ -105283,6 +110566,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All": {
@@ -105387,6 +110671,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All-NativeFonts": {
@@ -105491,6 +110776,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win7-Clang-Golo-CPU-AVX-x86_64-Debug-All-NativeFonts_GDI": {
@@ -105595,6 +110881,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win7-Clang-Golo-CPU-AVX-x86_64-Release-All": {
@@ -105699,6 +110986,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win8-Clang-Golo-CPU-AVX-x86-Debug-All": {
@@ -105803,6 +111091,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win8-Clang-Golo-CPU-AVX-x86-Release-All": {
@@ -105907,6 +111196,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win8-Clang-Golo-CPU-AVX-x86_64-Debug-All": {
@@ -106011,6 +111301,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-Win8-Clang-Golo-CPU-AVX-x86_64-Release-All": {
@@ -106115,6 +111406,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Debug-All": {
@@ -106214,6 +111506,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All": {
@@ -106313,6 +111606,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Debug-All": {
@@ -106412,6 +111706,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Release-All": {
@@ -106511,6 +111806,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All": {
@@ -106610,6 +111906,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Debug-All-Metal": {
@@ -106709,6 +112006,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     },
     "Upload-Test-iOS-Clang-iPhone7-GPU-PowerVRGT7600-arm64-Release-All": {
@@ -106808,6 +112106,7 @@
       },
       "io_timeout_ns": 3600000000000,
       "isolate": "swarm_recipe.isolate",
+      "max_attempts": 2,
       "service_account": "skia-external-gm-uploader@skia-swarming-bots.iam.gserviceaccount.com"
     }
   }
diff --git a/infra/bots/win_toolchain_utils.py b/infra/bots/win_toolchain_utils.py
deleted file mode 100644
index f8033e9..0000000
--- a/infra/bots/win_toolchain_utils.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2016 Google Inc.
-#
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-
-"""Utilities for manipulating the win_toolchain.json file."""
-
-
-import json
-import os
-import stat
-
-
-PLACEHOLDER = '<(TOOLCHAIN_BASE_DIR)'
-
-
-def _replace_prefix(val, before, after):
-  """Replace the given prefix with the given string."""
-  if val.startswith(before):
-    return val.replace(before, after, 1)
-  return val
-
-
-def _replace(val, before, after):
-  """Replace occurrences of one string with another within the data."""
-  if isinstance(val, basestring):
-    return _replace_prefix(val, before, after)
-  elif isinstance(val, (list, tuple)):
-    return [_replace(elem, before, after) for elem in val]
-  elif isinstance(val, dict):
-    return {_replace(k, before, after):
-            _replace(v, before, after) for k, v in val.iteritems()}
-  raise Exception('Cannot replace variable: %s' % val)
-
-
-def _replace_in_file(filename, before, after):
-  """Replace occurrences of one string with another within the file."""
-  # Make the file writeable, or the below won't work.
-  os.chmod(filename, stat.S_IWRITE)
-
-  with open(filename) as f:
-    contents = json.load(f)
-  new_contents = _replace(contents, before, after)
-  with open(filename, 'w') as f:
-    json.dump(new_contents, f)
-
-
-def abstract(win_toolchain_json, old_path):
-  """Replace absolute paths in win_toolchain.json with placeholders."""
-  _replace_in_file(win_toolchain_json, old_path, PLACEHOLDER)
-
-
-def resolve(win_toolchain_json, new_path):
-  """Replace placeholders in win_toolchain.json with absolute paths."""
-  _replace_in_file(win_toolchain_json, PLACEHOLDER, new_path)
diff --git a/infra/branch-config/cq.cfg b/infra/branch-config/cq.cfg
index d843067..03e594b 100644
--- a/infra/branch-config/cq.cfg
+++ b/infra/branch-config/cq.cfg
@@ -34,6 +34,7 @@
       builders { name: "Build-Debian9-Clang-cf_x86_phone-eng-Android_Framework" }
       builders { name: "Build-Debian9-Clang-host-sdk-Android_Framework" }
       builders { name: "Build-Debian9-Clang-x86_64-Debug" }
+      builders { name: "Build-Debian9-Clang-x86_64-Debug-Tidy" }
       builders { name: "Build-Debian9-GCC-x86_64-Debug-NoGPU" }
       builders { name: "Build-Debian9-GCC-x86_64-Release" }
       builders { name: "Build-Mac-Clang-arm64-Debug-iOS" }
@@ -52,6 +53,7 @@
       builders { name: "Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs" }
       builders { name: "Test-Debian9-EMCC-GCE-CPU-AVX2-asmjs-Release-All-PathKit" }
       builders { name: "Test-Debian9-EMCC-GCE-CPU-AVX2-wasm-Release-All-PathKit" }
+      builders { name: "Test-Debian9-EMCC-GCE-GPU-AVX2-wasm-Release-All-CanvasKit" }
       builders { name: "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-ASAN" }
       builders { name: "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan" }
       builders { name: "Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All" }
diff --git a/infra/config/recipes.cfg b/infra/config/recipes.cfg
index 859924b..c263ce5 100644
--- a/infra/config/recipes.cfg
+++ b/infra/config/recipes.cfg
@@ -14,12 +14,12 @@
   "deps": {
     "depot_tools": {
       "branch": "master",
-      "revision": "dd530811aba99d0ae85136dbd01bcf81ce3e3b5f",
+      "revision": "4157ba1c3c7a78d10887b50fbeda06f1bfb34860",
       "url": "https://chromium.googlesource.com/chromium/tools/depot_tools.git"
     },
     "recipe_engine": {
       "branch": "master",
-      "revision": "d0deba23a671b236ec535a9a54aa78b6dcf142ab",
+      "revision": "3535e529c7b925fb486cfaccc1f89e739402d54b",
       "url": "https://chromium.googlesource.com/infra/luci/recipes-py.git"
     }
   },
diff --git a/infra/skqp/build_apk.sh b/infra/skqp/build_apk.sh
index d189002..d307f65 100755
--- a/infra/skqp/build_apk.sh
+++ b/infra/skqp/build_apk.sh
@@ -9,4 +9,4 @@
     rm -rf /OUT/*  # Clean out previous builds
     export SKQP_OUTPUT_DIR=/OUT
 fi
-"$(dirname "$0")"/../../tools/skqp/make_universal_apk.py x86
+"$(dirname "$0")"/../../tools/skqp/make_universal_apk.py arm arm64 x86 x64
diff --git a/infra/skqp/run_skqp.sh b/infra/skqp/run_skqp.sh
index 8f76200..8cce375 100755
--- a/infra/skqp/run_skqp.sh
+++ b/infra/skqp/run_skqp.sh
@@ -21,7 +21,7 @@
 # Some extra sleep to make sure the emulator is awake and ready for installation
 sleep 10
 
-adb install -r /OUT/skqp-x86-debug.apk
+adb install -r /OUT/skqp-universal-debug.apk
 adb logcat -c
 
 tmp_file="$(mktemp "${TMPDIR:-/tmp}/skqp.XXXXXXXXXX")"
@@ -30,5 +30,7 @@
 
 adb logcat -d TestRunner org.skia.skqp skia DEBUG "*:S"
 
-grep -q '^OK ' "$tmp_file"
+if ! grep -q '^OK ' "$tmp_file"; then
+    echo 'this test failed'
+fi
 
diff --git a/modules/pathkit/CHANGELOG.md b/modules/pathkit/CHANGELOG.md
index 4ea69d1..6cde21c 100644
--- a/modules/pathkit/CHANGELOG.md
+++ b/modules/pathkit/CHANGELOG.md
@@ -1,15 +1,24 @@
+# PathKit Changelog
+All notable changes to this project will be documented in this file.
 
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-Trunk
------
+## [Unreleased]
 
-New Features:
+### Fixed
+ - Potential bug in `ready()` if already loaded.
 
+## [0.5.1] 2019-01-04
 
-Bug Fixes:
+### Changed
+ - `PathKitInit(...).then()` is no longer the recommended way to initialize things.
+It will be removed in 0.6.0. Use `PathKitInit(...).ready()`, which returns a real Promise.
 
+## [0.5.0] 2018-12-17
 
-v0.4.2: 2018-11-07
-------------------
+Updated PathKit to use same FOSS license as Skia proper.
+
+## [0.4.2] 2018-11-07
 
 Beginning of changelog.
diff --git a/modules/pathkit/compile.sh b/modules/pathkit/compile.sh
index 6f1e0c2..e019de1 100755
--- a/modules/pathkit/compile.sh
+++ b/modules/pathkit/compile.sh
@@ -120,6 +120,7 @@
 --bind \
 --pre-js $BASE_DIR/helper.js \
 --pre-js $BASE_DIR/chaining.js \
+--post-js $BASE_DIR/ready.js \
 -DSK_DISABLE_READBUFFER=1 \
 -fno-rtti -fno-exceptions -DEMSCRIPTEN_HAS_UNBOUND_TYPE_NAMES=0 \
 $WASM_CONF \
diff --git a/modules/pathkit/npm-asmjs/CODE_OF_CONDUCT.md b/modules/pathkit/npm-asmjs/CODE_OF_CONDUCT.md
index 921d113..0999289 100644
--- a/modules/pathkit/npm-asmjs/CODE_OF_CONDUCT.md
+++ b/modules/pathkit/npm-asmjs/CODE_OF_CONDUCT.md
@@ -69,8 +69,8 @@
 behavior is threatening or harassing, report it. We are dedicated to providing
 an environment where participants feel welcome and safe.
 
-Reports should be directed to [Joe Gregorio](mailto:jcgregorio@google.com), the
-Project Steward(s) for *pulito*. It is the Project Steward’s duty to
+Reports should be directed to [Heather Miller](mailto:hcm@google.com), the
+Project Steward(s) for *pathkit*. It is the Project Steward’s duty to
 receive and address reported violations of the code of conduct. They will then
 work with a committee consisting of representatives from the Open Source
 Programs Office and the Google Open Source Strategy team. If for any reason you
diff --git a/modules/pathkit/npm-asmjs/CONTRIBUTING.md b/modules/pathkit/npm-asmjs/CONTRIBUTING.md
index c980350..5fa3c4a 100644
--- a/modules/pathkit/npm-asmjs/CONTRIBUTING.md
+++ b/modules/pathkit/npm-asmjs/CONTRIBUTING.md
@@ -17,7 +17,5 @@
 
 ## Code reviews
 
-All submissions, including submissions by project members, require review. We
-use GitHub pull requests for this purpose. Consult
-[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
-information on using pull requests.
+All submissions, including submissions by project members, require review.
+Please see the guidelines for contributing code at https://skia.org/dev/contrib/
\ No newline at end of file
diff --git a/modules/pathkit/npm-asmjs/LICENSE b/modules/pathkit/npm-asmjs/LICENSE
index 7a4a3ea..fc53e3e 100644
--- a/modules/pathkit/npm-asmjs/LICENSE
+++ b/modules/pathkit/npm-asmjs/LICENSE
@@ -1,202 +1,29 @@
+// Copyright (c) 2011 Google Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
\ No newline at end of file
+--------------------------------------------------------------------------------
diff --git a/modules/pathkit/npm-asmjs/README.md b/modules/pathkit/npm-asmjs/README.md
index a04007f..ff71af1 100644
--- a/modules/pathkit/npm-asmjs/README.md
+++ b/modules/pathkit/npm-asmjs/README.md
@@ -5,7 +5,7 @@
     <script src="/node_modules/pathkit-asmjs/bin/pathkit.js"></script>
     PathKitInit({
         locateFile: (file) => '/node_modules/pathkit-asmjs/bin/'+file,
-    }).then((PathKit) => {
+    }).ready().then((PathKit) => {
         // Code goes here using PathKit
     });
 
@@ -14,7 +14,7 @@
 is used to tell the JS loader where to find the .js.mem file. By default, it will
 look for /pathkit.js.mem, so if this is not the case, use `locateFile` to configure
 this properly.
-The `PathKit` object returned through the .then() callback is fully loaded and ready to use.
+The `PathKit` object returned upon resolution of the .ready() Promise is fully loaded and ready to use.
 
 See the [API page](https://skia.org/user/modules/pathkit) and
 [example.html](https://github.com/google/skia/blob/master/modules/pathkit/npm-asmjs/example.html)
@@ -29,7 +29,7 @@
 In the JS code, use require():
 
     const PathKitInit = require('pathkit-asmjs/bin/pathkit.js')
-    PathKitInit().then((PathKit) => {
+    PathKitInit().ready().then((PathKit) => {
         // Code goes here using PathKit
     })
 
diff --git a/modules/pathkit/npm-asmjs/example.html b/modules/pathkit/npm-asmjs/example.html
index c50c453..a2fc4c1 100644
--- a/modules/pathkit/npm-asmjs/example.html
+++ b/modules/pathkit/npm-asmjs/example.html
@@ -53,7 +53,7 @@
 
   PathKitInit({
     locateFile: (file) => '/node_modules/pathkit-asmjs/bin/'+file,
-  }).then((PathKit) => {
+  }).ready().then((PathKit) => {
     window.PathKit = PathKit;
     OutputsExample(PathKit);
     Path2DExample(PathKit);
diff --git a/modules/pathkit/npm-asmjs/package.json b/modules/pathkit/npm-asmjs/package.json
index 306c543..70f773c 100644
--- a/modules/pathkit/npm-asmjs/package.json
+++ b/modules/pathkit/npm-asmjs/package.json
@@ -1,11 +1,11 @@
 {
   "name": "pathkit-asmjs",
-  "version": "0.4.2",
+  "version": "0.5.1",
   "description": "A asm.js version of Skia's PathOps toolkit",
   "main": "bin/pathkit.js",
   "homepage": "https://github.com/google/skia/tree/master/modules/pathkit",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1"
   },
-  "license": "Apache-2.0"
+  "license": "BSD-3-Clause"
 }
diff --git a/modules/pathkit/npm-wasm/CODE_OF_CONDUCT.md b/modules/pathkit/npm-wasm/CODE_OF_CONDUCT.md
index 921d113..0999289 100644
--- a/modules/pathkit/npm-wasm/CODE_OF_CONDUCT.md
+++ b/modules/pathkit/npm-wasm/CODE_OF_CONDUCT.md
@@ -69,8 +69,8 @@
 behavior is threatening or harassing, report it. We are dedicated to providing
 an environment where participants feel welcome and safe.
 
-Reports should be directed to [Joe Gregorio](mailto:jcgregorio@google.com), the
-Project Steward(s) for *pulito*. It is the Project Steward’s duty to
+Reports should be directed to [Heather Miller](mailto:hcm@google.com), the
+Project Steward(s) for *pathkit*. It is the Project Steward’s duty to
 receive and address reported violations of the code of conduct. They will then
 work with a committee consisting of representatives from the Open Source
 Programs Office and the Google Open Source Strategy team. If for any reason you
diff --git a/modules/pathkit/npm-wasm/CONTRIBUTING.md b/modules/pathkit/npm-wasm/CONTRIBUTING.md
index c980350..2f2f0af 100644
--- a/modules/pathkit/npm-wasm/CONTRIBUTING.md
+++ b/modules/pathkit/npm-wasm/CONTRIBUTING.md
@@ -17,7 +17,5 @@
 
 ## Code reviews
 
-All submissions, including submissions by project members, require review. We
-use GitHub pull requests for this purpose. Consult
-[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
-information on using pull requests.
+All submissions, including submissions by project members, require review.
+Please see the guidelines for contributing code at https://skia.org/dev/contrib/
diff --git a/modules/pathkit/npm-wasm/LICENSE b/modules/pathkit/npm-wasm/LICENSE
index 7a4a3ea..fc53e3e 100644
--- a/modules/pathkit/npm-wasm/LICENSE
+++ b/modules/pathkit/npm-wasm/LICENSE
@@ -1,202 +1,29 @@
+// Copyright (c) 2011 Google Inc. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
\ No newline at end of file
+--------------------------------------------------------------------------------
diff --git a/modules/pathkit/npm-wasm/README.md b/modules/pathkit/npm-wasm/README.md
index 571bcfa..815e417 100644
--- a/modules/pathkit/npm-wasm/README.md
+++ b/modules/pathkit/npm-wasm/README.md
@@ -5,7 +5,7 @@
     <script src="/node_modules/pathkit-wasm/bin/pathkit.js"></script>
     PathKitInit({
         locateFile: (file) => '/node_modules/pathkit-wasm/bin/'+file,
-    }).then((PathKit) => {
+    }).ready().then((PathKit) => {
         // Code goes here using PathKit
     });
 
@@ -14,7 +14,7 @@
 is used to tell the JS loader where to find the .wasm file. By default, it will
 look for /pathkit.wasm, so if this is not the case, use `locateFile` to configure
 this properly.
-The `PathKit` object returned through the .then() callback is fully loaded and ready to use.
+The `PathKit` object returned upon resolution of the .ready() Promise is fully loaded and ready to use.
 
 See the [API page](https://skia.org/user/modules/pathkit) and
 [example.html](https://github.com/google/skia/blob/master/modules/pathkit/npm-wasm/example.html)
@@ -29,7 +29,7 @@
 In the JS code, use require():
 
     const PathKitInit = require('pathkit-wasm/bin/pathkit.js')
-    PathKitInit().then((PathKit) => {
+    PathKitInit().ready().then((PathKit) => {
         // Code goes here using PathKit
     })
 
diff --git a/modules/pathkit/npm-wasm/example.html b/modules/pathkit/npm-wasm/example.html
index 488f6ec..d7c2469 100644
--- a/modules/pathkit/npm-wasm/example.html
+++ b/modules/pathkit/npm-wasm/example.html
@@ -53,7 +53,7 @@
 
   PathKitInit({
     locateFile: (file) => '/node_modules/pathkit-wasm/bin/'+file,
-  }).then((PathKit) => {
+  }).ready().then((PathKit) => {
     window.PathKit = PathKit;
     OutputsExample(PathKit);
     Path2DExample(PathKit);
diff --git a/modules/pathkit/npm-wasm/package.json b/modules/pathkit/npm-wasm/package.json
index a18e613..a09c0b3 100644
--- a/modules/pathkit/npm-wasm/package.json
+++ b/modules/pathkit/npm-wasm/package.json
@@ -1,11 +1,11 @@
 {
   "name": "pathkit-wasm",
-  "version": "0.4.2",
+  "version": "0.5.1",
   "description": "A WASM version of Skia's PathOps toolkit",
   "main": "bin/pathkit.js",
   "homepage": "https://github.com/google/skia/tree/master/modules/pathkit",
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1"
   },
-  "license": "Apache-2.0"
+  "license": "BSD-3-Clause"
 }
diff --git a/modules/pathkit/ready.js b/modules/pathkit/ready.js
new file mode 100644
index 0000000..e054f62
--- /dev/null
+++ b/modules/pathkit/ready.js
@@ -0,0 +1,18 @@
+// See https://github.com/kripken/emscripten/issues/5820#issuecomment-385722568
+// for context on why the .then() that comes with Module breaks things (e.g. infinite loops)
+// and why the below fixes it.
+Module['ready'] = function() {
+  return new Promise(function (resolve, reject) {
+    delete Module['then'];
+    Module['onAbort'] = reject;
+    if (runtimeInitialized) {
+      resolve(Module);
+    } else {
+      addOnPostRun(function() {
+        resolve(Module);
+      });
+    }
+  });
+}
+// TODO(kjlubick): Shut .then() entirely off in 0.6.0 by uncommenting below.
+// delete Module['then'];
\ No newline at end of file
diff --git a/modules/pathkit/tests/effects.spec.js b/modules/pathkit/tests/effects.spec.js
index cf1e1d5..29cdbd5 100644
--- a/modules/pathkit/tests/effects.spec.js
+++ b/modules/pathkit/tests/effects.spec.js
@@ -1,3 +1,4 @@
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
 
 describe('PathKit\'s Path Behavior', function() {
     // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
@@ -8,7 +9,7 @@
         } else {
             PathKitInit({
                 locateFile: (file) => '/pathkit/'+file,
-            }).then((_PathKit) => {
+            }).ready().then((_PathKit) => {
                 PathKit = _PathKit;
                 resolve();
             });
diff --git a/modules/pathkit/tests/path.spec.js b/modules/pathkit/tests/path.spec.js
index 7ee1aad..80a779a 100644
--- a/modules/pathkit/tests/path.spec.js
+++ b/modules/pathkit/tests/path.spec.js
@@ -1,3 +1,4 @@
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
 
 describe('PathKit\'s Path Behavior', function() {
     // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
@@ -8,7 +9,7 @@
         } else {
             PathKitInit({
                 locateFile: (file) => '/pathkit/'+file,
-            }).then((_PathKit) => {
+            }).ready().then((_PathKit) => {
                 PathKit = _PathKit;
                 resolve();
             });
@@ -158,7 +159,7 @@
     }
 
     describe('Command arrays', function(){
-        it('does NOT approximates conics when dumping as toCmds', function(done){
+        it('does NOT approximates conics when dumping as toCmds', function(done) {
             LoadPathKit.then(catchException(done, () => {
                 let path = PathKit.NewPath();
                 path.moveTo(20, 120);
diff --git a/modules/pathkit/tests/path2d.spec.js b/modules/pathkit/tests/path2d.spec.js
index a1bc763..6c2b3ef 100644
--- a/modules/pathkit/tests/path2d.spec.js
+++ b/modules/pathkit/tests/path2d.spec.js
@@ -1,4 +1,4 @@
-
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
 
 describe('PathKit\'s Path2D API', function() {
     // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
@@ -9,7 +9,7 @@
         } else {
             PathKitInit({
                 locateFile: (file) => '/pathkit/'+file,
-            }).then((_PathKit) => {
+            }).ready().then((_PathKit) => {
                 PathKit = _PathKit;
                 resolve();
             });
diff --git a/modules/pathkit/tests/pathops.spec.js b/modules/pathkit/tests/pathops.spec.js
index 927719a..720bd8c 100644
--- a/modules/pathkit/tests/pathops.spec.js
+++ b/modules/pathkit/tests/pathops.spec.js
@@ -1,4 +1,4 @@
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
 
 var dumpErrors = false;
 var container;
@@ -84,7 +84,7 @@
         } else {
             PathKitInit({
                 locateFile: (file) => '/pathkit/'+file,
-            }).then((_PathKit) => {
+            }).ready().then((_PathKit) => {
                 PathKit = _PathKit;
                 PATHOP_MAP = {
                     'kIntersect_SkPathOp':         PathKit.PathOp.INTERSECT,
diff --git a/modules/pathkit/tests/svg.spec.js b/modules/pathkit/tests/svg.spec.js
index ad9e6df..02cfa4e 100644
--- a/modules/pathkit/tests/svg.spec.js
+++ b/modules/pathkit/tests/svg.spec.js
@@ -1,4 +1,4 @@
-
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
 describe('PathKit\'s SVG Behavior', function() {
     // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
     var PathKit = null;
@@ -8,7 +8,7 @@
         } else {
             PathKitInit({
                 locateFile: (file) => '/pathkit/'+file,
-            }).then((_PathKit) => {
+            }).ready().then((_PathKit) => {
                 PathKit = _PathKit;
                 resolve();
             });
diff --git a/modules/pathkit/tests/util.spec.js b/modules/pathkit/tests/util.spec.js
index 92981b3..ed65dc5 100644
--- a/modules/pathkit/tests/util.spec.js
+++ b/modules/pathkit/tests/util.spec.js
@@ -1,5 +1,5 @@
 // Tests for util-related things
-
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
 describe('PathKit\'s CubicMap Behavior', function() {
     // Note, don't try to print the PathKit object - it can cause Karma/Jasmine to lock up.
     var PathKit = null;
@@ -9,7 +9,7 @@
         } else {
             PathKitInit({
                 locateFile: (file) => '/pathkit/'+file,
-            }).then((_PathKit) => {
+            }).ready().then((_PathKit) => {
                 PathKit = _PathKit;
                 resolve();
             });
@@ -64,4 +64,4 @@
         }));
     });
 
-});
\ No newline at end of file
+});
diff --git a/modules/skottie/BUILD.gn b/modules/skottie/BUILD.gn
index 1178183..ef105e5 100644
--- a/modules/skottie/BUILD.gn
+++ b/modules/skottie/BUILD.gn
@@ -73,11 +73,21 @@
       testonly = true
 
       configs += [ "../..:skia_private" ]
+      include_dirs = [
+        "../../tools",
+        "../../tools/flags",
+        "../../tools/fonts",
+      ]
       sources = [
+        "../../tools/Resources.cpp",
+        "../../tools/fonts/SkTestFontMgr.cpp",
+        "../../tools/fonts/SkTestSVGTypeface.cpp",
+        "../../tools/fonts/SkTestTypeface.cpp",
         "fuzz/FuzzSkottieJSON.cpp",
       ]
 
       deps = [
+        "../..:experimental_svg_model",
         "../..:skia",
       ]
 
diff --git a/modules/skottie/fuzz/FuzzSkottieJSON.cpp b/modules/skottie/fuzz/FuzzSkottieJSON.cpp
index 23c77ce..252fd8d 100644
--- a/modules/skottie/fuzz/FuzzSkottieJSON.cpp
+++ b/modules/skottie/fuzz/FuzzSkottieJSON.cpp
@@ -6,8 +6,10 @@
  */
 
 #include "SkData.h"
-#include "Skottie.h"
+#include "SkFontMgrPriv.h"
 #include "SkStream.h"
+#include "SkTestFontMgr.h"
+#include "Skottie.h"
 
 void FuzzSkottieJSON(sk_sp<SkData> bytes) {
     SkMemoryStream stream(bytes);
@@ -20,6 +22,7 @@
 
 #if defined(IS_FUZZING_WITH_LIBFUZZER)
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    gSkFontMgr_DefaultFactory = &sk_tool_utils::MakePortableFontMgr;
     auto bytes = SkData::MakeWithoutCopy(data, size);
     FuzzSkottieJSON(bytes);
     return 0;
diff --git a/modules/skottie/include/SkottieProperty.h b/modules/skottie/include/SkottieProperty.h
index a738697..a3dee71 100644
--- a/modules/skottie/include/SkottieProperty.h
+++ b/modules/skottie/include/SkottieProperty.h
@@ -62,11 +62,11 @@
     const sk_sp<NodeT> fNode;
 };
 
-class TransformAdapter;
+class TransformAdapter2D;
 
 using ColorPropertyHandle     = PropertyHandle<ColorPropertyValue    , sksg::Color         >;
 using OpacityPropertyHandle   = PropertyHandle<OpacityPropertyValue  , sksg::OpacityEffect >;
-using TransformPropertyHandle = PropertyHandle<TransformPropertyValue, TransformAdapter    >;
+using TransformPropertyHandle = PropertyHandle<TransformPropertyValue, TransformAdapter2D  >;
 
 /**
  * A PropertyObserver can be used to track and manipulate certain properties of "interesting"
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index 7b31d99..65fed85 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -29,6 +29,7 @@
 #include "SkottiePriv.h"
 #include "SkottieProperty.h"
 #include "SkottieValue.h"
+#include "SkTraceEvent.h"
 
 #include <cmath>
 
@@ -60,14 +61,14 @@
     fLogger->log(lvl, buff, jsonstr.c_str());
 }
 
-sk_sp<sksg::Matrix> AnimationBuilder::attachMatrix(const skjson::ObjectValue& t,
-                                                   AnimatorScope* ascope,
-                                                   sk_sp<sksg::Matrix> parentMatrix) const {
+sk_sp<sksg::Matrix> AnimationBuilder::attachMatrix2D(const skjson::ObjectValue& t,
+                                                     AnimatorScope* ascope,
+                                                     sk_sp<sksg::Matrix> parentMatrix) const {
     static const VectorValue g_default_vec_0   = {  0,   0},
                              g_default_vec_100 = {100, 100};
 
     auto matrix = sksg::Matrix::Make(SkMatrix::I(), parentMatrix);
-    auto adapter = sk_make_sp<TransformAdapter>(matrix);
+    auto adapter = sk_make_sp<TransformAdapter2D>(matrix);
 
     auto bound = this->bindProperty<VectorValue>(t["a"], ascope,
             [adapter](const VectorValue& a) {
@@ -106,6 +107,58 @@
     return (bound || dispatched) ? matrix : parentMatrix;
 }
 
+sk_sp<sksg::Matrix> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& t,
+                                                     AnimatorScope* ascope,
+                                                     sk_sp<sksg::Matrix> parentMatrix) const {
+    static const VectorValue g_default_vec_0   = {  0,   0,   0},
+                             g_default_vec_100 = {100, 100, 100};
+
+    auto matrix = sksg::Matrix::Make(SkMatrix::I(), parentMatrix);
+    auto adapter = sk_make_sp<TransformAdapter3D>(matrix);
+
+    auto bound = this->bindProperty<VectorValue>(t["a"], ascope,
+            [adapter](const VectorValue& a) {
+                adapter->setAnchorPoint(TransformAdapter3D::Vec3(a));
+            }, g_default_vec_0);
+    bound |= this->bindProperty<VectorValue>(t["p"], ascope,
+            [adapter](const VectorValue& p) {
+                adapter->setPosition(TransformAdapter3D::Vec3(p));
+            }, g_default_vec_0);
+    bound |= this->bindProperty<VectorValue>(t["s"], ascope,
+            [adapter](const VectorValue& s) {
+                adapter->setScale(TransformAdapter3D::Vec3(s));
+            }, g_default_vec_100);
+
+    // Orientation and rx/ry/rz are mapped to the same rotation property -- the difference is
+    // in how they get interpolated (vector vs. scalar/decomposed interpolation).
+    bound |= this->bindProperty<VectorValue>(t["or"], ascope,
+            [adapter](const VectorValue& o) {
+                adapter->setRotation(TransformAdapter3D::Vec3(o));
+            }, g_default_vec_0);
+
+    bound |= this->bindProperty<ScalarValue>(t["rx"], ascope,
+            [adapter](const ScalarValue& rx) {
+                const auto& r = adapter->getRotation();
+                adapter->setRotation(TransformAdapter3D::Vec3({rx, r.fY, r.fZ}));
+            }, 0.0f);
+
+    bound |= this->bindProperty<ScalarValue>(t["ry"], ascope,
+            [adapter](const ScalarValue& ry) {
+                const auto& r = adapter->getRotation();
+                adapter->setRotation(TransformAdapter3D::Vec3({r.fX, ry, r.fZ}));
+            }, 0.0f);
+
+    bound |= this->bindProperty<ScalarValue>(t["rz"], ascope,
+            [adapter](const ScalarValue& rz) {
+                const auto& r = adapter->getRotation();
+                adapter->setRotation(TransformAdapter3D::Vec3({r.fX, r.fY, rz}));
+            }, 0.0f);
+
+    // TODO: dispatch 3D transform properties
+
+    return bound ? matrix : parentMatrix;
+}
+
 sk_sp<sksg::RenderNode> AnimationBuilder::attachOpacity(const skjson::ObjectValue& jtransform,
                                                         AnimatorScope* ascope,
                                                         sk_sp<sksg::RenderNode> childNode) const {
@@ -250,7 +303,7 @@
     return dispatched;
 }
 
-bool AnimationBuilder::dispatchTransformProperty(const sk_sp<TransformAdapter>& t) const {
+bool AnimationBuilder::dispatchTransformProperty(const sk_sp<TransformAdapter2D>& t) const {
     bool dispatched = false;
 
     if (fPropertyObserver) {
@@ -337,6 +390,8 @@
 }
 
 sk_sp<Animation> Animation::Builder::make(const char* data, size_t data_len) {
+    TRACE_EVENT0("skottie", TRACE_FUNC);
+
     // Sanitize factory args.
     class NullResourceProvider final : public ResourceProvider {
         sk_sp<SkData> load(const char[], const char[]) const override { return nullptr; }
@@ -431,6 +486,8 @@
 }
 
 void Animation::render(SkCanvas* canvas, const SkRect* dstR) const {
+    TRACE_EVENT0("skottie", TRACE_FUNC);
+
     if (!fScene)
         return;
 
@@ -444,6 +501,8 @@
 }
 
 void Animation::seek(SkScalar t) {
+    TRACE_EVENT0("skottie", TRACE_FUNC);
+
     if (!fScene)
         return;
 
diff --git a/modules/skottie/src/SkottieAdapter.cpp b/modules/skottie/src/SkottieAdapter.cpp
index 30c4ed5..0d53869 100644
--- a/modules/skottie/src/SkottieAdapter.cpp
+++ b/modules/skottie/src/SkottieAdapter.cpp
@@ -9,6 +9,7 @@
 
 #include "SkFont.h"
 #include "SkMatrix.h"
+#include "SkMatrix44.h"
 #include "SkPath.h"
 #include "SkRRect.h"
 #include "SkSGColor.h"
@@ -34,6 +35,8 @@
 RRectAdapter::RRectAdapter(sk_sp<sksg::RRect> wrapped_node)
     : fRRectNode(std::move(wrapped_node)) {}
 
+RRectAdapter::~RRectAdapter() = default;
+
 void RRectAdapter::apply() {
     // BM "position" == "center position"
     auto rr = SkRRect::MakeRectXY(SkRect::MakeXYWH(fPosition.x() - fSize.width() / 2,
@@ -44,10 +47,12 @@
    fRRectNode->setRRect(rr);
 }
 
-TransformAdapter::TransformAdapter(sk_sp<sksg::Matrix> matrix)
+TransformAdapter2D::TransformAdapter2D(sk_sp<sksg::Matrix> matrix)
     : fMatrixNode(std::move(matrix)) {}
 
-SkMatrix TransformAdapter::totalMatrix() const {
+TransformAdapter2D::~TransformAdapter2D() = default;
+
+SkMatrix TransformAdapter2D::totalMatrix() const {
     SkMatrix t = SkMatrix::MakeTrans(-fAnchorPoint.x(), -fAnchorPoint.y());
 
     t.postScale(fScale.x() / 100, fScale.y() / 100); // 100% based
@@ -58,7 +63,42 @@
     return t;
 }
 
-void TransformAdapter::apply() {
+void TransformAdapter2D::apply() {
+    fMatrixNode->setMatrix(this->totalMatrix());
+}
+
+TransformAdapter3D::Vec3::Vec3(const VectorValue& v) {
+    fX = v.size() > 0 ? v[0] : 0;
+    fY = v.size() > 1 ? v[1] : 0;
+    fZ = v.size() > 2 ? v[2] : 0;
+}
+
+TransformAdapter3D::TransformAdapter3D(sk_sp<sksg::Matrix> matrix)
+    : fMatrixNode(std::move(matrix)) {}
+
+TransformAdapter3D::~TransformAdapter3D() = default;
+
+SkMatrix TransformAdapter3D::totalMatrix() const {
+    SkMatrix44 t;
+
+    t.setTranslate(-fAnchorPoint.fX, -fAnchorPoint.fY, -fAnchorPoint.fZ);
+    t.postScale(fScale.fX / 100, fScale.fY / 100, fScale.fZ / 100);
+
+    // TODO: SkMatrix44:postRotate()?
+    SkMatrix44 r;
+    r.setRotateDegreesAbout(1, 0, 0, fRotation.fX);
+    t.postConcat(r);
+    r.setRotateDegreesAbout(0, 1, 0, fRotation.fY);
+    t.postConcat(r);
+    r.setRotateDegreesAbout(0, 0, 1, fRotation.fZ);
+    t.postConcat(r);
+
+    t.postTranslate(fPosition.fX, fPosition.fY, fPosition.fZ);
+
+    return t;
+}
+
+void TransformAdapter3D::apply() {
     fMatrixNode->setMatrix(this->totalMatrix());
 }
 
@@ -66,6 +106,8 @@
     : fPathNode(std::move(wrapped_node))
     , fType(t) {}
 
+PolyStarAdapter::~PolyStarAdapter() = default;
+
 void PolyStarAdapter::apply() {
     static constexpr int kMaxPointCount = 100000;
     const auto count = SkToUInt(SkTPin(SkScalarRoundToInt(fPointCount), 0, kMaxPointCount));
@@ -152,6 +194,8 @@
     SkASSERT(fTrimEffect);
 }
 
+TrimEffectAdapter::~TrimEffectAdapter() = default;
+
 void TrimEffectAdapter::apply() {
     // BM semantics: start/end are percentages, offset is "degrees" (?!).
     const auto  start = fStart  / 100,
@@ -207,15 +251,13 @@
     fStrokeColor->setStyle(SkPaint::kStroke_Style);
 }
 
+TextAdapter::~TextAdapter() = default;
+
 sk_sp<SkTextBlob> TextAdapter::makeBlob() const {
-    // TODO: convert to SkFont (missing getFontSpacing, measureText).
-    SkPaint font;
-    font.setTypeface(fText.fTypeface);
-    font.setTextSize(fText.fTextSize);
+    SkFont font(fText.fTypeface, fText.fTextSize);
     font.setHinting(kNo_SkFontHinting);
-    font.setSubpixelText(true);
-    font.setAntiAlias(true);
-    font.setTextEncoding(kUTF8_SkTextEncoding);
+    font.setSubpixel(true);
+    font.setEdging(SkFont::Edging::kAntiAlias);
 
     const auto align_fract = [](SkTextUtils::Align align) {
         switch (align) {
@@ -226,8 +268,7 @@
         return 0.0f; // go home, msvc...
     }(fText.fAlign);
 
-    const auto line_spacing = font.getFontSpacing();
-    const auto blob_font    = SkFont::LEGACY_ExtractFromPaint(font);
+    const auto line_spacing = font.getSpacing();
     float y_off             = 0;
     SkSTArray<256, SkGlyphID, true> line_glyph_buffer;
     SkTextBlobBuilder builder;
@@ -235,14 +276,15 @@
     const auto& push_line = [&](const char* start, const char* end) {
         if (end > start) {
             const auto len   = SkToSizeT(end - start);
-            line_glyph_buffer.reset(font.textToGlyphs(start, len, nullptr));
-            SkAssertResult(font.textToGlyphs(start, len, line_glyph_buffer.data())
+            line_glyph_buffer.reset(font.countText(start, len, kUTF8_SkTextEncoding));
+            SkAssertResult(font.textToGlyphs(start, len, kUTF8_SkTextEncoding, line_glyph_buffer.data(),
+                    line_glyph_buffer.count())
                            == line_glyph_buffer.count());
 
             const auto x_off = align_fract != 0
-                    ? align_fract * font.measureText(start, len)
+                    ? align_fract * font.measureText(start, len, kUTF8_SkTextEncoding)
                     : 0;
-            const auto& buf  = builder.allocRun(blob_font, line_glyph_buffer.count(), x_off, y_off);
+            const auto& buf  = builder.allocRun(font, line_glyph_buffer.count(), x_off, y_off);
             if (!buf.glyphs) {
                 return;
             }
diff --git a/modules/skottie/src/SkottieAdapter.h b/modules/skottie/src/SkottieAdapter.h
index a162e06..1ab9a7b 100644
--- a/modules/skottie/src/SkottieAdapter.h
+++ b/modules/skottie/src/SkottieAdapter.h
@@ -44,9 +44,10 @@
     p_type f##p_name = p_default;                   \
   public:
 
-class RRectAdapter final : public SkRefCnt {
+class RRectAdapter final : public SkNVRefCnt<RRectAdapter> {
 public:
     explicit RRectAdapter(sk_sp<sksg::RRect>);
+    ~RRectAdapter();
 
     ADAPTER_PROPERTY(Position, SkPoint , SkPoint::Make(0, 0))
     ADAPTER_PROPERTY(Size    , SkSize  ,  SkSize::Make(0, 0))
@@ -56,17 +57,16 @@
     void apply();
 
     sk_sp<sksg::RRect> fRRectNode;
-
-    using INHERITED = SkRefCnt;
 };
 
-class PolyStarAdapter final : public SkRefCnt {
+class PolyStarAdapter final : public SkNVRefCnt<PolyStarAdapter> {
 public:
     enum class Type {
         kStar, kPoly,
     };
 
     PolyStarAdapter(sk_sp<sksg::Path>, Type);
+    ~PolyStarAdapter();
 
     ADAPTER_PROPERTY(Position      , SkPoint , SkPoint::Make(0, 0))
     ADAPTER_PROPERTY(PointCount    , SkScalar, 0)
@@ -81,13 +81,12 @@
 
     sk_sp<sksg::Path> fPathNode;
     Type              fType;
-
-    using INHERITED = SkRefCnt;
 };
 
-class TransformAdapter final : public SkRefCnt {
+class TransformAdapter2D final : public SkNVRefCnt<TransformAdapter2D> {
 public:
-    explicit TransformAdapter(sk_sp<sksg::Matrix>);
+    explicit TransformAdapter2D(sk_sp<sksg::Matrix>);
+    ~TransformAdapter2D();
 
     ADAPTER_PROPERTY(AnchorPoint, SkPoint , SkPoint::Make(0, 0))
     ADAPTER_PROPERTY(Position   , SkPoint , SkPoint::Make(0, 0))
@@ -102,8 +101,35 @@
     void apply();
 
     sk_sp<sksg::Matrix> fMatrixNode;
+};
 
-    using INHERITED = SkRefCnt;
+class TransformAdapter3D final : public SkNVRefCnt<TransformAdapter3D> {
+public:
+    explicit TransformAdapter3D(sk_sp<sksg::Matrix>);
+    ~TransformAdapter3D();
+
+    struct Vec3 {
+        float fX, fY, fZ;
+
+        explicit Vec3(const VectorValue&);
+
+        bool operator==(const Vec3& other) const {
+            return fX == other.fX && fY == other.fY && fZ == other.fZ;
+        }
+        bool operator!=(const Vec3& other) const { return !(*this == other); }
+    };
+
+    ADAPTER_PROPERTY(AnchorPoint, Vec3, Vec3({  0,   0,   0}))
+    ADAPTER_PROPERTY(Position   , Vec3, Vec3({  0,   0,   0}))
+    ADAPTER_PROPERTY(Rotation   , Vec3, Vec3({  0,   0,   0}))
+    ADAPTER_PROPERTY(Scale      , Vec3, Vec3({100, 100, 100}))
+
+    SkMatrix totalMatrix() const;
+
+private:
+    void apply();
+
+    sk_sp<sksg::Matrix> fMatrixNode;
 };
 
 class GradientAdapter : public SkRefCnt {
@@ -125,8 +151,6 @@
 
 private:
     void apply();
-
-    using INHERITED = SkRefCnt;
 };
 
 class LinearGradientAdapter final : public GradientAdapter {
@@ -149,9 +173,10 @@
     using INHERITED = GradientAdapter;
 };
 
-class TrimEffectAdapter final : public SkRefCnt {
+class TrimEffectAdapter final : public SkNVRefCnt<TrimEffectAdapter> {
 public:
     explicit TrimEffectAdapter(sk_sp<sksg::TrimEffect>);
+    ~TrimEffectAdapter();
 
     ADAPTER_PROPERTY(Start , SkScalar,   0)
     ADAPTER_PROPERTY(End   , SkScalar, 100)
@@ -161,13 +186,12 @@
     void apply();
 
     sk_sp<sksg::TrimEffect> fTrimEffect;
-
-    using INHERITED = SkRefCnt;
 };
 
-class TextAdapter final : public SkRefCnt {
+class TextAdapter final : public SkNVRefCnt<TextAdapter> {
 public:
     explicit TextAdapter(sk_sp<sksg::Group> root);
+    ~TextAdapter();
 
     ADAPTER_PROPERTY(Text, TextValue, TextValue())
 
@@ -186,8 +210,6 @@
 
     bool                   fHadFill   : 1, //  - state cached from the prev apply()
                            fHadStroke : 1; //  /
-
-    using INHERITED = SkRefCnt;
 };
 
 #undef ADAPTER_PROPERTY
diff --git a/modules/skottie/src/SkottieLayer.cpp b/modules/skottie/src/SkottieLayer.cpp
index 2786ed5..2bb6264 100644
--- a/modules/skottie/src/SkottieLayer.cpp
+++ b/modules/skottie/src/SkottieLayer.cpp
@@ -449,10 +449,11 @@
         auto parent_matrix = this->AttachParentLayerMatrix(jlayer, abuilder, layer_index);
 
         if (const skjson::ObjectValue* jtransform = jlayer["ks"]) {
-            return *fLayerMatrixMap.set(layer_index,
-                                        abuilder->attachMatrix(*jtransform, fScope,
-                                                               std::move(parent_matrix)));
+            auto matrix_node = (ParseDefault<int>(jlayer["ddd"], 0) == 0)
+                ? abuilder->attachMatrix2D(*jtransform, fScope, std::move(parent_matrix))
+                : abuilder->attachMatrix3D(*jtransform, fScope, std::move(parent_matrix));
 
+            return *fLayerMatrixMap.set(layer_index, std::move(matrix_node));
         }
         return nullptr;
     }
diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h
index b3c15e5..ee41f37 100644
--- a/modules/skottie/src/SkottiePriv.h
+++ b/modules/skottie/src/SkottiePriv.h
@@ -71,8 +71,10 @@
 
     sk_sp<sksg::Color> attachColor(const skjson::ObjectValue&, AnimatorScope*,
                                    const char prop_name[]) const;
-    sk_sp<sksg::Matrix> attachMatrix(const skjson::ObjectValue&, AnimatorScope*,
-                                     sk_sp<sksg::Matrix>) const;
+    sk_sp<sksg::Matrix> attachMatrix2D(const skjson::ObjectValue&, AnimatorScope*,
+                                       sk_sp<sksg::Matrix>) const;
+    sk_sp<sksg::Matrix> attachMatrix3D(const skjson::ObjectValue&, AnimatorScope*,
+                                       sk_sp<sksg::Matrix>) const;
     sk_sp<sksg::RenderNode> attachOpacity(const skjson::ObjectValue&, AnimatorScope*,
                                       sk_sp<sksg::RenderNode>) const;
     sk_sp<sksg::Path> attachPath(const skjson::Value&, AnimatorScope*) const;
@@ -119,7 +121,7 @@
 
     bool dispatchColorProperty(const sk_sp<sksg::Color>&) const;
     bool dispatchOpacityProperty(const sk_sp<sksg::OpacityEffect>&) const;
-    bool dispatchTransformProperty(const sk_sp<TransformAdapter>&) const;
+    bool dispatchTransformProperty(const sk_sp<TransformAdapter2D>&) const;
 
     // Delay resolving the fontmgr until it is actually needed.
     struct LazyResolveFontMgr {
diff --git a/modules/skottie/src/SkottieProperty.cpp b/modules/skottie/src/SkottieProperty.cpp
index 0d27017..143b97a6 100644
--- a/modules/skottie/src/SkottieProperty.cpp
+++ b/modules/skottie/src/SkottieProperty.cpp
@@ -52,10 +52,10 @@
 }
 
 template <>
-PropertyHandle<TransformPropertyValue, TransformAdapter>::~PropertyHandle() {}
+PropertyHandle<TransformPropertyValue, TransformAdapter2D>::~PropertyHandle() {}
 
 template <>
-TransformPropertyValue PropertyHandle<TransformPropertyValue, TransformAdapter>::get() const {
+TransformPropertyValue PropertyHandle<TransformPropertyValue, TransformAdapter2D>::get() const {
     return {
         fNode->getAnchorPoint(),
         fNode->getPosition(),
@@ -67,7 +67,7 @@
 }
 
 template <>
-void PropertyHandle<TransformPropertyValue, TransformAdapter>::set(
+void PropertyHandle<TransformPropertyValue, TransformAdapter2D>::set(
         const TransformPropertyValue& t) {
     fNode->setAnchorPoint(t.fAnchorPoint);
     fNode->setPosition(t.fPosition);
diff --git a/modules/skottie/src/SkottieShapeLayer.cpp b/modules/skottie/src/SkottieShapeLayer.cpp
index 197faec..76b5658 100644
--- a/modules/skottie/src/SkottieShapeLayer.cpp
+++ b/modules/skottie/src/SkottieShapeLayer.cpp
@@ -596,7 +596,7 @@
         // of the dangling/uncommitted ones.
         AnimatorScope local_scope;
 
-        if ((shape_matrix = this->attachMatrix(*jtransform, &local_scope, nullptr))) {
+        if ((shape_matrix = this->attachMatrix2D(*jtransform, &local_scope, nullptr))) {
             shape_wrapper = sksg::Transform::Make(std::move(shape_wrapper), shape_matrix);
         }
         shape_wrapper = this->attachOpacity(*jtransform, &local_scope, std::move(shape_wrapper));
diff --git a/modules/sksg/src/SkSGText.cpp b/modules/sksg/src/SkSGText.cpp
index 0d6bed9..b70b192 100644
--- a/modules/sksg/src/SkSGText.cpp
+++ b/modules/sksg/src/SkSGText.cpp
@@ -57,23 +57,7 @@
     //  1) SkTextBlob has some trouble computing accurate bounds with alignment.
     //  2) SkPaint::Align is slated for deprecation.
 
-    // First, convert to glyphIDs.
-    SkSTArray<256, SkGlyphID, true> glyphs;
-    glyphs.reset(font.countText(fText.c_str(), fText.size(), kUTF8_SkTextEncoding));
-    SkAssertResult(font.textToGlyphs(fText.c_str(), fText.size(), kUTF8_SkTextEncoding,
-                                     glyphs.begin(), glyphs.count()));
-
-    // Next, build the cached blob.
-    SkTextBlobBuilder builder;
-    const auto& buf = builder.allocRun(font, glyphs.count(), 0, 0, nullptr);
-    if (!buf.glyphs) {
-        fBlob.reset();
-        return SkRect::MakeEmpty();
-    }
-
-    memcpy(buf.glyphs, glyphs.begin(), glyphs.count() * sizeof(SkGlyphID));
-
-    fBlob = builder.make();
+    fBlob = SkTextBlob::MakeFromText(fText.c_str(), fText.size(), font, kUTF8_SkTextEncoding);
     if (!fBlob) {
         return SkRect::MakeEmpty();
     }
diff --git a/modules/skshaper/BUILD.gn b/modules/skshaper/BUILD.gn
index eb67f90..cb221e3 100644
--- a/modules/skshaper/BUILD.gn
+++ b/modules/skshaper/BUILD.gn
@@ -16,25 +16,24 @@
 source_set("skshaper") {
   if (skia_enable_skshaper) {
     public_configs = [ ":public_config" ]
-    public = [ "include/SkShaper.h" ]
+    public = [
+      "include/SkShaper.h",
+    ]
     deps = [
       "../..:skia",
     ]
+    sources = [
+      "src/SkShaper.cpp",
+    ]
     if (target_cpu == "wasm") {
-      sources = [
-        "src/SkShaper_primitive.cpp",
-      ]
+      sources += [ "src/SkShaper_primitive.cpp" ]
     } else {
-      sources = [
-        "src/SkShaper_harfbuzz.cpp",
-      ]
+      sources += [ "src/SkShaper_harfbuzz.cpp" ]
       deps += [
         "//third_party/harfbuzz",
         "//third_party/icu",
       ]
     }
     configs += [ "../../:skia_private" ]
-
   }
 }
-
diff --git a/modules/skshaper/include/SkShaper.h b/modules/skshaper/include/SkShaper.h
index 92c236c..391e583 100644
--- a/modules/skshaper/include/SkShaper.h
+++ b/modules/skshaper/include/SkShaper.h
@@ -11,14 +11,14 @@
 #include <memory>
 
 #include "SkPoint.h"
+#include "SkTextBlob.h"
 #include "SkTypeface.h"
 
 class SkFont;
-class SkTextBlobBuilder;
 
 /**
    Shapes text using HarfBuzz and places the shaped text into a
-   TextBlob.
+   client-managed buffer.
 
    If compiled without HarfBuzz, fall back on SkPaint::textToGlyphs.
  */
@@ -27,8 +27,22 @@
     SkShaper(sk_sp<SkTypeface> face);
     ~SkShaper();
 
+    class LineHandler {
+    public:
+        virtual ~LineHandler() = default;
+
+        struct Buffer {
+            SkGlyphID* glyphs;    // required
+            SkPoint*   positions; // required
+            char*      utf8text;  // optional
+            uint32_t*  clusters;  // optional
+        };
+
+        virtual Buffer newLineBuffer(const SkFont&, int glyphCount, int utf8textCount) = 0;
+    };
+
     bool good() const;
-    SkPoint shape(SkTextBlobBuilder* dest,
+    SkPoint shape(LineHandler* handler,
                   const SkFont& srcPaint,
                   const char* utf8text,
                   size_t textBytes,
@@ -44,4 +58,17 @@
     std::unique_ptr<Impl> fImpl;
 };
 
+/**
+ * Helper for shaping text directly into a SkTextBlob.
+ */
+class SkTextBlobBuilderLineHandler final : public SkShaper::LineHandler {
+public:
+    sk_sp<SkTextBlob> makeBlob();
+
+    SkShaper::LineHandler::Buffer newLineBuffer(const SkFont&, int, int) override;
+
+private:
+    SkTextBlobBuilder fBuilder;
+};
+
 #endif  // SkShaper_DEFINED
diff --git a/modules/skshaper/src/SkShaper.cpp b/modules/skshaper/src/SkShaper.cpp
new file mode 100644
index 0000000..78f973c
--- /dev/null
+++ b/modules/skshaper/src/SkShaper.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkShaper.h"
+
+#include "SkTextBlobPriv.h"
+
+SkShaper::LineHandler::Buffer SkTextBlobBuilderLineHandler::newLineBuffer(const SkFont& font,
+                                                                          int glyphCount,
+                                                                          int textCount) {
+    const auto& runBuffer = SkTextBlobBuilderPriv::AllocRunTextPos(&fBuilder, font, glyphCount,
+                                                                   textCount, SkString());
+    return { runBuffer.glyphs,
+             runBuffer.points(),
+             runBuffer.utf8text,
+             runBuffer.clusters };
+}
+
+sk_sp<SkTextBlob> SkTextBlobBuilderLineHandler::makeBlob() {
+    return fBuilder.make();
+}
diff --git a/modules/skshaper/src/SkShaper_harfbuzz.cpp b/modules/skshaper/src/SkShaper_harfbuzz.cpp
index ced56ee..beb5923 100644
--- a/modules/skshaper/src/SkShaper_harfbuzz.cpp
+++ b/modules/skshaper/src/SkShaper_harfbuzz.cpp
@@ -22,7 +22,6 @@
 #include "SkTFitsIn.h"
 #include "SkTLazy.h"
 #include "SkTemplates.h"
-#include "SkTextBlobPriv.h"
 #include "SkTo.h"
 #include "SkTypeface.h"
 #include "SkTypes.h"
@@ -396,16 +395,16 @@
     bool fHasVisual;
 };
 struct ShapedRun {
-    ShapedRun(const char* utf8Start, const char* utf8End, int numGlyphs, const SkFont& paint,
+    ShapedRun(const char* utf8Start, const char* utf8End, int numGlyphs, const SkFont& font,
               UBiDiLevel level, std::unique_ptr<ShapedGlyph[]> glyphs)
-        : fUtf8Start(utf8Start), fUtf8End(utf8End), fNumGlyphs(numGlyphs), fPaint(paint)
+        : fUtf8Start(utf8Start), fUtf8End(utf8End), fNumGlyphs(numGlyphs), fFont(font)
         , fLevel(level), fGlyphs(std::move(glyphs))
     {}
 
     const char* fUtf8Start;
     const char* fUtf8End;
     int fNumGlyphs;
-    SkFont fPaint;
+    SkFont fFont;
     UBiDiLevel fLevel;
     std::unique_ptr<ShapedGlyph[]> fGlyphs;
 };
@@ -414,21 +413,26 @@
     return (level & 1) == 0;
 }
 
-static void append(SkTextBlobBuilder* b, const ShapedRun& run, int start, int end, SkPoint* p) {
+static void append(SkShaper::LineHandler* handler, const ShapedRun& run, int start, int end,
+                   SkPoint* p) {
     unsigned len = end - start;
-    SkPaint tmpPaint;
-    run.fPaint.LEGACY_applyToPaint(&tmpPaint);
-    auto runBuffer = SkTextBlobBuilderPriv::AllocRunTextPos(b, tmpPaint, len,
-            run.fUtf8End - run.fUtf8Start, SkString());
-    memcpy(runBuffer.utf8text, run.fUtf8Start, run.fUtf8End - run.fUtf8Start);
+
+    const auto buffer = handler->newLineBuffer(run.fFont, len, run.fUtf8End - run.fUtf8Start);
+    SkASSERT(buffer.glyphs);
+    SkASSERT(buffer.positions);
+
+    if (buffer.utf8text) {
+        memcpy(buffer.utf8text, run.fUtf8Start, run.fUtf8End - run.fUtf8Start);
+    }
 
     for (unsigned i = 0; i < len; i++) {
         // Glyphs are in logical order, but output ltr since PDF readers seem to expect that.
         const ShapedGlyph& glyph = run.fGlyphs[is_LTR(run.fLevel) ? start + i : end - 1 - i];
-        runBuffer.glyphs[i] = glyph.fID;
-        runBuffer.clusters[i] = glyph.fCluster;
-        reinterpret_cast<SkPoint*>(runBuffer.pos)[i] =
-                SkPoint::Make(p->fX + glyph.fOffset.fX, p->fY - glyph.fOffset.fY);
+        buffer.glyphs[i] = glyph.fID;
+        buffer.positions[i] = SkPoint::Make(p->fX + glyph.fOffset.fX, p->fY - glyph.fOffset.fY);
+        if (buffer.clusters) {
+            buffer.clusters[i] = glyph.fCluster;
+        }
         p->fX += glyph.fAdvance.fX;
         p->fY += glyph.fAdvance.fY;
     }
@@ -518,7 +522,7 @@
            fImpl->fBreakIterator;
 }
 
-SkPoint SkShaper::shape(SkTextBlobBuilder* builder,
+SkPoint SkShaper::shape(LineHandler* handler,
                         const SkFont& srcPaint,
                         const char* utf8,
                         size_t utf8Bytes,
@@ -526,7 +530,7 @@
                         SkPoint point,
                         SkScalar width) const {
     sk_sp<SkFontMgr> fontMgr = SkFontMgr::RefDefault();
-    SkASSERT(builder);
+    SkASSERT(handler);
     UBiDiLevel defaultLevel = leftToRight ? UBIDI_DEFAULT_LTR : UBIDI_DEFAULT_RTL;
     //hb_script_t script = ...
 
@@ -641,8 +645,8 @@
                                            std::unique_ptr<ShapedGlyph[]>(new ShapedGlyph[len]));
         int scaleX, scaleY;
         hb_font_get_scale(font->currentHBFont(), &scaleX, &scaleY);
-        double textSizeY = run.fPaint.getSize() / scaleY;
-        double textSizeX = run.fPaint.getSize() / scaleX * run.fPaint.getScaleX();
+        double textSizeY = run.fFont.getSize() / scaleY;
+        double textSizeX = run.fFont.getSize() / scaleX * run.fFont.getScaleX();
         for (unsigned i = 0; i < len; i++) {
             ShapedGlyph& glyph = run.fGlyphs[i];
             glyph.fID = info[i].codepoint;
@@ -732,7 +736,7 @@
 
         if (previousRunIndex != runIndex) {
             SkFontMetrics metrics;
-            runs[runIndex].fPaint.getMetrics(&metrics);
+            runs[runIndex].fFont.getMetrics(&metrics);
             maxAscent = SkTMin(maxAscent, metrics.fAscent);
             maxDescent = SkTMax(maxDescent, metrics.fDescent);
             maxLeading = SkTMax(maxLeading, metrics.fLeading);
@@ -763,7 +767,7 @@
             int endGlyphIndex = (logicalIndex == runIndex)
                               ? glyphIndex + 1
                               : runs[logicalIndex].fNumGlyphs;
-            append(builder, runs[logicalIndex], startGlyphIndex, endGlyphIndex, &currentPoint);
+            append(handler, runs[logicalIndex], startGlyphIndex, endGlyphIndex, &currentPoint);
         }
 
         currentPoint.fY += maxDescent + maxLeading;
diff --git a/modules/skshaper/src/SkShaper_primitive.cpp b/modules/skshaper/src/SkShaper_primitive.cpp
index 1dd6da6..3d54262 100644
--- a/modules/skshaper/src/SkShaper_primitive.cpp
+++ b/modules/skshaper/src/SkShaper_primitive.cpp
@@ -8,7 +8,6 @@
 #include "SkShaper.h"
 
 #include "SkStream.h"
-#include "SkTextBlob.h"
 #include "SkTo.h"
 #include "SkTypeface.h"
 
@@ -32,49 +31,45 @@
     return (((0xE5 << 24) >> ((unsigned)c >> 4 << 1)) & 3) + 1;
 }
 
-SkPoint SkShaper::shape(SkTextBlobBuilder* builder,
-                        const SkFont& srcPaint,
+SkPoint SkShaper::shape(LineHandler* handler,
+                        const SkFont& srcFont,
                         const char* utf8text,
                         size_t textBytes,
                         bool leftToRight,
                         SkPoint point,
                         SkScalar width) const {
     sk_ignore_unused_variable(leftToRight);
+    sk_ignore_unused_variable(width);
 
-    SkFont paint(srcPaint);
-    paint.setTypeface(fImpl->fTypeface);
-    int glyphCount = paint.countText(utf8text, textBytes, SkTextEncoding::kUTF8);
+    SkFont font(srcFont);
+    font.setTypeface(fImpl->fTypeface);
+    int glyphCount = font.countText(utf8text, textBytes, SkTextEncoding::kUTF8);
     if (glyphCount <= 0) {
         return point;
     }
-    SkRect bounds;
-    SkFontMetrics metrics;
-    paint.getMetrics(&metrics);
-    point.fY -= metrics.fAscent;
-    (void)paint.measureText(utf8text, textBytes, SkTextEncoding::kUTF8, &bounds);
-    SkPaint tmpPaint;
-    paint.LEGACY_applyToPaint(&tmpPaint);
-    const SkTextBlobBuilder::RunBuffer& runBuffer =
-        builder->allocRunTextPosH(tmpPaint, glyphCount, point.y(), textBytes, SkString(), &bounds);
-    memcpy(runBuffer.utf8text, utf8text, textBytes);
-    const char* txtPtr = utf8text;
-    for (int i = 0; i < glyphCount; ++i) {
-        // Each charater maps to exactly one glyph via SkGlyphCache::unicharToGlyph().
-        runBuffer.clusters[i] = SkToU32(txtPtr - utf8text);
-        txtPtr += utf8_lead_byte_to_count(txtPtr);
-        SkASSERT(txtPtr <= utf8text + textBytes);
-    }
-    (void)paint.textToGlyphs(utf8text, textBytes, SkTextEncoding::kUTF8,
-                             runBuffer.glyphs, glyphCount);
-    // replace with getPos()?
-    (void)paint.getWidths(runBuffer.glyphs, glyphCount, runBuffer.pos);
-    SkScalar x = point.x();
-    for (int i = 0; i < glyphCount; ++i) {
-        SkScalar w = runBuffer.pos[i];
-        runBuffer.pos[i] = x;
-        x += w;
-    }
-    point.fY += metrics.fDescent + metrics.fLeading;
 
-    return point;
+    SkFontMetrics metrics;
+    font.getMetrics(&metrics);
+    point.fY -= metrics.fAscent;
+
+    const auto buffer = handler->newLineBuffer(font, glyphCount, textBytes);
+    SkAssertResult(font.textToGlyphs(utf8text, textBytes, SkTextEncoding::kUTF8, buffer.glyphs,
+                                     glyphCount) == glyphCount);
+    font.getPos(buffer.glyphs, glyphCount, buffer.positions, point);
+
+    if (buffer.utf8text) {
+        memcpy(buffer.utf8text, utf8text, textBytes);
+    }
+
+    if (buffer.clusters) {
+        const char* txtPtr = utf8text;
+        for (int i = 0; i < glyphCount; ++i) {
+            // Each charater maps to exactly one glyph via SkGlyphCache::unicharToGlyph().
+            buffer.clusters[i] = SkToU32(txtPtr - utf8text);
+            txtPtr += utf8_lead_byte_to_count(txtPtr);
+            SkASSERT(txtPtr <= utf8text + textBytes);
+        }
+    }
+
+    return point + SkVector::Make(0, metrics.fDescent + metrics.fLeading);
 }
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc
index 6e050f3..aafc0d0 100644
--- a/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc
+++ b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc
@@ -140,7 +140,7 @@
         height_ = height;
 
         if (ar_session_ != nullptr) {
-            ArSession_setDisplayGeometry(ar_session_, display_rotation, width, height);;
+            ArSession_setDisplayGeometry(ar_session_, display_rotation, width, height);
         }
     }
 
diff --git a/platform_tools/android/apps/build.gradle b/platform_tools/android/apps/build.gradle
index e894ae5..1805fc5 100644
--- a/platform_tools/android/apps/build.gradle
+++ b/platform_tools/android/apps/build.gradle
@@ -1,5 +1,8 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 
+import java.io.File
+import java.nio.file.Paths
+import org.apache.tools.ant.taskdefs.condition.Os
 
 buildscript {
     repositories {
@@ -25,7 +28,12 @@
     appVariants.all{ variant ->
         def buildNativeLib = project.task("${variant.name}_BuildSkiaLib", type:Exec) {
             workingDir '../../../..' // top-level skia directory
-            commandLine constructBuildCommand(project, variant, appName).split()
+            final String cmd = constructBuildCommand(project, variant, appName)
+            if (Os.isFamily(Os.FAMILY_WINDOWS)) {
+                commandLine "cmd", "/c", cmd
+            } else {
+                commandLine cmd.split()
+            }
         }
         buildNativeLib.onlyIf { !project.hasProperty("suppressNativeBuild") }
 
@@ -91,8 +99,8 @@
 
 def constructBuildCommand(project, variant, appName) {
     String depotToolsDir = null
-    for (String entry : System.getenv("PATH").split(":")) {
-        if (entry.contains("depot_tools")) {
+    for (String entry : System.getenv("PATH").split(File.pathSeparator)) {
+        if (Paths.get(entry).endsWith("depot_tools")) {
             depotToolsDir = entry;
             break;
         }
@@ -106,7 +114,7 @@
                 " depot_tools or define depot_tools.dir in local.properties")
     }
 
+    String ninja = Paths.get(depotToolsDir, "ninja")
     String out_dir = getVariantOutDir(project, variant).skiaOut
-
-    return "${depotToolsDir}/ninja -C $out_dir $appName"
+    return "$ninja -C $out_dir $appName"
 }
diff --git a/platform_tools/android/apps/skqp/src/main/assets/files.checksum b/platform_tools/android/apps/skqp/src/main/assets/files.checksum
index 61b5836..d5d7c54 100644
--- a/platform_tools/android/apps/skqp/src/main/assets/files.checksum
+++ b/platform_tools/android/apps/skqp/src/main/assets/files.checksum
@@ -1 +1 @@
-ab6b5893f0b6e8fae6636ce0fbd8ca44
+b76b82044b84148c3bad143b5910384b
diff --git a/platform_tools/android/apps/skqp/src/main/assets/skqp/rendertests.txt b/platform_tools/android/apps/skqp/src/main/assets/skqp/rendertests.txt
index 436ab0f..0597c0c 100644
--- a/platform_tools/android/apps/skqp/src/main/assets/skqp/rendertests.txt
+++ b/platform_tools/android/apps/skqp/src/main/assets/skqp/rendertests.txt
@@ -28,7 +28,8 @@
 arcs_as_paths,0
 arcto,-1
 arithmode,0
-atlastext,-1
+atlastext,0
+b_119394958,-1
 badpaint,-1
 bezier_conic_effects,-1
 bezier_quad_effects,-1
@@ -66,6 +67,7 @@
 bleed_downscale,0
 bleed_image,-1
 blend,0
+blob_rsxform,0
 blur2rects,-1
 blur2rectsnonninepatch,0
 blurSmallRadii,0
@@ -192,10 +194,12 @@
 crbug_892988,-1
 crbug_899512,0
 crbug_905548,0
+crbug_918512,-1
 croppedrects,0
 cross_context_image,-1
 cubicclosepath,-1
 cubicpath,-1
+daa,-1
 dash_line_zero_off_interval,-1
 dashcircle,-1
 dashcircle2,-1
@@ -259,6 +263,7 @@
 extractbitmap,-1
 fadefilter,0
 fancy_gradients,-1
+fancyblobunderline,-1
 fast_slow_blurimagefilter,0
 fatpathfill,-1
 fillcircle,-1
@@ -292,6 +297,7 @@
 fontmgr_bounds_1_-0.25,-1
 fontmgr_iter,-1
 fontmgr_match,-1
+fontregen,-1
 fontscaler,-1
 fontscalerdistortable,-1
 format4444,0
@@ -406,6 +412,7 @@
 largeglyphblur,0
 lattice,-1
 lattice2,0
+lattice_alpha,0
 lcdblendmodes,0
 lcdoverlap,-1
 lcdtext,-1
@@ -427,6 +434,7 @@
 longpathdash,-1
 longwavyline,-1
 lumafilter,-1
+maddash,-1
 makeRasterImage,0
 makecolorspace,0
 mandoline,-1
@@ -543,11 +551,11 @@
 rrect_draw_aa,-1
 rrect_draw_bw,0
 rrect_effect,-1
+save_behind,-1
 savelayer_clipmask,-1
 savelayer_clipped,0
 savelayer_coverage,-1
 savelayer_initfromprev,-1
-savelayer_lcdtext,0
 savelayer_maskfilter,-1
 savelayer_unclipped,0
 savelayer_with_backdrop,-1
@@ -635,6 +643,7 @@
 testgradient,-1
 text_scale_skew,-1
 textblob,-1
+textblob_intercepts,0
 textblobblockreordering,0
 textblobcolortrans,0
 textblobgeometrychange,-1
@@ -648,6 +657,8 @@
 textfilter_color,0
 textfilter_image,0
 texture_domain_effect,-1
+texture_domain_effect_bilerp,-1
+texture_domain_effect_mipmap,-1
 thinconcavepaths,-1
 thinrects,-1
 thinstrokedrects,-1
@@ -655,7 +666,7 @@
 tiledscaledbitmap,-1
 tileimagefilter,-1
 tilemode_bitmap,0
-tilemode_decal,0
+tilemode_decal,-1
 tilemode_gradient,-1
 tilemodes,-1
 tilemodes_npot,-1
diff --git a/platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt b/platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt
index aa168f5..245553a 100644
--- a/platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt
+++ b/platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt
@@ -2,6 +2,20 @@
 ApplyGamma
 BasicDrawOpAtlas
 BlurMaskBiggerThanDest
+CCPR_busyPath
+CCPR_cache_animationAtlasReuse
+CCPR_cache_deferredCleanup
+CCPR_cache_hashTable
+CCPR_cache_mostlyVisible
+CCPR_cache_multiFlush
+CCPR_cache_multiTileCache
+CCPR_cache_partialInvalidate
+CCPR_cache_recycleEntries
+CCPR_cleanup
+CCPR_cleanupWithTexAllocFail
+CCPR_parseEmptyPath
+CCPR_unrefPerOpListPathsBeforeOps
+CCPR_unregisterCulledOps
 ClearOp
 ClipMaskCache
 ComposedImageFilterBounds_Gpu
@@ -11,6 +25,7 @@
 DDLFlushWhileRecording
 DDLInvalidRecorder
 DDLMakeRenderTargetTest
+DDLMultipleDDLs
 DDLOperatorEqTest
 DDLSurfaceCharacterizationTest
 DDLTextureFlagsTest
@@ -31,13 +46,6 @@
 Gr1x1TextureMipMappedTest
 GrAtlasTextOpPreparation
 GrBackendTextureImageMipMappedTest
-GrCCPRTest_busyPath
-GrCCPRTest_cache
-GrCCPRTest_cleanup
-GrCCPRTest_cleanupWithTexAllocFail
-GrCCPRTest_parseEmptyPath
-GrCCPRTest_unrefPerOpListPathsBeforeOps
-GrCCPRTest_unregisterCulledOps
 GrClipBounds
 GrContextFactory_NVPRContextOptionHasPathRenderingSupport
 GrContextFactory_NoPathRenderingIfNVPRDisabled
@@ -52,6 +60,7 @@
 GrDrawOpAtlasConfig_Basic
 GrImageSnapshotMipMappedTest
 GrMeshTest
+GrOpListFlushCount
 GrPathKeys
 GrPipelineDynamicStateTest
 GrPorterDuff
@@ -86,10 +95,10 @@
 ImageScalePixels_Gpu
 InitialTextureClear
 LazyDeinstantiation
+LazyProxyDeinstantiateTest
 LazyProxyFailedInstantiationTest
 LazyProxyReleaseTest
 LazyProxyTest
-LazyProxyUninstantiateTest
 OnFlushCallbackTest
 OpChainTest
 OverdrawSurface_Gpu
@@ -104,6 +113,7 @@
 ProxyRefTest
 RGB565TextureTest
 RGBA4444TextureTest
+ReadOnlyTexture
 ReadPixels_Gpu
 ReadPixels_Texture
 ReadWriteAlpha
@@ -168,6 +178,7 @@
 TextBlobCache
 TextBlobStressAbnormal
 TextBlobStressCache
+TextureIdleProcTest
 TextureProxyTest
 TextureStripAtlasManagerColorFilterTest
 TextureStripAtlasManagerGradientTest
diff --git a/public.bzl b/public.bzl
index b360a11..60ce79d 100644
--- a/public.bzl
+++ b/public.bzl
@@ -300,6 +300,7 @@
         "src/ports/SkFontMgr_empty_factory.cpp",
         "src/ports/SkFontMgr_fontconfig.cpp",
         "src/ports/SkFontMgr_fontconfig_factory.cpp",
+        "src/ports/SkFontMgr_fuchsia.cpp",
         "src/ports/SkImageGenerator_none.cpp",
         "src/ports/SkTLS_none.cpp",
     ],
@@ -327,6 +328,7 @@
         "src/ports/SkFontMgr_custom_embedded_factory.cpp",
         "src/ports/SkFontMgr_custom_empty_factory.cpp",
         "src/ports/SkFontMgr_empty_factory.cpp",
+        "src/ports/SkFontMgr_fuchsia.cpp",
         "src/ports/SkImageGenerator_none.cpp",
         "src/ports/SkTLS_none.cpp",
     ],
@@ -358,6 +360,7 @@
         "src/ports/SkFontMgr_custom_embedded_factory.cpp",
         "src/ports/SkFontMgr_custom_empty_factory.cpp",
         "src/ports/SkFontMgr_empty_factory.cpp",
+        "src/ports/SkFontMgr_fuchsia.cpp",
         "src/ports/SkImageGenerator_none.cpp",
         "src/ports/SkTLS_none.cpp",
     ],
diff --git a/samplecode/SampleAAGeometry.cpp b/samplecode/SampleAAGeometry.cpp
index 9e60891..af3ebf2 100644
--- a/samplecode/SampleAAGeometry.cpp
+++ b/samplecode/SampleAAGeometry.cpp
@@ -491,7 +491,7 @@
     static const int kMaxStateCount = 3;
     SkPaint fDisabled;
     SkPaint fStates[kMaxStateCount];
-    SkPaint fLabel;
+    SkFont  fLabelFont;
 
     ButtonPaints() {
         fStates[0].setAntiAlias(true);
@@ -501,9 +501,7 @@
         fStates[1].setStrokeWidth(3);
         fStates[2] = fStates[1];
         fStates[2].setColor(0xFFcf0000);
-        fLabel.setAntiAlias(true);
-        fLabel.setTextSize(25.0f);
-        fLabel.setStyle(SkPaint::kFill_Style);
+        fLabelFont.setSize(25.0f);
     }
 };
 
@@ -542,8 +540,8 @@
             return;
         }
         canvas->drawRect(fBounds, paints.fStates[fState]);
-        SkTextUtils::DrawText(canvas, &fLabel, 1, fBounds.centerX(), fBounds.fBottom - 5,
-                              paints.fLabel, SkTextUtils::kCenter_Align);
+        SkTextUtils::Draw(canvas, &fLabel, 1, kUTF8_SkTextEncoding, fBounds.centerX(), fBounds.fBottom - 5,
+                          paints.fLabelFont, SkPaint(), SkTextUtils::kCenter_Align);
     }
 
     void toggle() {
@@ -564,6 +562,9 @@
     SkPaint fLabel;
     SkPaint fValue;
 
+    SkFont fLabelFont;
+    SkFont fValueFont;
+
     ControlPaints() {
         fOutline.setAntiAlias(true);
         fOutline.setStyle(SkPaint::kStroke_Style);
@@ -572,9 +573,9 @@
         fFill.setAntiAlias(true);
         fFill.setColor(0x7fff0000);
         fLabel.setAntiAlias(true);
-        fLabel.setTextSize(13.0f);
+        fLabelFont.setSize(13.0f);
         fValue.setAntiAlias(true);
-        fValue.setTextSize(11.0f);
+        fValueFont.setSize(11.0f);
     }
 };
 
@@ -610,9 +611,9 @@
         canvas->drawLine(fBounds.fLeft - 5, fYLo, fBounds.fRight + 5, fYLo, paints.fIndicator);
         SkString label;
         label.printf("%0.3g", fValLo);
-        canvas->drawString(label, fBounds.fLeft + 5, fYLo - 5, paints.fValue);
-        canvas->drawString(fName, fBounds.fLeft, fBounds.bottom() + 11,
-                paints.fLabel);
+        canvas->drawString(label, fBounds.fLeft + 5, fYLo - 5, paints.fValueFont, paints.fValue);
+        canvas->drawString(fName, fBounds.fLeft, fBounds.bottom() + 11, paints.fLabelFont,
+                           paints.fLabel);
     }
 };
 
@@ -787,8 +788,8 @@
     SkPaint fActivePaint;
     SkPaint fComplexPaint;
     SkPaint fCoveragePaint;
-    SkPaint fLegendLeftPaint;
-    SkPaint fLegendRightPaint;
+    SkFont fLegendLeftFont;
+    SkFont fLegendRightFont;
     SkPaint fPointPaint;
     SkPaint fSkeletonPaint;
     SkPaint fLightSkeletonPaint;
@@ -862,9 +863,8 @@
         fActivePaint.setStrokeWidth(5);
         fComplexPaint = fActivePaint;
         fComplexPaint.setColor(SK_ColorBLUE);
-        fLegendLeftPaint.setAntiAlias(true);
-        fLegendLeftPaint.setTextSize(13);
-        fLegendRightPaint = fLegendLeftPaint;
+        fLegendLeftFont.setSize(13);
+        fLegendRightFont = fLegendLeftFont;
         construct_path(fPath);
         fFillButton.fVisible = fSkeletonButton.fVisible = fFilterButton.fVisible
                 = fBisectButton.fVisible = fJoinButton.fVisible = fInOutButton.fVisible = true;
@@ -1811,12 +1811,11 @@
     SkScalar bottomOffset = this->height() - 10;
     for (int index = kKeyCommandCount - 1; index >= 0; --index) {
         bottomOffset -= 15;
-        SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionL,
-                this->width() - 160, bottomOffset,
-                fLegendLeftPaint);
+        SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionL, this->width() - 160, bottomOffset,
+                                fLegendLeftFont, SkPaint());
         SkTextUtils::DrawString(canvas, kKeyCommandList[index].fDescriptionR,
                 this->width() - 20, bottomOffset,
-                fLegendRightPaint, SkTextUtils::kRight_Align);
+                fLegendRightFont, SkPaint(), SkTextUtils::kRight_Align);
     }
 }
 
diff --git a/samplecode/SampleAll.cpp b/samplecode/SampleAll.cpp
deleted file mode 100644
index 50239d6..0000000
--- a/samplecode/SampleAll.cpp
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "Sample.h"
-
-#include "DecodeFile.h"
-#include "Sk1DPathEffect.h"
-#include "Sk2DPathEffect.h"
-#include "SkBlurMask.h"
-#include "SkBlurMaskFilter.h"
-#include "SkCanvas.h"
-#include "SkColorMatrixFilter.h"
-#include "SkColorPriv.h"
-#include "SkCornerPathEffect.h"
-#include "SkDashPathEffect.h"
-#include "SkDiscretePathEffect.h"
-#include "SkEmbossMaskFilter.h"
-#include "SkGradientShader.h"
-#include "SkMath.h"
-#include "SkPath.h"
-#include "SkPathMeasure.h"
-#include "SkPicture.h"
-#include "SkPictureRecorder.h"
-#include "SkRandom.h"
-#include "SkReadBuffer.h"
-#include "SkRegion.h"
-#include "SkShader.h"
-#include "SkTypeface.h"
-#include "SkUTF.h"
-#include "SkWriteBuffer.h"
-
-#include <math.h>
-
-class Dot2DPathEffect : public Sk2DPathEffect {
-public:
-    Dot2DPathEffect(SkScalar radius, const SkMatrix& matrix)
-        : Sk2DPathEffect(matrix), fRadius(radius) {}
-
-protected:
-    void next(const SkPoint& loc, int u, int v, SkPath* dst) const override {
-        dst->addCircle(loc.fX, loc.fY, fRadius);
-    }
-
-    void flatten(SkWriteBuffer& buffer) const override {
-        this->INHERITED::flatten(buffer);
-        buffer.writeScalar(fRadius);
-    }
-
-private:
-    SK_FLATTENABLE_HOOKS(Dot2DPathEffect)
-
-    SkScalar fRadius;
-
-    typedef Sk2DPathEffect INHERITED;
-};
-
-class DemoView : public Sample {
-public:
-    DemoView() {}
-
-protected:
-    virtual bool onQuery(Sample::Event* evt) {
-        if (Sample::TitleQ(*evt)) {
-            Sample::TitleR(evt, "Demo");
-            return true;
-        }
-        return this->INHERITED::onQuery(evt);
-    }
-
-    virtual bool onClick(Click* click) {
-        return this->INHERITED::onClick(click);
-    }
-
-    void makePath(SkPath& path) {
-        path.addCircle(SkIntToScalar(20), SkIntToScalar(20), SkIntToScalar(20),
-            SkPath::kCCW_Direction);
-        for (int index = 0; index < 10; index++) {
-            SkScalar x = (float) cos(index / 10.0f * 2 * 3.1415925358f);
-            SkScalar y = (float) sin(index / 10.0f * 2 * 3.1415925358f);
-            x *= index & 1 ? 7 : 14;
-            y *= index & 1 ? 7 : 14;
-            x += SkIntToScalar(20);
-            y += SkIntToScalar(20);
-            if (index == 0)
-                path.moveTo(x, y);
-            else
-                path.lineTo(x, y);
-        }
-        path.close();
-    }
-
-    virtual void onDrawContent(SkCanvas* canvas) {
-        canvas->save();
-        this->drawPicture(canvas, 0);
-        canvas->restore();
-
-        {
-            SkPictureRecorder recorder;
-            {
-                SkCanvas* record = recorder.beginRecording(320, 480, nullptr, 0);
-                this->drawPicture(record, 120);
-            }
-            sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
-
-            canvas->translate(0, SkIntToScalar(120));
-
-            SkRect clip;
-            clip.set(0, 0, SkIntToScalar(160), SkIntToScalar(160));
-            do {
-                canvas->save();
-                canvas->clipRect(clip);
-                picture->playback(canvas);
-                canvas->restore();
-                if (clip.fRight < SkIntToScalar(320))
-                    clip.offset(SkIntToScalar(160), 0);
-                else if (clip.fBottom < SkIntToScalar(480))
-                    clip.offset(-SkIntToScalar(320), SkIntToScalar(160));
-                else
-                    break;
-            } while (true);
-        }
-    }
-
-    void drawPicture(SkCanvas* canvas, int spriteOffset) {
-        SkMatrix matrix; matrix.reset();
-        SkPaint paint;
-        SkPath path;
-        SkPoint start = {0, 0};
-        SkPoint stop = { SkIntToScalar(40), SkIntToScalar(40) };
-        SkRect rect = {0, 0, SkIntToScalar(40), SkIntToScalar(40) };
-        SkRect rect2 = {0, 0, SkIntToScalar(65), SkIntToScalar(20) };
-        SkScalar left = 0, top = 0, x = 0, y = 0;
-        int index;
-
-        char ascii[] = "ascii...";
-        int asciiLength = sizeof(ascii) - 1;
-        char utf8[] = "utf8" "\xe2\x80\xa6";
-
-        makePath(path);
-        SkTDArray<SkPoint> pos;
-        pos.setCount(asciiLength);
-        for (index = 0;  index < asciiLength; index++)
-            pos[index].set(SkIntToScalar((unsigned int)index * 10),
-                                       SkIntToScalar((unsigned int)index * 2));
-        SkTDArray<SkPoint> pos2;
-        pos2.setCount(asciiLength);
-        for (index = 0;  index < asciiLength; index++)
-            pos2[index].set(SkIntToScalar((unsigned int)index * 10),
-                                        SkIntToScalar(20));
-
-        // shaders
-        SkPoint linearPoints[] = { { 0, 0, }, { SkIntToScalar(40), SkIntToScalar(40) } };
-        SkColor linearColors[] = { SK_ColorRED, SK_ColorBLUE };
-        SkScalar* linearPos = nullptr;
-        int linearCount = 2;
-        SkShader::TileMode linearMode = SkShader::kMirror_TileMode;
-        auto linear = SkGradientShader::MakeLinear(linearPoints,
-            linearColors, linearPos, linearCount, linearMode);
-
-        SkPoint radialCenter = { SkIntToScalar(25), SkIntToScalar(25) };
-        SkScalar radialRadius = SkIntToScalar(25);
-        SkColor radialColors[] = { SK_ColorGREEN, SK_ColorGRAY, SK_ColorRED };
-        SkScalar radialPos[] = { 0, SkIntToScalar(3) / 5, SkIntToScalar(1)};
-        int radialCount = 3;
-        SkShader::TileMode radialMode = SkShader::kRepeat_TileMode;
-        auto radial = SkGradientShader::MakeRadial(radialCenter,
-            radialRadius, radialColors, radialPos, radialCount,
-            radialMode);
-
-        SkEmbossMaskFilter::Light light;
-        light.fDirection[0] = SK_Scalar1/2;
-        light.fDirection[1] = SK_Scalar1/2;
-        light.fDirection[2] = SK_Scalar1/3;
-        light.fAmbient        = 0x48;
-        light.fSpecular        = 0x80;
-
-        auto lightingFilter = SkColorMatrixFilter::MakeLightingFilter(
-            0xff89bc45, 0xff112233);
-
-        canvas->save();
-        canvas->translate(SkIntToScalar(0), SkIntToScalar(5));
-        paint.setAntiAlias(true);
-        paint.setFilterQuality(kLow_SkFilterQuality);
-        // !!! draw through a clip
-        paint.setColor(SK_ColorLTGRAY);
-        paint.setStyle(SkPaint::kFill_Style);
-        SkRect clip = {0, 0, SkIntToScalar(320), SkIntToScalar(120)};
-        canvas->clipRect(clip);
-        paint.setShader(SkShader::MakeBitmapShader(fTx,
-            SkShader::kMirror_TileMode, SkShader::kRepeat_TileMode));
-        canvas->drawPaint(paint);
-        canvas->save();
-
-        // line (exercises xfermode, colorShader, colorFilter, filterShader)
-        paint.setColor(SK_ColorGREEN);
-        paint.setStrokeWidth(SkIntToScalar(10));
-        paint.setStyle(SkPaint::kStroke_Style);
-        paint.setBlendMode(SkBlendMode::kXor);
-        paint.setColorFilter(lightingFilter);
-        canvas->drawLine(start, stop, paint); // should not be green
-        paint.setBlendMode(SkBlendMode::kSrcOver);
-        paint.setColorFilter(nullptr);
-
-        // rectangle
-        paint.setStyle(SkPaint::kFill_Style);
-        canvas->translate(SkIntToScalar(50), 0);
-        paint.setColor(SK_ColorYELLOW);
-        paint.setShader(linear);
-        paint.setPathEffect(pathEffectTest());
-        canvas->drawRect(rect, paint);
-        paint.setPathEffect(nullptr);
-
-        // circle w/ emboss & transparent (exercises 3dshader)
-        canvas->translate(SkIntToScalar(50), 0);
-        paint.setMaskFilter(SkEmbossMaskFilter::Make(
-                                     SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(12)/5), light));
-        canvas->drawOval(rect, paint);
-        canvas->translate(SkIntToScalar(10), SkIntToScalar(10));
-//        paint.setShader(transparentShader)->unref();
-        canvas->drawOval(rect, paint);
-        canvas->translate(0, SkIntToScalar(-10));
-
-        // path
-        canvas->translate(SkIntToScalar(50), 0);
-        paint.setColor(SK_ColorRED);
-        paint.setStyle(SkPaint::kStroke_Style);
-        paint.setStrokeWidth(SkIntToScalar(5));
-        paint.setShader(radial);
-        paint.setMaskFilter(nullptr);
-        canvas->drawPath(path, paint);
-
-        paint.setShader(nullptr);
-        // bitmap
-        canvas->translate(SkIntToScalar(50), 0);
-        paint.setStyle(SkPaint::kFill_Style);
-        canvas->drawBitmap(fBug, left, top, &paint);
-
-        canvas->translate(-SkIntToScalar(30), SkIntToScalar(30));
-        paint.setShader(shaderTest()); // test compose shader
-        canvas->drawRect(rect2, paint);
-        paint.setShader(nullptr);
-
-        canvas->restore();
-        // text
-        canvas->translate(0, SkIntToScalar(60));
-        canvas->save();
-        paint.setColor(SK_ColorGRAY);
-        canvas->drawPosText(ascii, asciiLength, pos.begin(), paint);
-        canvas->drawPosText(ascii, asciiLength, pos2.begin(), paint);
-
-        canvas->translate(SkIntToScalar(50), 0);
-        paint.setColor(SK_ColorCYAN);
-        canvas->drawText(utf8, sizeof(utf8) - 1, x, y, paint);
-
-        canvas->translate(0, SkIntToScalar(60));
-        paint.setTextEncoding(kUTF8_SkTextEncoding);
-        canvas->restore();
-    }
-
-    virtual Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned modi) {
-        fClickPt.set(x, y);
-        return this->INHERITED::onFindClickHandler(x, y, modi);
-    }
-
-    sk_sp<SkPathEffect> pathEffectTest() {
-        static const int gXY[] = { 1, 0, 0, -1, 2, -1, 3, 0, 2, 1, 0, 1 };
-        SkScalar gPhase = 0;
-        SkPath path;
-        path.moveTo(SkIntToScalar(gXY[0]), SkIntToScalar(gXY[1]));
-        for (unsigned i = 2; i < SK_ARRAY_COUNT(gXY); i += 2)
-            path.lineTo(SkIntToScalar(gXY[i]), SkIntToScalar(gXY[i+1]));
-        path.close();
-        path.offset(SkIntToScalar(-6), 0);
-        auto outer = SkPath1DPathEffect::Make(path, SkIntToScalar(12),
-            gPhase, SkPath1DPathEffect::kRotate_Style);
-        auto inner = SkDiscretePathEffect::Make(SkIntToScalar(2),
-            SkIntToScalar(1)/10); // SkCornerPathEffect(SkIntToScalar(2));
-        return SkPathEffect::MakeCompose(outer, inner);
-    }
-
-    sk_sp<SkShader> shaderTest() {
-        SkPoint pts[] = { { 0, 0, }, { SkIntToScalar(100), 0 } };
-        SkColor colors[] = { SK_ColorRED, SK_ColorBLUE };
-        auto shaderA = SkGradientShader::MakeLinear(pts, colors, nullptr,
-            2, SkShader::kClamp_TileMode);
-        pts[1].set(0, SkIntToScalar(100));
-        SkColor colors2[] = {SK_ColorBLACK,  SkColorSetARGB(0x80, 0, 0, 0)};
-        auto shaderB = SkGradientShader::MakeLinear(pts, colors2, nullptr,
-            2, SkShader::kClamp_TileMode);
-        return SkShader::MakeComposeShader(std::move(shaderA), std::move(shaderB),
-                                           SkBlendMode::kDstIn);
-    }
-
-    virtual void startTest() {
-        decode_file("/Users/caryclark/Desktop/bugcirc.gif", &fBug);
-        decode_file("/Users/caryclark/Desktop/tbcirc.gif", &fTb);
-        decode_file("/Users/caryclark/Desktop/05psp04.gif", &fTx);
-    }
-
-private:
-    SkPoint fClickPt;
-    SkBitmap fBug, fTb, fTx;
-    typedef Sample INHERITED;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-DEF_SAMPLE( return new DemoView(); )
diff --git a/samplecode/SampleAnimatedImage.cpp b/samplecode/SampleAnimatedImage.cpp
index 0d9adad..8f73935 100644
--- a/samplecode/SampleAnimatedImage.cpp
+++ b/samplecode/SampleAnimatedImage.cpp
@@ -9,6 +9,7 @@
 #include "SkAnimatedImage.h"
 #include "SkAnimTimer.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkPaint.h"
 #include "SkPictureRecorder.h"
 #include "SkRect.h"
@@ -30,19 +31,17 @@
 
 protected:
     void onDrawBackground(SkCanvas* canvas) override {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        constexpr SkScalar kTextSize = 20;
-        paint.setTextSize(kTextSize);
+        SkFont font;
+        font.setSize(20);
 
         SkString str = SkStringPrintf("Press '%c' to start/pause; '%c' to reset.",
                 kPauseKey, kResetKey);
         const char* text = str.c_str();
         SkRect bounds;
-        paint.measureText(text, strlen(text), &bounds);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
         fYOffset = bounds.height();
 
-        canvas->drawText(text, strlen(text), 5, fYOffset, paint);
+        canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, 5, fYOffset, font, SkPaint());
         fYOffset *= 2;
     }
 
diff --git a/samplecode/SampleAnimatedText.cpp b/samplecode/SampleAnimatedText.cpp
index 1aa50e2..ba46a87 100644
--- a/samplecode/SampleAnimatedText.cpp
+++ b/samplecode/SampleAnimatedText.cpp
@@ -24,11 +24,10 @@
 SkRandom gRand;
 
 static void DrawTheText(SkCanvas* canvas, const char text[], size_t length, SkScalar x, SkScalar y,
-                        const SkPaint& paint) {
-    SkPaint p(paint);
-
-    p.setSubpixelText(true);
-    canvas->drawText(text, length, x, y, p);
+                        const SkFont& font, const SkPaint& paint) {
+    SkFont f(font);
+    f.setSubpixel(true);
+    canvas->drawSimpleText(text, length, kUTF8_SkTextEncoding, x, y, f, paint);
 }
 
 // This sample demonstrates the cache behavior of bitmap vs. distance field text
@@ -69,8 +68,9 @@
     }
 
     void onDrawContent(SkCanvas* canvas) override {
+        SkFont font(SkTypeface::MakeFromFile("/skimages/samplefont.ttf"));
+
         SkPaint paint;
-        paint.setTypeface(SkTypeface::MakeFromFile("/skimages/samplefont.ttf"));
         paint.setAntiAlias(true);
         paint.setFilterQuality(kMedium_SkFilterQuality);
 
@@ -118,15 +118,15 @@
 
         SkScalar y = SkIntToScalar(0);
         for (int i = 12; i <= 26; i++) {
-            paint.setTextSize(SkIntToScalar(i*fSizeScale));
-            y += paint.getFontSpacing();
-            DrawTheText(canvas, text, length, SkIntToScalar(110), y, paint);
+            font.setSize(SkIntToScalar(i*fSizeScale));
+            y += font.getSpacing();
+            DrawTheText(canvas, text, length, SkIntToScalar(110), y, font, paint);
         }
         canvas->restore();
 
-        paint.setTextSize(16);
+        font.setSize(16);
 //        canvas->drawString(outString, 512.f, 540.f, paint);
-        canvas->drawString(modeString, 768.f, 540.f, paint);
+        canvas->drawString(modeString, 768.f, 540.f, font, paint);
     }
 
     bool onAnimate(const SkAnimTimer& timer) override {
diff --git a/samplecode/SampleArc.cpp b/samplecode/SampleArc.cpp
index a64c47f..58e4057 100644
--- a/samplecode/SampleArc.cpp
+++ b/samplecode/SampleArc.cpp
@@ -84,19 +84,7 @@
     sk_sp<MyDrawable> fAnimatingDrawable;
     sk_sp<SkDrawable> fRootDrawable;
 
-    ArcsView() {
-        testparse();
-        fSweep = SkIntToScalar(100);
-        this->setBGColor(0xFFDDDDDD);
-
-        fRect.set(0, 0, SkIntToScalar(200), SkIntToScalar(200));
-        fRect.offset(SkIntToScalar(20), SkIntToScalar(20));
-        fAnimatingDrawable = sk_make_sp<MyDrawable>(fRect);
-
-        SkPictureRecorder recorder;
-        this->drawRoot(recorder.beginRecording(SkRect::MakeWH(800, 500)));
-        fRootDrawable = recorder.finishRecordingAsDrawable();
-    }
+    ArcsView() { }
 
 protected:
     bool onQuery(Sample::Event* evt) override {
@@ -116,15 +104,13 @@
     }
 
     static void DrawLabel(SkCanvas* canvas, const SkRect& rect, SkScalar start, SkScalar sweep) {
-        SkPaint paint;
-        paint.setAntiAlias(true);
-
+        SkFont font;
         SkString    str;
         str.appendScalar(start);
         str.append(", ");
         str.appendScalar(sweep);
-        SkTextUtils::DrawString(canvas, str, rect.centerX(),
-                         rect.fBottom + paint.getTextSize() * 5/4, paint,
+        SkTextUtils::DrawString(canvas, str.c_str(), rect.centerX(),
+                         rect.fBottom + font.getSize() * 5/4, font, SkPaint(),
                                 SkTextUtils::kCenter_Align);
     }
 
@@ -183,6 +169,20 @@
         DrawArcs(canvas);
     }
 
+    void onOnceBeforeDraw() override {
+        testparse();
+        fSweep = SkIntToScalar(100);
+        this->setBGColor(0xFFDDDDDD);
+
+        fRect.set(0, 0, SkIntToScalar(200), SkIntToScalar(200));
+        fRect.offset(SkIntToScalar(20), SkIntToScalar(20));
+        fAnimatingDrawable = sk_make_sp<MyDrawable>(fRect);
+
+        SkPictureRecorder recorder;
+        this->drawRoot(recorder.beginRecording(SkRect::MakeWH(800, 500)));
+        fRootDrawable = recorder.finishRecordingAsDrawable();
+    }
+
     void onDrawContent(SkCanvas* canvas) override {
         canvas->drawDrawable(fRootDrawable.get());
     }
diff --git a/samplecode/SampleAtlas.cpp b/samplecode/SampleAtlas.cpp
index 652ef14..946c5e5 100644
--- a/samplecode/SampleAtlas.cpp
+++ b/samplecode/SampleAtlas.cpp
@@ -45,7 +45,6 @@
     SkCanvas* canvas = surface->getCanvas();
 
     SkPaint paint;
-    paint.setAntiAlias(true);
     SkRandom rand;
 
     const SkScalar half = cellSize * SK_ScalarHalf;
@@ -57,8 +56,8 @@
             paint.setColor(rand.nextU());
             paint.setAlpha(0xFF);
             int index = i % strlen(s);
-            SkTextUtils::DrawText(canvas, &s[index], 1, x + half, y + half + half/2, paint,
-                                  SkTextUtils::kCenter_Align);
+            SkTextUtils::Draw(canvas, &s[index], 1, kUTF8_SkTextEncoding, x + half, y + half + half/2, SkFont(), paint,
+                              SkTextUtils::kCenter_Align);
             i += 1;
         }
     }
diff --git a/samplecode/SampleBitmapRect.cpp b/samplecode/SampleBitmapRect.cpp
index 61f33618..9dcf98c 100644
--- a/samplecode/SampleBitmapRect.cpp
+++ b/samplecode/SampleBitmapRect.cpp
@@ -9,6 +9,7 @@
 #include "SkAnimTimer.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkGraphics.h"
 #include "SkPath.h"
@@ -160,18 +161,17 @@
 
     const int BIG_H = 120;
 
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setTextSize(SkIntToScalar(BIG_H));
+    SkFont font;
+    font.setSize(SkIntToScalar(BIG_H));
 
-    const int BIG_W = SkScalarRoundToInt(paint.measureText(gText, strlen(gText)));
+    const int BIG_W = SkScalarRoundToInt(font.measureText(gText, strlen(gText), kUTF8_SkTextEncoding));
 
     bm->allocN32Pixels(BIG_W, BIG_H);
     bm->eraseColor(SK_ColorWHITE);
 
     SkCanvas canvas(*bm);
 
-    canvas.drawString(gText, 0, paint.getTextSize()*4/5, paint);
+    canvas.drawSimpleText(gText, strlen(gText), kUTF8_SkTextEncoding, 0, font.getSize()*4/5, font, SkPaint());
 }
 
 class BitmapRectView2 : public Sample {
diff --git a/samplecode/SampleBlur.cpp b/samplecode/SampleBlur.cpp
deleted file mode 100644
index 6b5481e..0000000
--- a/samplecode/SampleBlur.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "Sample.h"
-#include "SkBitmap.h"
-#include "SkBlurMask.h"
-#include "SkCanvas.h"
-#include "SkColorPriv.h"
-#include "SkGradientShader.h"
-#include "SkMaskFilter.h"
-#include "SkUTF.h"
-
-class BlurView : public Sample {
-    SkBitmap    fBM;
-public:
-    BlurView() {}
-
-protected:
-    virtual bool onQuery(Sample::Event* evt) {
-        if (Sample::TitleQ(*evt)) {
-            Sample::TitleR(evt, "Blur");
-            return true;
-        }
-        return this->INHERITED::onQuery(evt);
-    }
-
-    void drawBG(SkCanvas* canvas) {
-        canvas->drawColor(0xFFDDDDDD);
-    }
-
-    virtual void onDrawContent(SkCanvas* canvas) {
-        drawBG(canvas);
-
-        SkBlurStyle NONE = SkBlurStyle(-999);
-        static const struct {
-            SkBlurStyle fStyle;
-            int         fCx, fCy;
-        } gRecs[] = {
-            { NONE,                                 0,  0 },
-            { kInner_SkBlurStyle,  -1,  0 },
-            { kNormal_SkBlurStyle,  0,  1 },
-            { kSolid_SkBlurStyle,   0, -1 },
-            { kOuter_SkBlurStyle,   1,  0 },
-        };
-
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setTextSize(25);
-        canvas->translate(-40, 0);
-
-        paint.setColor(SK_ColorBLUE);
-        for (size_t i = 0; i < SK_ARRAY_COUNT(gRecs); i++) {
-            if (gRecs[i].fStyle != NONE) {
-                paint.setMaskFilter(SkMaskFilter::MakeBlur(gRecs[i].fStyle,
-                                    SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(20))));
-            } else {
-                paint.setMaskFilter(nullptr);
-            }
-            canvas->drawCircle(200 + gRecs[i].fCx*100.f,
-                               200 + gRecs[i].fCy*100.f, 50, paint);
-        }
-        // draw text
-        {
-            paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
-                                                       SkBlurMask::ConvertRadiusToSigma(4)));
-            SkScalar x = SkIntToScalar(70);
-            SkScalar y = SkIntToScalar(400);
-            paint.setColor(SK_ColorBLACK);
-            canvas->drawString("Hamburgefons Style", x, y, paint);
-            canvas->drawString("Hamburgefons Style", x, y + SkIntToScalar(50), paint);
-            paint.setMaskFilter(nullptr);
-            paint.setColor(SK_ColorWHITE);
-            x -= SkIntToScalar(2);
-            y -= SkIntToScalar(2);
-            canvas->drawString("Hamburgefons Style", x, y, paint);
-        }
-    }
-
-private:
-    typedef Sample INHERITED;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-DEF_SAMPLE( return new BlurView(); )
diff --git a/samplecode/SampleChineseFling.cpp b/samplecode/SampleChineseFling.cpp
index b8110f3..a556bd6 100644
--- a/samplecode/SampleChineseFling.cpp
+++ b/samplecode/SampleChineseFling.cpp
@@ -20,16 +20,6 @@
 #include "GrContextPriv.h"
 #endif
 
-static void make_paint(SkPaint* paint, sk_sp<SkTypeface> typeface) {
-  static const int kTextSize = 56;
-
-  paint->setAntiAlias(true);
-  paint->setColor(0xDE000000);
-  paint->setTypeface(typeface);
-  paint->setTextSize(kTextSize);
-  paint->setTextEncoding(kUTF32_SkTextEncoding);
-}
-
 static sk_sp<SkTypeface> chinese_typeface() {
 #ifdef SK_BUILD_FOR_ANDROID
     return MakeResourceAsTypeface("fonts/NotoSansCJK-Regular.ttc");
@@ -68,7 +58,7 @@
         canvas->clear(0xFFDDDDDD);
 
         SkPaint paint;
-        make_paint(&paint, fTypeface);
+        paint.setColor(0xDE000000);
 
         // draw a consistent run of the 'words' - one word per line
         int index = fIndex;
@@ -93,10 +83,8 @@
     void init() {
         fTypeface = chinese_typeface();
 
-        SkPaint paint;
-        make_paint(&paint, fTypeface);
-
-        paint.getFontMetrics(&fMetrics);
+        SkFont font(fTypeface, 56);
+        font.getMetrics(&fMetrics);
 
         SkUnichar glyphs[kWordLength];
         for (int32_t i = 0; i < kNumBlobs; ++i) {
@@ -104,7 +92,7 @@
 
             SkTextBlobBuilder builder;
             sk_tool_utils::add_to_text_blob_w_len(&builder, (const char*) glyphs, kWordLength*4,
-                                                  paint, 0, 0);
+                                                  kUTF32_SkTextEncoding, font, 0, 0);
 
             fBlobs.emplace_back(builder.make());
         }
@@ -165,9 +153,6 @@
         SkPaint paint;
         paint.setAntiAlias(true);
         paint.setColor(0xDE000000);
-        paint.setTypeface(fTypeface);
-        paint.setTextSize(11);
-        paint.setTextEncoding(kUTF32_SkTextEncoding);
 
         if (afterFirstFrame) {
 #if SK_SUPPORT_GPU
@@ -215,14 +200,11 @@
     void init() {
         fTypeface = chinese_typeface();
 
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setColor(0xDE000000);
-        paint.setTypeface(fTypeface);
-        paint.setTextSize(11);
-        paint.setTextEncoding(kUTF32_SkTextEncoding);
+        SkFont font(fTypeface, 11);
+        font.getMetrics(&fMetrics);
 
-        paint.getFontMetrics(&fMetrics);
+        SkPaint paint;
+        paint.setColor(0xDE000000);
 
         SkUnichar glyphs[45];
         for (int32_t i = 0; i < kNumBlobs; ++i) {
@@ -234,7 +216,8 @@
                 this->createRandomLine(glyphs, currentLineLength);
 
                 sk_tool_utils::add_to_text_blob_w_len(&builder, (const char*) glyphs,
-                                                      currentLineLength*4, paint, 0, y);
+                                                      currentLineLength*4, kUTF32_SkTextEncoding,
+                                                      font, 0, y);
                 y += fMetrics.fDescent - fMetrics.fAscent + fMetrics.fLeading;
                 paragraphLength -= 45;
             }
diff --git a/samplecode/SampleComplexClip.cpp b/samplecode/SampleComplexClip.cpp
index fea5dd1..f4da38c 100644
--- a/samplecode/SampleComplexClip.cpp
+++ b/samplecode/SampleComplexClip.cpp
@@ -7,6 +7,7 @@
 
 #include "Sample.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkPath.h"
 #include "SkClipOpPriv.h"
 
@@ -65,6 +66,9 @@
         clipB.close();
         SkColor colorB = SK_ColorRED;
 
+        SkFont font;
+        font.setSize(20);
+
         SkPaint paint;
         paint.setAntiAlias(true);
 
@@ -122,19 +126,17 @@
                 paint.setColor(colorB);
                 canvas->drawPath(clipB, paint);
 
-                paint.setTextSize(SkIntToScalar(20));
-
                 SkScalar txtX = SkIntToScalar(55);
                 paint.setColor(colorA);
                 const char* aTxt = invA ? "InverseA " : "A ";
-                canvas->drawString(aTxt, txtX, SkIntToScalar(220), paint);
-                txtX += paint.measureText(aTxt, strlen(aTxt));
+                canvas->drawSimpleText(aTxt, strlen(aTxt), kUTF8_SkTextEncoding, txtX, SkIntToScalar(220), font, paint);
+                txtX += font.measureText(aTxt, strlen(aTxt), kUTF8_SkTextEncoding);
                 paint.setColor(SK_ColorBLACK);
-                canvas->drawString(gOps[op].fName,
-                                    txtX, SkIntToScalar(220), paint);
-                txtX += paint.measureText(gOps[op].fName, strlen(gOps[op].fName));
+                canvas->drawSimpleText(gOps[op].fName, strlen(gOps[op].fName), kUTF8_SkTextEncoding,
+                                    txtX, 220, font, paint);
+                txtX += font.measureText(gOps[op].fName, strlen(gOps[op].fName), kUTF8_SkTextEncoding);
                 paint.setColor(colorB);
-                canvas->drawString("B", txtX, SkIntToScalar(220), paint);
+                canvas->drawSimpleText("B", 1, kUTF8_SkTextEncoding, txtX, 220, font, paint);
 
                 canvas->translate(SkIntToScalar(250),0);
             }
diff --git a/samplecode/SampleCusp.cpp b/samplecode/SampleCusp.cpp
index 7eb85f1..32ba90d 100644
--- a/samplecode/SampleCusp.cpp
+++ b/samplecode/SampleCusp.cpp
@@ -4,9 +4,11 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+
 #include "Sample.h"
 #include "SkAnimTimer.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkGeometry.h"
 #include "SkPath.h"
 #include <string>
@@ -165,7 +167,7 @@
         canvas->drawPath(path, p);
         // draw time to make it easier to guess when the bad cubic was drawn
         std::string timeStr = std::to_string((float) (curTime - start) / 1000.f);
-        canvas->drawString(timeStr.c_str(), 20, 20, SkPaint());
+        canvas->drawSimpleText(timeStr.c_str(), timeStr.size(), kUTF8_SkTextEncoding, 20, 20, SkFont(), SkPaint());
         SkDebugf("");
     }
 
diff --git a/samplecode/SampleDegenerateTwoPtRadials.cpp b/samplecode/SampleDegenerateTwoPtRadials.cpp
index b9044c1..7392c66 100644
--- a/samplecode/SampleDegenerateTwoPtRadials.cpp
+++ b/samplecode/SampleDegenerateTwoPtRadials.cpp
@@ -8,6 +8,7 @@
 #include "Sample.h"
 #include "SkAnimTimer.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkString.h"
 
@@ -68,10 +69,8 @@
         draw_gradient2(canvas, SkRect::MakeXYWH(l, t, w, h), delta);
         SkString txt;
         txt.appendf("gap at \"tangent\" pt = %f", SkScalarToFloat(delta));
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setColor(SK_ColorBLACK);
-        canvas->drawString(txt, l + w/2 + w*DELTA_SCALE*delta, t + h + SK_Scalar1 * 10, paint);
+        canvas->drawString(txt, l + w / 2 + w * DELTA_SCALE * delta, t + h + SK_Scalar1 * 10,
+                           SkFont(), SkPaint());
     }
 
     bool onAnimate(const SkAnimTimer& timer) override {
diff --git a/samplecode/SampleFilter2.cpp b/samplecode/SampleFilter2.cpp
index e4a43ca..7a7acf0 100644
--- a/samplecode/SampleFilter2.cpp
+++ b/samplecode/SampleFilter2.cpp
@@ -80,25 +80,21 @@
                     x = SkScalarRoundToScalar(x);
                     y = SkScalarRoundToScalar(y);
                     canvas->drawBitmap(fBitmaps[i], x, y, &paint);
+                    SkFont font;
+                    font.setSize(SkIntToScalar(18));
                     if (i == 0) {
-                        SkPaint p;
-                        p.setAntiAlias(true);
-                        p.setTextSize(SkIntToScalar(18));
                         SkString s("dither=");
                         s.appendS32(paint.isDither());
                         s.append(" filter=");
                         s.appendS32(paint.getFilterQuality() != kNone_SkFilterQuality);
-                        SkTextUtils::DrawString(canvas, s, x + W/2, y - p.getTextSize(), p,
+                        SkTextUtils::DrawString(canvas, s.c_str(), x + W/2, y - font.getSize(), font, SkPaint(),
                                                 SkTextUtils::kCenter_Align);
                     }
                     if (k+j == 2) {
-                        SkPaint p;
-                        p.setAntiAlias(true);
-                        p.setTextSize(SkIntToScalar(18));
                         SkString s;
                         s.append(" depth=");
                         s.appendS32(fBitmaps[i].colorType() == kRGB_565_SkColorType ? 16 : 32);
-                        SkTextUtils::DrawString(canvas, s, x + W + SkIntToScalar(4), y + H/2, p);
+                        SkTextUtils::DrawString(canvas, s.c_str(), x + W + SkIntToScalar(4), y + H/2, font, SkPaint());
                     }
                 }
             }
diff --git a/samplecode/SampleFilterFuzz.cpp b/samplecode/SampleFilterFuzz.cpp
deleted file mode 100644
index 4bb6fcc..0000000
--- a/samplecode/SampleFilterFuzz.cpp
+++ /dev/null
@@ -1,810 +0,0 @@
-/*
- * Copyright 2013 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-#include "Sample.h"
-#include "Sk1DPathEffect.h"
-#include "Sk2DPathEffect.h"
-#include "SkAlphaThresholdFilter.h"
-#include "SkBlurImageFilter.h"
-#include "SkCanvas.h"
-#include "SkColorFilter.h"
-#include "SkColorFilterImageFilter.h"
-#include "SkColorMatrixFilter.h"
-#include "SkComposeImageFilter.h"
-#include "SkCornerPathEffect.h"
-#include "SkDashPathEffect.h"
-#include "SkData.h"
-#include "SkDiscretePathEffect.h"
-#include "SkDisplacementMapEffect.h"
-#include "SkDropShadowImageFilter.h"
-#include "SkEmbossMaskFilter.h"
-#include "SkImageSource.h"
-#include "SkLightingImageFilter.h"
-#include "SkLumaColorFilter.h"
-#include "SkMagnifierImageFilter.h"
-#include "SkMatrixConvolutionImageFilter.h"
-#include "SkMergeImageFilter.h"
-#include "SkMorphologyImageFilter.h"
-#include "SkOffsetImageFilter.h"
-#include "SkPaintImageFilter.h"
-#include "SkPerlinNoiseShader.h"
-#include "SkPictureImageFilter.h"
-#include "SkPictureRecorder.h"
-#include "SkPoint3.h"
-#include "SkRandom.h"
-#include "SkRegion.h"
-#include "SkTableColorFilter.h"
-#include "SkTileImageFilter.h"
-#include "SkTypeface.h"
-#include "SkXfermodeImageFilter.h"
-#if SK_SUPPORT_GPU
-#include "text/GrSDFMaskFilter.h"
-#endif
-#include <stdio.h>
-#include <time.h>
-
-//#define SK_ADD_RANDOM_BIT_FLIPS
-//#define SK_FUZZER_IS_VERBOSE
-
-static const uint32_t kSeed = (uint32_t)(time(nullptr));
-static SkRandom gRand(kSeed);
-static bool return_large = false;
-static bool return_undef = false;
-
-static const int kBitmapSize = 24;
-
-static int R(float x) {
-    return (int)floor(SkScalarToFloat(gRand.nextUScalar1()) * x);
-}
-
-#if defined _WIN32
-#pragma warning ( push )
-// we are intentionally causing an overflow here
-//      (warning C4756: overflow in constant arithmetic)
-#pragma warning ( disable : 4756 )
-#endif
-
-static float huge() {
-    double d = 1e100;
-    float f = (float)d;
-    return f;
-}
-
-#if defined _WIN32
-#pragma warning ( pop )
-#endif
-
-static float make_number(bool positiveOnly) {
-    float f = positiveOnly ? 1.0f : 0.0f;
-    float v = f;
-    int sel;
-
-    if (return_large) sel = R(6); else sel = R(4);
-    if (!return_undef && sel == 0) sel = 1;
-
-    if (R(2) == 1) v = (float)(R(100)+f); else
-
-    switch (sel) {
-        case 0: break;
-        case 1: v = f; break;
-        case 2: v = 0.000001f; break;
-        case 3: v = 10000.0f; break;
-        case 4: v = 2000000000.0f; break;
-        case 5: v = huge(); break;
-    }
-
-    if (!positiveOnly && (R(4) == 1)) v = -v;
-    return v;
-}
-
-static SkScalar make_scalar(bool positiveOnly = false) {
-    return make_number(positiveOnly);
-}
-
-static SkString make_string() {
-    int length = R(1000);
-    SkString str(length);
-    for (int i = 0; i < length; ++i) {
-        str[i] = static_cast<char>(R(256));
-    }
-    return str;
-}
-
-static SkString make_font_name() {
-    int sel = R(8);
-
-    switch(sel) {
-        case 0: return SkString("Courier New");
-        case 1: return SkString("Helvetica");
-        case 2: return SkString("monospace");
-        case 3: return SkString("sans-serif");
-        case 4: return SkString("serif");
-        case 5: return SkString("Times");
-        case 6: return SkString("Times New Roman");
-        case 7:
-        default:
-            return make_string();
-    }
-}
-
-static bool make_bool() {
-    return R(2) == 1;
-}
-
-static SkRect make_rect() {
-    return SkRect::MakeWH(SkIntToScalar(R(static_cast<float>(kBitmapSize))),
-                          SkIntToScalar(R(static_cast<float>(kBitmapSize))));
-}
-
-static SkRegion make_region() {
-    SkIRect iRegion = SkIRect::MakeXYWH(R(static_cast<float>(kBitmapSize)),
-                                        R(static_cast<float>(kBitmapSize)),
-                                        R(static_cast<float>(kBitmapSize)),
-                                        R(static_cast<float>(kBitmapSize)));
-    return SkRegion(iRegion);
-}
-
-static SkMatrix make_matrix() {
-    SkMatrix m;
-    for (int i = 0; i < 9; ++i) {
-        m[i] = make_scalar();
-    }
-    return m;
-}
-
-static SkBlendMode make_xfermode() {
-    return static_cast<SkBlendMode>(R((int)SkBlendMode::kLastMode+1));
-}
-
-static SkFontHinting make_paint_hinting() {
-    return static_cast<SkFontHinting>(R(static_cast<unsigned>(kFull_SkFontHinting)+1));
-}
-
-static SkPaint::Style make_paint_style() {
-    return static_cast<SkPaint::Style>(R(SkPaint::kStrokeAndFill_Style+1));
-}
-
-static SkPaint::Cap make_paint_cap() {
-    return static_cast<SkPaint::Cap>(R(SkPaint::kDefault_Cap+1));
-}
-
-static SkPaint::Join make_paint_join() {
-    return static_cast<SkPaint::Join>(R(SkPaint::kDefault_Join+1));
-}
-
-static SkTextEncoding make_paint_text_encoding() {
-    return static_cast<SkTextEncoding>(R((int)kGlyphID_SkTextEncoding+1));
-}
-
-static SkBlurStyle make_blur_style() {
-    return static_cast<SkBlurStyle>(R(kLastEnum_SkBlurStyle+1));
-}
-
-static bool make_blur_mask_filter_respectctm() {
-    return static_cast<bool>(R(2));
-}
-
-static SkFilterQuality make_filter_quality() {
-    return static_cast<SkFilterQuality>(R(kHigh_SkFilterQuality+1));
-}
-
-static SkFontStyle make_typeface_style() {
-    return SkFontStyle::Normal();
-}
-
-static SkPath1DPathEffect::Style make_path_1d_path_effect_style() {
-    return static_cast<SkPath1DPathEffect::Style>(R((int)SkPath1DPathEffect::kLastEnum_Style + 1));
-}
-
-static SkColor make_color() {
-    return (R(2) == 1) ? 0xFFC0F0A0 : 0xFF000090;
-}
-
-static SkDropShadowImageFilter::ShadowMode make_shadow_mode() {
-    return (R(2) == 1) ? SkDropShadowImageFilter::kDrawShadowAndForeground_ShadowMode :
-                         SkDropShadowImageFilter::kDrawShadowOnly_ShadowMode;
-}
-
-static SkPoint3 make_point() {
-    return SkPoint3::Make(make_scalar(), make_scalar(), make_scalar(true));
-}
-
-static SkDisplacementMapEffect::ChannelSelectorType make_channel_selector_type() {
-    return static_cast<SkDisplacementMapEffect::ChannelSelectorType>(R(4)+1);
-}
-
-static bool valid_for_raster_canvas(const SkImageInfo& info) {
-    switch (info.colorType()) {
-        case kAlpha_8_SkColorType:
-        case kRGB_565_SkColorType:
-            return true;
-        case kN32_SkColorType:
-            return kPremul_SkAlphaType == info.alphaType() ||
-                   kOpaque_SkAlphaType == info.alphaType();
-        default:
-            break;
-    }
-    return false;
-}
-
-static SkColorType rand_colortype() {
-    return (SkColorType)R(kLastEnum_SkColorType + 1);
-}
-
-static void rand_bitmap_for_canvas(SkBitmap* bitmap) {
-    SkImageInfo info;
-    do {
-        info = SkImageInfo::Make(kBitmapSize, kBitmapSize, rand_colortype(),
-                                 kPremul_SkAlphaType);
-    } while (!valid_for_raster_canvas(info) || !bitmap->tryAllocPixels(info));
-}
-
-static void make_g_bitmap(SkBitmap& bitmap) {
-    rand_bitmap_for_canvas(&bitmap);
-
-    SkCanvas canvas(bitmap);
-    canvas.clear(0x00000000);
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setColor(0xFF884422);
-    paint.setTextSize(SkIntToScalar(kBitmapSize/2));
-    const char* str = "g";
-    canvas.drawString(str, SkIntToScalar(kBitmapSize/8),
-                    SkIntToScalar(kBitmapSize/4), paint);
-}
-
-static void make_checkerboard_bitmap(SkBitmap& bitmap) {
-    rand_bitmap_for_canvas(&bitmap);
-
-    SkCanvas canvas(bitmap);
-    canvas.clear(0x00000000);
-    SkPaint darkPaint;
-    darkPaint.setColor(0xFF804020);
-    SkPaint lightPaint;
-    lightPaint.setColor(0xFF244484);
-    const int i = kBitmapSize / 8;
-    const SkScalar f = SkIntToScalar(i);
-    for (int y = 0; y < kBitmapSize; y += i) {
-        for (int x = 0; x < kBitmapSize; x += i) {
-            canvas.save();
-            canvas.translate(SkIntToScalar(x), SkIntToScalar(y));
-            canvas.drawRect(SkRect::MakeXYWH(0, 0, f, f), darkPaint);
-            canvas.drawRect(SkRect::MakeXYWH(f, 0, f, f), lightPaint);
-            canvas.drawRect(SkRect::MakeXYWH(0, f, f, f), lightPaint);
-            canvas.drawRect(SkRect::MakeXYWH(f, f, f, f), darkPaint);
-            canvas.restore();
-        }
-    }
-}
-
-static const SkBitmap& make_bitmap() {
-    static SkBitmap bitmap[2];
-    static bool initialized = false;
-    if (!initialized) {
-        make_g_bitmap(bitmap[0]);
-        make_checkerboard_bitmap(bitmap[1]);
-        initialized = true;
-    }
-    return bitmap[R(2)];
-}
-
-static sk_sp<SkData> make_3Dlut(int* cubeDimension, bool invR, bool invG, bool invB) {
-    int size = 4 << R(5);
-    auto data = SkData::MakeUninitialized(sizeof(SkColor) * size * size * size);
-    SkColor* pixels = (SkColor*)(data->writable_data());
-    SkAutoTMalloc<uint8_t> lutMemory(size);
-    SkAutoTMalloc<uint8_t> invLutMemory(size);
-    uint8_t* lut = lutMemory.get();
-    uint8_t* invLut = invLutMemory.get();
-    const int maxIndex = size - 1;
-    for (int i = 0; i < size; i++) {
-        lut[i] = (i * 255) / maxIndex;
-        invLut[i] = ((maxIndex - i) * 255) / maxIndex;
-    }
-    for (int r = 0; r < size; ++r) {
-        for (int g = 0; g < size; ++g) {
-            for (int b = 0; b < size; ++b) {
-                pixels[(size * ((size * b) + g)) + r] = SkColorSetARGB(0xFF,
-                        invR ? invLut[r] : lut[r],
-                        invG ? invLut[g] : lut[g],
-                        invB ? invLut[b] : lut[b]);
-            }
-        }
-    }
-    if (cubeDimension) {
-        *cubeDimension = size;
-    }
-    return data;
-}
-
-static void drawSomething(SkCanvas* canvas) {
-    SkPaint paint;
-
-    canvas->save();
-    canvas->scale(0.5f, 0.5f);
-    canvas->drawBitmap(make_bitmap(), 0, 0, nullptr);
-    canvas->restore();
-
-    paint.setAntiAlias(true);
-
-    paint.setColor(SK_ColorRED);
-    canvas->drawCircle(SkIntToScalar(kBitmapSize/2), SkIntToScalar(kBitmapSize/2), SkIntToScalar(kBitmapSize/3), paint);
-    paint.setColor(SK_ColorBLACK);
-    paint.setTextSize(SkIntToScalar(kBitmapSize/3));
-    canvas->drawString("Picture", SkIntToScalar(kBitmapSize/2), SkIntToScalar(kBitmapSize/4), paint);
-}
-
-static void rand_color_table(uint8_t* table) {
-    for (int i = 0; i < 256; ++i) {
-        table[i] = R(256);
-    }
-}
-
-static sk_sp<SkColorFilter> make_color_filter() {
-    switch (R(6)) {
-        case 0: {
-            SkScalar array[20];
-            for (int i = 0; i < 20; ++i) {
-                array[i] = make_scalar();
-            }
-            return SkColorFilter::MakeMatrixFilterRowMajor255(array);
-        }
-        case 1:
-            return SkLumaColorFilter::Make();
-        case 2: {
-            uint8_t tableA[256];
-            uint8_t tableR[256];
-            uint8_t tableG[256];
-            uint8_t tableB[256];
-            rand_color_table(tableA);
-            rand_color_table(tableR);
-            rand_color_table(tableG);
-            rand_color_table(tableB);
-            return SkTableColorFilter::MakeARGB(tableA, tableR, tableG, tableB);
-        }
-        case 3:
-            return SkColorFilter::MakeModeFilter(make_color(), make_xfermode());
-        case 4:
-            return SkColorMatrixFilter::MakeLightingFilter(make_color(), make_color());
-        case 5:
-        default:
-            break;
-    }
-    return nullptr;
-}
-
-static SkPath make_path() {
-    SkPath path;
-    int numOps = R(30);
-    for (int i = 0; i < numOps; ++i) {
-        switch (R(6)) {
-            case 0:
-                path.moveTo(make_scalar(), make_scalar());
-                break;
-            case 1:
-                path.lineTo(make_scalar(), make_scalar());
-                break;
-            case 2:
-                path.quadTo(make_scalar(), make_scalar(), make_scalar(), make_scalar());
-                break;
-            case 3:
-                path.conicTo(make_scalar(), make_scalar(), make_scalar(), make_scalar(), make_scalar());
-                break;
-            case 4:
-                path.cubicTo(make_scalar(), make_scalar(), make_scalar(),
-                             make_scalar(), make_scalar(), make_scalar());
-                break;
-            case 5:
-            default:
-                path.arcTo(make_scalar(), make_scalar(), make_scalar(), make_scalar(), make_scalar());
-                break;
-
-        }
-    }
-    path.close();
-    return path;
-}
-
-static sk_sp<SkPathEffect> make_path_effect(bool canBeNull = true) {
-    sk_sp<SkPathEffect> pathEffect;
-    if (canBeNull && (R(3) == 1)) { return pathEffect; }
-
-    switch (R(8)) {
-        case 0:
-            pathEffect = SkPath2DPathEffect::Make(make_matrix(), make_path());
-            break;
-        case 1:
-            pathEffect = SkPathEffect::MakeCompose(make_path_effect(false),
-                                                   make_path_effect(false));
-            break;
-        case 2:
-            pathEffect = SkCornerPathEffect::Make(make_scalar());
-            break;
-        case 3: {
-            int count = R(10);
-            SkScalar intervals[10];
-            for (int i = 0; i < count; ++i) {
-                intervals[i] = make_scalar();
-            }
-            pathEffect = SkDashPathEffect::Make(intervals, count, make_scalar());
-            break;
-        }
-        case 4:
-            pathEffect = SkDiscretePathEffect::Make(make_scalar(), make_scalar());
-            break;
-        case 5:
-            pathEffect = SkPath1DPathEffect::Make(make_path(), make_scalar(), make_scalar(),
-                                                  make_path_1d_path_effect_style());
-            break;
-        case 6:
-            pathEffect = SkLine2DPathEffect::Make(make_scalar(), make_matrix());
-            break;
-        case 7:
-        default:
-            pathEffect = SkPathEffect::MakeSum(make_path_effect(false),
-                                               make_path_effect(false));
-            break;
-    }
-    return pathEffect;
-}
-
-static sk_sp<SkMaskFilter> make_mask_filter() {
-    sk_sp<SkMaskFilter> maskFilter;
-#if SK_SUPPORT_GPU
-    switch (R(4)) {
-#else
-    switch (R(3)) {
-#endif
-        case 0:
-            maskFilter = SkMaskFilter::MakeBlur(make_blur_style(), make_scalar(),
-                                                make_blur_mask_filter_respectctm());
-        case 1: {
-            SkEmbossMaskFilter::Light light;
-            for (int i = 0; i < 3; ++i) {
-                light.fDirection[i] = make_scalar();
-            }
-            light.fPad = R(65536);
-            light.fAmbient = R(256);
-            light.fSpecular = R(256);
-            maskFilter = SkEmbossMaskFilter::Make(make_scalar(), light);
-        }
-#if SK_SUPPORT_GPU
-        case 2:
-            maskFilter = GrSDFMaskFilter::Make();
-        case 3:
-#else
-        case 2:
-#endif
-        default:
-            break;
-    }
-    return maskFilter;
-}
-
-static sk_sp<SkImageFilter> make_image_filter(bool canBeNull = true);
-
-static SkPaint make_paint() {
-    SkPaint paint;
-    paint.setHinting(make_paint_hinting());
-    paint.setAntiAlias(make_bool());
-    paint.setDither(make_bool());
-    paint.setLinearText(make_bool());
-    paint.setSubpixelText(make_bool());
-    paint.setLCDRenderText(make_bool());
-    paint.setEmbeddedBitmapText(make_bool());
-    paint.setAutohinted(make_bool());
-    paint.setFakeBoldText(make_bool());
-    paint.setFilterQuality(make_filter_quality());
-    paint.setStyle(make_paint_style());
-    paint.setColor(make_color());
-    paint.setStrokeWidth(make_scalar());
-    paint.setStrokeMiter(make_scalar());
-    paint.setStrokeCap(make_paint_cap());
-    paint.setStrokeJoin(make_paint_join());
-    paint.setColorFilter(make_color_filter());
-    paint.setBlendMode(make_xfermode());
-    paint.setPathEffect(make_path_effect());
-    paint.setMaskFilter(make_mask_filter());
-
-    if (false) {
-        // our validating buffer does not support typefaces yet, so skip this for now
-        paint.setTypeface(SkTypeface::MakeFromName(make_font_name().c_str(),
-                                                   make_typeface_style()));
-    }
-
-    paint.setImageFilter(make_image_filter());
-    sk_sp<SkData> data(make_3Dlut(nullptr, make_bool(), make_bool(), make_bool()));
-    paint.setTextSize(make_scalar());
-    paint.setTextScaleX(make_scalar());
-    paint.setTextSkewX(make_scalar());
-    paint.setTextEncoding(make_paint_text_encoding());
-    return paint;
-}
-
-static sk_sp<SkImageFilter> make_image_filter(bool canBeNull) {
-    sk_sp<SkImageFilter> filter;
-
-    // Add a 1 in 3 chance to get a nullptr input
-    if (canBeNull && (R(3) == 1)) {
-        return filter;
-    }
-
-    enum { ALPHA_THRESHOLD, MERGE, COLOR, BLUR, MAGNIFIER,
-           XFERMODE, OFFSET, MATRIX, MATRIX_CONVOLUTION, COMPOSE,
-           DISTANT_LIGHT, POINT_LIGHT, SPOT_LIGHT, NOISE, DROP_SHADOW,
-           MORPHOLOGY, BITMAP, DISPLACE, TILE, PICTURE, PAINT, NUM_FILTERS };
-
-    switch (R(NUM_FILTERS)) {
-    case ALPHA_THRESHOLD:
-        filter = SkAlphaThresholdFilter::Make(make_region(),
-                                              make_scalar(),
-                                              make_scalar(),
-                                              make_image_filter());
-        break;
-    case MERGE:
-        filter = SkMergeImageFilter::Make(make_image_filter(),
-                                          make_image_filter());
-        break;
-    case COLOR: {
-        sk_sp<SkColorFilter> cf(make_color_filter());
-        filter = cf ? SkColorFilterImageFilter::Make(std::move(cf), make_image_filter())
-                    : nullptr;
-        break;
-    }
-    case BLUR:
-        filter = SkBlurImageFilter::Make(make_scalar(true),
-                                         make_scalar(true),
-                                         make_image_filter());
-        break;
-    case MAGNIFIER:
-        filter = SkMagnifierImageFilter::Make(make_rect(),
-                                              make_scalar(true),
-                                              make_image_filter());
-        break;
-    case XFERMODE:
-        filter = SkXfermodeImageFilter::Make(make_xfermode(),
-                                             make_image_filter(),
-                                             make_image_filter(),
-                                             nullptr);
-        break;
-    case OFFSET:
-        filter = SkOffsetImageFilter::Make(make_scalar(), make_scalar(), make_image_filter());
-        break;
-    case MATRIX:
-        filter = SkImageFilter::MakeMatrixFilter(make_matrix(),
-                                                 (SkFilterQuality)R(4),
-                                                 make_image_filter());
-        break;
-    case MATRIX_CONVOLUTION: {
-        SkImageFilter::CropRect cropR(SkRect::MakeWH(SkIntToScalar(kBitmapSize),
-                                                     SkIntToScalar(kBitmapSize)));
-        SkISize size = SkISize::Make(R(10)+1, R(10)+1);
-        int arraySize = size.width() * size.height();
-        SkTArray<SkScalar> kernel(arraySize);
-        for (int i = 0; i < arraySize; ++i) {
-            kernel.push_back() = make_scalar();
-        }
-        SkIPoint kernelOffset = SkIPoint::Make(R(SkIntToScalar(size.width())),
-                                               R(SkIntToScalar(size.height())));
-
-        filter = SkMatrixConvolutionImageFilter::Make(size,
-                                                      kernel.begin(),
-                                                      make_scalar(),
-                                                      make_scalar(),
-                                                      kernelOffset,
-                                                      (SkMatrixConvolutionImageFilter::TileMode)R(3),
-                                                      R(2) == 1,
-                                                      make_image_filter(),
-                                                      &cropR);
-        break;
-    }
-    case COMPOSE:
-        filter = SkComposeImageFilter::Make(make_image_filter(), make_image_filter());
-        break;
-    case DISTANT_LIGHT:
-        filter = (R(2) == 1)
-                 ? SkLightingImageFilter::MakeDistantLitDiffuse(make_point(), make_color(),
-                                                                make_scalar(), make_scalar(),
-                                                                make_image_filter())
-                 : SkLightingImageFilter::MakeDistantLitSpecular(make_point(), make_color(),
-                                                                 make_scalar(), make_scalar(),
-                                                                 SkIntToScalar(R(10)),
-                                                                 make_image_filter());
-        break;
-    case POINT_LIGHT:
-        filter = (R(2) == 1)
-                 ? SkLightingImageFilter::MakePointLitDiffuse(make_point(), make_color(),
-                                                              make_scalar(), make_scalar(),
-                                                              make_image_filter())
-                 : SkLightingImageFilter::MakePointLitSpecular(make_point(), make_color(),
-                                                               make_scalar(), make_scalar(),
-                                                               SkIntToScalar(R(10)),
-                                                               make_image_filter());
-        break;
-    case SPOT_LIGHT:
-        filter = (R(2) == 1)
-                 ? SkLightingImageFilter::MakeSpotLitDiffuse(SkPoint3::Make(0, 0, 0),
-                                                             make_point(), make_scalar(),
-                                                             make_scalar(), make_color(),
-                                                             make_scalar(), make_scalar(),
-                                                             make_image_filter())
-                 : SkLightingImageFilter::MakeSpotLitSpecular(SkPoint3::Make(0, 0, 0),
-                                                              make_point(), make_scalar(),
-                                                              make_scalar(), make_color(),
-                                                              make_scalar(), make_scalar(),
-                                                              SkIntToScalar(R(10)),
-                                                              make_image_filter());
-        break;
-    case NOISE: {
-        sk_sp<SkShader> shader((R(2) == 1)
-                ? SkPerlinNoiseShader::MakeFractalNoise(make_scalar(true), make_scalar(true),
-                                                        R(10.0f), make_scalar())
-                : SkPerlinNoiseShader::MakeTurbulence(make_scalar(true), make_scalar(true),
-                                                      R(10.0f), make_scalar()));
-        SkPaint paint;
-        paint.setShader(shader);
-        SkImageFilter::CropRect cropR(SkRect::MakeWH(SkIntToScalar(kBitmapSize),
-                                                     SkIntToScalar(kBitmapSize)));
-        filter = SkPaintImageFilter::Make(paint, &cropR);
-        break;
-    }
-    case DROP_SHADOW:
-        filter = SkDropShadowImageFilter::Make(make_scalar(),
-                                               make_scalar(),
-                                               make_scalar(true),
-                                               make_scalar(true),
-                                               make_color(),
-                                               make_shadow_mode(),
-                                               make_image_filter(),
-                                               nullptr);
-        break;
-    case MORPHOLOGY:
-        if (R(2) == 1) {
-            filter = SkDilateImageFilter::Make(R(static_cast<float>(kBitmapSize)),
-                                               R(static_cast<float>(kBitmapSize)),
-                                               make_image_filter());
-        } else {
-            filter = SkErodeImageFilter::Make(R(static_cast<float>(kBitmapSize)),
-                                              R(static_cast<float>(kBitmapSize)),
-                                              make_image_filter());
-        }
-        break;
-    case BITMAP: {
-        sk_sp<SkImage> image(SkImage::MakeFromBitmap(make_bitmap()));
-        if (R(2) == 1) {
-            filter = SkImageSource::Make(std::move(image),
-                                         make_rect(),
-                                         make_rect(),
-                                         kHigh_SkFilterQuality);
-        } else {
-            filter = SkImageSource::Make(std::move(image));
-        }
-        break;
-    }
-    case DISPLACE:
-        filter = SkDisplacementMapEffect::Make(make_channel_selector_type(),
-                                               make_channel_selector_type(),
-                                               make_scalar(),
-                                               make_image_filter(false),
-                                               make_image_filter());
-        break;
-    case TILE:
-        filter = SkTileImageFilter::Make(make_rect(), make_rect(), make_image_filter(false));
-        break;
-    case PICTURE: {
-        SkRTreeFactory factory;
-        SkPictureRecorder recorder;
-        SkCanvas* recordingCanvas = recorder.beginRecording(SkIntToScalar(kBitmapSize),
-                                                            SkIntToScalar(kBitmapSize),
-                                                            &factory, 0);
-        drawSomething(recordingCanvas);
-        sk_sp<SkPicture> pict(recorder.finishRecordingAsPicture());
-        filter = SkPictureImageFilter::Make(pict, make_rect());
-        break;
-    }
-    case PAINT: {
-        SkImageFilter::CropRect cropR(make_rect());
-        filter = SkPaintImageFilter::Make(make_paint(), &cropR);
-        break;
-    }
-    default:
-        break;
-    }
-    return (filter || canBeNull) ? filter : make_image_filter(canBeNull);
-}
-
-static sk_sp<SkImageFilter> make_serialized_image_filter() {
-    sk_sp<SkImageFilter> filter(make_image_filter(false));
-    sk_sp<SkData> data(filter->serialize());
-    const unsigned char* ptr = static_cast<const unsigned char*>(data->data());
-    size_t len = data->size();
-#ifdef SK_ADD_RANDOM_BIT_FLIPS
-    unsigned char* p = const_cast<unsigned char*>(ptr);
-    for (size_t i = 0; i < len; ++i, ++p) {
-        if (R(250) == 1) { // 0.4% of the time, flip a bit or byte
-            if (R(10) == 1) { // Then 10% of the time, change a whole byte
-                switch(R(3)) {
-                case 0:
-                    *p ^= 0xFF; // Flip entire byte
-                    break;
-                case 1:
-                    *p = 0xFF; // Set all bits to 1
-                    break;
-                case 2:
-                    *p = 0x00; // Set all bits to 0
-                    break;
-                }
-            } else {
-                *p ^= (1 << R(8));
-            }
-        }
-    }
-#endif // SK_ADD_RANDOM_BIT_FLIPS
-    return SkImageFilter::Deserialize(ptr, len);
-}
-
-static void drawClippedBitmap(SkCanvas* canvas, int x, int y, const SkPaint& paint) {
-    canvas->save();
-    canvas->clipRect(SkRect::MakeXYWH(SkIntToScalar(x), SkIntToScalar(y),
-        SkIntToScalar(kBitmapSize), SkIntToScalar(kBitmapSize)));
-    canvas->drawBitmap(make_bitmap(), SkIntToScalar(x), SkIntToScalar(y), &paint);
-    canvas->restore();
-}
-
-static void do_fuzz(SkCanvas* canvas) {
-    sk_sp<SkImageFilter> filter = make_serialized_image_filter();
-
-#ifdef SK_FUZZER_IS_VERBOSE
-    static uint32_t numFilters = 0;
-    static uint32_t numValidFilters = 0;
-    if (0 == numFilters) {
-        printf("Fuzzing with %u\n", kSeed);
-    }
-    numFilters++;
-    if (filter) {
-        numValidFilters++;
-    }
-    printf("Filter no : %u. Valid filters so far : %u\r", numFilters, numValidFilters);
-    fflush(stdout);
-#endif
-
-    SkPaint paint;
-    paint.setImageFilter(filter);
-    drawClippedBitmap(canvas, 0, 0, paint);
-}
-
-//////////////////////////////////////////////////////////////////////////////
-
-class ImageFilterFuzzView : public Sample {
-public:
-    ImageFilterFuzzView() {
-        this->setBGColor(0xFFDDDDDD);
-    }
-
-protected:
-    virtual bool onQuery(Sample::Event* evt) {
-        if (Sample::TitleQ(*evt)) {
-            Sample::TitleR(evt, "ImageFilterFuzzer");
-            return true;
-        }
-        return this->INHERITED::onQuery(evt);
-    }
-
-    void drawBG(SkCanvas* canvas) {
-        canvas->drawColor(0xFFDDDDDD);
-    }
-
-    virtual void onDrawContent(SkCanvas* canvas) {
-        do_fuzz(canvas);
-    }
-
-private:
-    typedef Sample INHERITED;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-DEF_SAMPLE( return new ImageFilterFuzzView(); )
diff --git a/samplecode/SampleGlyphTransform.cpp b/samplecode/SampleGlyphTransform.cpp
index e8567d1..ff42251 100644
--- a/samplecode/SampleGlyphTransform.cpp
+++ b/samplecode/SampleGlyphTransform.cpp
@@ -40,7 +40,8 @@
 
     void onDrawContent(SkCanvas* canvas) override {
         SkPaint paint;
-        paint.setTypeface(fEmojiFont.fTypeface);
+
+        SkFont font(fEmojiFont.fTypeface);
         const char* text = fEmojiFont.fText;
 
         double baseline = this->height() / 2;
@@ -54,8 +55,9 @@
 
         // d3 by default anchors text around the middle
         SkRect bounds;
-        paint.measureText(text, strlen(text), &bounds);
-        canvas->drawString(text, -bounds.centerX(), -bounds.centerY(), paint);
+        font.measureText(text, strlen(text), kUTF8_SkTextEncoding, &bounds);
+        canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, -bounds.centerX(), -bounds.centerY(),
+                               font, paint);
     }
 
     bool onAnimate(const SkAnimTimer& timer) override {
diff --git a/samplecode/SampleLCD.cpp b/samplecode/SampleLCD.cpp
index 22aaba6..f209e97 100644
--- a/samplecode/SampleLCD.cpp
+++ b/samplecode/SampleLCD.cpp
@@ -4,6 +4,7 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+#include <SkFont.h>
 #include "Sample.h"
 #include "SkCanvas.h"
 #include "SkPaint.h"
@@ -30,7 +31,6 @@
         this->drawBG(canvas);
 
         SkPaint paint;
-        paint.setAntiAlias(true);
 
         SkScalar textSize = SkIntToScalar(6);
         SkScalar delta = SK_Scalar1;
@@ -40,16 +40,17 @@
         SkScalar x1 = SkIntToScalar(310);
         SkScalar y = SkIntToScalar(20);
 
+        SkFont font;
         for (int i = 0; i < 20; i++) {
-            paint.setTextSize(textSize);
+            font.setSize(textSize);
             textSize += delta;
 
-            paint.setLCDRenderText(false);
-            canvas->drawText(text, len, x0, y, paint);
-            paint.setLCDRenderText(true);
-            canvas->drawText(text, len, x1, y, paint);
+            font.setEdging(SkFont::Edging::kAntiAlias);
+            canvas->drawSimpleText(text, len, kUTF8_SkTextEncoding, x0, y, font, paint);
+            font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
+            canvas->drawSimpleText(text, len, kUTF8_SkTextEncoding, x1, y, font, paint);
 
-            y += paint.getFontSpacing();
+            y += font.getSpacing();
         }
     }
 
diff --git a/samplecode/SampleMeasure.cpp b/samplecode/SampleMeasure.cpp
deleted file mode 100644
index 2a4817e..0000000
--- a/samplecode/SampleMeasure.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-#include "Sample.h"
-#include "SkCanvas.h"
-#include "SkGradientShader.h"
-#include "SkPath.h"
-#include "SkRegion.h"
-#include "SkShader.h"
-#include "SkUTF.h"
-#include "Sk1DPathEffect.h"
-#include "SkCornerPathEffect.h"
-#include "SkPathMeasure.h"
-#include "SkRandom.h"
-#include "SkColorPriv.h"
-#include "SkColorFilter.h"
-
-// exercise scale/linear
-struct Setting {
-    bool        fLinearText;
-};
-
-static const Setting gSettings[] = {
-    { false },
-    { true  },
-};
-
-static void doMeasure(SkCanvas* canvas, const SkPaint& paint, const char text[]) {
-    SkScalar    dy = paint.getFontMetrics(nullptr);
-
-    size_t      len = strlen(text);
-    SkAutoTMalloc<SkScalar> autoWidths(len);
-    SkScalar*   widths = autoWidths.get();
-    SkAutoTMalloc<SkRect> autoRects(len);
-    SkRect*     rects = autoRects.get();
-    SkRect      bounds;
-
-    SkPaint p(paint);
-    for (size_t i = 0; i < SK_ARRAY_COUNT(gSettings); i++) {
-        p.setLinearText(gSettings[i].fLinearText);
-
-        int n = p.getTextWidths(text, len, widths, rects);
-        SkScalar w = p.measureText(text, len, &bounds);
-
-        p.setStyle(SkPaint::kFill_Style);
-        p.setColor(0x8888FF88);
-        canvas->drawRect(bounds, p);
-        p.setColor(0xFF000000);
-        canvas->drawText(text, len, 0, 0, p);
-
-        p.setStyle(SkPaint::kStroke_Style);
-        p.setStrokeWidth(0);
-        p.setColor(0xFFFF0000);
-        SkScalar x = 0;
-        for (int j = 0; j < n; j++) {
-            SkRect r = rects[j];
-            r.offset(x, 0);
-            canvas->drawRect(r, p);
-            x += widths[j];
-        }
-
-        p.setColor(0xFF0000FF);
-        canvas->drawLine(0, 0, w, 0, p);
-        p.setStrokeWidth(SkIntToScalar(4));
-        canvas->drawPoint(x, 0, p);
-
-        canvas->translate(0, dy);
-    }
-}
-
-class MeasureView : public Sample {
-public:
-    SkPaint fPaint;
-
-    MeasureView() {
-        fPaint.setAntiAlias(true);
-        fPaint.setTextSize(SkIntToScalar(64));
-        this->setBGColor(0xFFDDDDDD);
-    }
-
-protected:
-    virtual bool onQuery(Sample::Event* evt) {
-        if (Sample::TitleQ(*evt)) {
-            Sample::TitleR(evt, "Measure");
-            return true;
-        }
-        return this->INHERITED::onQuery(evt);
-    }
-
-    virtual void onDrawContent(SkCanvas* canvas) {
-        canvas->translate(fPaint.getTextSize(), fPaint.getTextSize());
-        doMeasure(canvas, fPaint, "Hamburgefons");
-    }
-
-private:
-    typedef Sample INHERITED;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-DEF_SAMPLE( return new MeasureView(); )
diff --git a/samplecode/SamplePolyToPoly.cpp b/samplecode/SamplePolyToPoly.cpp
index 24c8b80..0e25e3c 100644
--- a/samplecode/SamplePolyToPoly.cpp
+++ b/samplecode/SamplePolyToPoly.cpp
@@ -75,7 +75,7 @@
         return this->INHERITED::onQuery(evt);
     }
 
-    static void doDraw(SkCanvas* canvas, SkPaint* paint, const int isrc[],
+    static void doDraw(SkCanvas* canvas, SkPaint* paint, const SkFont& font, const int isrc[],
                        const int idst[], int count) {
         SkMatrix matrix;
         SkPoint src[4], dst[4];
@@ -97,14 +97,14 @@
         canvas->drawLine(0, D, D, 0, *paint);
 
         SkFontMetrics fm;
-        paint->getFontMetrics(&fm);
+        font.getMetrics(&fm);
         paint->setColor(SK_ColorRED);
         paint->setStyle(SkPaint::kFill_Style);
         SkScalar x = D/2;
         float y = D/2 - (fm.fAscent + fm.fDescent)/2;
         SkString str;
         str.appendS32(count);
-        SkTextUtils::DrawString(canvas, str, x, y, *paint, SkTextUtils::kCenter_Align);
+        SkTextUtils::DrawString(canvas, str.c_str(), x, y, font, *paint, SkTextUtils::kCenter_Align);
 
         canvas->restore();
     }
@@ -113,14 +113,16 @@
         SkPaint paint;
         paint.setAntiAlias(true);
         paint.setStrokeWidth(SkIntToScalar(4));
-        paint.setTextSize(SkIntToScalar(40));
+
+        SkFont font;
+        font.setSize(40);
 
         canvas->save();
         canvas->translate(SkIntToScalar(10), SkIntToScalar(10));
         // translate (1 point)
         const int src1[] = { 0, 0 };
         const int dst1[] = { 5, 5 };
-        doDraw(canvas, &paint, src1, dst1, 1);
+        doDraw(canvas, &paint, font, src1, dst1, 1);
         canvas->restore();
 
         canvas->save();
@@ -128,7 +130,7 @@
         // rotate/uniform-scale (2 points)
         const int src2[] = { 32, 32, 64, 32 };
         const int dst2[] = { 32, 32, 64, 48 };
-        doDraw(canvas, &paint, src2, dst2, 2);
+        doDraw(canvas, &paint, font, src2, dst2, 2);
         canvas->restore();
 
         canvas->save();
@@ -136,7 +138,7 @@
         // rotate/skew (3 points)
         const int src3[] = { 0, 0, 64, 0, 0, 64 };
         const int dst3[] = { 0, 0, 96, 0, 24, 64 };
-        doDraw(canvas, &paint, src3, dst3, 3);
+        doDraw(canvas, &paint, font, src3, dst3, 3);
         canvas->restore();
 
         canvas->save();
@@ -144,7 +146,7 @@
         // perspective (4 points)
         const int src4[] = { 0, 0, 64, 0, 64, 64, 0, 64 };
         const int dst4[] = { 0, 0, 96, 0, 64, 96, 0, 64 };
-        doDraw(canvas, &paint, src4, dst4, 4);
+        doDraw(canvas, &paint, font, src4, dst4, 4);
         canvas->restore();
     }
 
diff --git a/samplecode/SampleQuadStroker.cpp b/samplecode/SampleQuadStroker.cpp
index 2eb6070..ca19de2 100644
--- a/samplecode/SampleQuadStroker.cpp
+++ b/samplecode/SampleQuadStroker.cpp
@@ -503,11 +503,13 @@
         paint.setStyle(SkPaint::kStroke_Style);
         paint.setColor(button.fEnabled ? 0xFF3F0000 : 0x6F3F0000);
         canvas->drawRect(button.fBounds, paint);
-        paint.setTextSize(25.0f);
         paint.setColor(button.fEnabled ? 0xFF3F0000 : 0x6F3F0000);
         paint.setStyle(SkPaint::kFill_Style);
-        SkTextUtils::DrawText(canvas, &button.fLabel, 1, button.fBounds.centerX(), button.fBounds.fBottom - 5,
-                paint, SkTextUtils::kCenter_Align);
+        SkFont font;
+        font.setSize(25.0f);
+        SkTextUtils::Draw(canvas, &button.fLabel, 1, kUTF8_SkTextEncoding,
+                button.fBounds.centerX(), button.fBounds.fBottom - 5,
+                font, paint, SkTextUtils::kCenter_Align);
     }
 
     void draw_control(SkCanvas* canvas, const SkRect& bounds, SkScalar value,
@@ -676,10 +678,10 @@
 
         if (fTextButton.fEnabled) {
             path.reset();
-            SkPaint paint;
-            paint.setAntiAlias(true);
-            paint.setTextSize(fTextSize);
-            paint.getTextPath(fText.c_str(), fText.size(), 0, fTextSize, &path);
+            SkFont font;
+            font.setSize(fTextSize);
+            SkTextUtils::GetPath(fText.c_str(), fText.size(), kUTF8_SkTextEncoding,
+                                 0, fTextSize, font, &path);
             setForText();
             draw_stroke(canvas, path, width * fWidthScale / fTextSize, fTextSize, true);
         }
diff --git a/samplecode/SampleRectanizer.cpp b/samplecode/SampleRectanizer.cpp
index 317d24c..2e7f92f 100644
--- a/samplecode/SampleRectanizer.cpp
+++ b/samplecode/SampleRectanizer.cpp
@@ -7,6 +7,7 @@
 
 #include "Sample.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkRandom.h"
 #include "SkPaint.h"
 #include "SkUTF.h"
@@ -88,8 +89,8 @@
             }
         }
 
-        SkPaint blackBigFont;
-        blackBigFont.setTextSize(20);
+        SkFont blackBigFont;
+        blackBigFont.setSize(20);
         SkPaint blackStroke;
         blackStroke.setStyle(SkPaint::kStroke_Style);
         SkPaint redFill;
@@ -121,13 +122,13 @@
                    100.0f * totArea / ((float)kWidth*kHeight),
                    fCurRandRect,
                    kNumRandRects);
-        canvas->drawString(str, 50, kHeight + 50, blackBigFont);
+        canvas->drawString(str, 50, kHeight + 50, blackBigFont, SkPaint());
 
         str.printf("Press \'j\' to toggle rectanizer");
-        canvas->drawString(str, 50, kHeight + 100, blackBigFont);
+        canvas->drawString(str, 50, kHeight + 100, blackBigFont, SkPaint());
 
         str.printf("Press \'h\' to toggle rects");
-        canvas->drawString(str, 50, kHeight + 150, blackBigFont);
+        canvas->drawString(str, 50, kHeight + 150, blackBigFont, SkPaint());
     }
 
 private:
diff --git a/samplecode/SampleRegion.cpp b/samplecode/SampleRegion.cpp
index a9526fa..2ae5f87 100644
--- a/samplecode/SampleRegion.cpp
+++ b/samplecode/SampleRegion.cpp
@@ -8,6 +8,7 @@
 #include "Sample.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkPath.h"
 #include "SkRegion.h"
@@ -53,20 +54,20 @@
 
 static void drawFadingText(SkCanvas* canvas,
                            const char* text, size_t len, SkScalar x, SkScalar y,
-                           const SkPaint& paint) {
+                           const SkFont& font, const SkPaint& paint) {
     // Need a bounds for the text
     SkRect bounds;
     SkFontMetrics fm;
 
-    paint.getFontMetrics(&fm);
-    bounds.set(x, y + fm.fTop, x + paint.measureText(text, len), y + fm.fBottom);
+    font.getMetrics(&fm);
+    bounds.set(x, y + fm.fTop, x + font.measureText(text, len, kUTF8_SkTextEncoding), y + fm.fBottom);
 
     // may need to outset bounds a little, to account for hinting and/or
     // antialiasing
     bounds.inset(-SkIntToScalar(2), -SkIntToScalar(2));
 
     canvas->saveLayer(&bounds, nullptr);
-    canvas->drawText(text, len, x, y, paint);
+    canvas->drawSimpleText(text, len, kUTF8_SkTextEncoding, x, y, font, paint);
 
     const SkPoint pts[] = {
         { bounds.fLeft, y },
@@ -89,28 +90,30 @@
 static void test_text(SkCanvas* canvas) {
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setTextSize(20);
+
+    SkFont font;
+    font.setSize(20);
 
     const char* str = "Hamburgefons";
     size_t len = strlen(str);
     SkScalar x = 20;
     SkScalar y = 20;
 
-    canvas->drawText(str, len, x, y, paint);
+    canvas->drawSimpleText(str, len, kUTF8_SkTextEncoding, x, y, font, paint);
 
     y += 20;
 
-    const SkPoint pts[] = { { x, y }, { x + paint.measureText(str, len), y } };
+    const SkPoint pts[] = { { x, y }, { x + font.measureText(str, len, kUTF8_SkTextEncoding), y } };
     const SkColor colors[] = { SK_ColorBLACK, SK_ColorBLACK, 0 };
     const SkScalar pos[] = { 0, 0.9f, 1 };
     paint.setShader(SkGradientShader::MakeLinear(pts, colors, pos,
                                                  SK_ARRAY_COUNT(colors),
                                                  SkShader::kClamp_TileMode));
-    canvas->drawText(str, len, x, y, paint);
+    canvas->drawSimpleText(str, len, kUTF8_SkTextEncoding, x, y, font, paint);
 
     y += 20;
     paint.setShader(nullptr);
-    drawFadingText(canvas, str, len, x, y, paint);
+    drawFadingText(canvas, str, len, x, y, font, paint);
 }
 
 static void scale_rect(SkIRect* dst, const SkIRect& src, float scale) {
@@ -182,10 +185,10 @@
     static void drawstr(SkCanvas* canvas, const char text[], const SkPoint& loc,
                         bool hilite) {
         SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setTextSize(SkIntToScalar(20));
         paint.setColor(hilite ? SK_ColorRED : 0x40FF0000);
-        canvas->drawString(text, loc.fX, loc.fY, paint);
+        SkFont font;
+        font.setSize(SkIntToScalar(20));
+        canvas->drawSimpleText(text, strlen(text), kUTF8_SkTextEncoding, loc.fX, loc.fY, font, paint);
     }
 
     void drawPredicates(SkCanvas* canvas, const SkPoint pts[]) {
@@ -300,9 +303,8 @@
             { SK_ColorBLUE,     "XOR",          SkRegion::kXOR_Op           }
         };
 
-        SkPaint textPaint;
-        textPaint.setAntiAlias(true);
-        textPaint.setTextSize(SK_Scalar1*24);
+        SkFont font;
+        font.setSize(SK_Scalar1*24);
 
         this->drawOrig(canvas, false);
         canvas->save();
@@ -313,7 +315,8 @@
         canvas->translate(0, SkIntToScalar(200));
 
         for (size_t op = 0; op < SK_ARRAY_COUNT(gOps); op++) {
-            canvas->drawString(gOps[op].fName, SkIntToScalar(75), SkIntToScalar(50), textPaint);
+            canvas->drawSimpleText(gOps[op].fName, strlen(gOps[op].fName), kUTF8_SkTextEncoding,
+                                   SkIntToScalar(75), SkIntToScalar(50), font, SkPaint());
 
             this->drawRgnOped(canvas, gOps[op].fOp, gOps[op].fColor);
 
diff --git a/samplecode/SampleShip.cpp b/samplecode/SampleShip.cpp
index a4871fa..2e95181 100644
--- a/samplecode/SampleShip.cpp
+++ b/samplecode/SampleShip.cpp
@@ -9,6 +9,7 @@
 #include "Resources.h"
 #include "SkAnimTimer.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkRSXform.h"
 #include "SkSurface.h"
 #include "Timer.h"
@@ -106,7 +107,9 @@
         SkPaint paint;
         paint.setFilterQuality(kLow_SkFilterQuality);
         paint.setColor(SK_ColorWHITE);
-        paint.setTextSize(15.0f);
+
+        SkFont font;
+        font.setSize(15.0f);
 
         fTimer.end();
 
@@ -148,7 +151,7 @@
         paint.setColor(SK_ColorBLACK);
         canvas->drawRect(SkRect::MakeXYWH(0, 0, 200, 24), paint);
         paint.setColor(SK_ColorWHITE);
-        canvas->drawString(outString, 5, 15, paint);
+        canvas->drawString(outString, 5, 15, font, paint);
     }
 
 #if 0
diff --git a/samplecode/SampleText.cpp b/samplecode/SampleText.cpp
deleted file mode 100644
index 8720c17..0000000
--- a/samplecode/SampleText.cpp
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "Sample.h"
-#include "SkCanvas.h"
-#include "SkReadBuffer.h"
-#include "SkWriteBuffer.h"
-#include "SkGradientShader.h"
-#include "SkGraphics.h"
-#include "SkPath.h"
-#include "SkRandom.h"
-#include "SkRegion.h"
-#include "SkShader.h"
-#include "SkUTF.h"
-#include "SkColorPriv.h"
-#include "SkColorFilter.h"
-#include "SkTime.h"
-#include "SkTypeface.h"
-#include "SkStream.h"
-
-static const struct {
-    const char* fName;
-    uint32_t    fFlags;
-    bool        fFlushCache;
-} gHints[] = {
-    { "Linear", SkPaint::kLinearText_Flag,     false },
-    { "Normal",   0,                           true },
-    { "Subpixel", SkPaint::kSubpixelText_Flag, true }
-};
-
-static void DrawTheText(SkCanvas* canvas, const char text[], size_t length, SkScalar x, SkScalar y,
-                        const SkPaint& paint, SkScalar clickX) {
-    SkPaint p(paint);
-
-#if 0
-    canvas->drawText(text, length, x, y, paint);
-#else
-    {
-        SkPoint pts[1000];
-        SkScalar xpos = x;
-        SkASSERT(length <= SK_ARRAY_COUNT(pts));
-        for (size_t i = 0; i < length; i++) {
-            pts[i].set(xpos, y);
-            xpos += paint.getTextSize();
-        }
-        canvas->drawPosText(text, length, pts, paint);
-    }
-#endif
-
-    p.setSubpixelText(true);
-    x += SkIntToScalar(180);
-    canvas->drawText(text, length, x, y, p);
-
-#ifdef SK_DEBUG
-    if (true) {
-        p.setSubpixelText(false);
-        p.setLinearText(true);
-        x += SkIntToScalar(180);
-        canvas->drawText(text, length, x, y, p);
-    }
-#endif
-}
-
-class TextSpeedView : public Sample {
-public:
-    TextSpeedView() {
-        fHints = 0;
-        fClickX = 0;
-    }
-
-protected:
-    bool onQuery(Sample::Event* evt) override {
-        if (Sample::TitleQ(*evt)) {
-            Sample::TitleR(evt, "Text");
-            return true;
-        }
-        return this->INHERITED::onQuery(evt);
-    }
-
-    static void make_textstrip(SkBitmap* bm) {
-        bm->allocPixels(SkImageInfo::Make(200, 18, kRGB_565_SkColorType,
-                                          kOpaque_SkAlphaType));
-        bm->eraseColor(SK_ColorWHITE);
-
-        SkCanvas    canvas(*bm);
-        SkPaint     paint;
-        const char* s = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit";
-
-        paint.setFlags(paint.getFlags() | SkPaint::kAntiAlias_Flag);
-        paint.setTextSize(SkIntToScalar(14));
-        canvas.drawString(s, SkIntToScalar(8), SkIntToScalar(14), paint);
-    }
-
-    static void fill_pts(SkPoint pts[], size_t n, SkRandom* rand) {
-        for (size_t i = 0; i < n; i++)
-            pts[i].set(rand->nextUScalar1() * 640, rand->nextUScalar1() * 480);
-    }
-
-    void onDrawContent(SkCanvas* canvas) override {
-        SkAutoCanvasRestore restore(canvas, false);
-        {
-            SkRect r;
-            r.set(0, 0, SkIntToScalar(1000), SkIntToScalar(20));
-       //     canvas->saveLayer(&r, nullptr, SkCanvas::kHasAlphaLayer_SaveFlag);
-        }
-
-        SkPaint paint;
-//        const uint16_t glyphs[] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 };
-        int         index = fHints % SK_ARRAY_COUNT(gHints);
-        index = 1;
-//        const char* style = gHints[index].fName;
-
-//        canvas->translate(0, SkIntToScalar(50));
-
-  //      canvas->drawString(style, SkIntToScalar(20), SkIntToScalar(20), paint);
-
-        paint.setTypeface(SkTypeface::MakeFromFile("/skimages/samplefont.ttf"));
-        paint.setAntiAlias(true);
-        paint.setFlags(paint.getFlags() | gHints[index].fFlags);
-
-        SkRect clip;
-        clip.set(SkIntToScalar(25), SkIntToScalar(34), SkIntToScalar(88), SkIntToScalar(155));
-
-        const char* text = "Hamburgefons";
-        size_t length = strlen(text);
-
-        SkScalar y = SkIntToScalar(0);
-        for (int i = 9; i <= 24; i++) {
-            paint.setTextSize(SkIntToScalar(i) /*+ (gRand.nextU() & 0xFFFF)*/);
-            for (SkScalar dx = 0; dx <= SkIntToScalar(3)/4;
-                                            dx += SkIntToScalar(1) /* /4 */) {
-                y += paint.getFontSpacing();
-                DrawTheText(canvas, text, length, SkIntToScalar(20) + dx, y, paint, fClickX);
-            }
-        }
-        if (gHints[index].fFlushCache) {
-//                SkGraphics::SetFontCacheUsed(0);
-        }
-    }
-
-    virtual Sample::Click* onFindClickHandler(SkScalar x, SkScalar y,
-                                              unsigned modi) override {
-        fClickX = x;
-        return this->INHERITED::onFindClickHandler(x, y, modi);
-    }
-
-    bool onClick(Click* click) override {
-        return this->INHERITED::onClick(click);
-    }
-
-private:
-    int fHints;
-    SkScalar fClickX;
-
-    typedef Sample INHERITED;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-DEF_SAMPLE( return new TextSpeedView(); )
diff --git a/samplecode/SampleTextAlpha.cpp b/samplecode/SampleTextAlpha.cpp
deleted file mode 100644
index 14885e8..0000000
--- a/samplecode/SampleTextAlpha.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "Sample.h"
-#include "SkBlurMask.h"
-#include "SkCanvas.h"
-#include "SkGradientShader.h"
-#include "SkGraphics.h"
-#include "SkMaskFilter.h"
-#include "SkPath.h"
-#include "SkRandom.h"
-#include "SkRegion.h"
-#include "SkShader.h"
-#include "SkUTF.h"
-#include "SkColorPriv.h"
-#include "SkColorFilter.h"
-#include "SkTime.h"
-#include "SkTypeface.h"
-
-#include "SkOSFile.h"
-#include "SkStream.h"
-
-class TextAlphaView : public Sample {
-public:
-    TextAlphaView() {
-        fByte = 0xFF;
-    }
-
-protected:
-    bool onQuery(Sample::Event* evt) override {
-        if (Sample::TitleQ(*evt)) {
-            Sample::TitleR(evt, "TextAlpha");
-            return true;
-        }
-        return this->INHERITED::onQuery(evt);
-    }
-
-    void onDrawContent(SkCanvas* canvas) override {
-        const char* str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-        SkPaint paint;
-        SkScalar    x = SkIntToScalar(10);
-        SkScalar    y = SkIntToScalar(20);
-
-        paint.setFlags(0x105);
-
-        paint.setARGB(fByte, 0xFF, 0xFF, 0xFF);
-
-        paint.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
-                                                   SkBlurMask::ConvertRadiusToSigma(3)));
-
-        SkRandom rand;
-
-        for (int ps = 6; ps <= 35; ps++) {
-            paint.setColor(rand.nextU() | (0xFF << 24));
-            paint.setTextSize(SkIntToScalar(ps));
-            paint.setTextSize(SkIntToScalar(24));
-            canvas->drawString(str, x, y, paint);
-            y += paint.getFontMetrics(nullptr);
-        }
-    }
-
-    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned) override {
-        return new Click(this);
-    }
-
-    bool onClick(Click* click) override {
-        int y = click->fICurr.fY;
-        if (y < 0) {
-            y = 0;
-        } else if (y > 255) {
-            y = 255;
-        }
-        fByte = y;
-        return true;
-    }
-
-private:
-    int fByte;
-
-    typedef Sample INHERITED;
-};
-
-//////////////////////////////////////////////////////////////////////////////
-
-DEF_SAMPLE( return new TextAlphaView(); )
diff --git a/samplecode/SampleTextBox.cpp b/samplecode/SampleTextBox.cpp
index 8762391..7931dfc 100644
--- a/samplecode/SampleTextBox.cpp
+++ b/samplecode/SampleTextBox.cpp
@@ -81,12 +81,12 @@
         paint.setColor(fg);
 
         for (int i = 9; i < 24; i += 2) {
-            SkTextBlobBuilder builder;
+            SkTextBlobBuilderLineHandler builder;
             paint.setTextSize(SkIntToScalar(i));
             SkFont font = SkFont::LEGACY_ExtractFromPaint(paint);
             SkPoint end = shaper.shape(&builder, font, gText, strlen(gText), true,
                                        { margin, margin }, w - margin);
-            canvas->drawTextBlob(builder.make(), 0, 0, paint);
+            canvas->drawTextBlob(builder.makeBlob(), 0, 0, paint);
 
             canvas->translate(0, end.y());
         }
diff --git a/samplecode/SampleTextEffects.cpp b/samplecode/SampleTextEffects.cpp
index 003d020..43b4827 100644
--- a/samplecode/SampleTextEffects.cpp
+++ b/samplecode/SampleTextEffects.cpp
@@ -16,6 +16,7 @@
 #include "SkColorPriv.h"
 #include "SkColorFilter.h"
 #include "SkStrokeRec.h"
+#include "SkTextUtils.h"
 #include "SkTypeface.h"
 
 #include "SkGradientShader.h"
@@ -128,13 +129,13 @@
         canvas->drawColor(SK_ColorWHITE);
     }
 
-    void drawdots(SkCanvas* canvas, SkString s, SkScalar x, SkScalar y, const SkPaint& p) {
+    void drawdots(SkCanvas* canvas, SkString s, SkScalar x, SkScalar y, const SkFont& font) {
         SkTDArray<SkPoint> pts;
         auto pe = makepe(fInterp, &pts);
 
         SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
         SkPath path, dstPath;
-        p.getTextPath(s.c_str(), s.size(), x, y, &path);
+        SkTextUtils::GetPath(s.c_str(), s.size(), kUTF8_SkTextEncoding, x, y, font, &path);
         pe->filterPath(&dstPath, path, &rec, nullptr);
 
         SkPaint paint;
@@ -150,16 +151,12 @@
         SkScalar x = SkIntToScalar(20);
         SkScalar y = SkIntToScalar(300);
 
-        SkPaint paint;
-        paint.setAntiAlias(true);
-        paint.setTextSize(SkIntToScalar(240));
-        paint.setTypeface(SkTypeface::MakeFromName("sans-serif", SkFontStyle::Bold()));
-        paint.setTypeface(fFace);
+        SkFont font(SkTypeface::MakeFromName("sans-serif", SkFontStyle::Bold()), 240);
 
         SkString str("9");
 
-        canvas->drawString(str, x, y, paint);
-        drawdots(canvas, str, x, y, paint);
+        canvas->drawString(str, x, y, font, SkPaint());
+        drawdots(canvas, str, x, y, font);
 
         if (false) {
             fInterp += fDx;
diff --git a/samplecode/SampleTiling.cpp b/samplecode/SampleTiling.cpp
index fc4a5fd..c2c02d8 100644
--- a/samplecode/SampleTiling.cpp
+++ b/samplecode/SampleTiling.cpp
@@ -106,12 +106,11 @@
                 for (size_t ky = 0; ky < SK_ARRAY_COUNT(gModes); ky++) {
                     SkPaint p;
                     SkString str;
-                    p.setAntiAlias(true);
                     p.setDither(true);
                     p.setLooper(fLooper);
                     str.printf("[%s,%s]", gModeNames[kx], gModeNames[ky]);
 
-                    SkTextUtils::DrawString(textCanvas, str, x + r.width()/2, y, p,
+                    SkTextUtils::DrawString(textCanvas, str.c_str(), x + r.width()/2, y, SkFont(), p,
                                             SkTextUtils::kCenter_Align);
 
                     x += r.width() * 4 / 3;
diff --git a/samplecode/SampleUnpremul.cpp b/samplecode/SampleUnpremul.cpp
index 3e670be..da7e42d 100644
--- a/samplecode/SampleUnpremul.cpp
+++ b/samplecode/SampleUnpremul.cpp
@@ -75,12 +75,14 @@
     void onDrawContent(SkCanvas* canvas) override {
         SkPaint paint;
         paint.setAntiAlias(true);
-        paint.setTextSize(SkIntToScalar(24));
+
+        SkFont font;
+        font.setSize(24);
         auto looper(
             SkBlurDrawLooper::Make(SK_ColorBLUE, SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(2)),
                                    0, 0));
         paint.setLooper(looper);
-        SkScalar height = paint.getFontMetrics(nullptr);
+        SkScalar height = font.getMetrics(nullptr);
         if (!fDecodeSucceeded) {
             SkString failure;
             if (fResPath.size() == 0) {
@@ -88,7 +90,7 @@
             } else {
                 failure.printf("Failed to decode %s", fCurrFile.c_str());
             }
-            canvas->drawString(failure, 0, height, paint);
+            canvas->drawString(failure, 0, height, font, paint);
             return;
         }
 
@@ -96,16 +98,16 @@
         SkString header(SkOSPath::Basename(fCurrFile.c_str()));
         header.appendf("     [%dx%d]     %s", fBitmap.width(), fBitmap.height(),
                        (fPremul ? "premultiplied" : "unpremultiplied"));
-        canvas->drawString(header, 0, height, paint);
+        canvas->drawString(header, 0, height, font, paint);
         canvas->translate(0, height);
 
         // Help messages
         header.printf("Press '%c' to move to the next image.'", fNextImageChar);
-        canvas->drawString(header, 0, height, paint);
+        canvas->drawString(header, 0, height, font, paint);
         canvas->translate(0, height);
 
         header.printf("Press '%c' to toggle premultiplied decode.", fTogglePremulChar);
-        canvas->drawString(header, 0, height, paint);
+        canvas->drawString(header, 0, height, font, paint);
 
         // Now draw the image itself.
         canvas->translate(height * 2, height * 2);
diff --git a/samplecode/SampleXfer.cpp b/samplecode/SampleXfer.cpp
index 6832e8d..bc6b911 100644
--- a/samplecode/SampleXfer.cpp
+++ b/samplecode/SampleXfer.cpp
@@ -52,10 +52,11 @@
         canvas->drawRoundRect(fRect, 8, 8, paint);
 
         paint.setColor(0xFFFFFFFF);
-        paint.setTextSize(16);
-        paint.setLCDRenderText(true);
-        SkTextUtils::DrawString(canvas, fLabel, fRect.centerX(), fRect.fTop + 0.68f * fRect.height(),
-                                paint, SkTextUtils::kCenter_Align);
+        SkFont font;
+        font.setSize(16);
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
+        SkTextUtils::DrawString(canvas, fLabel.c_str(), fRect.centerX(), fRect.fTop + 0.68f * fRect.height(),
+                                font, paint, SkTextUtils::kCenter_Align);
     }
 
     bool hitTest(SkScalar x, SkScalar y) {
diff --git a/samplecode/SampleXfermodesBlur.cpp b/samplecode/SampleXfermodesBlur.cpp
index 0eeab5b..75ad9d1 100644
--- a/samplecode/SampleXfermodesBlur.cpp
+++ b/samplecode/SampleXfermodesBlur.cpp
@@ -10,6 +10,7 @@
 #include "SkBlurMask.h"
 #include "SkCanvas.h"
 #include "SkCornerPathEffect.h"
+#include "SkFont.h"
 #include "SkGradientShader.h"
 #include "SkGraphics.h"
 #include "SkPath.h"
@@ -27,8 +28,8 @@
 #include "SkColorPriv.h"
 #include "SkBlurMaskFilter.h"
 
-static void setNamedTypeface(SkPaint* paint, const char name[]) {
-    paint->setTypeface(SkTypeface::MakeFromName(name, SkFontStyle()));
+static void setNamedTypeface(SkFont* font, const char name[]) {
+    font->setTypeface(SkTypeface::MakeFromName(name, SkFontStyle()));
 }
 
 static uint16_t gBG[] = { 0xFFFF, 0xCCCF, 0xCCCF, 0xFFFF };
@@ -83,29 +84,6 @@
     virtual void onDrawContent(SkCanvas* canvas) {
         canvas->translate(SkIntToScalar(10), SkIntToScalar(20));
 
-        if (false) {
-            SkPaint paint;
-            paint.setAntiAlias(true);
-            paint.setTextSize(50);
-            paint.setTypeface(SkTypeface::MakeFromName("Arial Unicode MS", SkFontStyle()));
-            char buffer[10];
-            size_t len = SkUTF::ToUTF8(0x8500, buffer);
-            canvas->drawText(buffer, len, 40, 40, paint);
-            return;
-        }
-        if (false) {
-            SkPaint paint;
-            paint.setAntiAlias(true);
-
-            SkRect r0 = { 0, 0, 10.5f, 20 };
-            SkRect r1 = { 10.5f, 10, 20, 30 };
-            paint.setColor(SK_ColorRED);
-            canvas->drawRect(r0, paint);
-            paint.setColor(SK_ColorBLUE);
-            canvas->drawRect(r1, paint);
-            return;
-        }
-
         const SkBlendMode gModes[] = {
             SkBlendMode::kClear,
             SkBlendMode::kSrc,
@@ -129,10 +107,9 @@
         auto s = SkShader::MakeBitmapShader(fBG, SkShader::kRepeat_TileMode,
                                             SkShader::kRepeat_TileMode, &m);
 
-        SkPaint labelP;
-        labelP.setAntiAlias(true);
-        labelP.setLCDRenderText(true);
-        setNamedTypeface(&labelP, "Menlo Regular");
+        SkFont font;
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
+        setNamedTypeface(&font, "Menlo Regular");
 
         const int W = 5;
 
@@ -158,7 +135,7 @@
                 canvas->drawRect(r, p);
 
                 const char* label = SkBlendMode_Name(gModes[i]);
-                SkTextUtils::DrawString(canvas, label, x + w/2, y - labelP.getTextSize()/2, labelP,
+                SkTextUtils::DrawString(canvas, label, x + w/2, y - font.getSize()/2, font, SkPaint(),
                                         SkTextUtils::kCenter_Align);
                 x += w + SkIntToScalar(10);
                 if ((i % W) == W - 1) {
diff --git a/site/dev/contrib/c++11.md b/site/dev/contrib/c++11.md
deleted file mode 100644
index 432ad88..0000000
--- a/site/dev/contrib/c++11.md
+++ /dev/null
@@ -1,57 +0,0 @@
-C++11 in Skia
-=============
-
-Skia uses C++11.  But as a library, we are technically limited by what our
-clients support and what our build bots support.
-
-Skia may also be limited by restrictions we choose put on ourselves.  This
-document is not concerned with C++11 policy in Skia, only its technical
-feasibility.  This is about what we can use, a superset of what we may use.
-
-The gist:
-
--   C++11 the language as supported by GCC 4.7 or later is pretty usable.
--   The C++11 standard library can generally be used, with some teething.
--   If you break a bot, that feature is not usable.
--   Local statics are not thread safe.
-
-
-Clients
--------
-
-The clients we pay most attention to are Chrome, Android, Mozilla, and a few
-internal Google projects.
-
-Chrome builds with a recent Clang on Mac and Linux and with a recent MSVC on
-Windows.  These toolchains are new enough to not be the weak link to use any
-C++11 language feature.  Chromium, however, builds against libstdc++4.6.4
-(STL and runtime) on Linux.  This precludes direct use of a number of type
-traits.
-
-Chrome intentionally disables thread-safe initialization of static variables,
-so we cannot rely on that.  Our bots disable this too, so keep an eye on TSAN.
-
-Android builds with either a somewhat aged GCC or a recent Clang.  They're
-generally not a weak link for C++11 language features.  Android's C++ standard
-library had historically been a pain, but seems to work fine these days.
-
-Mozilla's current weak link is a minimum requirement of GCC 4.7.  Most features
-marked in red on Mozilla's C++11 [feature
-matrix](https://developer.mozilla.org/en-US/docs/Using_CXX_in_Mozilla_code) are
-marked that way because they arrived in GCC 4.8.  Their minimum-supported Clang
-and MSVC toolchains are pretty good, but MSVC 2013 will become the weak link soon.
-
-Internal Google projects tend to support C++11 completely, including the
-full C++11 standard library.
-
-
-Bots
-----
-
-Most of our bots are pretty up-to-date: the Windows bots use MSVC 2013, the Mac
-bots a recent Clang, and the Linux bots GCC 4.8 or a recent Clang.  Our Android
-bots use a recent toolchain from Android (see above), and our Chrome bots use
-Chrome's toolchains (see above).  I'm not exactly sure what our Chrome OS bots
-are using.  They're probably our weak link right now, though problems are rare.
-
-I believe our bots' ability to use C++11 matches Mozilla's list nearly identically.
diff --git a/site/dev/contrib/flatten.md b/site/dev/contrib/flatten.md
deleted file mode 100644
index ce6839b..0000000
--- a/site/dev/contrib/flatten.md
+++ /dev/null
@@ -1,86 +0,0 @@
-Flattenables
-============
-
-Many objects in Skia, such as `SkShaders` and other effects on `SkPaint`, need to be 
-flattened into a data stream for either transport or as part of the key to the 
-font cache. Classes for these objects should derive from `SkFlattenable` or one of 
-its subclasses. If you create a new flattenable class, you need to make sure you 
-do a few things so that it will work on all platforms:
-
-1: Override the method `flatten` (the default scope is protected):
-
-<!--?prettify?-->
-~~~~
-virtual void flatten(SkFlattenableWriteBuffer& buffer) const override {
-    this->INHERITED::flatten(buffer);
-    // Write any private data that needs to be stored to recreate this object
-}
-~~~~
-
-2: Override the (protected) constructor that creates an object from an 
-`SkFlattenableReadBuffer`:
-
-<!--?prettify?-->
-~~~~
-SkNewClass(SkFlattenableReadBuffer& buffer)
-: INHERITED(buffer) {
-    // Read the data from the buffer in the same order as it was written to the
-    // SkFlattenableWriteBuffer and construct the new object
-}
-~~~~
-
-3: Declare a set of deserialization procs for your object in the class declaration:
-We have a macro for this:
-
-<!--?prettify?-->
-~~~~
-private:
-
-SK_FLATTENABLE_HOOKS(SkNewClass)
-~~~~
-
-4: If your class is declared in a `.cpp` file or in a private header file, create a 
-function to register its group:
-This occurs in cases where the classes are hidden behind a factory, like many effects 
-and shaders are.  Then in the parent class header file (such as `SkGradientShader`) you 
-need to add:
-
-<!--?prettify?-->
-~~~~
-public:
-
-static void RegisterFlattenables();
-~~~~
-
-Then in the cpp file you define all the members of the group together:
-
-<!--?prettify?-->
-~~~~
-void SkGroupClass::RegisterFlattenables() {
-    SK_REGISTER_FLATTENABLE(SkMemberClass1);
-
-    SK_REGISTER_FLATTENABLE(SkMemberClass2);
-
-    // etc
-}
-~~~~
-
-
-5: Register your flattenable with the global registrar:
-You need to add one line to `SkFlattenable::InitalizeFlattenables()`. To register the 
-flattenable in a Skia build, that function is defined in `SkGlobalInitialization_default.cpp`. 
-For Chromium, it is in `SkGlobalInitialization_chromium.cpp`.
-For a single flattenable add
-
-<!--?prettify?-->
-~~~~
-SK_REGISTER_FLATTENABLE(SkNewClass);
-~~~~
-
-For a group, add
-
-<!--?prettify?-->
-~~~~
-SkGroupClass::RegisterFlattenables();
-~~~~
-
diff --git a/site/dev/contrib/index.md b/site/dev/contrib/index.md
index 9f74a40..7727416 100644
--- a/site/dev/contrib/index.md
+++ b/site/dev/contrib/index.md
@@ -28,7 +28,7 @@
 Keep your code submissions small and targeted.  
 When possible, have a fellow contributor review your change in advance of submission.  
 Propose new features to the project leads by opening a feature bug or posting to 
-skia-discuss ahead of development.  For more information, see [How to submit a patch](./contrib/submit).
+skia-discuss ahead of development.  For more information, see [How to submit a patch](./submit).
 
 For background on the project and an outline of the types of roles interested parties 
 can take on, see [Project Roles](../../roles).
diff --git a/site/dev/contrib/simd.md b/site/dev/contrib/simd.md
deleted file mode 100644
index 6ec29c5..0000000
--- a/site/dev/contrib/simd.md
+++ /dev/null
@@ -1,141 +0,0 @@
-Skia's New Approach to SIMD
-===========================
-
-Most hot software paths in Skia are implemented with processor-specific SIMD instructions.  For graphics performance, the parallelism from SIMD is essential: there is simply no realistic way to eek the same performance out of portable C++ code as we can from the SSE family of instruction sets on x86 or from NEON on ARM or from MIPS32's DSP instructions.  Depending on the particular code path and math involved, we see 2, 4, 8, or even ~16x performance increases over portable code when really exploiting the processor-specific SIMD instructions.
-
-But the SIMD code we've piled up over the years has some serious problems.  It's often quite low-level, with poor factoring leading to verbose, bug prone, and difficult to read code.  SIMD instrinsic types and functions take a good long while to get used to reading, let alone writing, and assembly is generally just a complete non-starter.  SIMD coverage of Skia methods is not dense: a particular drawing routine might be specialized for NEON but not for SSE, or might have a MIPS DSP implementation but no NEON.  Even when we have full instruction set coverage, the implementations of these specialized routines may not produce identical results, either when compared with each other or with our portable fallback code.  The SIMD implementations are often simply incorrect, but the code is so fragile and difficult to understand, we can't fix it.  There are long lived bugs in our tracker involving crashes and buffer under- and overflows that we simply cannot fix because no one on the team understands the code involved.  And finally, to top it all off, the code isn't always even really that fast.
-
-This all needs to change.  I want Skia developers to be able to write correct, clear, and fast code, and in software rendering, SIMD is the only way to get "fast".  This document outlines a new vision for how Skia will use SIMD instructions with no compromises, writing clear code _once_ that runs quickly on all platforms we support.
-
-The Plan
---------
-
-We're going to wrap low-level platform-specific instrinsics with zero-cost abstractions with interfaces matching Skia's higher-level-but-still-quite-low-level use cases.  Skia code will write to this interface _once_, which then compiles to efficient SSE, NEON, or portable code (MIPS is quite TBD, for now group it conceptually under portable code) via platform-specific backends.  The key here is to find the right sweet spot of abstraction that allows us to express the graphical concepts we want in Skia while allowing each of those platform-specific backends flexibility to implement those concepts as efficiently as possible.
-
-While Skia uses a mix of float, 32-bit, 16-bit, and 8-bit integer SIMD instructions, 32-bit integers fall quite behind the rest in usage.  Since we tend to operate on 8888 ARGB values, 8-bit SIMD tends to be the most natural and fastest approach, but when multiplication gets involved (essentially all the time), 16-bit SIMD inevitably gets tangled in there.  For some operations like division, square roots, or math with high range or precision requirements, we expand our 8-bit pixel components up to floats, and working with a single pixel as a 4-float vector becomes most natural.  This plan focuses on how we'll deal with these majority cases: floats, and 8- and 16-bit integers.
-
-`SkNf` for floats
----------------
-
-Wrapping floats with an API that allows efficient implementation on SSE and NEON is by far the easiest task involved here.  Both SSE and NEON naturally work with 128-bit vectors of 4 floats, and they have a near 1-to-1 correspondence between operations.  Indeed, the correspondence is so close that it's tempting to solve this problem by picking one set of intrinsics, e.g. NEON, and just `#define`ing portable and SSE implementations of NEON:
-
-    #define float32x4_t __m128
-    #define vmulq_f32 _mm_mul_ps
-    #define vaddq_f32 _mm_add_ps
-    #define vld1q_f32 _mm_loadu_ps
-    #define vst1q_f32 _mm_storeu_ps
-    ...
-
-This temptation starts to break down when you notice:
-
--   there are operations that don't quite correspond, e.g. `_mm_movemask_ps`; and
--   math written with either SSE or NEON instrinsics is still very hard to read; and
--   sometimes we want to work with 4 floats, but sometimes 2, maybe even 8, etc.
-
-So we use a wrapper class `SkNf<N>`, parameterized on N, how many floats the vector contains, constrained at compile time to be a power of 2.  `SkNf` provides all the methods you'd expect on a vector of N floats: loading and storing from float arrays, all the usual arithmetic operators, min and max, low and high precision reciprocal and sqrt, all the usual comparison operators, and a `.thenElse()` method acting as a non-branching ternary `?:` operator.  To support Skia's main graphic needs, `SkNf` can also load and store from a vector of N _bytes_, converting up to a float when loading and rounding down to [0,255] when storing.
-
-As a convenience, `SkNf<N>` has two default implementations: `SkNf<1>` performs all these operations on a single float, and the generic `SkNf<N>` simply recurses onto two `SkNf<N/2>`.  This allows our different backends to inject specialiations where most natural: the portable backend does nothing, so all `SkNf<N>` recurse down to the default `SkNf<1>`;  the NEON backend specializes `SkNf<2>` with `float32x2_t` and 64-bit SIMD methods, and `SkNf<4>` with `float32x4_t` and 128-bit SIMD methods; the SSE backend specializes both `SkNf<4>` and `SkNf<2>` to use the full or lower half of an `__m128` vector, respectively.  A future AVX backend could simply drop in an `SkNf<8>` specialization.
-
-Our most common float use cases are working with 2D coordinates and with 4-float-component pixels.  Since these are so common, we've made simple typedefs for these two use cases, `Sk2f` and `Sk4f`, and also versions reminding you that it can work with vectors of `SkScalar` (a Skia-specific float typedef) too: `Sk2s`, `Sk4s`.
-
-`SkNf` in practice
-----------------
-
-To date we have implemented several parts of Skia using `Sk4f`:
-
-  1. `SkColorMatrixFilter`
-  2. `SkRadialGradient`
-  3. `SkColorCubeFilter`
-  4. Three complicated `SkXfermode` subclasses: `ColorBurn`, `ColorDodge`, and `SoftLight`.
-
-In all these cases, we have been able to write a single implementation, producing the same results cross-platform.  The first three of those sites using `Sk4f` are entirely newly vectorized, and run much faster than the previous portable implementations.  The 3 `Sk4f` transfermodes replaced portable, SSE, and NEON implementations which all produced different results, and the `Sk4f` versions are all faster than their predecessors.
-
-`SkColorCubeFilter` stands out as a particularly good example of how and why to use `Sk4f` over custom platform-specific intrinsics.  Starting from some portable code and a rather slow SSE-only sketch, a Google Chromium dev, an Intel contributor, and I worked together to write an `Sk4f` version that's more than twice as fast as the original, and runs fast on _both_ x86 and ARM.
-
-`SkPx` for 8- and 16-bit fixed point math
-----------------------------------------
-
-Building an abstraction layer over 8- and 16-bit fixed point math has proven to be quite a challenge.  In fixed point, NEON and SSE again have some overlap, and they could probably be implemented in terms of each other if you were willing to sacrifice performance on SSE in favor of NEON or vice versa.  But unlike with floats, where `SkNf` is really a pretty thin veneer over very similar operations, to really get the best performance out of each fixed point instruction set you need to work in rather different idioms.
-
-`SkPx`, our latest approach (there have been alpha `Sk16b` and beta `Sk4px` predecessors) to 8- and 16-bit SIMD  tries to abstract over those idioms to again allow Skia developers to write one piece of clear graphics code that different backends can translate into their native intrinsics idiomatically.
-
-`SkPx` is really a family of three related types:
-
-  1. `SkPx` itself represents between 1 and `SkPx::N` 8888 ARGB pixels, where `SkPx::N` is a backend-specific compile-time power of 2.
-  2. `SkPx::Wide` represents those same pixels, but with 16-bits of space per component.
-  3.  `SkPx::Alpha` represents the alpha channels of those same pixels.
-
-`SkPx`, `Wide` and `Alpha` create a somewhat complicated algebra of operations entirely motivated by the graphical operations we need to perform.  Here are some examples:
-
-    SkPx::LoadN(const uint32_t*)   -> SkPx  // Load full cruising-speed SkPx.
-    SkPx::Load(n, const uint32_t*) -> SkPx  // For the 0<n<N ragged tail.
-    
-    SkPx.storeN(uint32_t*)   // Store a full SkPx.
-    SkPx.store(n, uint32_t*) // For the ragged 0<n<N tail.
-
-    SkPx + SkPx -> SkPx
-    SkPx - SkPx -> SkPx
-    SkPx.saturatedAdd(SkPx) -> SkPx
-
-    SkPx.alpha() -> Alpha   // Extract alpha channels.
-    Alpha::LoadN(const uint8_t*)   -> Alpha  // Like SkPx loads, in 8-bit steps.
-    Alpha::Load(n, const uint8_t*) -> Alpha
-
-    SkPx.widenLo()   -> Wide  // argb -> 0a0r0g0b
-    SkPx.widenHi()   -> Wide  // argb -> a0r0g0b0
-    SkPx.widenLoHi() -> Wide  // argb -> aarrggbb
-
-    Wide + Wide -> Wide
-    Wide - Wide -> Wide
-    Wide << bits -> Wide
-    Wide >> bits -> Wide
-
-    SkPx * Alpha -> Wide    // 8 x 8 -> 16 bit
-    Wide.div255() -> SkPx   // 16-bit -> 8 bit
-
-    // A faster approximation of (SkPx * Alpha).div255().
-    SkPx.approxMulDiv255(Alpha) -> SkPx
-
-We allow each `SkPx` backend to choose how it physically represents `SkPx`, `SkPx::Wide`, and `SkPx::Alpha` and to choose any power of two as its `SkPx::N` sweet spot.  Code working with `SkPx` typically runs a loop like this:
-
-    while (n >= SkPx::N) {
-    	// Apply some_function() to SkPx::N pixels.
-    	some_function(SkPx::LoadN(src), SkPx::LoadN(dst)).storeN(dst);
-    	src += SkPx::N; dst += SkPx::N; n -= SkPx::N;
-    }
-    if (n > 0) {
-    	// Finish up the tail of 0<n<N pixels.
-    	some_function(SkPx::Load(n, src), SkPx::Load(n, dst)).store(n, dst);
-    }
-
-The portable code is of course the simplest place to start looking at implementation details: its `SkPx` is just `uint8_t[4]`, its `SkPx::Wide` `uint16_t[4]`, and its `SkPx::Alpha` just `uint8_t`.  Its preferred number of pixels to work with is `SkPx::N = 1`.  (Amusingly, GCC and Clang seem pretty good about autovectorizing this backend using 32-bit math, which typically ends up within ~2x of the best we can do ourselves.)
-
-The most important difference between SSE and NEON when working in fixed point is that SSE works most naturally with 4 interlaced pixels at a time (argbargbargbargb), while NEON works most naturally with 8 planar pixels at a time (aaaaaaaa, rrrrrrrr, gggggggg, bbbbbbbb).  Trying to jam one of these instruction sets into the other's idiom ends up somewhere between not quite optimal (working with interlaced pixels in NEON) and ridiculously inefficient (trying to work with planar pixels in SSE).
-
-So `SkPx`'s SSE backend sets N to 4 pixels, stores them interlaced in an `__m128i`, representing `Wide` as two `__m128i` and `Alpha` as an `__m128i` with each pixel's alpha component replicated four times.  `SkPx`'s NEON backend works with 8 planar pixels, loading them with `vld4_u8` into an `uint8x8x4_t` struct of 4 8-component `uint8x8_t` planes.  `Alpha` is just a single `uint8x8_t` 8-component plane, and `Wide` is NEON's natural choice, `uint16x8x4_t`.
-
-(It's fun to speculate what an AVX2 backend might look like.  Do we make `SkPx` declare it wants to work with 8 pixels at a time, or leave it at 4?  Does `SkPx` become `__m256i`, or maybe only `SkPx::Wide` does?  What's the best way to represent `Alpha`?  And of course, what about AVX-512?)
-
-Keeping `Alpha` as a single dense `uint8x8_t` plane allows the NEON backend to be much more efficient with operations involving `Alpha`.  We'd love to do this in SSE too, where we store `Alpha` somewhat inefficiently with each alpha component replicated 4 times, but SSE simply doesn't expose efficient ways to transpose interlaced pixels into planar pixels and vice versa.  We could write them ourselves, but only as rather complex compound operations that slow things down more than they help.
-
-These details will inevitably change over time.  The important takeaway here is, to really work at peak throughput in SIMD fixed point, you need to work with the idiom of the instruction set, and `SkPx` is a design that can present a consistent interface to abstract away backend details for you.
-
-`SkPx` in practice
-----------------
-
-I am in the process of rolling out `SkPx`.  Some Skia code is already using its precursor, `Sk4px`, which is a bit like `SkPx` that forces `N=4` and restricts the layout to always use interlaced pixels: i.e. fine for SSE, not great for NEON.
-
-  1. All ~20 other `SkXfermode` subclasses that are not implemented with `SkNf`.
-  2. `SkBlitRow::Color32`
-  3. `SkBlitMask::BlitColor`
-
-I can certainly say that the `Sk4px` and `SkPx` implementations of these methods are clearer, less buggy, and that all the `SkXfermode` implementations sped up at least 2x when porting from custom per-platform intrinsics.  `Sk4px` has lead to some pretty bad performance regressions that `SkPx` is designed to avoid.  This is an area of active experiementation and iteration.
-
-In Summary
-----------
-
-I am confident that Skia developers soon will be able to write single, clear, maintainable, and of course _fast_,  graphical algorithms using `SkNf` and `SkPx`.  As I have been porting our algorithms, I have perversely enjoyed replacing thousands of lines of unmaintainable code with usually mere dozens of readable code.
-
-I'm also confident that if you're looking to use floats, `SkNf` is ready.  Do not write NEON or SSE SIMD code if you're looking to use floats, and do not accept external contributions that do so.  Use `SkNf` instead.
-
-`SkPx` is less proven, and while its design and early tests look promising, it's still at the stage where we should try it aware that we might need to fall back on hand-written SSE or NEON.
diff --git a/site/dev/sheriffing/android.md b/site/dev/sheriffing/android.md
index c3a2310..c9ef21e 100644
--- a/site/dev/sheriffing/android.md
+++ b/site/dev/sheriffing/android.md
@@ -17,7 +17,7 @@
 
 1) Monitor and approve the semi-autonomous [git merges](https://googleplex-android-review.git.corp.google.com/#/q/owner:31977622648%2540project.gserviceaccount.com+status:open) from Skia's repository into the Android source tree. See autoroller documentation <a href="#autoroller_doc">here</a> for details on how to interact with it.
 
-2) Stay on top of incoming Android-related bugs in both the [Skia](https://bugs.chromium.org/p/skia/issues/list?can=2&q=OpSys%3DAndroid&sort=-id&colspec=ID+Type+Status+Priority+Owner+Summary&cells=tiles) and [Android](https://buganizer.corp.google.com/issues?q=componentid:1346%20status:open) bug trackers.  For Skia bugs, this means triaging and assigning all Android bugs that are currently unassigned.  For Android, this means following the [Android guidelines](go/android-buganizer) to verifying that all Skia bugs are TL-triaged (if not reach out to djsollen@).
+2) Stay on top of incoming Android-related bugs in both the [Skia](https://bugs.chromium.org/p/skia/issues/list?can=2&q=OpSys%3DAndroid&sort=-id&colspec=ID+Type+Status+Priority+Owner+Summary&cells=tiles) and [Android](https://buganizer.corp.google.com/issues?q=assignee:skia-android-triage%20status:open) bug trackers.  For Skia bugs, this means triaging and assigning all Android bugs that are currently unassigned.  For Android, this means following the [Android guidelines](go/android-buganizer) to verifying that all Skia bugs are TL-triaged (if not reach out to djsollen@).
 
 The RoboCop's job is NOT to address issues in Perf and Gold. You'll get your chance when you are the general Skia sheriff.
 
diff --git a/site/user/api/SkCanvas_Reference.md b/site/user/api/SkCanvas_Reference.md
index 90a235e..11a4fe6 100644
--- a/site/user/api/SkCanvas_Reference.md
+++ b/site/user/api/SkCanvas_Reference.md
@@ -52,11 +52,11 @@
     void <a href='#SkCanvas_restore'>restore()</a>;
     int <a href='#SkCanvas_getSaveCount'>getSaveCount</a>() const;
     void <a href='#SkCanvas_restoreToCount'>restoreToCount</a>(int saveCount);
-    void translate(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
-    void scale(<a href='undocumented#SkScalar'>SkScalar</a> sx, <a href='undocumented#SkScalar'>SkScalar</a> sy);
-    void rotate(<a href='undocumented#SkScalar'>SkScalar</a> degrees);
-    void rotate(<a href='undocumented#SkScalar'>SkScalar</a> degrees, <a href='undocumented#SkScalar'>SkScalar</a> px, <a href='undocumented#SkScalar'>SkScalar</a> py);
-    void skew(<a href='undocumented#SkScalar'>SkScalar</a> sx, <a href='undocumented#SkScalar'>SkScalar</a> sy);
+    void <a href='#SkCanvas_translate'>translate</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
+    void <a href='#SkCanvas_scale'>scale</a>(<a href='undocumented#SkScalar'>SkScalar</a> sx, <a href='undocumented#SkScalar'>SkScalar</a> sy);
+    void <a href='#SkCanvas_rotate'>rotate</a>(<a href='undocumented#SkScalar'>SkScalar</a> degrees);
+    void <a href='#SkCanvas_rotate'>rotate</a>(<a href='undocumented#SkScalar'>SkScalar</a> degrees, <a href='undocumented#SkScalar'>SkScalar</a> px, <a href='undocumented#SkScalar'>SkScalar</a> py);
+    void <a href='#SkCanvas_skew'>skew</a>(<a href='undocumented#SkScalar'>SkScalar</a> sx, <a href='undocumented#SkScalar'>SkScalar</a> sy);
     void <a href='#SkCanvas_concat'>concat</a>(const <a href='SkMatrix_Reference#SkMatrix'>SkMatrix</a>& <a href='SkMatrix_Reference#Matrix'>matrix</a>);
     void <a href='#SkCanvas_setMatrix'>setMatrix</a>(const <a href='SkMatrix_Reference#SkMatrix'>SkMatrix</a>& <a href='SkMatrix_Reference#Matrix'>matrix</a>);
     void <a href='#SkCanvas_resetMatrix'>resetMatrix</a>();
@@ -77,7 +77,7 @@
     <a href='SkIRect_Reference#SkIRect'>SkIRect</a> <a href='#SkCanvas_getDeviceClipBounds'>getDeviceClipBounds</a>() const;
     bool <a href='#SkCanvas_getDeviceClipBounds'>getDeviceClipBounds</a>(<a href='SkIRect_Reference#SkIRect'>SkIRect</a>* bounds) const;
     void <a href='#SkCanvas_drawColor'>drawColor</a>(<a href='SkColor_Reference#SkColor'>SkColor</a> <a href='SkColor_Reference#Color'>color</a>, <a href='SkBlendMode_Reference#SkBlendMode'>SkBlendMode</a> mode = <a href='SkBlendMode_Reference#SkBlendMode'>SkBlendMode</a>::<a href='#SkBlendMode_kSrcOver'>kSrcOver</a>);
-    void clear(<a href='SkColor_Reference#SkColor'>SkColor</a> <a href='SkColor_Reference#Color'>color</a>);
+    void <a href='#SkCanvas_clear'>clear</a>(<a href='SkColor_Reference#SkColor'>SkColor</a> <a href='SkColor_Reference#Color'>color</a>);
     void <a href='#SkCanvas_discard'>discard()</a>;
     void <a href='#SkCanvas_drawPaint'>drawPaint</a>(const <a href='SkPaint_Reference#SkPaint'>SkPaint</a>& <a href='SkPaint_Reference#Paint'>paint</a>);
 
@@ -1645,7 +1645,7 @@
 </pre>
 
 Saves <a href='SkMatrix_Reference#SkMatrix'>SkMatrix</a> and clip, and allocates a <a href='SkBitmap_Reference#SkBitmap'>SkBitmap</a> for subsequent drawing.
-<a href='SkPaint_Reference#LCD_Text'>LCD text</a> is preserved when the <a href='SkCanvas_Reference#Layer'>layer</a> is drawn to the prior <a href='SkCanvas_Reference#Layer'>layer</a>.
+LCD <a href='undocumented#Text'>text</a> is preserved when the <a href='SkCanvas_Reference#Layer'>layer</a> is drawn to the prior <a href='SkCanvas_Reference#Layer'>layer</a>.
 
 Calling <a href='#SkCanvas_restore'>restore()</a> discards changes to <a href='SkMatrix_Reference#SkMatrix'>SkMatrix</a> and clip, and draws <a href='SkCanvas_Reference#Layer'>layer</a>.
 
@@ -1661,8 +1661,8 @@
 
 Call <a href='#SkCanvas_restoreToCount'>restoreToCount</a>() with returned value to restore this and subsequent saves.
 
-Draw <a href='undocumented#Text'>text</a> on an opaque background so that  <a href='SkPaint_Reference#LCD_Text'>LCD text</a> blends correctly with the
-prior <a href='SkCanvas_Reference#Layer'>layer</a>.  <a href='SkPaint_Reference#LCD_Text'>LCD text</a> drawn on a background with transparency may result in
+Draw <a href='undocumented#Text'>text</a> on an opaque background so that LCD <a href='undocumented#Text'>text</a> blends correctly with the
+prior <a href='SkCanvas_Reference#Layer'>layer</a>. LCD <a href='undocumented#Text'>text</a> drawn on a background with transparency may result in
 incorrect blending.
 
 ### Parameters
@@ -1763,7 +1763,7 @@
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkCanvas_kPreserveLCDText_SaveLayerFlag'><code>SkCanvas::kPreserveLCDText_SaveLayerFlag</code></a></td>
     <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>2</td>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
-Creates <a href='SkCanvas_Reference#Layer'>Layer</a> for  <a href='SkPaint_Reference#LCD_Text'>LCD text</a>. Flag is ignored if <a href='SkCanvas_Reference#Layer'>Layer</a> <a href='SkPaint_Reference#Paint'>Paint</a> contains
+Creates <a href='SkCanvas_Reference#Layer'>Layer</a> for LCD <a href='undocumented#Text'>text</a>. Flag is ignored if <a href='SkCanvas_Reference#Layer'>Layer</a> <a href='SkPaint_Reference#Paint'>Paint</a> contains
 <a href='#Image_Filter'>Image_Filter</a> or <a href='#Color_Filter'>Color_Filter</a>.
 </td>
   </tr>
@@ -1796,8 +1796,8 @@
     struct <a href='#SkCanvas_SaveLayerRec'>SaveLayerRec</a> {
 
         <a href='#SkCanvas_SaveLayerRec_SaveLayerRec'>SaveLayerRec()</a>;
-        <a href='#SkCanvas_SaveLayerRec'>SaveLayerRec</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>* bounds, const <a href='SkPaint_Reference#SkPaint'>SkPaint</a>* <a href='SkPaint_Reference#Paint'>paint</a>, <a href='#SkCanvas_SaveLayerFlags'>SaveLayerFlags</a> saveLayerFlags = 0);
-        <a href='#SkCanvas_SaveLayerRec'>SaveLayerRec</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>* bounds, const <a href='SkPaint_Reference#SkPaint'>SkPaint</a>* <a href='SkPaint_Reference#Paint'>paint</a>, const <a href='undocumented#SkImageFilter'>SkImageFilter</a>* backdrop,
+        <a href='#SkCanvas_SaveLayerRec_SaveLayerRec'>SaveLayerRec</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>* bounds, const <a href='SkPaint_Reference#SkPaint'>SkPaint</a>* <a href='SkPaint_Reference#Paint'>paint</a>, <a href='#SkCanvas_SaveLayerFlags'>SaveLayerFlags</a> saveLayerFlags = 0);
+        <a href='#SkCanvas_SaveLayerRec_SaveLayerRec'>SaveLayerRec</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>* bounds, const <a href='SkPaint_Reference#SkPaint'>SkPaint</a>* <a href='SkPaint_Reference#Paint'>paint</a>, const <a href='undocumented#SkImageFilter'>SkImageFilter</a>* backdrop,
                      <a href='#SkCanvas_SaveLayerFlags'>SaveLayerFlags</a> saveLayerFlags);
 
         const <a href='SkRect_Reference#SkRect'>SkRect</a>* <a href='#SkCanvas_SaveLayerRec_fBounds'>fBounds</a> = nullptr;
@@ -1864,7 +1864,7 @@
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkCanvas_SaveLayerRec_fSaveLayerFlags'><code>fSaveLayerFlags</code></a></td>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
 <a href='#SkCanvas_SaveLayerRec_fSaveLayerFlags'>fSaveLayerFlags</a> are used to create <a href='SkCanvas_Reference#Layer'>Layer</a> without transparency,
-create <a href='SkCanvas_Reference#Layer'>Layer</a> for  <a href='SkPaint_Reference#LCD_Text'>LCD text</a>, and to create <a href='SkCanvas_Reference#Layer'>Layer</a> with the
+create <a href='SkCanvas_Reference#Layer'>Layer</a> for LCD <a href='undocumented#Text'>text</a>, and to create <a href='SkCanvas_Reference#Layer'>Layer</a> with the
 contents of the previous <a href='SkCanvas_Reference#Layer'>Layer</a>.
 </td>
   </tr>
@@ -2035,7 +2035,7 @@
 
 ### Return Value
 
-depth of save  <a href='#State_Stack'>state stack</a>
+depth of save  <a href='#State_Stack'>state stack</a> before this call was made.
 
 ### Example
 
@@ -5219,13 +5219,13 @@
 
 Draws <a href='#Text_Blob'>Text_Blob</a> <a href='#SkCanvas_drawTextBlob_blob'>blob</a> at (<a href='#SkCanvas_drawTextBlob_x'>x</a>, <a href='#SkCanvas_drawTextBlob_y'>y</a>), using Clip, <a href='SkMatrix_Reference#Matrix'>Matrix</a>, and <a href='SkPaint_Reference#Paint'>Paint</a> <a href='#SkCanvas_drawTextBlob_paint'>paint</a>.
 
-<a href='#SkCanvas_drawTextBlob_blob'>blob</a> contains <a href='undocumented#Glyph'>Glyphs</a>, their positions, and <a href='#SkCanvas_drawTextBlob_paint'>paint</a> attributes specific to <a href='undocumented#Text'>text</a>: <a href='undocumented#Typeface'>Typeface</a>, <a href='#Paint_Text_Size'>Paint_Text_Size</a>, <a href='#Paint_Text_Scale_X'>Paint_Text_Scale_X</a>,
-<a href='#Paint_Text_Skew_X'>Paint_Text_Skew_X</a>, <a href='#Paint_Hinting'>Paint_Hinting</a>, <a href='#Paint_Anti_Alias'>Anti_Alias</a>, <a href='#Paint_Fake_Bold'>Paint_Fake_Bold</a>,
-<a href='#Paint_Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a>, <a href='#Paint_Full_Hinting_Spacing'>Full_Hinting_Spacing</a>, <a href='#Paint_LCD_Text'>LCD_Text</a>, <a href='#Paint_Linear_Text'>Linear_Text</a>,
-and <a href='#Paint_Subpixel_Text'>Subpixel_Text</a>
+<a href='#SkCanvas_drawTextBlob_blob'>blob</a> contains <a href='undocumented#Glyph'>Glyphs</a>, their positions, and <a href='#SkCanvas_drawTextBlob_paint'>paint</a> attributes specific to <a href='undocumented#Text'>text</a>: <a href='undocumented#Typeface'>Typeface</a>, <a href='#Font_Size'>Font_Size</a>, <a href='#Font_Scale_X'>Font_Scale_X</a>,
+<a href='#Font_Skew_X'>Font_Skew_X</a>, <a href='#Font_Hinting'>Font_Hinting</a>, <a href='#Paint_Anti_Alias'>Paint_Anti_Alias</a>, <a href='#Font_Embolden'>Font_Embolden</a>, <a href='#Font_Force_Hinting'>Font_Force_Hinting</a>,
+<a href='#Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a>, <a href='#Font_Hinting_Spacing'>Font_Hinting_Spacing</a>, <a href='#Font_Anti_Alias'>Font_Anti_Alias</a>, <a href='#Font_Linear'>Font_Linear</a>,
+and <a href='#Font_Subpixel'>Font_Subpixel</a>
 .
 
-<a href='#Paint_Text_Encoding'>Paint_Text_Encoding</a> must be set to <a href='SkPaint_Reference#SkPaint'>SkPaint</a>::<a href='#SkPaint_kGlyphID_TextEncoding'>kGlyphID_TextEncoding</a>.
+<a href='#Paint_Text_Encoding'>Paint_Text_Encoding</a> must be set to <a href='undocumented#kGlyphID_SkTextEncoding'>kGlyphID_SkTextEncoding</a>.
 
 Elements of <a href='#SkCanvas_drawTextBlob_paint'>paint</a>: <a href='#Paint_Anti_Alias'>Anti_Alias</a>, <a href='#Blend_Mode'>Blend_Mode</a>, <a href='SkColor_Reference#Color'>Color</a> including <a href='#Color_Alpha'>Color_Alpha</a>,
 <a href='#Color_Filter'>Color_Filter</a>, <a href='#Paint_Dither'>Paint_Dither</a>, <a href='#Draw_Looper'>Draw_Looper</a>, <a href='#Mask_Filter'>Mask_Filter</a>, <a href='#Path_Effect'>Path_Effect</a>, <a href='undocumented#Shader'>Shader</a>, and
@@ -5251,7 +5251,7 @@
 
 ### Example
 
-<div><fiddle-embed name="a207bbd7317bfbdadbda8af884631e46"></fiddle-embed></div>
+<div><fiddle-embed name="005502b502c1282cb8d306d6c8d998fb"></fiddle-embed></div>
 
 ### See Also
 
@@ -5267,13 +5267,13 @@
 
 Draws <a href='#Text_Blob'>Text_Blob</a> <a href='#SkCanvas_drawTextBlob_2_blob'>blob</a> at (<a href='#SkCanvas_drawTextBlob_2_x'>x</a>, <a href='#SkCanvas_drawTextBlob_2_y'>y</a>), using Clip, <a href='SkMatrix_Reference#Matrix'>Matrix</a>, and <a href='SkPaint_Reference#Paint'>Paint</a> <a href='#SkCanvas_drawTextBlob_2_paint'>paint</a>.
 
-<a href='#SkCanvas_drawTextBlob_2_blob'>blob</a> contains <a href='undocumented#Glyph'>Glyphs</a>, their positions, and <a href='#SkCanvas_drawTextBlob_2_paint'>paint</a> attributes specific to <a href='undocumented#Text'>text</a>: <a href='undocumented#Typeface'>Typeface</a>, <a href='#Paint_Text_Size'>Paint_Text_Size</a>, <a href='#Paint_Text_Scale_X'>Paint_Text_Scale_X</a>,
-<a href='#Paint_Text_Skew_X'>Paint_Text_Skew_X</a>, <a href='#Paint_Hinting'>Paint_Hinting</a>, <a href='#Paint_Anti_Alias'>Anti_Alias</a>, <a href='#Paint_Fake_Bold'>Paint_Fake_Bold</a>,
-<a href='#Paint_Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a>, <a href='#Paint_Full_Hinting_Spacing'>Full_Hinting_Spacing</a>, <a href='#Paint_LCD_Text'>LCD_Text</a>, <a href='#Paint_Linear_Text'>Linear_Text</a>,
-and <a href='#Paint_Subpixel_Text'>Subpixel_Text</a>
+<a href='#SkCanvas_drawTextBlob_2_blob'>blob</a> contains <a href='undocumented#Glyph'>Glyphs</a>, their positions, and <a href='#SkCanvas_drawTextBlob_2_paint'>paint</a> attributes specific to <a href='undocumented#Text'>text</a>: <a href='undocumented#Typeface'>Typeface</a>, <a href='#Font_Size'>Font_Size</a>, <a href='#Font_Scale_X'>Font_Scale_X</a>,
+<a href='#Font_Skew_X'>Font_Skew_X</a>, <a href='#Font_Hinting'>Font_Hinting</a>, <a href='#Paint_Anti_Alias'>Paint_Anti_Alias</a>, <a href='#Font_Embolden'>Font_Embolden</a>, <a href='#Font_Force_Hinting'>Font_Force_Hinting</a>,
+<a href='#Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a>, <a href='#Font_Hinting_Spacing'>Font_Hinting_Spacing</a>, <a href='#Font_Anti_Alias'>Font_Anti_Alias</a>, <a href='#Font_Linear'>Font_Linear</a>,
+and <a href='#Font_Subpixel'>Font_Subpixel</a>
 .
 
-<a href='#Paint_Text_Encoding'>Paint_Text_Encoding</a> must be set to <a href='SkPaint_Reference#SkPaint'>SkPaint</a>::<a href='#SkPaint_kGlyphID_TextEncoding'>kGlyphID_TextEncoding</a>.
+<a href='#Paint_Text_Encoding'>Paint_Text_Encoding</a> must be set to <a href='undocumented#kGlyphID_SkTextEncoding'>kGlyphID_SkTextEncoding</a>.
 
 Elements of <a href='#SkCanvas_drawTextBlob_2_paint'>paint</a>: <a href='#Path_Effect'>Path_Effect</a>, <a href='#Mask_Filter'>Mask_Filter</a>, <a href='undocumented#Shader'>Shader</a>, <a href='#Color_Filter'>Color_Filter</a>,
 <a href='#Image_Filter'>Image_Filter</a>, and <a href='#Draw_Looper'>Draw_Looper</a>; apply to <a href='#SkCanvas_drawTextBlob_2_blob'>blob</a>.
diff --git a/site/user/api/SkColor4f_Reference.md b/site/user/api/SkColor4f_Reference.md
index 2d31477..c24a269 100644
--- a/site/user/api/SkColor4f_Reference.md
+++ b/site/user/api/SkColor4f_Reference.md
@@ -18,13 +18,13 @@
     float <a href='#SkRGBA4f_fB'>fB</a>;
     float <a href='#SkRGBA4f_fA'>fA</a>;
 
-    bool operator==(const <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a>& other) const;
-    bool operator!=(const <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a>& other) const;
-    <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a> operator*(float scale) const;
-    <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a> operator*(const <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a>& scale) const;
+    bool <a href='#SkRGBA4f_equal1_operator'>operator==</a>(const <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a>& other) const;
+    bool <a href='#SkRGBA4f_notequal1_operator'>operator!=</a>(const <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a>& other) const;
+    <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a> <a href='#SkRGBA4f_multiply_operator'>operator*</a>(float scale) const;
+    <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a> <a href='#SkRGBA4f_multiply1_operator'>operator*</a>(const <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a>& scale) const;
     const float* <a href='#SkRGBA4f_vec'>vec()</a> const;
     float* <a href='#SkRGBA4f_vec'>vec()</a>;
-    float <a href='#SkRGBA4f_array1_operator'>operator[]</a>(int index) const;
+    float <a href='#SkRGBA4f_array_operator'>operator[]</a>(int index) const;
     float& <a href='#SkRGBA4f_array1_operator'>operator[]</a>(int index);
     bool <a href='#SkRGBA4f_isOpaque'>isOpaque</a>() const;
     static <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a> <a href='#SkRGBA4f_FromColor'>FromColor</a>(<a href='SkColor_Reference#SkColor'>SkColor</a> <a href='SkColor_Reference#Color'>color</a>);
@@ -121,7 +121,7 @@
 
 ### See Also
 
-operator!=(const <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a>& <a href='#SkRGBA4f_equal1_operator_other'>other</a>) const
+<a href='#SkRGBA4f_notequal1_operator'>operator!=</a>(const <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a>& <a href='#SkRGBA4f_equal1_operator_other'>other</a>) const
 
 <a name='SkRGBA4f_notequal1_operator'></a>
 
@@ -159,7 +159,7 @@
 
 ### See Also
 
-operator==(const <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a>& <a href='#SkRGBA4f_notequal1_operator_other'>other</a>) const
+<a href='#SkRGBA4f_equal1_operator'>operator==</a>(const <a href='SkColor4f_Reference#SkRGBA4f'>SkRGBA4f</a>& <a href='#SkRGBA4f_notequal1_operator_other'>other</a>) const
 
 <a name='SkRGBA4f_multiply_operator'></a>
 
diff --git a/site/user/api/SkFont_Reference.md b/site/user/api/SkFont_Reference.md
index 4c3e127..fb55705 100644
--- a/site/user/api/SkFont_Reference.md
+++ b/site/user/api/SkFont_Reference.md
@@ -2,16 +2,109 @@
 ===
 
 
-<a name='Advance'></a>
-
-<a name='Engine'></a>
-
 <pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
 class <a href='SkFont_Reference#SkFont'>SkFont</a> {
     // <i><a href='SkFont_Reference#SkFont'>SkFont</a> interface</i>
 };
 </pre>
 
+<a name='Advance'></a>
+
+<a name='Engine'></a>
+
+<a name='Size'></a>
+
+<a href='#Font_Size'>Font_Size</a> adjusts the overall <a href='undocumented#Text'>text</a> <a href='undocumented#Size'>size</a> in <a href='SkPoint_Reference#Point'>points</a>.
+<a href='#Font_Size'>Font_Size</a> can be set to any positive value or zero.
+<a href='#Font_Size'>Font_Size</a> defaults to 12.
+<a href='#Font_Size'>Font_Size</a>
+
+<a name='Scale_X'></a>
+
+<a href='#Font_Scale_X'>Font_Scale_X</a> adjusts the <a href='undocumented#Text'>text</a> horizontal scale.
+<a href='undocumented#Text'>Text</a> scaling approximates condensed and expanded type faces when the actual face
+is not available.
+<a href='#Font_Scale_X'>Font_Scale_X</a> can be set to any value.
+<a href='#Font_Scale_X'>Font_Scale_X</a> defaults to 1.
+
+<a name='Skew_X'></a>
+
+<a href='#Font_Skew_X'>Font_Skew_X</a> adjusts the <a href='undocumented#Text'>text</a> horizontal slant.
+<a href='undocumented#Text'>Text</a> skewing approximates italic and oblique type faces when the actual face
+is not available.
+<a href='#Font_Skew_X'>Font_Skew_X</a> can be set to any value.
+<a href='#Font_Skew_X'>Font_Skew_X</a> defaults to 0.
+
+<a name='Embolden'></a>
+
+<a href='#Font_Embolden'>Font_Embolden</a> approximates the bold <a href='SkFont_Reference#Font'>font</a> style accompanying a normal <a href='SkFont_Reference#Font'>font</a> when a bold <a href='SkFont_Reference#Font'>font</a> face
+is not available. Skia does not provide <a href='SkFont_Reference#Font'>font</a> substitution; it is up to the client to find the
+bold <a href='SkFont_Reference#Font'>font</a> face using the platform <a href='#Font_Manager'>Font_Manager</a>.
+
+Use <a href='#Font_Skew_X'>Font_Skew_X</a> to approximate an italic <a href='SkFont_Reference#Font'>font</a> style when the italic <a href='SkFont_Reference#Font'>font</a> face
+is not available.
+
+A FreeType based port may define SK_USE_FREETYPE_EMBOLDEN at compile time to direct
+the  <a href='SkFont_Reference#Font_Engine'>font engine</a> to create the bold <a href='undocumented#Glyph'>Glyphs</a>. Otherwise, the extra bold is computed
+by increasing the stroke width and setting the <a href='SkPaint_Reference#SkPaint'>SkPaint</a>::<a href='#SkPaint_Style'>Style</a> to
+<a href='SkPaint_Reference#SkPaint'>SkPaint</a>::<a href='#SkPaint_kStrokeAndFill_Style'>kStrokeAndFill_Style</a> as needed.
+
+<a href='#Font_Embolden'>Font_Embolden</a> is disabled by default.
+
+<a name='Hinting_Spacing'></a>
+
+If Hinting is set to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kFull'>kFull</a>, <a href='#Font_Hinting_Spacing'>Hinting_Spacing</a> adjusts the character
+spacing by the difference of the hinted and unhinted <a href='#Left_Side_Bearing'>Left_Side_Bearing</a> and
+<a href='#Right_Side_Bearing'>Right_Side_Bearing</a>. <a href='#Font_Hinting_Spacing'>Hinting_Spacing</a> only applies to platforms that use
+FreeType as their <a href='#Font_Engine'>Font_Engine</a>.
+
+<a href='#Font_Hinting_Spacing'>Hinting_Spacing</a> is not related to <a href='undocumented#Text'>text</a> kerning, where the space between
+a specific pair of characters is adjusted using <a href='undocumented#Data'>data</a> in the <a href='SkFont_Reference#Font'>font</a> kerning tables.
+
+<a name='Linear'></a>
+
+<a href='#Font_Linear'>Font_Linear</a> selects whether <a href='undocumented#Text'>text</a> is rendered as a <a href='undocumented#Glyph'>Glyph</a> or as a <a href='SkPath_Reference#Path'>Path</a>.
+If <a href='#Font_Linear'>Font_Linear</a> is set, it has the same effect as setting Hinting to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNormal'>kNormal</a>.
+If <a href='#Font_Linear'>Font_Linear</a> is clear, it is the same as setting Hinting to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNone'>kNone</a>.
+
+<a name='Subpixel'></a>
+
+<a href='#Font_Subpixel'>Font_Subpixel</a> uses the <a href='undocumented#Pixel'>pixel</a> transparency to represent a fractional offset.
+As the opaqueness of the <a href='SkColor_Reference#Color'>color</a> increases, the edge of the <a href='undocumented#Glyph'>glyph</a> appears to move
+towards the outside of the <a href='undocumented#Pixel'>pixel</a>.
+
+<a name='Anti_Alias'></a>
+
+When set, <a href='#Paint_Anti_Alias'>Anti_Alias</a> positions <a href='undocumented#Glyph'>glyphs</a> within a <a href='undocumented#Pixel'>pixel</a>, using <a href='SkColor_Reference#Alpha'>alpha</a> and
+possibly RGB striping. It can take advantage of the organization of RGB stripes
+that create a <a href='SkColor_Reference#Color'>color</a>, and relies on the small <a href='undocumented#Size'>size</a> of the stripe and visual perception
+to make the <a href='SkColor_Reference#Color'>color</a> fringing imperceptible.
+
+<a href='#Paint_Anti_Alias'>Anti_Alias</a> can be enabled on devices that orient stripes horizontally
+or vertically, and that order the <a href='SkColor_Reference#Color'>color</a> components as RGB or BGR. Internally, the
+<a href='undocumented#Glyph'>glyph</a> cache may store multiple copies of the same <a href='undocumented#Glyph'>glyph</a> with different <a href='SkFont_Reference#Subpixel'>sub-pixel</a>
+positions, requiring more memory.
+
+<a name='Force_Hinting'></a>
+
+If Hinting is set to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNormal'>kNormal</a> or <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kFull'>kFull</a>, <a href='#Font_Force_Hinting'>Force_Hinting</a>
+instructs the <a href='#Font_Manager'>Font_Manager</a> to always hint <a href='undocumented#Glyph'>Glyphs</a>.
+<a href='#Font_Force_Hinting'>Force_Hinting</a> has no effect if Hinting is set to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNone'>kNone</a> or
+<a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kSlight'>kSlight</a>.
+
+<a href='#Font_Force_Hinting'>Force_Hinting</a> only affects platforms that use FreeType as the <a href='#Font_Manager'>Font_Manager</a>.
+
+<a name='Embedded_Bitmaps'></a>
+
+<a href='#Font_Embedded_Bitmaps'>Embedded_Bitmaps</a> allows selecting custom sized <a href='SkBitmap_Reference#Bitmap'>bitmap</a> <a href='undocumented#Glyph'>Glyphs</a>.
+<a href='#Font_Embedded_Bitmaps'>Embedded_Bitmaps</a> when set chooses an embedded <a href='SkBitmap_Reference#Bitmap'>bitmap</a> <a href='undocumented#Glyph'>glyph</a> over an outline contained
+in a <a href='SkFont_Reference#Font'>font</a> if the platform supports this option.
+
+FreeType selects the <a href='SkBitmap_Reference#Bitmap'>bitmap</a> <a href='undocumented#Glyph'>glyph</a> if available when <a href='#Font_Embedded_Bitmaps'>Embedded_Bitmaps</a> is set, and selects
+the outline <a href='undocumented#Glyph'>glyph</a> if <a href='#Font_Embedded_Bitmaps'>Embedded_Bitmaps</a> is clear.
+Windows may select the <a href='SkBitmap_Reference#Bitmap'>bitmap</a> <a href='undocumented#Glyph'>glyph</a> but is not required to do so.
+<a href='#OS_X'>OS_X</a> and iOS do not support this option.
+
 <a name='SkFont'></a>
 
 ---
@@ -30,7 +123,7 @@
     <a href='#SkFont_empty_constructor'>SkFont()</a>;
     <a href='#SkFont_SkTypeface_SkScalar'>SkFont</a>(<a href='undocumented#sk_sp'>sk_sp</a><<a href='undocumented#SkTypeface'>SkTypeface</a>> <a href='undocumented#Typeface'>typeface</a>, <a href='undocumented#SkScalar'>SkScalar</a> <a href='undocumented#Size'>size</a>);
     <a href='#SkFont_SkTypeface_SkScalar_SkScalar_SkScalar'>SkFont</a>(<a href='undocumented#sk_sp'>sk_sp</a><<a href='undocumented#SkTypeface'>SkTypeface</a>> <a href='undocumented#Typeface'>typeface</a>, <a href='undocumented#SkScalar'>SkScalar</a> <a href='undocumented#Size'>size</a>, <a href='undocumented#SkScalar'>SkScalar</a> scaleX, <a href='undocumented#SkScalar'>SkScalar</a> skewX);
-    bool operator==(const <a href='SkFont_Reference#SkFont'>SkFont</a>& <a href='SkFont_Reference#Font'>font</a>) const;
+    bool <a href='#SkFont_equal1_operator'>operator==</a>(const <a href='SkFont_Reference#SkFont'>SkFont</a>& <a href='SkFont_Reference#Font'>font</a>) const;
     bool <a href='#SkFont_isForceAutoHinting'>isForceAutoHinting</a>() const;
     bool <a href='#SkFont_isEmbeddedBitmaps'>isEmbeddedBitmaps</a>() const;
     bool <a href='#SkFont_isSubpixel'>isSubpixel</a>() const;
@@ -300,7 +393,7 @@
 bool <a href='#SkFont_isSubpixel'>isSubpixel</a>()const
 </pre>
 
-Returns true if <a href='undocumented#Glyph'>glyphs</a> at different sub-pixel positions may differ on <a href='undocumented#Pixel'>pixel</a> edge coverage.
+Returns true if <a href='undocumented#Glyph'>glyphs</a> at different <a href='SkFont_Reference#Subpixel'>sub-pixel</a> positions may differ on <a href='undocumented#Pixel'>pixel</a> edge coverage.
 
 ### Return Value
 
@@ -420,12 +513,12 @@
 void <a href='#SkFont_setSubpixel'>setSubpixel</a>(bool subpixel)
 </pre>
 
-Requests, but does not require, that <a href='undocumented#Glyph'>glyphs</a> respect sub-pixel positioning.
+Requests, but does not require, that <a href='undocumented#Glyph'>glyphs</a> respect <a href='SkFont_Reference#Subpixel'>sub-pixel</a> positioning.
 
 ### Parameters
 
 <table>  <tr>    <td><a name='SkFont_setSubpixel_subpixel'><code><strong>subpixel</strong></code></a></td>
-    <td>setting for sub-pixel positioning</td>
+    <td>setting for <a href='SkFont_Reference#Subpixel'>sub-pixel</a> positioning</td>
   </tr>
 </table>
 
@@ -991,7 +1084,7 @@
     <td>number of bytes in <a href='#SkFont_containsText_text'>text</a> array</td>
   </tr>
   <tr>    <td><a name='SkFont_containsText_encoding'><code><strong>encoding</strong></code></a></td>
-    <td><a href='#SkFont_containsText_text'>text</a> <a href='#SkFont_containsText_encoding'>encoding</a></td>
+    <td><a href='undocumented#Text_Encoding'>text encoding</a></td>
   </tr>
 </table>
 
@@ -1020,7 +1113,7 @@
 The <a href='#SkFont_breakText_text'>text</a> fragment fits if its advance width is less than or equal to <a href='#SkFont_breakText_maxWidth'>maxWidth</a>.
 Measures only while the advance is less than or equal to <a href='#SkFont_breakText_maxWidth'>maxWidth</a>.
 Returns the advance or the <a href='#SkFont_breakText_text'>text</a> fragment in <a href='#SkFont_breakText_measuredWidth'>measuredWidth</a> if it not nullptr.
-Uses <a href='#SkFont_breakText_encoding'>encoding</a> to decode <a href='#SkFont_breakText_text'>text</a>, <a href='undocumented#SkTypeface'>SkTypeface</a> to get the <a href='SkFont_Reference#Font'>font</a> metrics,
+Uses <a href='#SkFont_breakText_encoding'>encoding</a> to decode <a href='#SkFont_breakText_text'>text</a>, <a href='undocumented#SkTypeface'>SkTypeface</a> to get the  <a href='undocumented#Font_Metrics'>font metrics</a>,
 and <a href='#SkFont_breakText_text'>text</a> <a href='undocumented#Size'>size</a> to scale the metrics.
 Does not scale the advance or bounds by fake bold.
 
@@ -1033,7 +1126,7 @@
     <td>number of bytes of <a href='#SkFont_breakText_text'>text</a> to measure</td>
   </tr>
   <tr>    <td><a name='SkFont_breakText_encoding'><code><strong>encoding</strong></code></a></td>
-    <td><a href='#SkFont_breakText_text'>text</a> <a href='#SkFont_breakText_encoding'>encoding</a></td>
+    <td><a href='undocumented#Text_Encoding'>text encoding</a></td>
   </tr>
   <tr>    <td><a name='SkFont_breakText_maxWidth'><code><strong>maxWidth</strong></code></a></td>
     <td>advance limit; <a href='#SkFont_breakText_text'>text</a> is measured while advance is less than <a href='#SkFont_breakText_maxWidth'>maxWidth</a></td>
@@ -1049,7 +1142,9 @@
 
 ### Example
 
-<div><fiddle-embed name="882e8e0103048009a25cfc20400492f7"></fiddle-embed></div>
+<div><fiddle-embed name="3cad18678254526be66ef162eecd1d23"><div><a href='undocumented#Line'>Line</a> under "Breakfast" shows desired width, shorter than available characters.
+<a href='undocumented#Line'>Line</a> under "Bre" shows measured width after breaking <a href='#SkFont_breakText_text'>text</a>.
+</div></fiddle-embed></div>
 
 ### See Also
 
diff --git a/site/user/api/SkIPoint_Reference.md b/site/user/api/SkIPoint_Reference.md
index 3eb7963..2476bef 100644
--- a/site/user/api/SkIPoint_Reference.md
+++ b/site/user/api/SkIPoint_Reference.md
@@ -16,11 +16,11 @@
     int32_t <a href='#SkIPoint_x'>x()</a> const;
     int32_t <a href='#SkIPoint_y'>y()</a> const;
     bool <a href='#SkIPoint_isZero'>isZero</a>() const;
-    void set(int32_t x, int32_t y);
+    void <a href='#SkIPoint_set'>set</a>(int32_t x, int32_t y);
     <a href='SkIPoint_Reference#SkIPoint'>SkIPoint</a> operator-() const;
     void <a href='#SkIPoint_addto_operator'>operator+=</a>(const <a href='SkIPoint_Reference#SkIVector'>SkIVector</a>& v);
     void <a href='#SkIPoint_subtractfrom_operator'>operator-=</a>(const <a href='SkIPoint_Reference#SkIVector'>SkIVector</a>& v);
-    bool equals(int32_t x, int32_t y) const;
+    bool <a href='#SkIPoint_equals'>equals</a>(int32_t x, int32_t y) const;
     friend bool <a href='#SkIPoint_equal_operator'>operator==</a>(const <a href='SkIPoint_Reference#SkIPoint'>SkIPoint</a>& a, const <a href='SkIPoint_Reference#SkIPoint'>SkIPoint</a>& b);
     friend bool <a href='#SkIPoint_notequal_operator'>operator!=</a>(const <a href='SkIPoint_Reference#SkIPoint'>SkIPoint</a>& a, const <a href='SkIPoint_Reference#SkIPoint'>SkIPoint</a>& b);
     friend <a href='SkIPoint_Reference#SkIVector'>SkIVector</a> <a href='#SkIPoint_subtract_operator'>operator-</a>(const <a href='SkIPoint_Reference#SkIPoint'>SkIPoint</a>& a, const <a href='SkIPoint_Reference#SkIPoint'>SkIPoint</a>& b);
diff --git a/site/user/api/SkIRect_Reference.md b/site/user/api/SkIRect_Reference.md
index 01d3c77..904dbac 100644
--- a/site/user/api/SkIRect_Reference.md
+++ b/site/user/api/SkIRect_Reference.md
@@ -37,33 +37,33 @@
     friend bool <a href='#SkIRect_equal_operator'>operator==</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& a, const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& b);
     friend bool <a href='#SkIRect_notequal_operator'>operator!=</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& a, const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& b);
     void <a href='#SkIRect_setEmpty'>setEmpty</a>();
-    void set(int32_t left, int32_t top, int32_t right, int32_t bottom);
+    void <a href='#SkIRect_set'>set</a>(int32_t left, int32_t top, int32_t right, int32_t bottom);
     void <a href='#SkIRect_setLTRB'>setLTRB</a>(int32_t left, int32_t top, int32_t right, int32_t bottom);
     void <a href='#SkIRect_setXYWH'>setXYWH</a>(int32_t x, int32_t y, int32_t width, int32_t height);
     <a href='SkIRect_Reference#SkIRect'>SkIRect</a> <a href='#SkIRect_makeOffset'>makeOffset</a>(int32_t dx, int32_t dy) const;
     <a href='SkIRect_Reference#SkIRect'>SkIRect</a> <a href='#SkIRect_makeInset'>makeInset</a>(int32_t dx, int32_t dy) const;
     <a href='SkIRect_Reference#SkIRect'>SkIRect</a> <a href='#SkIRect_makeOutset'>makeOutset</a>(int32_t dx, int32_t dy) const;
-    void offset(int32_t dx, int32_t dy);
-    void offset(const <a href='SkIPoint_Reference#SkIPoint'>SkIPoint</a>& delta);
+    void <a href='#SkIRect_offset'>offset</a>(int32_t dx, int32_t dy);
+    void <a href='#SkIRect_offset'>offset</a>(const <a href='SkIPoint_Reference#SkIPoint'>SkIPoint</a>& delta);
     void <a href='#SkIRect_offsetTo'>offsetTo</a>(int32_t newX, int32_t newY);
-    void inset(int32_t dx, int32_t dy);
-    void outset(int32_t dx, int32_t dy);
+    void <a href='#SkIRect_inset'>inset</a>(int32_t dx, int32_t dy);
+    void <a href='#SkIRect_outset'>outset</a>(int32_t dx, int32_t dy);
     void <a href='#SkIRect_adjust'>adjust</a>(int32_t dL, int32_t dT, int32_t dR, int32_t dB);
-    bool contains(int32_t x, int32_t y) const;
-    bool contains(int32_t left, int32_t top, int32_t right, int32_t bottom) const;
-    bool contains(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& r) const;
-    bool contains(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r) const;
+    bool <a href='#SkIRect_contains'>contains</a>(int32_t x, int32_t y) const;
+    bool <a href='#SkIRect_contains'>contains</a>(int32_t left, int32_t top, int32_t right, int32_t bottom) const;
+    bool <a href='#SkIRect_contains'>contains</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& r) const;
+    bool <a href='#SkIRect_contains'>contains</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r) const;
     bool <a href='#SkIRect_containsNoEmptyCheck'>containsNoEmptyCheck</a>(int32_t left, int32_t top,
                               int32_t right, int32_t bottom) const;
     bool <a href='#SkIRect_containsNoEmptyCheck'>containsNoEmptyCheck</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& r) const;
-    bool intersect(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& r);
+    bool <a href='#SkIRect_intersect'>intersect</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& r);
     bool <a href='#SkIRect_intersectNoEmptyCheck'>intersectNoEmptyCheck</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& a, const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& b);
-    bool intersect(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& a, const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& b);
-    bool intersect(int32_t left, int32_t top, int32_t right, int32_t bottom);
+    bool <a href='#SkIRect_intersect'>intersect</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& a, const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& b);
+    bool <a href='#SkIRect_intersect'>intersect</a>(int32_t left, int32_t top, int32_t right, int32_t bottom);
     static bool <a href='#SkIRect_Intersects'>Intersects</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& a, const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& b);
     static bool <a href='#SkIRect_IntersectsNoEmptyCheck'>IntersectsNoEmptyCheck</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& a, const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& b);
-    void join(int32_t left, int32_t top, int32_t right, int32_t bottom);
-    void join(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& r);
+    void <a href='#SkIRect_join'>join</a>(int32_t left, int32_t top, int32_t right, int32_t bottom);
+    void <a href='#SkIRect_join'>join</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& r);
     void <a href='#SkIRect_sort'>sort()</a>;
     <a href='SkIRect_Reference#SkIRect'>SkIRect</a> <a href='#SkIRect_makeSorted'>makeSorted</a>() const;
     static const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& <a href='#SkIRect_EmptyIRect'>EmptyIRect</a>();
diff --git a/site/user/api/SkImageInfo_Reference.md b/site/user/api/SkImageInfo_Reference.md
index f14d7bc..d24f5ee 100644
--- a/site/user/api/SkImageInfo_Reference.md
+++ b/site/user/api/SkImageInfo_Reference.md
@@ -978,8 +978,8 @@
     uint64_t <a href='#SkImageInfo_minRowBytes64'>minRowBytes64</a>() const;
     size_t <a href='#SkImageInfo_minRowBytes'>minRowBytes</a>() const;
     size_t <a href='#SkImageInfo_computeOffset'>computeOffset</a>(int x, int y, size_t rowBytes) const;
-    bool operator==(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& other) const;
-    bool operator!=(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& other) const;
+    bool <a href='#SkImageInfo_equal1_operator'>operator==</a>(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& other) const;
+    bool <a href='#SkImageInfo_notequal1_operator'>operator!=</a>(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& other) const;
     size_t <a href='#SkImageInfo_computeByteSize'>computeByteSize</a>(size_t rowBytes) const;
     size_t <a href='#SkImageInfo_computeMinByteSize'>computeMinByteSize</a>() const;
     static bool <a href='#SkImageInfo_ByteSizeOverflowed'>ByteSizeOverflowed</a>(size_t byteSize);
@@ -2059,7 +2059,7 @@
 
 ### See Also
 
-operator!=(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& <a href='#SkImageInfo_equal1_operator_other'>other</a>) const <a href='undocumented#SkColorSpace'>SkColorSpace</a>::<a href='#SkColorSpace_Equals'>Equals</a>
+<a href='#SkImageInfo_notequal1_operator'>operator!=</a>(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& <a href='#SkImageInfo_equal1_operator_other'>other</a>) const <a href='undocumented#SkColorSpace'>SkColorSpace</a>::<a href='#SkColorSpace_Equals'>Equals</a>
 
 <a name='SkImageInfo_notequal1_operator'></a>
 
@@ -2100,7 +2100,7 @@
 
 ### See Also
 
-operator==(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& <a href='#SkImageInfo_notequal1_operator_other'>other</a>) const <a href='undocumented#SkColorSpace'>SkColorSpace</a>::<a href='#SkColorSpace_Equals'>Equals</a>
+<a href='#SkImageInfo_equal1_operator'>operator==</a>(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& <a href='#SkImageInfo_notequal1_operator_other'>other</a>) const <a href='undocumented#SkColorSpace'>SkColorSpace</a>::<a href='#SkColorSpace_Equals'>Equals</a>
 
 <a name='SkImageInfo_computeByteSize'></a>
 
diff --git a/site/user/api/SkImage_Reference.md b/site/user/api/SkImage_Reference.md
index ae71d73..de24356 100644
--- a/site/user/api/SkImage_Reference.md
+++ b/site/user/api/SkImage_Reference.md
@@ -649,7 +649,9 @@
 
 ### Example
 
-<div><fiddle-embed name="f40e1ebba6b067714062b81877b22fa1" gpu="true"></fiddle-embed></div>
+<div><fiddle-embed name="2b1e46354d823dbb53fa6af570135329" gpu="true"><div><a href='#SkImage_MakeFromTexture_2_textureReleaseProc'>textureReleaseProc</a> may be called at some later <a href='SkPoint_Reference#Point'>point</a> in time. In this example,
+<a href='#SkImage_MakeFromTexture_2_textureReleaseProc'>textureReleaseProc</a> has no effect on the drawing.
+</div></fiddle-embed></div>
 
 ### See Also
 
diff --git a/site/user/api/SkMatrix_Reference.md b/site/user/api/SkMatrix_Reference.md
index b1d514f..eada7a1 100644
--- a/site/user/api/SkMatrix_Reference.md
+++ b/site/user/api/SkMatrix_Reference.md
@@ -35,8 +35,8 @@
     bool <a href='#SkMatrix_preservesRightAngles'>preservesRightAngles</a>(<a href='undocumented#SkScalar'>SkScalar</a> tol = <a href='undocumented#SK_ScalarNearlyZero'>SK_ScalarNearlyZero</a>) const;
 
     static constexpr int <a href='#SkMatrix_kMScaleX'>kMScaleX</a> = 0    static constexpr int <a href='#SkMatrix_kMSkewX'>kMSkewX</a> = 1    static constexpr int <a href='#SkMatrix_kMTransX'>kMTransX</a> = 2    static constexpr int <a href='#SkMatrix_kMSkewY'>kMSkewY</a> = 3    static constexpr int <a href='#SkMatrix_kMScaleY'>kMScaleY</a> = 4    static constexpr int <a href='#SkMatrix_kMTransY'>kMTransY</a> = 5    static constexpr int <a href='#SkMatrix_kMPersp0'>kMPersp0</a> = 6    static constexpr int <a href='#SkMatrix_kMPersp1'>kMPersp1</a> = 7    static constexpr int <a href='#SkMatrix_kMPersp2'>kMPersp2</a> = 8    static constexpr int <a href='#SkMatrix_kAScaleX'>kAScaleX</a> = 0    static constexpr int <a href='#SkMatrix_kASkewY'>kASkewY</a> = 1    static constexpr int <a href='#SkMatrix_kASkewX'>kASkewX</a> = 2    static constexpr int <a href='#SkMatrix_kAScaleY'>kAScaleY</a> = 3    static constexpr int <a href='#SkMatrix_kATransX'>kATransX</a> = 4    static constexpr int <a href='#SkMatrix_kATransY'>kATransY</a> = 5
-    <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkMatrix_array1_operator'>operator[]</a>(int index) const;
-    <a href='undocumented#SkScalar'>SkScalar</a> get(int index) const;
+    <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkMatrix_array_operator'>operator[]</a>(int index) const;
+    <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkMatrix_get'>get</a>(int index) const;
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkMatrix_getScaleX'>getScaleX</a>() const;
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkMatrix_getScaleY'>getScaleY</a>() const;
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkMatrix_getSkewY'>getSkewY</a>() const;
@@ -46,7 +46,7 @@
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkMatrix_getPerspX'>getPerspX</a>() const;
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkMatrix_getPerspY'>getPerspY</a>() const;
     <a href='undocumented#SkScalar'>SkScalar</a>& <a href='#SkMatrix_array1_operator'>operator[]</a>(int index);
-    void set(int index, <a href='undocumented#SkScalar'>SkScalar</a> value);
+    void <a href='#SkMatrix_set'>set</a>(int index, <a href='undocumented#SkScalar'>SkScalar</a> value);
     void <a href='#SkMatrix_setScaleX'>setScaleX</a>(<a href='undocumented#SkScalar'>SkScalar</a> v);
     void <a href='#SkMatrix_setScaleY'>setScaleY</a>(<a href='undocumented#SkScalar'>SkScalar</a> v);
     void <a href='#SkMatrix_setSkewY'>setSkewY</a>(<a href='undocumented#SkScalar'>SkScalar</a> v);
diff --git a/site/user/api/SkPaint_Reference.md b/site/user/api/SkPaint_Reference.md
index 55c6de4..6fcdcde 100644
--- a/site/user/api/SkPaint_Reference.md
+++ b/site/user/api/SkPaint_Reference.md
@@ -131,22 +131,13 @@
     void <a href='#SkPaint_setTextScaleX'>setTextScaleX</a>(<a href='undocumented#SkScalar'>SkScalar</a> scaleX);
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPaint_getTextSkewX'>getTextSkewX</a>() const;
     void <a href='#SkPaint_setTextSkewX'>setTextSkewX</a>(<a href='undocumented#SkScalar'>SkScalar</a> skewX);
-
-    enum <a href='#SkPaint_TextEncoding'>TextEncoding</a> : uint8_t {
-        <a href='#SkPaint_kUTF8_TextEncoding'>kUTF8_TextEncoding</a>,
-        <a href='#SkPaint_kUTF16_TextEncoding'>kUTF16_TextEncoding</a>,
-        <a href='#SkPaint_kUTF32_TextEncoding'>kUTF32_TextEncoding</a>,
-        <a href='#SkPaint_kGlyphID_TextEncoding'>kGlyphID_TextEncoding</a>,
-    };
-
-    <a href='#SkPaint_TextEncoding'>TextEncoding</a> <a href='#SkPaint_getTextEncoding'>getTextEncoding</a>() const;
-    void <a href='#SkPaint_setTextEncoding'>setTextEncoding</a>(<a href='#SkPaint_TextEncoding'>TextEncoding</a> encoding);
+    <a href='undocumented#SkTextEncoding'>SkTextEncoding</a> <a href='#SkPaint_getTextEncoding'>getTextEncoding</a>() const;
+    void <a href='#SkPaint_setTextEncoding'>setTextEncoding</a>(<a href='undocumented#SkTextEncoding'>SkTextEncoding</a> encoding);
 
     typedef <a href='undocumented#SkFontMetrics'>SkFontMetrics</a> <a href='#SkPaint_FontMetrics'>FontMetrics</a>;
 
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPaint_getFontMetrics'>getFontMetrics</a>(<a href='undocumented#SkFontMetrics'>SkFontMetrics</a>* metrics) const;
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPaint_getFontSpacing'>getFontSpacing</a>() const;
-    <a href='SkRect_Reference#SkRect'>SkRect</a> <a href='#SkPaint_getFontBounds'>getFontBounds</a>() const;
     int <a href='#SkPaint_textToGlyphs'>textToGlyphs</a>(const void* <a href='undocumented#Text'>text</a>, size_t byteLength,
                      <a href='undocumented#SkGlyphID'>SkGlyphID</a> <a href='undocumented#Glyph'>glyphs</a>[]) const;
     bool <a href='#SkPaint_containsText'>containsText</a>(const void* <a href='undocumented#Text'>text</a>, size_t byteLength) const;
@@ -154,22 +145,12 @@
     int <a href='#SkPaint_countText'>countText</a>(const void* <a href='undocumented#Text'>text</a>, size_t byteLength) const;
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPaint_measureText'>measureText</a>(const void* <a href='undocumented#Text'>text</a>, size_t length, <a href='SkRect_Reference#SkRect'>SkRect</a>* bounds) const;
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPaint_measureText'>measureText</a>(const void* <a href='undocumented#Text'>text</a>, size_t length) const;
-    size_t <a href='#SkPaint_breakText'>breakText</a>(const void* <a href='undocumented#Text'>text</a>, size_t length, <a href='undocumented#SkScalar'>SkScalar</a> maxWidth,
-                      <a href='undocumented#SkScalar'>SkScalar</a>* measuredWidth = nullptr) const;
     int <a href='#SkPaint_getTextWidths'>getTextWidths</a>(const void* <a href='undocumented#Text'>text</a>, size_t byteLength, <a href='undocumented#SkScalar'>SkScalar</a> widths[],
                       <a href='SkRect_Reference#SkRect'>SkRect</a> bounds[] = nullptr) const;
     void <a href='#SkPaint_getTextPath'>getTextPath</a>(const void* <a href='undocumented#Text'>text</a>, size_t length, <a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y,
                      <a href='SkPath_Reference#SkPath'>SkPath</a>* <a href='SkPath_Reference#Path'>path</a>) const;
     void <a href='#SkPaint_getPosTextPath'>getPosTextPath</a>(const void* <a href='undocumented#Text'>text</a>, size_t length,
                         const <a href='SkPoint_Reference#SkPoint'>SkPoint</a> pos[], <a href='SkPath_Reference#SkPath'>SkPath</a>* <a href='SkPath_Reference#Path'>path</a>) const;
-    int <a href='#SkPaint_getTextIntercepts'>getTextIntercepts</a>(const void* <a href='undocumented#Text'>text</a>, size_t length, <a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y,
-                          const <a href='undocumented#SkScalar'>SkScalar</a> bounds[2], <a href='undocumented#SkScalar'>SkScalar</a>* intervals) const;
-    int <a href='#SkPaint_getPosTextIntercepts'>getPosTextIntercepts</a>(const void* <a href='undocumented#Text'>text</a>, size_t length, const <a href='SkPoint_Reference#SkPoint'>SkPoint</a> pos[],
-                             const <a href='undocumented#SkScalar'>SkScalar</a> bounds[2], <a href='undocumented#SkScalar'>SkScalar</a>* intervals) const;
-    int <a href='#SkPaint_getPosTextHIntercepts'>getPosTextHIntercepts</a>(const void* <a href='undocumented#Text'>text</a>, size_t length, const <a href='undocumented#SkScalar'>SkScalar</a> xpos[],
-                              <a href='undocumented#SkScalar'>SkScalar</a> constY, const <a href='undocumented#SkScalar'>SkScalar</a> bounds[2], <a href='undocumented#SkScalar'>SkScalar</a>* intervals) const;
-    int <a href='#SkPaint_getTextBlobIntercepts'>getTextBlobIntercepts</a>(const <a href='SkTextBlob_Reference#SkTextBlob'>SkTextBlob</a>* blob, const <a href='undocumented#SkScalar'>SkScalar</a> bounds[2],
-                              <a href='undocumented#SkScalar'>SkScalar</a>* intervals) const;
     bool <a href='#SkPaint_nothingToDraw'>nothingToDraw</a>() const;
 };
 
@@ -220,29 +201,29 @@
 | <a href='#Color_Filter'>Color_Filter</a> | nullptr |
 | Dither | false |
 | <a href='#Draw_Looper'>Draw_Looper</a> | nullptr |
-| <a href='#Paint_Fake_Bold'>Fake_Bold</a> | false |
 | <a href='#Filter_Quality'>Filter_Quality</a> | <a href='undocumented#kNone_SkFilterQuality'>kNone_SkFilterQuality</a> |
-| <a href='#Paint_Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a> | false |
-| <a href='#Paint_Automatic_Hinting'>Automatic_Hinting</a> | false |
-| <a href='#Paint_Full_Hinting_Spacing'>Full_Hinting_Spacing</a> | false |
-| Hinting | <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNormal'>kNormal</a> |
+| <a href='#Font_Force_Hinting'>Font_Force_Hinting</a> | false |
+| <a href='#Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a> | false |
+| <a href='#Font_Embolden'>Font_Embolden</a> | false |
+| <a href='#Font_Hinting'>Font_Hinting</a> | <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNormal'>kNormal</a> |
+| <a href='#Font_Hinting_Spacing'>Font_Hinting_Spacing</a> | false |
+| <a href='#Font_Anti_Alias'>Font_Anti_Alias</a> | false |
+| <a href='#Font_Linear'>Font_Linear</a> | false |
+| <a href='#Font_Scale_X'>Font_Scale_X</a> | 1 |
+| <a href='#Font_Size'>Font_Size</a> | 12 |
+| <a href='#Font_Skew_X'>Font_Skew_X</a> | 0 |
+| <a href='#Font_Subpixel'>Font_Subpixel</a> | false |
 | <a href='#Image_Filter'>Image_Filter</a> | nullptr |
-| <a href='#Paint_LCD_Text'>LCD_Text</a> | false |
-| <a href='#Paint_Linear_Text'>Linear_Text</a> | false |
 | <a href='#Paint_Miter_Limit'>Miter_Limit</a> | 4 |
 | <a href='#Mask_Filter'>Mask_Filter</a> | nullptr |
 | <a href='#Path_Effect'>Path_Effect</a> | nullptr |
 | <a href='undocumented#Shader'>Shader</a> | nullptr |
 | <a href='#SkPaint_Style'>Style</a> | <a href='#SkPaint_kFill_Style'>kFill_Style</a> |
-| <a href='#Paint_Text_Encoding'>Text_Encoding</a> | <a href='#SkPaint_kUTF8_TextEncoding'>kUTF8_TextEncoding</a> |
-| <a href='#Paint_Text_Scale_X'>Text_Scale_X</a> | 1 |
-| <a href='#Paint_Text_Size'>Text_Size</a> | 12 |
-| <a href='#Paint_Text_Skew_X'>Text_Skew_X</a> | 0 |
+| <a href='#Text_Encoding'>Text_Encoding</a> | <a href='undocumented#kUTF8_SkTextEncoding'>kUTF8_SkTextEncoding</a> |
 | <a href='undocumented#Typeface'>Typeface</a> | nullptr |
 | <a href='#Paint_Stroke_Cap'>Stroke_Cap</a> | <a href='#SkPaint_kButt_Cap'>kButt_Cap</a> |
 | <a href='#Paint_Stroke_Join'>Stroke_Join</a> | <a href='#SkPaint_kMiter_Join'>kMiter_Join</a> |
 | <a href='#Paint_Stroke_Width'>Stroke_Width</a> | 0 |
-| <a href='#Paint_Subpixel_Text'>Subpixel_Text</a> | false |
 
 The flags, <a href='undocumented#Text'>text</a> <a href='undocumented#Size'>size</a>, hinting, and miter limit may be overridden at compile time by defining
 <a href='SkPaint_Reference#Paint'>paint</a> default values. The overrides may be included in "SkUserConfig.h" or predefined by the
@@ -679,25 +660,25 @@
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaint_kFakeBoldText_Flag'><code>SkPaint::kFakeBoldText_Flag</code></a></td>
     <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>0x0020</td>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
-mask for setting Fake_Bold</td>
+mask for setting Font_Embolden</td>
   </tr>
   <tr>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaint_kLinearText_Flag'><code>SkPaint::kLinearText_Flag</code></a></td>
     <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>0x0040</td>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
-mask for setting Linear_Text</td>
+mask for setting Font_Linear</td>
   </tr>
   <tr style='background-color: #f0f0f0; '>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaint_kSubpixelText_Flag'><code>SkPaint::kSubpixelText_Flag</code></a></td>
     <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>0x0080</td>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
-mask for setting Subpixel_Text</td>
+mask for setting Font_Subpixel</td>
   </tr>
   <tr>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaint_kLCDRenderText_Flag'><code>SkPaint::kLCDRenderText_Flag</code></a></td>
     <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>0x0200</td>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
-mask for setting LCD_Text</td>
+mask for setting Font_Anti_Alias</td>
   </tr>
   <tr style='background-color: #f0f0f0; '>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaint_kEmbeddedBitmapText_Flag'><code>SkPaint::kEmbeddedBitmapText_Flag</code></a></td>
@@ -709,7 +690,7 @@
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaint_kAutoHinting_Flag'><code>SkPaint::kAutoHinting_Flag</code></a></td>
     <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>0x0800</td>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
-mask for setting Automatic_Hinting</td>
+mask for setting Font_Force_Hinting</td>
   </tr>
   <tr style='background-color: #f0f0f0; '>
     <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaint_kAllFlags'><code>SkPaint::kAllFlags</code></a></td>
@@ -980,12 +961,12 @@
 
 ---
 
-<a href='#Paint_LCD_Text'>LCD_Text</a> and <a href='#Paint_Subpixel_Text'>Subpixel_Text</a> increase the precision of <a href='undocumented#Glyph'>glyph</a> position.
+<a href='#Font_Anti_Alias'>Font_Anti_Alias</a> and <a href='#Font_Subpixel'>Font_Subpixel</a> increase the precision of <a href='undocumented#Glyph'>glyph</a> position.
 
 When set, <a href='#SkPaint_Flags'>Flags</a> <a href='#SkPaint_kLCDRenderText_Flag'>kLCDRenderText_Flag</a> takes advantage of the organization of RGB stripes that
 create a <a href='SkColor_Reference#Color'>color</a>, and relies
 on the small <a href='undocumented#Size'>size</a> of the stripe and visual perception to make the <a href='SkColor_Reference#Color'>color</a> fringing imperceptible.
-<a href='#Paint_LCD_Text'>LCD_Text</a> can be enabled on devices that orient stripes horizontally or vertically, and that order
+<a href='#Font_Anti_Alias'>Font_Anti_Alias</a> can be enabled on devices that orient stripes horizontally or vertically, and that order
 the <a href='SkColor_Reference#Color'>color</a> components as RGB or BGR.
 
 <a href='#SkPaint_Flags'>Flags</a> <a href='#SkPaint_kSubpixelText_Flag'>kSubpixelText_Flag</a> uses the <a href='undocumented#Pixel'>pixel</a> transparency to represent a fractional offset.
@@ -994,21 +975,21 @@
 
 Either or both techniques can be enabled.
 <a href='#SkPaint_kLCDRenderText_Flag'>kLCDRenderText_Flag</a> and <a href='#SkPaint_kSubpixelText_Flag'>kSubpixelText_Flag</a> are clear by default.
-<a href='#Paint_LCD_Text'>LCD_Text</a> or <a href='#Paint_Subpixel_Text'>Subpixel_Text</a> can be enabled by default by setting <a href='undocumented#SkPaintDefaults_Flags'>SkPaintDefaults_Flags</a> to
+<a href='#Font_Anti_Alias'>Font_Anti_Alias</a> or <a href='#Font_Subpixel'>Font_Subpixel</a> can be enabled by default by setting <a href='undocumented#SkPaintDefaults_Flags'>SkPaintDefaults_Flags</a> to
 <a href='#SkPaint_kLCDRenderText_Flag'>kLCDRenderText_Flag</a> or <a href='#SkPaint_kSubpixelText_Flag'>kSubpixelText_Flag</a> (or both) at compile time.
 
 ### Example
 
-<div><fiddle-embed name="4606ae1be792d6bc46d496432f050ee9"><div>Four commas are drawn normally and with combinations of <a href='#Paint_LCD_Text'>LCD_Text</a> and <a href='#Paint_Subpixel_Text'>Subpixel_Text</a>.
-When <a href='#Paint_Subpixel_Text'>Subpixel_Text</a> is disabled, the comma <a href='undocumented#Glyph'>Glyphs</a> are identical, but not evenly spaced.
-When <a href='#Paint_Subpixel_Text'>Subpixel_Text</a> is enabled, the comma <a href='undocumented#Glyph'>Glyphs</a> are unique, but appear evenly spaced.
+<div><fiddle-embed name="4606ae1be792d6bc46d496432f050ee9"><div>Four commas are drawn normally and with combinations of <a href='#Font_Anti_Alias'>Font_Anti_Alias</a> and <a href='#Font_Subpixel'>Font_Subpixel</a>.
+When <a href='#Font_Subpixel'>Font_Subpixel</a> is disabled, the comma <a href='undocumented#Glyph'>Glyphs</a> are identical, but not evenly spaced.
+When <a href='#Font_Subpixel'>Font_Subpixel</a> is enabled, the comma <a href='undocumented#Glyph'>Glyphs</a> are unique, but appear evenly spaced.
 </div></fiddle-embed></div>
 
 <a name='Linear_Text'></a>
 
-<a href='#Paint_Linear_Text'>Linear_Text</a> selects whether <a href='undocumented#Text'>text</a> is rendered as a <a href='undocumented#Glyph'>Glyph</a> or as a <a href='SkPath_Reference#Path'>Path</a>.
-If <a href='#SkPaint_kLinearText_Flag'>kLinearText_Flag</a> is set, it has the same effect as setting Hinting to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNormal'>kNormal</a>.
-If <a href='#SkPaint_kLinearText_Flag'>kLinearText_Flag</a> is clear, it is the same as setting Hinting to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNone'>kNone</a>.
+<a href='#Font_Linear'>Font_Linear</a> selects whether <a href='undocumented#Text'>text</a> is rendered as a <a href='undocumented#Glyph'>Glyph</a> or as a <a href='SkPath_Reference#Path'>Path</a>.
+If <a href='#Font_Linear'>Font_Linear</a> is set, it has the same effect as setting Hinting to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNormal'>kNormal</a>.
+If <a href='#Font_Linear'>Font_Linear</a> is clear, it is the same as setting Hinting to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNone'>kNone</a>.
 
 <a name='SkPaint_isLinearText'></a>
 
@@ -1078,7 +1059,7 @@
 bool <a href='#SkPaint_isSubpixelText'>isSubpixelText</a>()const
 </pre>
 
-Returns true if <a href='undocumented#Glyph'>glyphs</a> at different sub-pixel positions may differ on <a href='undocumented#Pixel'>pixel</a> edge coverage.
+Returns true if <a href='undocumented#Glyph'>glyphs</a> at different <a href='SkFont_Reference#Subpixel'>sub-pixel</a> positions may differ on <a href='undocumented#Pixel'>pixel</a> edge coverage.
 
 Equivalent to <a href='#SkPaint_getFlags'>getFlags</a>() masked with <a href='#SkPaint_kSubpixelText_Flag'>kSubpixelText_Flag</a>.
 
@@ -1107,7 +1088,7 @@
 void <a href='#SkPaint_setSubpixelText'>setSubpixelText</a>(bool subpixelText)
 </pre>
 
-Requests, but does not require, that <a href='undocumented#Glyph'>glyphs</a> respect sub-pixel positioning.
+Requests, but does not require, that <a href='undocumented#Glyph'>glyphs</a> respect <a href='SkFont_Reference#Subpixel'>sub-pixel</a> positioning.
 
 Sets <a href='#SkPaint_kSubpixelText_Flag'>kSubpixelText_Flag</a> if <a href='#SkPaint_setSubpixelText_subpixelText'>subpixelText</a> is true.
 Clears <a href='#SkPaint_kSubpixelText_Flag'>kSubpixelText_Flag</a> if <a href='#SkPaint_setSubpixelText_subpixelText'>subpixelText</a> is false.
@@ -1133,10 +1114,10 @@
 
 <a name='LCD_Text'></a>
 
-When set, <a href='#SkPaint_Flags'>Flags</a> <a href='#SkPaint_kLCDRenderText_Flag'>kLCDRenderText_Flag</a> takes advantage of the organization of RGB stripes that
+When set, <a href='#Font_Anti_Alias'>Font_Anti_Alias</a> takes advantage of the organization of RGB stripes that
 create a <a href='SkColor_Reference#Color'>color</a>, and relies
 on the small <a href='undocumented#Size'>size</a> of the stripe and visual perception to make the <a href='SkColor_Reference#Color'>color</a> fringing imperceptible.
-<a href='#Paint_LCD_Text'>LCD_Text</a> can be enabled on devices that orient stripes horizontally or vertically, and that order
+<a href='#Font_Anti_Alias'>Font_Anti_Alias</a> can be enabled on devices that orient stripes horizontally or vertically, and that order
 the <a href='SkColor_Reference#Color'>color</a> components as RGB or BGR.
 
 <a name='SkPaint_isLCDRenderText'></a>
@@ -1200,11 +1181,11 @@
 
 </fiddle-embed></div>
 
-<a name='Font_Embedded_Bitmaps'></a>
+<a name='Embedded_Bitmaps'></a>
 
 ---
 
-<a href='#Paint_Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a> allows selecting custom sized <a href='SkBitmap_Reference#Bitmap'>bitmap</a> <a href='undocumented#Glyph'>Glyphs</a>.
+<a href='#Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a> allows selecting custom sized <a href='SkBitmap_Reference#Bitmap'>bitmap</a> <a href='undocumented#Glyph'>Glyphs</a>.
 <a href='#SkPaint_Flags'>Flags</a> <a href='#SkPaint_kEmbeddedBitmapText_Flag'>kEmbeddedBitmapText_Flag</a> when set chooses an embedded <a href='SkBitmap_Reference#Bitmap'>bitmap</a> <a href='undocumented#Glyph'>glyph</a> over an outline contained
 in a <a href='SkFont_Reference#Font'>font</a> if the platform supports this option.
 
@@ -1213,8 +1194,8 @@
 Windows may select the <a href='SkBitmap_Reference#Bitmap'>bitmap</a> <a href='undocumented#Glyph'>glyph</a> but is not required to do so.
 <a href='#OS_X'>OS_X</a> and iOS do not support this option.
 
-<a href='#Paint_Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a> is disabled by default.
-<a href='#Paint_Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a> can be enabled by default by setting <a href='undocumented#SkPaintDefaults_Flags'>SkPaintDefaults_Flags</a> to
+<a href='#Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a> is disabled by default.
+<a href='#Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a> can be enabled by default by setting <a href='undocumented#SkPaintDefaults_Flags'>SkPaintDefaults_Flags</a> to
 <a href='#SkPaint_kEmbeddedBitmapText_Flag'>kEmbeddedBitmapText_Flag</a> at compile time.
 
 ### Example
@@ -1305,12 +1286,12 @@
 
 <a name='Automatic_Hinting'></a>
 
-If Hinting is set to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNormal'>kNormal</a> or <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kFull'>kFull</a>, <a href='#Paint_Automatic_Hinting'>Automatic_Hinting</a>
+If Hinting is set to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNormal'>kNormal</a> or <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kFull'>kFull</a>, <a href='#Font_Force_Hinting'>Font_Force_Hinting</a>
 instructs the <a href='#Font_Manager'>Font_Manager</a> to always hint <a href='undocumented#Glyph'>Glyphs</a>.
-<a href='#Paint_Automatic_Hinting'>Automatic_Hinting</a> has no effect if Hinting is set to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNone'>kNone</a> or
+<a href='#Font_Force_Hinting'>Font_Force_Hinting</a> has no effect if Hinting is set to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNone'>kNone</a> or
 <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kSlight'>kSlight</a>.
 
-<a href='#Paint_Automatic_Hinting'>Automatic_Hinting</a> only affects platforms that use FreeType as the <a href='#Font_Manager'>Font_Manager</a>.
+<a href='#Font_Force_Hinting'>Font_Force_Hinting</a> only affects platforms that use FreeType as the <a href='#Font_Manager'>Font_Manager</a>.
 
 <a name='SkPaint_isAutohinted'></a>
 
@@ -1358,7 +1339,7 @@
 Sets whether to always hint <a href='undocumented#Glyph'>glyphs</a>.
 If <a href='SkPaint_Reference#SkPaint'>SkPaint</a>::Hinting is set to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNormal'>kNormal</a> or <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kFull'>kFull</a>
 and <a href='#SkPaint_setAutohinted_useAutohinter'>useAutohinter</a> is set, instructs the  <a href='undocumented#Font_Manager'>font manager</a> to always hint <a href='undocumented#Glyph'>glyphs</a>.
-<a href='SkPaint_Reference#Automatic_Hinting'>auto-hinting</a> has no effect if <a href='SkPaint_Reference#SkPaint'>SkPaint</a>::Hinting is set to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNone'>kNone</a> or
+<a href='#SkPaint_setAutohinted_useAutohinter'>useAutohinter</a> has no effect if <a href='SkPaint_Reference#SkPaint'>SkPaint</a>::Hinting is set to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kNone'>kNone</a> or
 <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kSlight'>kSlight</a>.
 
 Only affects platforms that use FreeType as the  <a href='undocumented#Font_Manager'>font manager</a>.
@@ -1385,7 +1366,7 @@
 
 ---
 
-<a href='#Paint_Fake_Bold'>Fake_Bold</a> approximates the bold <a href='SkFont_Reference#Font'>font</a> style accompanying a normal <a href='SkFont_Reference#Font'>font</a> when a bold <a href='SkFont_Reference#Font'>font</a> face
+<a href='#Font_Embolden'>Font_Embolden</a> approximates the bold <a href='SkFont_Reference#Font'>font</a> style accompanying a normal <a href='SkFont_Reference#Font'>font</a> when a bold <a href='SkFont_Reference#Font'>font</a> face
 is not available. Skia does not provide <a href='SkFont_Reference#Font'>font</a> substitution; it is up to the client to find the
 bold <a href='SkFont_Reference#Font'>font</a> face using the platform <a href='#Font_Manager'>Font_Manager</a>.
 
@@ -1396,7 +1377,7 @@
 the  <a href='SkFont_Reference#Font_Engine'>font engine</a> to create the bold <a href='undocumented#Glyph'>Glyphs</a>. Otherwise, the extra bold is computed
 by increasing the  <a href='#Stroke_Width'>stroke width</a> and setting the <a href='#SkPaint_Style'>Style</a> to <a href='#SkPaint_kStrokeAndFill_Style'>kStrokeAndFill_Style</a> as needed.
 
-<a href='#Paint_Fake_Bold'>Fake_Bold</a> is disabled by default.
+<a href='#Font_Embolden'>Font_Embolden</a> is disabled by default.
 
 ### Example
 
@@ -1464,16 +1445,6 @@
 
 </fiddle-embed></div>
 
-<a name='Full_Hinting_Spacing'></a>
-
-if Hinting is set to <a href='undocumented#SkFontHinting'>SkFontHinting</a>::<a href='#SkFontHinting_kFull'>kFull</a>, <a href='#Paint_Full_Hinting_Spacing'>Full_Hinting_Spacing</a> adjusts the character
-spacing by the difference of the hinted and unhinted <a href='#Left_Side_Bearing'>Left_Side_Bearing</a> and
-<a href='#Right_Side_Bearing'>Right_Side_Bearing</a>. <a href='#Paint_Full_Hinting_Spacing'>Full_Hinting_Spacing</a> only applies to platforms that use
-FreeType as their <a href='#Font_Engine'>Font_Engine</a>.
-
-<a href='#Paint_Full_Hinting_Spacing'>Full_Hinting_Spacing</a> is not related to <a href='undocumented#Text'>text</a> kerning, where the space between
-a specific pair of characters is adjusted using <a href='undocumented#Data'>data</a> in the <a href='SkFont_Reference#Font'>font</a> kerning tables.
-
 <a name='Filter_Quality_Methods'></a>
 
 ---
@@ -3578,74 +3549,9 @@
 
 ---
 
-<a name='SkPaint_TextEncoding'></a>
-
----
-
-<pre style="padding: 1em 1em 1em 1em;width: 62.5em; background-color: #f0f0f0">
-    enum <a href='#SkPaint_TextEncoding'>TextEncoding</a> : uint8_t {
-        <a href='#SkPaint_kUTF8_TextEncoding'>kUTF8_TextEncoding</a>,
-        <a href='#SkPaint_kUTF16_TextEncoding'>kUTF16_TextEncoding</a>,
-        <a href='#SkPaint_kUTF32_TextEncoding'>kUTF32_TextEncoding</a>,
-        <a href='#SkPaint_kGlyphID_TextEncoding'>kGlyphID_TextEncoding</a>,
-    };
-
-</pre>
-
-<a href='#SkPaint_TextEncoding'>TextEncoding</a> determines whether <a href='undocumented#Text'>text</a> specifies character codes and their encoded
-<a href='undocumented#Size'>size</a>, or <a href='undocumented#Glyph'>glyph</a> indices. Characters are encoded as specified by the
-<a href='https://unicode.org/standard/standard.html'>Unicode standard</a></a> .
-
-Character codes encoded <a href='undocumented#Size'>size</a> are specified by UTF-8, UTF-16, or UTF-32.
-All character code formats are able to represent all of Unicode, differing only
-in the total storage required.
-
-<a href='https://tools.ietf.org/html/rfc3629'>UTF-8 (RFC 3629)</a></a> encodes each character as one or more 8-bit bytes.
-
-<a href='https://tools.ietf.org/html/rfc2781'>UTF-16 (RFC 2781)</a></a> encodes each character as one or two 16-bit words.
-
-<a href='https://www.unicode.org/versions/Unicode5.0.0/ch03.pdf'>UTF-32</a></a> encodes each character as one 32-bit word.
-
-<a href='#Font_Manager'>Font_Manager</a> uses <a href='SkFont_Reference#Font'>font</a> <a href='undocumented#Data'>data</a> to convert character code <a href='SkPoint_Reference#Point'>points</a> into <a href='undocumented#Glyph'>glyph</a> indices.
-A <a href='undocumented#Glyph'>glyph</a> index is a 16-bit word.
-
-<a href='#SkPaint_TextEncoding'>TextEncoding</a> is set to <a href='#SkPaint_kUTF8_TextEncoding'>kUTF8_TextEncoding</a> by default.
-
-### Constants
-
-<table style='border-collapse: collapse; width: 62.5em'>
-  <tr><th style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>Const</th>
-<th style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>Value</th>
-<th style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>Description</th></tr>
-  <tr style='background-color: #f0f0f0; '>
-    <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaint_kUTF8_TextEncoding'><code>SkPaint::kUTF8_TextEncoding</code></a></td>
-    <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>0</td>
-    <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
-uses bytes to represent UTF-8 or ASCII</td>
-  </tr>
-  <tr>
-    <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaint_kUTF16_TextEncoding'><code>SkPaint::kUTF16_TextEncoding</code></a></td>
-    <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>1</td>
-    <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
-uses two byte words to represent most of Unicode</td>
-  </tr>
-  <tr style='background-color: #f0f0f0; '>
-    <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaint_kUTF32_TextEncoding'><code>SkPaint::kUTF32_TextEncoding</code></a></td>
-    <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>2</td>
-    <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
-uses four byte words to represent all of Unicode</td>
-  </tr>
-  <tr>
-    <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '><a name='SkPaint_kGlyphID_TextEncoding'><code>SkPaint::kGlyphID_TextEncoding</code></a></td>
-    <td style='text-align: center; border: 2px solid #dddddd; padding: 8px; '>3</td>
-    <td style='text-align: left; border: 2px solid #dddddd; padding: 8px; '>
-uses two byte words to represent glyph indices</td>
-  </tr>
-</table>
-
 ### Example
 
-<div><fiddle-embed name="b29294e7f29d160a1b46abf2dcec9d2a"><div>First <a href='undocumented#Line'>line</a> is encoded in UTF-8.
+<div><fiddle-embed name="767fa4e7b6300e16a419f9881f0f9d3d"><div>First <a href='undocumented#Line'>line</a> is encoded in UTF-8.
 Second <a href='undocumented#Line'>line</a> is encoded in UTF-16.
 Third <a href='undocumented#Line'>line</a> is encoded in UTF-32.
 Fourth <a href='undocumented#Line'>line</a> has 16-bit <a href='undocumented#Glyph'>glyph</a> indices.
@@ -3656,27 +3562,25 @@
 ---
 
 <pre style="padding: 1em 1em 1em 1em; width: 62.5em;background-color: #f0f0f0">
-<a href='#SkPaint_TextEncoding'>TextEncoding</a> <a href='#SkPaint_getTextEncoding'>getTextEncoding</a>()const
+<a href='undocumented#SkTextEncoding'>SkTextEncoding</a> <a href='#SkPaint_getTextEncoding'>getTextEncoding</a>()const
 </pre>
 
-Returns <a href='SkPaint_Reference#SkPaint'>SkPaint</a>::<a href='#SkPaint_TextEncoding'>TextEncoding</a>.
-<a href='SkPaint_Reference#SkPaint'>SkPaint</a>::<a href='#SkPaint_TextEncoding'>TextEncoding</a> determines how character code <a href='SkPoint_Reference#Point'>points</a> are mapped to <a href='SkFont_Reference#Font'>font</a> <a href='undocumented#Glyph'>glyph</a> indices.
+Returns the <a href='undocumented#Text'>text</a> encoding. <a href='undocumented#Text'>Text</a> encoding describes how to interpret the <a href='undocumented#Text'>text</a> bytes pass
+to methods like <a href='#SkPaint_measureText'>measureText</a>() and <a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a>::<a href='#SkCanvas_drawText'>drawText</a>().
 
 ### Return Value
 
-one of: <a href='#SkPaint_kUTF8_TextEncoding'>kUTF8_TextEncoding</a>, <a href='#SkPaint_kUTF16_TextEncoding'>kUTF16_TextEncoding</a>, <a href='#SkPaint_kUTF32_TextEncoding'>kUTF32_TextEncoding</a>, or
-
-<a href='#SkPaint_kGlyphID_TextEncoding'>kGlyphID_TextEncoding</a>
+the <a href='undocumented#Text'>text</a> encoding
 
 ### Example
 
-<div><fiddle-embed name="c6cc2780a9828b3af8c4621c12b29a1b">
+<div><fiddle-embed name="0d21e968e9a4c78c902ae3ef494941a0">
 
 #### Example Output
 
 ~~~~
-kUTF8_TextEncoding == text encoding
-kGlyphID_TextEncoding == text encoding
+kUTF8_SkTextEncoding == text encoding
+kGlyphID_SkTextEncoding == text encoding
 ~~~~
 
 </fiddle-embed></div>
@@ -3686,25 +3590,22 @@
 ---
 
 <pre style="padding: 1em 1em 1em 1em; width: 62.5em;background-color: #f0f0f0">
-void <a href='#SkPaint_setTextEncoding'>setTextEncoding</a>(<a href='#SkPaint_TextEncoding'>TextEncoding</a> encoding)
+void <a href='#SkPaint_setTextEncoding'>setTextEncoding</a>(<a href='undocumented#SkTextEncoding'>SkTextEncoding</a> encoding)
 </pre>
 
-Sets <a href='SkPaint_Reference#SkPaint'>SkPaint</a>::<a href='#SkPaint_TextEncoding'>TextEncoding</a> to <a href='#SkPaint_setTextEncoding_encoding'>encoding</a>.
-<a href='SkPaint_Reference#SkPaint'>SkPaint</a>::<a href='#SkPaint_TextEncoding'>TextEncoding</a> determines how character code <a href='SkPoint_Reference#Point'>points</a> are mapped to <a href='SkFont_Reference#Font'>font</a> <a href='undocumented#Glyph'>glyph</a> indices.
-Invalid values for <a href='#SkPaint_setTextEncoding_encoding'>encoding</a> are ignored.
+Sets the  <a href='#Text_Encoding'>text encoding</a>. <a href='undocumented#Text'>Text</a> <a href='#SkPaint_setTextEncoding_encoding'>encoding</a> describes how to interpret the <a href='undocumented#Text'>text</a> bytes pass
+to methods like <a href='#SkPaint_measureText'>measureText</a>() and <a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a>::<a href='#SkCanvas_drawText'>drawText</a>().
 
 ### Parameters
 
 <table>  <tr>    <td><a name='SkPaint_setTextEncoding_encoding'><code><strong>encoding</strong></code></a></td>
-    <td>one of: <a href='#SkPaint_kUTF8_TextEncoding'>kUTF8_TextEncoding</a>, <a href='#SkPaint_kUTF16_TextEncoding'>kUTF16_TextEncoding</a>, <a href='#SkPaint_kUTF32_TextEncoding'>kUTF32_TextEncoding</a>, or</td>
+    <td>the new  <a href='#Text_Encoding'>text encoding</a></td>
   </tr>
 </table>
 
-<a href='#SkPaint_kGlyphID_TextEncoding'>kGlyphID_TextEncoding</a>
-
 ### Example
 
-<div><fiddle-embed name="6d9ffdd3c5543e9f12972a06dd4a0ce5">
+<div><fiddle-embed name="a5d1ba0dbf42afb797ffdb07647b5cb9">
 
 #### Example Output
 
@@ -3753,7 +3654,7 @@
 
 ### See Also
 
-<a href='#Paint_Text_Size'>Text_Size</a> <a href='undocumented#Typeface'>Typeface</a> <a href='#Paint_Typeface_Methods'>Typeface_Methods</a>
+<a href='#Font_Size'>Font_Size</a> <a href='undocumented#Typeface'>Typeface</a> <a href='#Paint_Typeface_Methods'>Typeface_Methods</a>
 
 <a name='SkPaint_getFontSpacing'></a>
 
@@ -3828,7 +3729,7 @@
 
 ### Example
 
-<div><fiddle-embed name="343e9471a7f7b5f09abdc3b44983433b"></fiddle-embed></div>
+<div><fiddle-embed name="d11136d8a74f63009da2a7f550710823"></fiddle-embed></div>
 
 <a name='SkPaint_countText'></a>
 
@@ -3913,7 +3814,7 @@
 
 ### Example
 
-<div><fiddle-embed name="083557b6f653d6fc00a34e01f87b74ff"><div><a href='#SkPaint_containsText'>containsText</a> returns true that <a href='undocumented#Glyph'>glyph</a> index is greater than zero, not
+<div><fiddle-embed name="6a68cb3c8b81a5976c81ee004f559247"><div><a href='#SkPaint_containsText'>containsText</a> returns true that <a href='undocumented#Glyph'>glyph</a> index is greater than zero, not
 that it corresponds to an entry in <a href='undocumented#Typeface'>Typeface</a>.
 </div>
 
@@ -3959,10 +3860,8 @@
   </tr>
 </table>
 
-### Example
-
-<div><fiddle-embed name="c12686b0b3e0a87d0a248bbfc57e9492"><div>Convert UTF-8 <a href='#SkPaint_glyphsToUnichars_text'>text</a> to <a href='#SkPaint_glyphsToUnichars_glyphs'>glyphs</a>; then convert <a href='#SkPaint_glyphsToUnichars_glyphs'>glyphs</a> to Unichar code <a href='SkPoint_Reference#Point'>points</a>.
-</div></fiddle-embed></div>
+<div>Convert UTF-8 <a href='#SkPaint_glyphsToUnichars_text'>text</a> to <a href='#SkPaint_glyphsToUnichars_glyphs'>glyphs</a>; then convert <a href='#SkPaint_glyphsToUnichars_glyphs'>glyphs</a> to Unichar code <a href='SkPoint_Reference#Point'>points</a>.
+</div>
 
 <a name='Measure_Text'></a>
 
@@ -4044,49 +3943,6 @@
 
 </fiddle-embed></div>
 
-<a name='SkPaint_breakText'></a>
-
----
-
-<pre style="padding: 1em 1em 1em 1em; width: 62.5em;background-color: #f0f0f0">
-size_t <a href='#SkPaint_breakText'>breakText</a>(const void* <a href='undocumented#Text'>text</a>, size_t length, <a href='undocumented#SkScalar'>SkScalar</a> maxWidth,
-                 <a href='undocumented#SkScalar'>SkScalar</a>* measuredWidth = nullptr)const
-</pre>
-
-Returns the bytes of <a href='#SkPaint_breakText_text'>text</a> that fit within <a href='#SkPaint_breakText_maxWidth'>maxWidth</a>.
-The <a href='#SkPaint_breakText_text'>text</a> fragment fits if its advance width is less than or equal to <a href='#SkPaint_breakText_maxWidth'>maxWidth</a>.
-Measures only while the advance is less than or equal to <a href='#SkPaint_breakText_maxWidth'>maxWidth</a>.
-Returns the advance or the <a href='#SkPaint_breakText_text'>text</a> fragment in <a href='#SkPaint_breakText_measuredWidth'>measuredWidth</a> if it not nullptr.
-Uses <a href='undocumented#SkTextEncoding'>SkTextEncoding</a> to decode <a href='#SkPaint_breakText_text'>text</a>, <a href='undocumented#SkTypeface'>SkTypeface</a> to get the  <a href='#Font_Metrics'>font metrics</a>,
-and  <a href='#Text_Size'>text size</a> to scale the metrics.
-Does not scale the advance or bounds by  <a href='#Fake_Bold'>fake bold</a> or <a href='undocumented#SkPathEffect'>SkPathEffect</a>.
-
-### Parameters
-
-<table>  <tr>    <td><a name='SkPaint_breakText_text'><code><strong>text</strong></code></a></td>
-    <td>character codes or <a href='undocumented#Glyph'>glyph</a> indices to be measured</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_breakText_length'><code><strong>length</strong></code></a></td>
-    <td>number of bytes of <a href='#SkPaint_breakText_text'>text</a> to measure</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_breakText_maxWidth'><code><strong>maxWidth</strong></code></a></td>
-    <td>advance limit; <a href='#SkPaint_breakText_text'>text</a> is measured while advance is less than <a href='#SkPaint_breakText_maxWidth'>maxWidth</a></td>
-  </tr>
-  <tr>    <td><a name='SkPaint_breakText_measuredWidth'><code><strong>measuredWidth</strong></code></a></td>
-    <td>returns the width of the <a href='#SkPaint_breakText_text'>text</a> less than or equal to <a href='#SkPaint_breakText_maxWidth'>maxWidth</a></td>
-  </tr>
-</table>
-
-### Return Value
-
-bytes of <a href='#SkPaint_breakText_text'>text</a> that fit, always less than or equal to <a href='#SkPaint_breakText_length'>length</a>
-
-### Example
-
-<div><fiddle-embed name="fd0033470ccbd5c7059670fdbf96cffc"><div><a href='undocumented#Line'>Line</a> under "Breakfast" shows desired width, shorter than available characters.
-<a href='undocumented#Line'>Line</a> under "Bre" shows measured width after breaking <a href='#SkPaint_breakText_text'>text</a>.
-</div></fiddle-embed></div>
-
 <a name='SkPaint_getTextWidths'></a>
 
 ---
@@ -4210,235 +4066,6 @@
 <div><fiddle-embed name="7f27c93472aa99a7542fb3493076f072"><div>Simplifies three <a href='undocumented#Glyph'>Glyphs</a> to eliminate overlaps, and strokes the result.
 </div></fiddle-embed></div>
 
-<a name='Text_Intercepts'></a>
-
-<a href='#Paint_Text_Intercepts'>Text_Intercepts</a> describe the intersection of drawn <a href='undocumented#Text'>text</a> <a href='undocumented#Glyph'>Glyphs</a> with a pair
-of <a href='undocumented#Line'>lines</a> parallel to the <a href='undocumented#Text'>text</a> advance. <a href='#Paint_Text_Intercepts'>Text_Intercepts</a> permits creating a
-underline that skips Descenders.
-
-<a name='SkPaint_getTextIntercepts'></a>
-
----
-
-<pre style="padding: 1em 1em 1em 1em; width: 62.5em;background-color: #f0f0f0">
-int <a href='#SkPaint_getTextIntercepts'>getTextIntercepts</a>(const void* <a href='undocumented#Text'>text</a>, size_t length, <a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y,
-                      const <a href='undocumented#SkScalar'>SkScalar</a> bounds[2], <a href='undocumented#SkScalar'>SkScalar</a>* intervals)const
-</pre>
-
-Returns the number of <a href='#SkPaint_getTextIntercepts_intervals'>intervals</a> that intersect <a href='#SkPaint_getTextIntercepts_bounds'>bounds</a>.
-<a href='#SkPaint_getTextIntercepts_bounds'>bounds</a> describes a pair of <a href='undocumented#Line'>lines</a> parallel to the <a href='#SkPaint_getTextIntercepts_text'>text</a> advance.
-The return count is zero or a multiple of two, and is at most twice the number of <a href='undocumented#Glyph'>glyphs</a> in
-the <a href='undocumented#String'>string</a>.
-Uses <a href='undocumented#SkTextEncoding'>SkTextEncoding</a> to decode <a href='#SkPaint_getTextIntercepts_text'>text</a>, <a href='undocumented#SkTypeface'>SkTypeface</a> to get the <a href='undocumented#Glyph'>glyph</a> <a href='SkPath_Reference#Path'>paths</a>,
-and  <a href='#Text_Size'>text size</a>,  <a href='#Fake_Bold'>fake bold</a>, and <a href='undocumented#SkPathEffect'>SkPathEffect</a> to scale and modify the <a href='undocumented#Glyph'>glyph</a> <a href='SkPath_Reference#Path'>paths</a>.
-Uses <a href='#SkPaint_getTextIntercepts_x'>x</a>, <a href='#SkPaint_getTextIntercepts_y'>y</a> to position <a href='#SkPaint_getTextIntercepts_intervals'>intervals</a>.
-
-Pass nullptr for <a href='#SkPaint_getTextIntercepts_intervals'>intervals</a> to determine the <a href='undocumented#Size'>size</a> of the interval array.
-
-<a href='#SkPaint_getTextIntercepts_intervals'>intervals</a> are cached to improve performance for multiple calls.
-
-### Parameters
-
-<table>  <tr>    <td><a name='SkPaint_getTextIntercepts_text'><code><strong>text</strong></code></a></td>
-    <td>character codes or <a href='undocumented#Glyph'>glyph</a> indices</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getTextIntercepts_length'><code><strong>length</strong></code></a></td>
-    <td>number of bytes of <a href='#SkPaint_getTextIntercepts_text'>text</a></td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getTextIntercepts_x'><code><strong>x</strong></code></a></td>
-    <td>x-axis value of the origin of the <a href='#SkPaint_getTextIntercepts_text'>text</a></td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getTextIntercepts_y'><code><strong>y</strong></code></a></td>
-    <td>y-axis value of the origin of the <a href='#SkPaint_getTextIntercepts_text'>text</a></td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getTextIntercepts_bounds'><code><strong>bounds</strong></code></a></td>
-    <td>lower and upper <a href='undocumented#Line'>line</a> parallel to the advance</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getTextIntercepts_intervals'><code><strong>intervals</strong></code></a></td>
-    <td>returned intersections; may be nullptr</td>
-  </tr>
-</table>
-
-### Return Value
-
-number of intersections; may be zero
-
-<div>Underline uses intercepts to draw on either side of the <a href='undocumented#Glyph'>glyph</a> Descender.
-</div>
-
-<a name='SkPaint_getPosTextIntercepts'></a>
-
----
-
-<pre style="padding: 1em 1em 1em 1em; width: 62.5em;background-color: #f0f0f0">
-int <a href='#SkPaint_getPosTextIntercepts'>getPosTextIntercepts</a>(const void* <a href='undocumented#Text'>text</a>, size_t length, const <a href='SkPoint_Reference#SkPoint'>SkPoint</a> pos[],
-                         const <a href='undocumented#SkScalar'>SkScalar</a> bounds[2], <a href='undocumented#SkScalar'>SkScalar</a>* intervals)const
-</pre>
-
-Returns the number of <a href='#SkPaint_getPosTextIntercepts_intervals'>intervals</a> that intersect <a href='#SkPaint_getPosTextIntercepts_bounds'>bounds</a>.
-<a href='#SkPaint_getPosTextIntercepts_bounds'>bounds</a> describes a pair of <a href='undocumented#Line'>lines</a> parallel to the <a href='#SkPaint_getPosTextIntercepts_text'>text</a> advance.
-The return count is zero or a multiple of two, and is at most twice the number of <a href='undocumented#Glyph'>glyphs</a> in
-the <a href='undocumented#String'>string</a>.
-Uses <a href='undocumented#SkTextEncoding'>SkTextEncoding</a> to decode <a href='#SkPaint_getPosTextIntercepts_text'>text</a>, <a href='undocumented#SkTypeface'>SkTypeface</a> to get the <a href='undocumented#Glyph'>glyph</a> <a href='SkPath_Reference#Path'>paths</a>,
-and  <a href='#Text_Size'>text size</a>,  <a href='#Fake_Bold'>fake bold</a>, and <a href='undocumented#SkPathEffect'>SkPathEffect</a> to scale and modify the <a href='undocumented#Glyph'>glyph</a> <a href='SkPath_Reference#Path'>paths</a>.
-Uses <a href='#SkPaint_getPosTextIntercepts_pos'>pos</a> array to position <a href='#SkPaint_getPosTextIntercepts_intervals'>intervals</a>.
-
-Pass nullptr for <a href='#SkPaint_getPosTextIntercepts_intervals'>intervals</a> to determine the <a href='undocumented#Size'>size</a> of the interval array.
-
-<a href='#SkPaint_getPosTextIntercepts_intervals'>intervals</a> are cached to improve performance for multiple calls.
-
-### Parameters
-
-<table>  <tr>    <td><a name='SkPaint_getPosTextIntercepts_text'><code><strong>text</strong></code></a></td>
-    <td>character codes or <a href='undocumented#Glyph'>glyph</a> indices</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getPosTextIntercepts_length'><code><strong>length</strong></code></a></td>
-    <td>number of bytes of <a href='#SkPaint_getPosTextIntercepts_text'>text</a></td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getPosTextIntercepts_pos'><code><strong>pos</strong></code></a></td>
-    <td>positions of each <a href='undocumented#Glyph'>glyph</a></td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getPosTextIntercepts_bounds'><code><strong>bounds</strong></code></a></td>
-    <td>lower and upper <a href='undocumented#Line'>line</a> parallel to the advance</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getPosTextIntercepts_intervals'><code><strong>intervals</strong></code></a></td>
-    <td>returned intersections; may be nullptr</td>
-  </tr>
-</table>
-
-### Return Value
-
-number of intersections; may be zero
-
-<div><a href='undocumented#Text'>Text</a> intercepts draw on either side of, but not inside, <a href='undocumented#Glyph'>Glyphs</a> in a run.
-</div>
-
-<a name='SkPaint_getPosTextHIntercepts'></a>
-
----
-
-<pre style="padding: 1em 1em 1em 1em; width: 62.5em;background-color: #f0f0f0">
-int <a href='#SkPaint_getPosTextHIntercepts'>getPosTextHIntercepts</a>(const void* <a href='undocumented#Text'>text</a>, size_t length, const <a href='undocumented#SkScalar'>SkScalar</a> xpos[], <a href='undocumented#SkScalar'>SkScalar</a> constY,
-                          const <a href='undocumented#SkScalar'>SkScalar</a> bounds[2], <a href='undocumented#SkScalar'>SkScalar</a>* intervals)const
-</pre>
-
-Returns the number of <a href='#SkPaint_getPosTextHIntercepts_intervals'>intervals</a> that intersect <a href='#SkPaint_getPosTextHIntercepts_bounds'>bounds</a>.
-<a href='#SkPaint_getPosTextHIntercepts_bounds'>bounds</a> describes a pair of <a href='undocumented#Line'>lines</a> parallel to the <a href='#SkPaint_getPosTextHIntercepts_text'>text</a> advance.
-The return count is zero or a multiple of two, and is at most twice the number of <a href='undocumented#Glyph'>glyphs</a> in
-the <a href='undocumented#String'>string</a>.
-Uses <a href='undocumented#SkTextEncoding'>SkTextEncoding</a> to decode <a href='#SkPaint_getPosTextHIntercepts_text'>text</a>, <a href='undocumented#SkTypeface'>SkTypeface</a> to get the <a href='undocumented#Glyph'>glyph</a> <a href='SkPath_Reference#Path'>paths</a>,
-and  <a href='#Text_Size'>text size</a>,  <a href='#Fake_Bold'>fake bold</a>, and <a href='undocumented#SkPathEffect'>SkPathEffect</a> to scale and modify the <a href='undocumented#Glyph'>glyph</a> <a href='SkPath_Reference#Path'>paths</a>.
-Uses <a href='#SkPaint_getPosTextHIntercepts_xpos'>xpos</a> array, <a href='#SkPaint_getPosTextHIntercepts_constY'>constY</a> to position <a href='#SkPaint_getPosTextHIntercepts_intervals'>intervals</a>.
-
-Pass nullptr for <a href='#SkPaint_getPosTextHIntercepts_intervals'>intervals</a> to determine the <a href='undocumented#Size'>size</a> of the interval array.
-
-<a href='#SkPaint_getPosTextHIntercepts_intervals'>intervals</a> are cached to improve performance for multiple calls.
-
-### Parameters
-
-<table>  <tr>    <td><a name='SkPaint_getPosTextHIntercepts_text'><code><strong>text</strong></code></a></td>
-    <td>character codes or <a href='undocumented#Glyph'>glyph</a> indices</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getPosTextHIntercepts_length'><code><strong>length</strong></code></a></td>
-    <td>number of bytes of <a href='#SkPaint_getPosTextHIntercepts_text'>text</a></td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getPosTextHIntercepts_xpos'><code><strong>xpos</strong></code></a></td>
-    <td>positions of each <a href='undocumented#Glyph'>glyph</a> on x-axis</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getPosTextHIntercepts_constY'><code><strong>constY</strong></code></a></td>
-    <td>position of each <a href='undocumented#Glyph'>glyph</a> on y-axis</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getPosTextHIntercepts_bounds'><code><strong>bounds</strong></code></a></td>
-    <td>lower and upper <a href='undocumented#Line'>line</a> parallel to the advance</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getPosTextHIntercepts_intervals'><code><strong>intervals</strong></code></a></td>
-    <td>returned intersections; may be nullptr</td>
-  </tr>
-</table>
-
-### Return Value
-
-number of intersections; may be zero
-
-<div><a href='undocumented#Text'>Text</a> intercepts do not take stroke thickness into consideration.
-</div>
-
-<a name='SkPaint_getTextBlobIntercepts'></a>
-
----
-
-<pre style="padding: 1em 1em 1em 1em; width: 62.5em;background-color: #f0f0f0">
-int <a href='#SkPaint_getTextBlobIntercepts'>getTextBlobIntercepts</a>(const <a href='SkTextBlob_Reference#SkTextBlob'>SkTextBlob</a>* blob, const <a href='undocumented#SkScalar'>SkScalar</a> bounds[2], <a href='undocumented#SkScalar'>SkScalar</a>* intervals)const
-</pre>
-
-Returns the number of <a href='#SkPaint_getTextBlobIntercepts_intervals'>intervals</a> that intersect <a href='#SkPaint_getTextBlobIntercepts_bounds'>bounds</a>.
-<a href='#SkPaint_getTextBlobIntercepts_bounds'>bounds</a> describes a pair of <a href='undocumented#Line'>lines</a> parallel to the <a href='undocumented#Text'>text</a> advance.
-The return count is zero or a multiple of two, and is at most twice the number of <a href='undocumented#Glyph'>glyphs</a> in
-the <a href='undocumented#String'>string</a>.
-Uses <a href='undocumented#SkTypeface'>SkTypeface</a> to get the <a href='undocumented#Glyph'>glyph</a> <a href='SkPath_Reference#Path'>paths</a>,
-and  <a href='#Text_Size'>text size</a>,  <a href='#Fake_Bold'>fake bold</a>, and <a href='undocumented#SkPathEffect'>SkPathEffect</a> to scale and modify the <a href='undocumented#Glyph'>glyph</a> <a href='SkPath_Reference#Path'>paths</a>.
-Uses run array to position <a href='#SkPaint_getTextBlobIntercepts_intervals'>intervals</a>.
-
-<a href='undocumented#SkTextEncoding'>SkTextEncoding</a> must be set to <a href='SkPaint_Reference#SkPaint'>SkPaint</a>::<a href='#SkPaint_kGlyphID_TextEncoding'>kGlyphID_TextEncoding</a>.
-
-Pass nullptr for <a href='#SkPaint_getTextBlobIntercepts_intervals'>intervals</a> to determine the <a href='undocumented#Size'>size</a> of the interval array.
-
-<a href='#SkPaint_getTextBlobIntercepts_intervals'>intervals</a> are cached to improve performance for multiple calls.
-
-### Parameters
-
-<table>  <tr>    <td><a name='SkPaint_getTextBlobIntercepts_blob'><code><strong>blob</strong></code></a></td>
-    <td><a href='undocumented#Glyph'>glyphs</a>, positions, and <a href='undocumented#Text'>text</a> <a href='SkPaint_Reference#Paint'>paint</a> attributes</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getTextBlobIntercepts_bounds'><code><strong>bounds</strong></code></a></td>
-    <td>lower and upper <a href='undocumented#Line'>line</a> parallel to the advance</td>
-  </tr>
-  <tr>    <td><a name='SkPaint_getTextBlobIntercepts_intervals'><code><strong>intervals</strong></code></a></td>
-    <td>returned intersections; may be nullptr</td>
-  </tr>
-</table>
-
-### Return Value
-
-number of intersections; may be zero
-
-### Example
-
-<div><fiddle-embed name="728ea9089f233a71a1062e364b8c73fa"></fiddle-embed></div>
-
-<a name='SkPaint_getFontBounds'></a>
-
----
-
-<pre style="padding: 1em 1em 1em 1em; width: 62.5em;background-color: #f0f0f0">
-<a href='SkRect_Reference#SkRect'>SkRect</a> <a href='#SkPaint_getFontBounds'>getFontBounds</a>()const
-</pre>
-
-Returns the union of bounds of all <a href='undocumented#Glyph'>glyphs</a>.
-Returned dimensions are computed by <a href='SkFont_Reference#Font'>font</a> manager from <a href='SkFont_Reference#Font'>font</a> <a href='undocumented#Data'>data</a>,
-ignoring <a href='SkPaint_Reference#SkPaint'>SkPaint</a>::Hinting. Includes <a href='SkFont_Reference#Font'>font</a> metrics, but not fake bold or <a href='undocumented#SkPathEffect'>SkPathEffect</a>.
-
-If <a href='undocumented#Text'>text</a> <a href='undocumented#Size'>size</a> is large, <a href='undocumented#Text'>text</a> scale is one, and <a href='undocumented#Text'>text</a> skew is zero,
-returns the bounds as:
-{ <a href='undocumented#SkFontMetrics'>SkFontMetrics</a>::<a href='#SkFontMetrics_fXMin'>fXMin</a>, <a href='undocumented#SkFontMetrics'>SkFontMetrics</a>::<a href='#SkFontMetrics_fTop'>fTop</a>, <a href='undocumented#SkFontMetrics'>SkFontMetrics</a>::<a href='#SkFontMetrics_fXMax'>fXMax</a>, <a href='undocumented#SkFontMetrics'>SkFontMetrics</a>::<a href='#SkFontMetrics_fBottom'>fBottom</a> }.
-
-### Return Value
-
-union of bounds of all <a href='undocumented#Glyph'>glyphs</a>
-
-### Example
-
-<div><fiddle-embed name="f29d005a75efd4746c6744004a0cb421">
-
-#### Example Output
-
-~~~~
-metrics bounds = { -12.2461, -14.7891, 21.5215, 5.55469 }
-font bounds    = { -12.2461, -14.7891, 21.5215, 5.55469 }
-~~~~
-
-</fiddle-embed></div>
-
 <a name='SkPaint_nothingToDraw'></a>
 
 ---
diff --git a/site/user/api/SkPath_Reference.md b/site/user/api/SkPath_Reference.md
index 511aaf5..465ff08 100644
--- a/site/user/api/SkPath_Reference.md
+++ b/site/user/api/SkPath_Reference.md
@@ -21,7 +21,7 @@
     friend bool <a href='#SkPath_equal_operator'>operator==</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& a, const <a href='SkPath_Reference#SkPath'>SkPath</a>& b);
     friend bool <a href='#SkPath_notequal_operator'>operator!=</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& a, const <a href='SkPath_Reference#SkPath'>SkPath</a>& b);
     bool <a href='#SkPath_isInterpolatable'>isInterpolatable</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& compare) const;
-    bool interpolate(const <a href='SkPath_Reference#SkPath'>SkPath</a>& ending, <a href='undocumented#SkScalar'>SkScalar</a> weight, <a href='SkPath_Reference#SkPath'>SkPath</a>* out) const;
+    bool <a href='#SkPath_interpolate'>interpolate</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& ending, <a href='undocumented#SkScalar'>SkScalar</a> weight, <a href='SkPath_Reference#SkPath'>SkPath</a>* out) const;
 
     enum <a href='#SkPath_FillType'>FillType</a> {
         <a href='#SkPath_kWinding_FillType'>kWinding_FillType</a>,
@@ -142,10 +142,10 @@
     <a href='SkPath_Reference#SkPath'>SkPath</a>& <a href='#SkPath_addPath'>addPath</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& src, const <a href='SkMatrix_Reference#SkMatrix'>SkMatrix</a>& <a href='SkMatrix_Reference#Matrix'>matrix</a>,
                     <a href='#SkPath_AddPathMode'>AddPathMode</a> mode = <a href='#SkPath_kAppend_AddPathMode'>kAppend_AddPathMode</a>);
     <a href='SkPath_Reference#SkPath'>SkPath</a>& <a href='#SkPath_reverseAddPath'>reverseAddPath</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& src);
-    void offset(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy, <a href='SkPath_Reference#SkPath'>SkPath</a>* dst) const;
-    void offset(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
-    void transform(const <a href='SkMatrix_Reference#SkMatrix'>SkMatrix</a>& <a href='SkMatrix_Reference#Matrix'>matrix</a>, <a href='SkPath_Reference#SkPath'>SkPath</a>* dst) const;
-    void transform(const <a href='SkMatrix_Reference#SkMatrix'>SkMatrix</a>& <a href='SkMatrix_Reference#Matrix'>matrix</a>);
+    void <a href='#SkPath_offset'>offset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy, <a href='SkPath_Reference#SkPath'>SkPath</a>* dst) const;
+    void <a href='#SkPath_offset'>offset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
+    void <a href='#SkPath_transform'>transform</a>(const <a href='SkMatrix_Reference#SkMatrix'>SkMatrix</a>& <a href='SkMatrix_Reference#Matrix'>matrix</a>, <a href='SkPath_Reference#SkPath'>SkPath</a>* dst) const;
+    void <a href='#SkPath_transform'>transform</a>(const <a href='SkMatrix_Reference#SkMatrix'>SkMatrix</a>& <a href='SkMatrix_Reference#Matrix'>matrix</a>);
     bool <a href='#SkPath_getLastPt'>getLastPt</a>(<a href='SkPoint_Reference#SkPoint'>SkPoint</a>* lastPt) const;
     void <a href='#SkPath_setLastPt'>setLastPt</a>(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y);
     void <a href='#SkPath_setLastPt'>setLastPt</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& p);
@@ -169,7 +169,7 @@
         <a href='#SkPath_kDone_Verb'>kDone_Verb</a>,
     };
 
-    bool contains(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y) const;
+    bool <a href='#SkPath_contains'>contains</a>(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y) const;
     void <a href='#SkPath_dump'>dump</a>(<a href='SkWStream_Reference#SkWStream'>SkWStream</a>* <a href='SkStream_Reference#Stream'>stream</a>, bool forceClose, bool dumpAsHex) const;
     void <a href='#SkPath_dump'>dump()</a> const;
     void <a href='#SkPath_dumpHex'>dumpHex</a>() const;
@@ -4983,9 +4983,9 @@
     class <a href='#SkPath_Iter'>Iter</a> {
 
         <a href='#SkPath_Iter_Iter'>Iter()</a>;
-        <a href='#SkPath_Iter'>Iter</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& <a href='SkPath_Reference#Path'>path</a>, bool forceClose);
+        <a href='#SkPath_Iter_Iter'>Iter</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& <a href='SkPath_Reference#Path'>path</a>, bool forceClose);
         void <a href='#SkPath_Iter_setPath'>setPath</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& <a href='SkPath_Reference#Path'>path</a>, bool forceClose);
-        <a href='#SkPath_Verb'>Verb</a> next(<a href='SkPoint_Reference#SkPoint'>SkPoint</a> pts[4], bool doConsumeDegenerates = true, bool exact = false);
+        <a href='#SkPath_Verb'>Verb</a> <a href='#SkPath_Iter_next'>next</a>(<a href='SkPoint_Reference#SkPoint'>SkPoint</a> pts[4], bool doConsumeDegenerates = true, bool exact = false);
         <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPath_Iter_conicWeight'>conicWeight</a>() const;
         bool <a href='#SkPath_Iter_isCloseLine'>isCloseLine</a>() const;
         bool <a href='#SkPath_Iter_isClosedContour'>isClosedContour</a>() const;
@@ -5337,9 +5337,9 @@
     class <a href='#SkPath_RawIter'>RawIter</a> {
 
         <a href='#SkPath_RawIter_RawIter'>RawIter()</a>;
-        <a href='#SkPath_RawIter'>RawIter</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& <a href='SkPath_Reference#Path'>path</a>);
+        <a href='#SkPath_RawIter_RawIter'>RawIter</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& <a href='SkPath_Reference#Path'>path</a>);
         void <a href='#SkPath_RawIter_setPath'>setPath</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& <a href='SkPath_Reference#Path'>path</a>);
-        <a href='#SkPath_Verb'>Verb</a> next(<a href='SkPoint_Reference#SkPoint'>SkPoint</a> pts[4]);
+        <a href='#SkPath_Verb'>Verb</a> <a href='#SkPath_RawIter_next'>next</a>(<a href='SkPoint_Reference#SkPoint'>SkPoint</a> pts[4]);
         <a href='#SkPath_Verb'>Verb</a> <a href='#SkPath_RawIter_peek'>peek()</a> const;
         <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPath_RawIter_conicWeight'>conicWeight</a>() const;
     };
diff --git a/site/user/api/SkPicture_Reference.md b/site/user/api/SkPicture_Reference.md
index 017aa4c..34bfdd5 100644
--- a/site/user/api/SkPicture_Reference.md
+++ b/site/user/api/SkPicture_Reference.md
@@ -15,7 +15,7 @@
                                          const <a href='undocumented#SkDeserialProcs'>SkDeserialProcs</a>* procs = nullptr);
     static <a href='undocumented#sk_sp'>sk_sp</a><<a href='SkPicture_Reference#SkPicture'>SkPicture</a>> <a href='#SkPicture_MakeFromData'>MakeFromData</a>(const void* <a href='undocumented#Data'>data</a>, size_t <a href='undocumented#Size'>size</a>,
                                          const <a href='undocumented#SkDeserialProcs'>SkDeserialProcs</a>* procs = nullptr);
-    virtual void playback(<a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a>* <a href='SkCanvas_Reference#Canvas'>canvas</a>, <a href='#SkPicture_AbortCallback'>AbortCallback</a>* callback = nullptr) const = 0;
+    virtual void <a href='#SkPicture_playback'>playback</a>(<a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a>* <a href='SkCanvas_Reference#Canvas'>canvas</a>, <a href='#SkPicture_AbortCallback'>AbortCallback</a>* callback = nullptr) const = 0;
     virtual <a href='SkRect_Reference#SkRect'>SkRect</a> <a href='#SkPicture_cullRect'>cullRect</a>() const = 0;
     uint32_t <a href='#SkPicture_uniqueID'>uniqueID</a>() const;
     <a href='undocumented#sk_sp'>sk_sp</a><<a href='undocumented#SkData'>SkData</a>> <a href='#SkPicture_serialize'>serialize</a>(const <a href='undocumented#SkSerialProcs'>SkSerialProcs</a>* procs = nullptr) const;
diff --git a/site/user/api/SkPixmap_Reference.md b/site/user/api/SkPixmap_Reference.md
index 1ab5803..aa2f410 100644
--- a/site/user/api/SkPixmap_Reference.md
+++ b/site/user/api/SkPixmap_Reference.md
@@ -12,7 +12,7 @@
     <a href='#SkPixmap_empty_constructor'>SkPixmap()</a>;
     <a href='#SkPixmap_const_SkImageInfo_const_star'>SkPixmap</a>(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& info, const void* addr, size_t <a href='#SkPixmap_rowBytes'>rowBytes</a>);
     void <a href='#SkPixmap_reset'>reset()</a>;
-    void reset(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& info, const void* addr, size_t <a href='#SkPixmap_rowBytes'>rowBytes</a>);
+    void <a href='#SkPixmap_reset'>reset</a>(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& info, const void* addr, size_t <a href='#SkPixmap_rowBytes'>rowBytes</a>);
     void <a href='#SkPixmap_setColorSpace'>setColorSpace</a>(<a href='undocumented#sk_sp'>sk_sp</a><<a href='undocumented#SkColorSpace'>SkColorSpace</a>> <a href='#SkPixmap_colorSpace'>colorSpace</a>);
     bool <a href='#SkPixmap_extractSubset'>extractSubset</a>(<a href='SkPixmap_Reference#SkPixmap'>SkPixmap</a>* subset, const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& area) const;
     const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& <a href='#SkPixmap_info'>info()</a> const;
diff --git a/site/user/api/SkPoint_Reference.md b/site/user/api/SkPoint_Reference.md
index 4adac47..1076877 100644
--- a/site/user/api/SkPoint_Reference.md
+++ b/site/user/api/SkPoint_Reference.md
@@ -16,29 +16,29 @@
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_x'>x()</a> const;
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_y'>y()</a> const;
     bool <a href='#SkPoint_isZero'>isZero</a>() const;
-    void set(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y);
+    void <a href='#SkPoint_set'>set</a>(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y);
     void <a href='#SkPoint_iset'>iset</a>(int32_t x, int32_t y);
     void <a href='#SkPoint_iset'>iset</a>(const <a href='SkIPoint_Reference#SkIPoint'>SkIPoint</a>& p);
     void <a href='#SkPoint_setAbs'>setAbs</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& pt);
     static void <a href='#SkPoint_Offset'>Offset</a>(<a href='SkPoint_Reference#SkPoint'>SkPoint</a> <a href='SkPoint_Reference#Point'>points</a>[], int count, const <a href='SkPoint_Reference#SkVector'>SkVector</a>& offset);
     static void <a href='#SkPoint_Offset'>Offset</a>(<a href='SkPoint_Reference#SkPoint'>SkPoint</a> <a href='SkPoint_Reference#Point'>points</a>[], int count, <a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
-    void offset(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
+    void <a href='#SkPoint_offset'>offset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_length'>length()</a> const;
     <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_distanceToOrigin'>distanceToOrigin</a>() const;
     bool <a href='#SkPoint_normalize'>normalize()</a>;
     bool <a href='#SkPoint_setNormalize'>setNormalize</a>(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y);
     bool <a href='#SkPoint_setLength'>setLength</a>(<a href='undocumented#SkScalar'>SkScalar</a> length);
     bool <a href='#SkPoint_setLength'>setLength</a>(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y, <a href='undocumented#SkScalar'>SkScalar</a> length);
-    void scale(<a href='undocumented#SkScalar'>SkScalar</a> scale, <a href='SkPoint_Reference#SkPoint'>SkPoint</a>* dst) const;
-    void scale(<a href='undocumented#SkScalar'>SkScalar</a> value);
+    void <a href='#SkPoint_scale'>scale</a>(<a href='undocumented#SkScalar'>SkScalar</a> scale, <a href='SkPoint_Reference#SkPoint'>SkPoint</a>* dst) const;
+    void <a href='#SkPoint_scale'>scale</a>(<a href='undocumented#SkScalar'>SkScalar</a> value);
     void <a href='#SkPoint_negate'>negate()</a>;
     <a href='SkPoint_Reference#SkPoint'>SkPoint</a> operator-() const;
     void <a href='#SkPoint_addto_operator'>operator+=</a>(const <a href='SkPoint_Reference#SkVector'>SkVector</a>& v);
     void <a href='#SkPoint_subtractfrom_operator'>operator-=</a>(const <a href='SkPoint_Reference#SkVector'>SkVector</a>& v);
-    <a href='SkPoint_Reference#SkPoint'>SkPoint</a> operator*(<a href='undocumented#SkScalar'>SkScalar</a> scale) const;
+    <a href='SkPoint_Reference#SkPoint'>SkPoint</a> <a href='#SkPoint_multiply_operator'>operator*</a>(<a href='undocumented#SkScalar'>SkScalar</a> scale) const;
     <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& <a href='#SkPoint_multiplyby_operator'>operator*=</a>(<a href='undocumented#SkScalar'>SkScalar</a> scale);
     bool <a href='#SkPoint_isFinite'>isFinite</a>() const;
-    bool equals(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y) const;
+    bool <a href='#SkPoint_equals'>equals</a>(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y) const;
     friend bool <a href='#SkPoint_equal_operator'>operator==</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& a, const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& b);
     friend bool <a href='#SkPoint_notequal_operator'>operator!=</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& a, const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& b);
     friend <a href='SkPoint_Reference#SkVector'>SkVector</a> <a href='#SkPoint_subtract_operator'>operator-</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& a, const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& b);
@@ -48,8 +48,8 @@
     static <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_Distance'>Distance</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& a, const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& b);
     static <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_DotProduct'>DotProduct</a>(const <a href='SkPoint_Reference#SkVector'>SkVector</a>& a, const <a href='SkPoint_Reference#SkVector'>SkVector</a>& b);
     static <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_CrossProduct'>CrossProduct</a>(const <a href='SkPoint_Reference#SkVector'>SkVector</a>& a, const <a href='SkPoint_Reference#SkVector'>SkVector</a>& b);
-    <a href='undocumented#SkScalar'>SkScalar</a> cross(const <a href='SkPoint_Reference#SkVector'>SkVector</a>& vec) const;
-    <a href='undocumented#SkScalar'>SkScalar</a> dot(const <a href='SkPoint_Reference#SkVector'>SkVector</a>& vec) const;
+    <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_cross'>cross</a>(const <a href='SkPoint_Reference#SkVector'>SkVector</a>& vec) const;
+    <a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_dot'>dot</a>(const <a href='SkPoint_Reference#SkVector'>SkVector</a>& vec) const;
 };
 
 </pre>
@@ -653,7 +653,7 @@
 
 ### See Also
 
-operator*(<a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_scale_scale'>scale</a>) const <a href='#SkPoint_multiplyby_operator'>operator*=</a>(<a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_scale_scale'>scale</a>) <a href='#SkPoint_setLength'>setLength</a>
+<a href='#SkPoint_multiply_operator'>operator*</a>(<a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_scale_scale'>scale</a>) const <a href='#SkPoint_multiplyby_operator'>operator*=</a>(<a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_scale_scale'>scale</a>) <a href='#SkPoint_setLength'>setLength</a>
 
 <a name='SkPoint_scale_2'></a>
 
@@ -678,7 +678,7 @@
 
 ### See Also
 
-operator*(<a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_scale'>scale</a>) const <a href='#SkPoint_multiplyby_operator'>operator*=</a>(<a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_scale'>scale</a>) <a href='#SkPoint_setLength'>setLength</a>
+<a href='#SkPoint_multiply_operator'>operator*</a>(<a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_scale'>scale</a>) const <a href='#SkPoint_multiplyby_operator'>operator*=</a>(<a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_scale'>scale</a>) <a href='#SkPoint_setLength'>setLength</a>
 
 <a name='SkPoint_negate'></a>
 
@@ -848,7 +848,7 @@
 
 ### See Also
 
-operator*(<a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_multiplyby_operator_scale'>scale</a>) const <a href='#SkPoint_scale'>scale()</a> <a href='#SkPoint_setLength'>setLength</a> <a href='#SkPoint_setNormalize'>setNormalize</a>
+<a href='#SkPoint_multiply_operator'>operator*</a>(<a href='undocumented#SkScalar'>SkScalar</a> <a href='#SkPoint_multiplyby_operator_scale'>scale</a>) const <a href='#SkPoint_scale'>scale()</a> <a href='#SkPoint_setLength'>setLength</a> <a href='#SkPoint_setNormalize'>setNormalize</a>
 
 <a name='SkPoint_isFinite'></a>
 
diff --git a/site/user/api/SkRRect_Reference.md b/site/user/api/SkRRect_Reference.md
index 3ec2d78..f6dc4db 100644
--- a/site/user/api/SkRRect_Reference.md
+++ b/site/user/api/SkRRect_Reference.md
@@ -54,24 +54,24 @@
     };
 
     const <a href='SkRect_Reference#SkRect'>SkRect</a>& <a href='#SkRRect_rect'>rect()</a> const;
-    <a href='SkPoint_Reference#SkVector'>SkVector</a> radii(<a href='#SkRRect_Corner'>Corner</a> corner) const;
+    <a href='SkPoint_Reference#SkVector'>SkVector</a> <a href='#SkRRect_radii'>radii</a>(<a href='#SkRRect_Corner'>Corner</a> corner) const;
     const <a href='SkRect_Reference#SkRect'>SkRect</a>& <a href='#SkRRect_getBounds'>getBounds</a>() const;
     friend bool <a href='#SkRRect_equal_operator'>operator==</a>(const <a href='SkRRect_Reference#SkRRect'>SkRRect</a>& a, const <a href='SkRRect_Reference#SkRRect'>SkRRect</a>& b);
     friend bool <a href='#SkRRect_notequal_operator'>operator!=</a>(const <a href='SkRRect_Reference#SkRRect'>SkRRect</a>& a, const <a href='SkRRect_Reference#SkRRect'>SkRRect</a>& b);
-    void inset(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy, <a href='SkRRect_Reference#SkRRect'>SkRRect</a>* dst) const;
-    void inset(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
-    void outset(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy, <a href='SkRRect_Reference#SkRRect'>SkRRect</a>* dst) const;
-    void outset(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
-    void offset(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
+    void <a href='#SkRRect_inset'>inset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy, <a href='SkRRect_Reference#SkRRect'>SkRRect</a>* dst) const;
+    void <a href='#SkRRect_inset'>inset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
+    void <a href='#SkRRect_outset'>outset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy, <a href='SkRRect_Reference#SkRRect'>SkRRect</a>* dst) const;
+    void <a href='#SkRRect_outset'>outset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
+    void <a href='#SkRRect_offset'>offset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
     <a href='SkRRect_Reference#SkRRect'>SkRRect</a> <a href='#SkRRect_makeOffset'>makeOffset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy) const;
-    bool contains(const <a href='SkRect_Reference#SkRect'>SkRect</a>& <a href='SkRect_Reference#Rect'>rect</a>) const;
+    bool <a href='#SkRRect_contains'>contains</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>& <a href='SkRect_Reference#Rect'>rect</a>) const;
     bool <a href='#SkRRect_isValid'>isValid</a>() const;
 
     static constexpr size_t <a href='#SkRRect_kSizeInMemory'>kSizeInMemory</a> = 12 * <a href='undocumented#sizeof()'>sizeof</a>(<a href='undocumented#SkScalar'>SkScalar</a>);
 
     size_t <a href='#SkRRect_writeToMemory'>writeToMemory</a>(void* buffer) const;
     size_t <a href='#SkRRect_readFromMemory'>readFromMemory</a>(const void* buffer, size_t length);
-    bool transform(const <a href='SkMatrix_Reference#SkMatrix'>SkMatrix</a>& <a href='SkMatrix_Reference#Matrix'>matrix</a>, <a href='SkRRect_Reference#SkRRect'>SkRRect</a>* dst) const;
+    bool <a href='#SkRRect_transform'>transform</a>(const <a href='SkMatrix_Reference#SkMatrix'>SkMatrix</a>& <a href='SkMatrix_Reference#Matrix'>matrix</a>, <a href='SkRRect_Reference#SkRRect'>SkRRect</a>* dst) const;
     void <a href='#SkRRect_dump'>dump</a>(bool asHex) const;
     void <a href='#SkRRect_dump'>dump()</a> const;
     void <a href='#SkRRect_dumpHex'>dumpHex</a>() const;
diff --git a/site/user/api/SkRect_Reference.md b/site/user/api/SkRect_Reference.md
index 9183828..92e3acc 100644
--- a/site/user/api/SkRect_Reference.md
+++ b/site/user/api/SkRect_Reference.md
@@ -41,40 +41,40 @@
     friend bool <a href='#SkRect_notequal_operator'>operator!=</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>& a, const <a href='SkRect_Reference#SkRect'>SkRect</a>& b);
     void <a href='#SkRect_toQuad'>toQuad</a>(<a href='SkPoint_Reference#SkPoint'>SkPoint</a> <a href='SkPath_Reference#Quad'>quad</a>[4]) const;
     void <a href='#SkRect_setEmpty'>setEmpty</a>();
-    void set(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& src);
-    void set(<a href='undocumented#SkScalar'>SkScalar</a> left, <a href='undocumented#SkScalar'>SkScalar</a> top, <a href='undocumented#SkScalar'>SkScalar</a> right, <a href='undocumented#SkScalar'>SkScalar</a> bottom);
+    void <a href='#SkRect_set'>set</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& src);
+    void <a href='#SkRect_set'>set</a>(<a href='undocumented#SkScalar'>SkScalar</a> left, <a href='undocumented#SkScalar'>SkScalar</a> top, <a href='undocumented#SkScalar'>SkScalar</a> right, <a href='undocumented#SkScalar'>SkScalar</a> bottom);
     void <a href='#SkRect_setLTRB'>setLTRB</a>(<a href='undocumented#SkScalar'>SkScalar</a> left, <a href='undocumented#SkScalar'>SkScalar</a> top, <a href='undocumented#SkScalar'>SkScalar</a> right, <a href='undocumented#SkScalar'>SkScalar</a> bottom);
     void <a href='#SkRect_iset'>iset</a>(int left, int top, int right, int bottom);
     void <a href='#SkRect_isetWH'>isetWH</a>(int width, int height);
-    void set(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a> pts[], int count);
+    void <a href='#SkRect_set'>set</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a> pts[], int count);
     void <a href='#SkRect_setBounds'>setBounds</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a> pts[], int count);
     bool <a href='#SkRect_setBoundsCheck'>setBoundsCheck</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a> pts[], int count);
     void <a href='#SkRect_setBoundsNoCheck'>setBoundsNoCheck</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a> pts[], int count);
-    void set(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& p0, const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& p1);
+    void <a href='#SkRect_set'>set</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& p0, const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& p1);
     void <a href='#SkRect_setXYWH'>setXYWH</a>(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y, <a href='undocumented#SkScalar'>SkScalar</a> width, <a href='undocumented#SkScalar'>SkScalar</a> height);
     void <a href='#SkRect_setWH'>setWH</a>(<a href='undocumented#SkScalar'>SkScalar</a> width, <a href='undocumented#SkScalar'>SkScalar</a> height);
     <a href='SkRect_Reference#SkRect'>SkRect</a> <a href='#SkRect_makeOffset'>makeOffset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy) const;
     <a href='SkRect_Reference#SkRect'>SkRect</a> <a href='#SkRect_makeInset'>makeInset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy) const;
     <a href='SkRect_Reference#SkRect'>SkRect</a> <a href='#SkRect_makeOutset'>makeOutset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy) const;
-    void offset(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
-    void offset(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& delta);
+    void <a href='#SkRect_offset'>offset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
+    void <a href='#SkRect_offset'>offset</a>(const <a href='SkPoint_Reference#SkPoint'>SkPoint</a>& delta);
     void <a href='#SkRect_offsetTo'>offsetTo</a>(<a href='undocumented#SkScalar'>SkScalar</a> newX, <a href='undocumented#SkScalar'>SkScalar</a> newY);
-    void inset(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
-    void outset(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
-    bool intersect(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r);
-    bool intersect(<a href='undocumented#SkScalar'>SkScalar</a> left, <a href='undocumented#SkScalar'>SkScalar</a> top, <a href='undocumented#SkScalar'>SkScalar</a> right, <a href='undocumented#SkScalar'>SkScalar</a> bottom);
-    bool intersect(const <a href='SkRect_Reference#SkRect'>SkRect</a>& a, const <a href='SkRect_Reference#SkRect'>SkRect</a>& b);
-    bool intersects(<a href='undocumented#SkScalar'>SkScalar</a> left, <a href='undocumented#SkScalar'>SkScalar</a> top, <a href='undocumented#SkScalar'>SkScalar</a> right, <a href='undocumented#SkScalar'>SkScalar</a> bottom) const;
-    bool intersects(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r) const;
+    void <a href='#SkRect_inset'>inset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
+    void <a href='#SkRect_outset'>outset</a>(<a href='undocumented#SkScalar'>SkScalar</a> dx, <a href='undocumented#SkScalar'>SkScalar</a> dy);
+    bool <a href='#SkRect_intersect'>intersect</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r);
+    bool <a href='#SkRect_intersect'>intersect</a>(<a href='undocumented#SkScalar'>SkScalar</a> left, <a href='undocumented#SkScalar'>SkScalar</a> top, <a href='undocumented#SkScalar'>SkScalar</a> right, <a href='undocumented#SkScalar'>SkScalar</a> bottom);
+    bool <a href='#SkRect_intersect'>intersect</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>& a, const <a href='SkRect_Reference#SkRect'>SkRect</a>& b);
+    bool <a href='#SkRect_intersects'>intersects</a>(<a href='undocumented#SkScalar'>SkScalar</a> left, <a href='undocumented#SkScalar'>SkScalar</a> top, <a href='undocumented#SkScalar'>SkScalar</a> right, <a href='undocumented#SkScalar'>SkScalar</a> bottom) const;
+    bool <a href='#SkRect_intersects'>intersects</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r) const;
     static bool <a href='#SkRect_Intersects'>Intersects</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>& a, const <a href='SkRect_Reference#SkRect'>SkRect</a>& b);
-    void join(<a href='undocumented#SkScalar'>SkScalar</a> left, <a href='undocumented#SkScalar'>SkScalar</a> top, <a href='undocumented#SkScalar'>SkScalar</a> right, <a href='undocumented#SkScalar'>SkScalar</a> bottom);
-    void join(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r);
+    void <a href='#SkRect_join'>join</a>(<a href='undocumented#SkScalar'>SkScalar</a> left, <a href='undocumented#SkScalar'>SkScalar</a> top, <a href='undocumented#SkScalar'>SkScalar</a> right, <a href='undocumented#SkScalar'>SkScalar</a> bottom);
+    void <a href='#SkRect_join'>join</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r);
     void <a href='#SkRect_joinNonEmptyArg'>joinNonEmptyArg</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r);
     void <a href='#SkRect_joinPossiblyEmptyRect'>joinPossiblyEmptyRect</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r);
-    bool contains(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y) const;
-    bool contains(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r) const;
-    bool contains(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& r) const;
-    void round(<a href='SkIRect_Reference#SkIRect'>SkIRect</a>* dst) const;
+    bool <a href='#SkRect_contains'>contains</a>(<a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y) const;
+    bool <a href='#SkRect_contains'>contains</a>(const <a href='SkRect_Reference#SkRect'>SkRect</a>& r) const;
+    bool <a href='#SkRect_contains'>contains</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& r) const;
+    void <a href='#SkRect_round'>round</a>(<a href='SkIRect_Reference#SkIRect'>SkIRect</a>* dst) const;
     void <a href='#SkRect_roundOut'>roundOut</a>(<a href='SkIRect_Reference#SkIRect'>SkIRect</a>* dst) const;
     void <a href='#SkRect_roundOut'>roundOut</a>(<a href='SkRect_Reference#SkRect'>SkRect</a>* dst) const;
     void <a href='#SkRect_roundIn'>roundIn</a>(<a href='SkIRect_Reference#SkIRect'>SkIRect</a>* dst) const;
diff --git a/site/user/api/SkRegion_Reference.md b/site/user/api/SkRegion_Reference.md
index 632a3c6..a14c93f 100644
--- a/site/user/api/SkRegion_Reference.md
+++ b/site/user/api/SkRegion_Reference.md
@@ -23,9 +23,9 @@
     explicit <a href='#SkRegion_copy_const_SkIRect'>SkRegion</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& <a href='SkRect_Reference#Rect'>rect</a>);
     <a href='#SkRegion_destructor'>~SkRegion()</a>;
     <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='#SkRegion_copy_operator'>operator=</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>);
-    bool operator==(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& other) const;
-    bool operator!=(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& other) const;
-    bool set(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& src);
+    bool <a href='#SkRegion_equal1_operator'>operator==</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& other) const;
+    bool <a href='#SkRegion_notequal1_operator'>operator!=</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& other) const;
+    bool <a href='#SkRegion_set'>set</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& src);
     void <a href='#SkRegion_swap'>swap</a>(<a href='SkRegion_Reference#SkRegion'>SkRegion</a>& other);
     bool <a href='#SkRegion_isEmpty'>isEmpty</a>() const;
     bool <a href='#SkRegion_isRect'>isRect</a>() const;
@@ -39,18 +39,18 @@
     bool <a href='#SkRegion_setRects'>setRects</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a> <a href='SkRect_Reference#Rect'>rects</a>[], int count);
     bool <a href='#SkRegion_setRegion'>setRegion</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>);
     bool <a href='#SkRegion_setPath'>setPath</a>(const <a href='SkPath_Reference#SkPath'>SkPath</a>& <a href='SkPath_Reference#Path'>path</a>, const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& clip);
-    bool intersects(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& <a href='SkRect_Reference#Rect'>rect</a>) const;
-    bool intersects(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& other) const;
-    bool contains(int32_t x, int32_t y) const;
-    bool contains(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& other) const;
-    bool contains(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& other) const;
+    bool <a href='#SkRegion_intersects'>intersects</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& <a href='SkRect_Reference#Rect'>rect</a>) const;
+    bool <a href='#SkRegion_intersects'>intersects</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& other) const;
+    bool <a href='#SkRegion_contains'>contains</a>(int32_t x, int32_t y) const;
+    bool <a href='#SkRegion_contains'>contains</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& other) const;
+    bool <a href='#SkRegion_contains'>contains</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& other) const;
     bool <a href='#SkRegion_quickContains'>quickContains</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& r) const;
     bool <a href='#SkRegion_quickContains'>quickContains</a>(int32_t left, int32_t top, int32_t right,
                        int32_t bottom) const;
     bool <a href='#SkRegion_quickReject'>quickReject</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& <a href='SkRect_Reference#Rect'>rect</a>) const;
     bool <a href='#SkRegion_quickReject'>quickReject</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& rgn) const;
-    void translate(int dx, int dy);
-    void translate(int dx, int dy, <a href='SkRegion_Reference#SkRegion'>SkRegion</a>* dst) const;
+    void <a href='#SkRegion_translate'>translate</a>(int dx, int dy);
+    void <a href='#SkRegion_translate'>translate</a>(int dx, int dy, <a href='SkRegion_Reference#SkRegion'>SkRegion</a>* dst) const;
 
     enum <a href='#SkRegion_Op'>Op</a> {
         <a href='#SkRegion_kDifference_Op'>kDifference_Op</a>,
@@ -89,9 +89,9 @@
     class <a href='#SkRegion_Iterator'>Iterator</a> {
     public:
         <a href='#SkRegion_Iterator_Iterator'>Iterator()</a>;
-        <a href='#SkRegion_Iterator'>Iterator</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>);
+        <a href='#SkRegion_Iterator_Iterator'>Iterator</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>);
         bool <a href='#SkRegion_Iterator_rewind'>rewind()</a>;
-        void reset(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>);
+        void <a href='#SkRegion_Iterator_reset'>reset</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>);
         bool <a href='#SkRegion_Iterator_done'>done()</a> const;
         void <a href='#SkRegion_Iterator_next'>next()</a>;
         const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& <a href='#SkRegion_Iterator_rect'>rect()</a>;
@@ -514,7 +514,7 @@
     class <a href='#SkRegion_Spanerator'>Spanerator</a> {
     public:
         <a href='#SkRegion_Spanerator'>Spanerator</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>, int y, int left, int right);
-        bool next(int* left, int* right);
+        bool <a href='#SkRegion_Spanerator_next'>next</a>(int* left, int* right);
     };
 </pre>
 
@@ -818,7 +818,7 @@
 
 ### See Also
 
-operator!=(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='#SkRegion_equal1_operator_other'>other</a>) const <a href='#SkRegion_copy_operator'>operator=</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>)
+<a href='#SkRegion_notequal1_operator'>operator!=</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='#SkRegion_equal1_operator_other'>other</a>) const <a href='#SkRegion_copy_operator'>operator=</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>)
 
 <a name='SkRegion_notequal1_operator'></a>
 
@@ -857,7 +857,7 @@
 
 ### See Also
 
-operator==(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='#SkRegion_notequal1_operator_other'>other</a>) const <a href='#SkRegion_copy_operator'>operator=</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>)
+<a href='#SkRegion_equal1_operator'>operator==</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='#SkRegion_notequal1_operator_other'>other</a>) const <a href='#SkRegion_copy_operator'>operator=</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& <a href='SkRegion_Reference#Region'>region</a>)
 
 <a name='SkRegion_set'></a>
 
@@ -976,7 +976,7 @@
 
 ### See Also
 
-<a href='#SkRegion_isRect'>isRect</a> <a href='#SkRegion_isComplex'>isComplex</a> operator==(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& other) const
+<a href='#SkRegion_isRect'>isRect</a> <a href='#SkRegion_isComplex'>isComplex</a> <a href='#SkRegion_equal1_operator'>operator==</a>(const <a href='SkRegion_Reference#SkRegion'>SkRegion</a>& other) const
 
 <a name='SkRegion_isRect'></a>
 
diff --git a/site/user/api/SkSurface_Reference.md b/site/user/api/SkSurface_Reference.md
index dfd2b65..9b60db5 100644
--- a/site/user/api/SkSurface_Reference.md
+++ b/site/user/api/SkSurface_Reference.md
@@ -78,7 +78,7 @@
     <a href='undocumented#sk_sp'>sk_sp</a><<a href='SkSurface_Reference#SkSurface'>SkSurface</a>> <a href='#SkSurface_makeSurface'>makeSurface</a>(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& imageInfo);
     <a href='undocumented#sk_sp'>sk_sp</a><<a href='SkImage_Reference#SkImage'>SkImage</a>> <a href='#SkSurface_makeImageSnapshot'>makeImageSnapshot</a>();
     <a href='undocumented#sk_sp'>sk_sp</a><<a href='SkImage_Reference#SkImage'>SkImage</a>> <a href='#SkSurface_makeImageSnapshot'>makeImageSnapshot</a>(const <a href='SkIRect_Reference#SkIRect'>SkIRect</a>& bounds);
-    void draw(<a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a>* <a href='SkCanvas_Reference#Canvas'>canvas</a>, <a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y, const <a href='SkPaint_Reference#SkPaint'>SkPaint</a>* <a href='SkPaint_Reference#Paint'>paint</a>);
+    void <a href='#SkSurface_draw'>draw</a>(<a href='SkCanvas_Reference#SkCanvas'>SkCanvas</a>* <a href='SkCanvas_Reference#Canvas'>canvas</a>, <a href='undocumented#SkScalar'>SkScalar</a> x, <a href='undocumented#SkScalar'>SkScalar</a> y, const <a href='SkPaint_Reference#SkPaint'>SkPaint</a>* <a href='SkPaint_Reference#Paint'>paint</a>);
     bool <a href='#SkSurface_peekPixels'>peekPixels</a>(<a href='SkPixmap_Reference#SkPixmap'>SkPixmap</a>* <a href='SkPixmap_Reference#Pixmap'>pixmap</a>);
     bool <a href='#SkSurface_readPixels'>readPixels</a>(const <a href='SkPixmap_Reference#SkPixmap'>SkPixmap</a>& dst, int srcX, int srcY);
     bool <a href='#SkSurface_readPixels'>readPixels</a>(const <a href='SkImageInfo_Reference#SkImageInfo'>SkImageInfo</a>& dstInfo, void* dstPixels, size_t dstRowBytes,
@@ -90,9 +90,9 @@
     void <a href='#SkSurface_flush'>flush()</a>;
     <a href='undocumented#GrSemaphoresSubmitted'>GrSemaphoresSubmitted</a> <a href='#SkSurface_flushAndSignalSemaphores'>flushAndSignalSemaphores</a>(int numSemaphores,
                                                    <a href='undocumented#GrBackendSemaphore'>GrBackendSemaphore</a> signalSemaphores[]);
-    bool wait(int numSemaphores, const <a href='undocumented#GrBackendSemaphore'>GrBackendSemaphore</a>* waitSemaphores);
+    bool <a href='#SkSurface_wait'>wait</a>(int numSemaphores, const <a href='undocumented#GrBackendSemaphore'>GrBackendSemaphore</a>* waitSemaphores);
     bool <a href='#SkSurface_characterize'>characterize</a>(<a href='undocumented#SkSurfaceCharacterization'>SkSurfaceCharacterization</a>* characterization) const;
-    bool draw(<a href='undocumented#SkDeferredDisplayList'>SkDeferredDisplayList</a>* deferredDisplayList);
+    bool <a href='#SkSurface_draw'>draw</a>(<a href='undocumented#SkDeferredDisplayList'>SkDeferredDisplayList</a>* deferredDisplayList);
 };
 
 </pre>
@@ -798,7 +798,7 @@
 
 ### Example
 
-<div><fiddle-embed name="640321e8ecfb3f9329f3bc6e1f02485f" gpu="true" cpu="true"><div><a href='SkPaint_Reference#LCD_Text'>LCD text</a> takes advantage of raster striping to improve resolution. Only one of
+<div><fiddle-embed name="640321e8ecfb3f9329f3bc6e1f02485f" gpu="true" cpu="true"><div>LCD <a href='undocumented#Text'>text</a> takes advantage of raster striping to improve resolution. Only one of
 the four combinations is correct, depending on whether monitor LCD striping is
 horizontal or vertical, and whether the order of the stripes is red blue green
 or red green blue.
diff --git a/site/user/api/SkTextBlobBuilder_Reference.md b/site/user/api/SkTextBlobBuilder_Reference.md
index 2fe2395..e628275 100644
--- a/site/user/api/SkTextBlobBuilder_Reference.md
+++ b/site/user/api/SkTextBlobBuilder_Reference.md
@@ -39,9 +39,9 @@
 
 <a href='#SkTextBlobBuilder_RunBuffer'>RunBuffer</a> supplies storage for <a href='undocumented#Glyph'>Glyphs</a> and positions within a run.
 
-A run is a sequence of <a href='undocumented#Glyph'>Glyphs</a> sharing <a href='#Paint_Font_Metrics'>Paint_Font_Metrics</a> and positioning.
+A run is a sequence of <a href='undocumented#Glyph'>Glyphs</a> sharing <a href='#Font_Metrics'>Font_Metrics</a> and positioning.
 Each run may position its <a href='undocumented#Glyph'>Glyphs</a> in one of three ways:
-by specifying where the first <a href='undocumented#Glyph'>Glyph</a> is drawn, and allowing <a href='#Paint_Font_Metrics'>Paint_Font_Metrics</a> to
+by specifying where the first <a href='undocumented#Glyph'>Glyph</a> is drawn, and allowing <a href='#Font_Metrics'>Font_Metrics</a> to
 determine the advance to subsequent <a href='undocumented#Glyph'>Glyphs</a>; by specifying a baseline, and
 the position on that baseline for each <a href='undocumented#Glyph'>Glyph</a> in run; or by providing <a href='SkPoint_Reference#Point'>Point</a>
 array, one per <a href='undocumented#Glyph'>Glyph</a>.<table style='border-collapse: collapse; width: 62.5em'>
@@ -151,7 +151,7 @@
 
 ### Example
 
-<div><fiddle-embed name="a6b09a31f2877ba813c6a4e9e3fef1fa">
+<div><fiddle-embed name="34c37c0212cc0aef670d96945d08fe24">
 
 #### Example Output
 
@@ -183,7 +183,7 @@
 
 <a href='undocumented#Glyph'>Glyphs</a> share metrics in <a href='#SkTextBlobBuilder_allocRun_font'>font</a>.
 
-<a href='undocumented#Glyph'>Glyphs</a> are positioned on a baseline at (<a href='#SkTextBlobBuilder_allocRun_x'>x</a>, <a href='#SkTextBlobBuilder_allocRun_y'>y</a>), using <a href='#SkTextBlobBuilder_allocRun_font'>font</a> metrics to
+<a href='undocumented#Glyph'>Glyphs</a> are positioned on a baseline at (<a href='#SkTextBlobBuilder_allocRun_x'>x</a>, <a href='#SkTextBlobBuilder_allocRun_y'>y</a>), using  <a href='undocumented#Font_Metrics'>font metrics</a> to
 determine their relative placement.
 
 <a href='#SkTextBlobBuilder_allocRun_bounds'>bounds</a> defines an optional bounding box, used to suppress drawing when <a href='SkTextBlob_Reference#SkTextBlob'>SkTextBlob</a>
diff --git a/site/user/api/SkTextBlob_Reference.md b/site/user/api/SkTextBlob_Reference.md
index 085e9a3..dee6ea0 100644
--- a/site/user/api/SkTextBlob_Reference.md
+++ b/site/user/api/SkTextBlob_Reference.md
@@ -11,6 +11,8 @@
 
     const <a href='SkRect_Reference#SkRect'>SkRect</a>& <a href='#SkTextBlob_bounds'>bounds()</a> const;
     uint32_t <a href='#SkTextBlob_uniqueID'>uniqueID</a>() const;
+    int <a href='#SkTextBlob_getIntercepts'>getIntercepts</a>(const <a href='undocumented#SkScalar'>SkScalar</a> bounds[2], <a href='undocumented#SkScalar'>SkScalar</a> intervals[],
+                      const <a href='SkPaint_Reference#SkPaint'>SkPaint</a>* <a href='SkPaint_Reference#Paint'>paint</a> = nullptr) const;
     static <a href='undocumented#sk_sp'>sk_sp</a><<a href='SkTextBlob_Reference#SkTextBlob'>SkTextBlob</a>> <a href='#SkTextBlob_MakeFromText'>MakeFromText</a>(const void* <a href='undocumented#Text'>text</a>, size_t byteLength, const <a href='SkFont_Reference#SkFont'>SkFont</a>& <a href='SkFont_Reference#Font'>font</a>,
                                       <a href='undocumented#SkTextEncoding'>SkTextEncoding</a> encoding = <a href='undocumented#kUTF8_SkTextEncoding'>kUTF8_SkTextEncoding</a>);
     static <a href='undocumented#sk_sp'>sk_sp</a><<a href='SkTextBlob_Reference#SkTextBlob'>SkTextBlob</a>> <a href='#SkTextBlob_MakeFromString'>MakeFromString</a>(const char* <a href='undocumented#String'>string</a>, const <a href='SkFont_Reference#SkFont'>SkFont</a>& <a href='SkFont_Reference#Font'>font</a>,
@@ -45,7 +47,7 @@
 
 ### Example
 
-<div><fiddle-embed name="a22a490fd43bcc1cd3e26430debfb99e"></fiddle-embed></div>
+<div><fiddle-embed name="fb8b2502bbe52d2029aecdf569dd9fdb"></fiddle-embed></div>
 
 ### See Also
 
@@ -67,12 +69,56 @@
 
 ### Example
 
-<div><fiddle-embed name="fdd9c8c4470cc4f725af779e9d6db6e4"></fiddle-embed></div>
+<div><fiddle-embed name="6e12cceca981ddabc0fc18c380543f34"></fiddle-embed></div>
 
 ### See Also
 
 <a href='undocumented#SkRefCnt'>SkRefCnt</a>
 
+<a name='Text_Intercepts'></a>
+
+<a href='#Text_Blob_Text_Intercepts'>Text_Intercepts</a> describe the intersection of drawn <a href='undocumented#Text'>text</a> <a href='undocumented#Glyph'>Glyphs</a> with a pair
+of <a href='undocumented#Line'>lines</a> parallel to the <a href='undocumented#Text'>text</a> advance. <a href='#Text_Blob_Text_Intercepts'>Text_Intercepts</a> permits creating a
+underline that skips Descenders.
+
+<a name='SkTextBlob_getIntercepts'></a>
+
+---
+
+<pre style="padding: 1em 1em 1em 1em; width: 62.5em;background-color: #f0f0f0">
+int <a href='#SkTextBlob_getIntercepts'>getIntercepts</a>(const <a href='undocumented#SkScalar'>SkScalar</a> bounds[2], <a href='undocumented#SkScalar'>SkScalar</a> intervals[], const <a href='SkPaint_Reference#SkPaint'>SkPaint</a>* <a href='SkPaint_Reference#Paint'>paint</a> = nullptr) const;
+</pre>
+
+Returns the number of <a href='#SkTextBlob_getIntercepts_intervals'>intervals</a> that intersect <a href='#SkTextBlob_getIntercepts_bounds'>bounds</a>.
+<a href='#SkTextBlob_getIntercepts_bounds'>bounds</a> describes a pair of <a href='undocumented#Line'>lines</a> parallel to the <a href='undocumented#Text'>text</a> advance.
+The return count is zero or a multiple of two, and is at most twice the number of <a href='undocumented#Glyph'>glyphs</a> in
+the the blob.
+
+Pass nullptr for <a href='#SkTextBlob_getIntercepts_intervals'>intervals</a> to determine the <a href='undocumented#Size'>size</a> of the interval array.
+
+Runs within the blob that contain <a href='undocumented#SkRSXform'>SkRSXform</a> are ignored when computing intercepts.
+
+### Parameters
+
+<table>  <tr>    <td><a name='SkTextBlob_getIntercepts_bounds'><code><strong>bounds</strong></code></a></td>
+    <td>lower and upper <a href='undocumented#Line'>line</a> parallel to the advance</td>
+  </tr>
+  <tr>    <td><a name='SkTextBlob_getIntercepts_intervals'><code><strong>intervals</strong></code></a></td>
+    <td>returned intersections; may be nullptr</td>
+  </tr>
+  <tr>    <td><a name='SkTextBlob_getIntercepts_paint'><code><strong>paint</strong></code></a></td>
+    <td>specifies stroking, <a href='undocumented#SkPathEffect'>SkPathEffect</a> that affects the result; may be nullptr</td>
+  </tr>
+</table>
+
+### Return Value
+
+number of intersections; may be zero
+
+### Example
+
+<div><fiddle-embed name="e9d4eb8ece521b1329e7433d4b243fdf"></fiddle-embed></div>
+
 <a name='SkTextBlob_MakeFromText'></a>
 
 ---
@@ -82,13 +128,13 @@
                                       <a href='undocumented#SkTextEncoding'>SkTextEncoding</a> encoding = <a href='undocumented#kUTF8_SkTextEncoding'>kUTF8_SkTextEncoding</a>)
 </pre>
 
-Creates <a href='#Text_Blob'>Text_Blob</a> with a single run. <a href='#SkTextBlob_MakeFromText_text'>text</a> meaning depends on <a href='#Paint_Text_Encoding'>Paint_Text_Encoding</a>;
+Creates <a href='#Text_Blob'>Text_Blob</a> with a single run. <a href='#SkTextBlob_MakeFromText_text'>text</a> meaning depends on <a href='#Text_Encoding'>Text_Encoding</a>;
 by default, <a href='#SkTextBlob_MakeFromText_text'>text</a> is encoded as UTF-8.
 
-<a href='#SkTextBlob_MakeFromText_font'>font</a> contains attributes used to define the run <a href='#SkTextBlob_MakeFromText_text'>text</a>: <a href='undocumented#Typeface'>Typeface</a>, <a href='#Paint_Text_Size'>Paint_Text_Size</a>, <a href='#Paint_Text_Scale_X'>Paint_Text_Scale_X</a>,
-<a href='#Paint_Text_Skew_X'>Paint_Text_Skew_X</a>, <a href='#Paint_Hinting'>Paint_Hinting</a>, <a href='#Paint_Anti_Alias'>Anti_Alias</a>, <a href='#Paint_Fake_Bold'>Paint_Fake_Bold</a>,
-<a href='#Paint_Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a>, <a href='#Paint_Full_Hinting_Spacing'>Full_Hinting_Spacing</a>, <a href='#Paint_LCD_Text'>LCD_Text</a>, <a href='#Paint_Linear_Text'>Linear_Text</a>,
-and <a href='#Paint_Subpixel_Text'>Subpixel_Text</a>
+<a href='#SkTextBlob_MakeFromText_font'>font</a> contains attributes used to define the run <a href='#SkTextBlob_MakeFromText_text'>text</a>: <a href='undocumented#Typeface'>Typeface</a>, <a href='#Font_Size'>Font_Size</a>, <a href='#Font_Scale_X'>Font_Scale_X</a>,
+<a href='#Font_Skew_X'>Font_Skew_X</a>, <a href='#Font_Hinting'>Font_Hinting</a>, <a href='#Paint_Anti_Alias'>Paint_Anti_Alias</a>, <a href='#Font_Embolden'>Font_Embolden</a>, <a href='#Font_Force_Hinting'>Font_Force_Hinting</a>,
+<a href='#Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a>, <a href='#Font_Hinting_Spacing'>Font_Hinting_Spacing</a>, <a href='#Font_Anti_Alias'>Font_Anti_Alias</a>, <a href='#Font_Linear'>Font_Linear</a>,
+and <a href='#Font_Subpixel'>Font_Subpixel</a>
 .
 
 ### Parameters
@@ -130,13 +176,13 @@
                                         <a href='undocumented#SkTextEncoding'>SkTextEncoding</a> encoding = <a href='undocumented#kUTF8_SkTextEncoding'>kUTF8_SkTextEncoding</a>)
 </pre>
 
-Creates <a href='#Text_Blob'>Text_Blob</a> with a single run. <a href='#SkTextBlob_MakeFromString_string'>string</a> meaning depends on <a href='#Paint_Text_Encoding'>Paint_Text_Encoding</a>;
+Creates <a href='#Text_Blob'>Text_Blob</a> with a single run. <a href='#SkTextBlob_MakeFromString_string'>string</a> meaning depends on <a href='#Text_Encoding'>Text_Encoding</a>;
 by default, <a href='#SkTextBlob_MakeFromString_string'>string</a> is encoded as UTF-8.
 
-<a href='#SkTextBlob_MakeFromString_font'>font</a> contains <a href='#Paint_Font_Metrics'>Paint_Font_Metrics</a> used to define the run <a href='undocumented#Text'>text</a>: <a href='undocumented#Typeface'>Typeface</a>, <a href='#Paint_Text_Size'>Paint_Text_Size</a>, <a href='#Paint_Text_Scale_X'>Paint_Text_Scale_X</a>,
-<a href='#Paint_Text_Skew_X'>Paint_Text_Skew_X</a>, <a href='#Paint_Hinting'>Paint_Hinting</a>, <a href='#Paint_Anti_Alias'>Anti_Alias</a>, <a href='#Paint_Fake_Bold'>Paint_Fake_Bold</a>,
-<a href='#Paint_Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a>, <a href='#Paint_Full_Hinting_Spacing'>Full_Hinting_Spacing</a>, <a href='#Paint_LCD_Text'>LCD_Text</a>, <a href='#Paint_Linear_Text'>Linear_Text</a>,
-and <a href='#Paint_Subpixel_Text'>Subpixel_Text</a>
+<a href='#SkTextBlob_MakeFromString_font'>font</a> contains <a href='#Font_Metrics'>Font_Metrics</a> used to define the run <a href='undocumented#Text'>text</a>: <a href='undocumented#Typeface'>Typeface</a>, <a href='#Font_Size'>Font_Size</a>, <a href='#Font_Scale_X'>Font_Scale_X</a>,
+<a href='#Font_Skew_X'>Font_Skew_X</a>, <a href='#Font_Hinting'>Font_Hinting</a>, <a href='#Paint_Anti_Alias'>Paint_Anti_Alias</a>, <a href='#Font_Embolden'>Font_Embolden</a>, <a href='#Font_Force_Hinting'>Font_Force_Hinting</a>,
+<a href='#Font_Embedded_Bitmaps'>Font_Embedded_Bitmaps</a>, <a href='#Font_Hinting_Spacing'>Font_Hinting_Spacing</a>, <a href='#Font_Anti_Alias'>Font_Anti_Alias</a>, <a href='#Font_Linear'>Font_Linear</a>,
+and <a href='#Font_Subpixel'>Font_Subpixel</a>
 .
 
 ### Parameters
diff --git a/site/user/api/catalog.htm b/site/user/api/catalog.htm
index ed1ad79..2c2431b 100644
--- a/site/user/api/catalog.htm
+++ b/site/user/api/catalog.htm
@@ -1401,8 +1401,8 @@
         "stdout": "[  1.0000   0.0000   3.0000][  0.0000   2.0000   4.0000][  0.0000   0.0000   1.0000]\\n"
     },
         "SkPaint_containsText": {
-    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    const uint16_t goodGlyph = 511;\n    const uint16_t zeroGlyph = 0;\n    const uint16_t badGlyph = 65535; // larger than glyph count in font\n    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);\n    SkDebugf(\"0x%04x %c= has glyph\\n\", goodGlyph,\n            paint.containsText(&goodGlyph, 2) ? '=' : '!');\n    SkDebugf(\"0x%04x %c= has glyph\\n\", zeroGlyph,\n            paint.containsText(&zeroGlyph, 2) ? '=' : '!');\n    SkDebugf(\"0x%04x %c= has glyph\\n\", badGlyph,\n            paint.containsText(&badGlyph, 2) ? '=' : '!');\n}",
-    "hash": "083557b6f653d6fc00a34e01f87b74ff",
+    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    const uint16_t goodGlyph = 511;\n    const uint16_t zeroGlyph = 0;\n    const uint16_t badGlyph = 65535; // larger than glyph count in font\n    paint.setTextEncoding(kGlyphID_SkTextEncoding);\n    SkDebugf(\"0x%04x %c= has glyph\\n\", goodGlyph,\n            paint.containsText(&goodGlyph, 2) ? '=' : '!');\n    SkDebugf(\"0x%04x %c= has glyph\\n\", zeroGlyph,\n            paint.containsText(&zeroGlyph, 2) ? '=' : '!');\n    SkDebugf(\"0x%04x %c= has glyph\\n\", badGlyph,\n            paint.containsText(&badGlyph, 2) ? '=' : '!');\n}",
+    "hash": "6a68cb3c8b81a5976c81ee004f559247",
     "file": "SkPaint_Reference",
     "name": "SkPaint::containsText",
         "stdout": "0x01ff == has glyph\\n0x0000 != has glyph\\n0xffff == has glyph\\n"
@@ -1491,13 +1491,6 @@
     "name": "SkPaint::getFlags",
         "stdout": "(SkPaint::kAntiAlias_Flag & paint.getFlags()) != 0\\n"
     },
-        "SkPaint_getFontBounds": {
-    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    SkFontMetrics fm;\n    paint.getFontMetrics(&fm);\n    SkRect fb = paint.getFontBounds();\n    SkDebugf(\"metrics bounds = { %g, %g, %g, %g }\\n\", fm.fXMin, fm.fTop, fm.fXMax, fm.fBottom );\n    SkDebugf(\"font bounds    = { %g, %g, %g, %g }\\n\", fb.fLeft, fb.fTop, fb.fRight, fm.fBottom );\n}",
-    "hash": "f29d005a75efd4746c6744004a0cb421",
-    "file": "SkPaint_Reference",
-    "name": "SkPaint::getFontBounds",
-        "stdout": "metrics bounds = { -12.2461, -14.7891, 21.5215, 5.55469 }\\nfont bounds    = { -12.2461, -14.7891, 21.5215, 5.55469 }\\n"
-    },
         "SkPaint_getFontSpacing": {
     "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    for (SkScalar textSize : { 12, 18, 24, 32 } ) {\n        paint.setTextSize(textSize);\n        SkDebugf(\"textSize: %g fontSpacing: %g\\n\", textSize, paint.getFontSpacing());\n    }\n}",
     "hash": "424741e26e1b174e43087d67422ce14f",
@@ -1583,11 +1576,11 @@
         "stdout": "SkPaint::kFill_Style == paint.getStyle()\\n"
     },
         "SkPaint_getTextEncoding": {
-    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    SkDebugf(\"kUTF8_TextEncoding %c= text encoding\\n\",\n            SkPaint::kUTF8_TextEncoding == paint.getTextEncoding() ? '=' : '!');\n    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);\n    SkDebugf(\"kGlyphID_TextEncoding %c= text encoding\\n\",\n            SkPaint::kGlyphID_TextEncoding == paint.getTextEncoding() ? '=' : '!');\n}",
-    "hash": "c6cc2780a9828b3af8c4621c12b29a1b",
+    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    SkDebugf(\"kUTF8_SkTextEncoding %c= text encoding\\n\",\n            kUTF8_SkTextEncoding == paint.getTextEncoding() ? '=' : '!');\n    paint.setTextEncoding(kGlyphID_SkTextEncoding);\n    SkDebugf(\"kGlyphID_SkTextEncoding %c= text encoding\\n\",\n            kGlyphID_SkTextEncoding == paint.getTextEncoding() ? '=' : '!');\n}",
+    "hash": "0d21e968e9a4c78c902ae3ef494941a0",
     "file": "SkPaint_Reference",
     "name": "SkPaint::getTextEncoding",
-        "stdout": "kUTF8_TextEncoding == text encoding\\nkGlyphID_TextEncoding == text encoding\\n"
+        "stdout": "kUTF8_SkTextEncoding == text encoding\\nkGlyphID_SkTextEncoding == text encoding\\n"
     },
         "SkPaint_getTextScaleX": {
     "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    SkDebugf(\"1 %c= default text scale x\\n\", 1 == paint.getTextScaleX() ? '=' : '!');\n}",
@@ -1891,8 +1884,8 @@
         "stdout": "paint1 == paint2\\n"
     },
         "SkPaint_setTextEncoding": {
-    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    paint.setTextEncoding((SkPaint::TextEncoding) 4);\n    SkDebugf(\"4 %c= text encoding\\n\", (SkPaint::TextEncoding) 4 == paint.getTextEncoding() ? '=' : '!');\n}",
-    "hash": "6d9ffdd3c5543e9f12972a06dd4a0ce5",
+    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    paint.setTextEncoding((SkTextEncoding) 4);\n    SkDebugf(\"4 %c= text encoding\\n\", (SkTextEncoding) 4 == paint.getTextEncoding() ? '=' : '!');\n}",
+    "hash": "a5d1ba0dbf42afb797ffdb07647b5cb9",
     "file": "SkPaint_Reference",
     "name": "SkPaint::setTextEncoding",
         "stdout": "4 != text encoding\\n"
@@ -3571,8 +3564,8 @@
         "stdout": "blob equals nullptr"
     },
         "SkTextBlobBuilder_make": {
-    "code": "void draw(SkCanvas* canvas) {\n    SkTextBlobBuilder builder;\n    sk_sp<SkTextBlob> blob = builder.make();\n    SkDebugf(\"blob \" \"%s\" \" nullptr\\n\", blob == nullptr ? \"equals\" : \"does not equal\");\n    SkPaint paint;\n    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);\n    SkFont font;\n    paint.textToGlyphs(\"x\", 1, builder.allocRun(font, 1, 20, 20).glyphs);\n    blob = builder.make();\n    SkDebugf(\"blob \" \"%s\" \" nullptr\\n\", blob == nullptr ? \"equals\" : \"does not equal\");\n    blob = builder.make();\n    SkDebugf(\"blob \" \"%s\" \" nullptr\\n\", blob == nullptr ? \"equals\" : \"does not equal\");\n}",
-    "hash": "a6b09a31f2877ba813c6a4e9e3fef1fa",
+    "code": "void draw(SkCanvas* canvas) {\n    SkTextBlobBuilder builder;\n    sk_sp<SkTextBlob> blob = builder.make();\n    SkDebugf(\"blob \" \"%s\" \" nullptr\\n\", blob == nullptr ? \"equals\" : \"does not equal\");\n    SkPaint paint;\n    paint.setTextEncoding(kGlyphID_SkTextEncoding);\n    SkFont font;\n    paint.textToGlyphs(\"x\", 1, builder.allocRun(font, 1, 20, 20).glyphs);\n    blob = builder.make();\n    SkDebugf(\"blob \" \"%s\" \" nullptr\\n\", blob == nullptr ? \"equals\" : \"does not equal\");\n    blob = builder.make();\n    SkDebugf(\"blob \" \"%s\" \" nullptr\\n\", blob == nullptr ? \"equals\" : \"does not equal\");\n}",
+    "hash": "34c37c0212cc0aef670d96945d08fe24",
     "file": "SkTextBlobBuilder_Reference",
     "name": "SkTextBlobBuilder::make()",
         "stdout": "blob equals nullptr\\nblob does not equal nullptr\\nblob equals nullptr\\n"
@@ -4252,10 +4245,10 @@
     "name": "Stroke_Width"
 },
     "Paint_Text_Encoding": {
-    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    const char hello8[] = \"Hello\" \"\\xE2\" \"\\x98\" \"\\xBA\";\n    const uint16_t hello16[] = { 'H', 'e', 'l', 'l', 'o', 0x263A };\n    const uint32_t hello32[] = { 'H', 'e', 'l', 'l', 'o', 0x263A };\n    paint.setTextSize(24);\n    canvas->drawText(hello8, sizeof(hello8) - 1, 10, 30, paint);\n    paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);\n    canvas->drawText(hello16, sizeof(hello16), 10, 60, paint);\n    paint.setTextEncoding(SkPaint::kUTF32_TextEncoding);\n    canvas->drawText(hello32, sizeof(hello32), 10, 90, paint);\n    uint16_t glyphs[SK_ARRAY_COUNT(hello32)];\n    paint.textToGlyphs(hello32, sizeof(hello32), glyphs);\n    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);\n    canvas->drawText(glyphs, sizeof(glyphs), 10, 120, paint);\n}\n",
+    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    const char hello8[] = \"Hello\" \"\\xE2\" \"\\x98\" \"\\xBA\";\n    const uint16_t hello16[] = { 'H', 'e', 'l', 'l', 'o', 0x263A };\n    const uint32_t hello32[] = { 'H', 'e', 'l', 'l', 'o', 0x263A };\n    paint.setTextSize(24);\n    canvas->drawText(hello8, sizeof(hello8) - 1, 10, 30, paint);\n    paint.setTextEncoding(kUTF16_SkTextEncoding);\n    canvas->drawText(hello16, sizeof(hello16), 10, 60, paint);\n    paint.setTextEncoding(kUTF32_SkTextEncoding);\n    canvas->drawText(hello32, sizeof(hello32), 10, 90, paint);\n    uint16_t glyphs[SK_ARRAY_COUNT(hello32)];\n    paint.textToGlyphs(hello32, sizeof(hello32), glyphs);\n    paint.setTextEncoding(kGlyphID_SkTextEncoding);\n    canvas->drawText(glyphs, sizeof(glyphs), 10, 120, paint);\n}\n",
     "width": 256,
     "height": 128,
-    "hash": "b29294e7f29d160a1b46abf2dcec9d2a",
+    "hash": "767fa4e7b6300e16a419f9881f0f9d3d",
     "file": "SkPaint_Reference",
     "name": "Text_Encoding"
 },
@@ -5244,10 +5237,10 @@
     "name": "SkCanvas::drawText"
 },
     "SkCanvas_drawTextBlob": {
-    "code": "void draw(SkCanvas* canvas) {\n    SkTextBlobBuilder textBlobBuilder;\n    const char bunny[] = \"/(^x^)\\\\\";\n    const int len = sizeof(bunny) - 1;\n    uint16_t glyphs[len];\n    SkPaint paint;\n    paint.textToGlyphs(bunny, len, glyphs);\n    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);\n    SkFont font;\n    int runs[] = { 3, 1, 3 };\n    SkPoint textPos = { 20, 100 };\n    int glyphIndex = 0;\n    for (auto runLen : runs) {\n        font.setSize(1 == runLen ? 20 : 50);\n        const SkTextBlobBuilder::RunBuffer& run =\n                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);\n        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);\n        paint.setTextSize(1 == runLen ? 20 : 50);\n        textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);\n        glyphIndex += runLen;\n    }\n    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();\n    paint.reset();\n    canvas->drawTextBlob(blob.get(), 0, 0, paint);\n}\n",
+    "code": "void draw(SkCanvas* canvas) {\n    SkTextBlobBuilder textBlobBuilder;\n    const char bunny[] = \"/(^x^)\\\\\";\n    const int len = sizeof(bunny) - 1;\n    uint16_t glyphs[len];\n    SkPaint paint;\n    paint.textToGlyphs(bunny, len, glyphs);\n    paint.setTextEncoding(kGlyphID_SkTextEncoding);\n    SkFont font;\n    int runs[] = { 3, 1, 3 };\n    SkPoint textPos = { 20, 100 };\n    int glyphIndex = 0;\n    for (auto runLen : runs) {\n        font.setSize(1 == runLen ? 20 : 50);\n        const SkTextBlobBuilder::RunBuffer& run =\n                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);\n        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);\n        paint.setTextSize(1 == runLen ? 20 : 50);\n        textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);\n        glyphIndex += runLen;\n    }\n    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();\n    paint.reset();\n    canvas->drawTextBlob(blob.get(), 0, 0, paint);\n}\n",
     "width": 256,
     "height": 120,
-    "hash": "a207bbd7317bfbdadbda8af884631e46",
+    "hash": "005502b502c1282cb8d306d6c8d998fb",
     "file": "SkCanvas_Reference",
     "name": "SkCanvas::drawTextBlob"
 },
@@ -7452,10 +7445,10 @@
     "name": "SkPaint::getTextWidths"
 },
     "SkPaint_glyphsToUnichars": {
-    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    const char hello[] = \"Hello!\";\n    const int count = sizeof(hello) - 1;\n    SkGlyphID glyphs[count];\n    if (count != paint.textToGlyphs(hello, count, glyphs)) {\n        return;\n    }\n    SkUnichar unichars[count];\n    paint.glyphsToUnichars(glyphs, count, unichars);\n    paint.setTextEncoding(SkPaint::kUTF32_TextEncoding);\n    canvas->drawText(unichars, sizeof(unichars), 10, 30, paint);\n}\n",
+    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    const char hello[] = \"Hello!\";\n    const int count = sizeof(hello) - 1;\n    SkGlyphID glyphs[count];\n    if (count != paint.textToGlyphs(hello, count, glyphs)) {\n        return;\n    }\n    SkUnichar unichars[count];\n    paint.glyphsToUnichars(glyphs, count, unichars);\n    paint.setTextEncoding(kUTF32_SkTextEncoding);\n    canvas->drawText(unichars, sizeof(unichars), 10, 30, paint);\n}\n",
     "width": 256,
     "height": 64,
-    "hash": "c12686b0b3e0a87d0a248bbfc57e9492",
+    "hash": "79c550ec6c34054ab60fbcd1b81adc03",
     "file": "SkPaint_Reference",
     "name": "SkPaint::glyphsToUnichars"
 },
@@ -7556,10 +7549,10 @@
     "name": "SkPaint::setTypeface"
 },
     "SkPaint_textToGlyphs": {
-    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };\n    std::vector<SkGlyphID> glyphs;\n    int count = paint.textToGlyphs(utf8, sizeof(utf8), nullptr);\n    glyphs.resize(count);\n    (void) paint.textToGlyphs(utf8, sizeof(utf8), &glyphs.front());\n    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);\n    paint.setTextSize(32);\n    canvas->drawText(&glyphs.front(), glyphs.size() * sizeof(SkGlyphID), 10, 40, paint);\n}\n",
+    "code": "void draw(SkCanvas* canvas) {\n    SkPaint paint;\n    const uint8_t utf8[] = { 0x24, 0xC2, 0xA2, 0xE2, 0x82, 0xAC, 0xC2, 0xA5, 0xC2, 0xA3 };\n    std::vector<SkGlyphID> glyphs;\n    int count = paint.textToGlyphs(utf8, sizeof(utf8), nullptr);\n    glyphs.resize(count);\n    (void) paint.textToGlyphs(utf8, sizeof(utf8), &glyphs.front());\n    paint.setTextEncoding(kGlyphID_SkTextEncoding);\n    paint.setTextSize(32);\n    canvas->drawText(&glyphs.front(), glyphs.size() * sizeof(SkGlyphID), 10, 40, paint);\n}\n",
     "width": 256,
     "height": 64,
-    "hash": "343e9471a7f7b5f09abdc3b44983433b",
+    "hash": "d11136d8a74f63009da2a7f550710823",
     "file": "SkPaint_Reference",
     "name": "SkPaint::textToGlyphs"
 },
@@ -9348,10 +9341,10 @@
     "name": "SkTextBlob::MakeFromText"
 },
     "SkTextBlob_bounds": {
-    "code": "void draw(SkCanvas* canvas) {\n    SkTextBlobBuilder textBlobBuilder;\n    const char bunny[] = \"/(^x^)\\\\\";\n    const int len = sizeof(bunny) - 1;\n    uint16_t glyphs[len];\n    SkPaint paint;\n    paint.textToGlyphs(bunny, len, glyphs);\n    paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);\n    SkFont font;\n    int runs[] = { 3, 1, 3 };\n    SkPoint textPos = { 20, 50 };\n    int glyphIndex = 0;\n    for (auto runLen : runs) {\n        font.setSize(1 == runLen ? 20 : 50);\n        paint.setTextSize(1 == runLen ? 20 : 50);\n        const SkTextBlobBuilder::RunBuffer& run =\n                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);\n        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);\n        textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);\n        glyphIndex += runLen;\n    }\n    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();\n    canvas->drawTextBlob(blob.get(), 0, 0, paint);\n    paint.setStyle(SkPaint::kStroke_Style);\n    canvas->drawRect(blob->bounds(), paint);\n}",
+    "code": "void draw(SkCanvas* canvas) {\n    SkTextBlobBuilder textBlobBuilder;\n    const char bunny[] = \"/(^x^)\\\\\";\n    const int len = sizeof(bunny) - 1;\n    uint16_t glyphs[len];\n    SkPaint paint;\n    paint.textToGlyphs(bunny, len, glyphs);\n    paint.setTextEncoding(kGlyphID_SkTextEncoding);\n    SkFont font;\n    int runs[] = { 3, 1, 3 };\n    SkPoint textPos = { 20, 50 };\n    int glyphIndex = 0;\n    for (auto runLen : runs) {\n        font.setSize(1 == runLen ? 20 : 50);\n        paint.setTextSize(1 == runLen ? 20 : 50);\n        const SkTextBlobBuilder::RunBuffer& run =\n                textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);\n        memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);\n        textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);\n        glyphIndex += runLen;\n    }\n    sk_sp<const SkTextBlob> blob = textBlobBuilder.make();\n    canvas->drawTextBlob(blob.get(), 0, 0, paint);\n    paint.setStyle(SkPaint::kStroke_Style);\n    canvas->drawRect(blob->bounds(), paint);\n}",
     "width": 256,
     "height": 70,
-    "hash": "a22a490fd43bcc1cd3e26430debfb99e",
+    "hash": "fb8b2502bbe52d2029aecdf569dd9fdb",
     "file": "SkTextBlob_Reference",
     "name": "SkTextBlob::bounds()"
 },
@@ -9372,10 +9365,10 @@
     "name": "SkTextBlob::serialize_2"
 },
     "SkTextBlob_uniqueID": {
-    "code": "void draw(SkCanvas* canvas) {\n    for (int index = 0; index < 2; ++index) {\n        SkTextBlobBuilder textBlobBuilder;\n        const char bunny[] = \"/(^x^)\\\\\";\n        const int len = sizeof(bunny) - 1;\n        uint16_t glyphs[len];\n        SkPaint paint;\n        paint.textToGlyphs(bunny, len, glyphs);\n        paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);\n        paint.setTextScaleX(0.5);\n        SkFont font;\n        font.setScaleX(0.5);\n        int runs[] = { 3, 1, 3 };\n        SkPoint textPos = { 20, 50 };\n        int glyphIndex = 0;\n        for (auto runLen : runs) {\n            font.setSize(1 == runLen ? 20 : 50);\n            paint.setTextSize(1 == runLen ? 20 : 50);\n            const SkTextBlobBuilder::RunBuffer& run =\n                    textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);\n            memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);\n            textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);\n            glyphIndex += runLen;\n        }\n        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();\n        paint.reset();\n        canvas->drawTextBlob(blob.get(), 0, 0, paint);\n        std::string id = \"unique ID:\" + std::to_string(blob->uniqueID());\n        canvas->drawString(id.c_str(), 30, blob->bounds().fBottom + 15, paint);\n        canvas->translate(blob->bounds().fRight + 10, 0);\n    }\n}",
+    "code": "void draw(SkCanvas* canvas) {\n    for (int index = 0; index < 2; ++index) {\n        SkTextBlobBuilder textBlobBuilder;\n        const char bunny[] = \"/(^x^)\\\\\";\n        const int len = sizeof(bunny) - 1;\n        uint16_t glyphs[len];\n        SkPaint paint;\n        paint.textToGlyphs(bunny, len, glyphs);\n        paint.setTextEncoding(kGlyphID_SkTextEncoding);\n        paint.setTextScaleX(0.5);\n        SkFont font;\n        font.setScaleX(0.5);\n        int runs[] = { 3, 1, 3 };\n        SkPoint textPos = { 20, 50 };\n        int glyphIndex = 0;\n        for (auto runLen : runs) {\n            font.setSize(1 == runLen ? 20 : 50);\n            paint.setTextSize(1 == runLen ? 20 : 50);\n            const SkTextBlobBuilder::RunBuffer& run =\n                    textBlobBuilder.allocRun(font, runLen, textPos.fX, textPos.fY);\n            memcpy(run.glyphs, &glyphs[glyphIndex], sizeof(glyphs[0]) * runLen);\n            textPos.fX += paint.measureText(&glyphs[glyphIndex], sizeof(glyphs[0]) * runLen, nullptr);\n            glyphIndex += runLen;\n        }\n        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();\n        paint.reset();\n        canvas->drawTextBlob(blob.get(), 0, 0, paint);\n        std::string id = \"unique ID:\" + std::to_string(blob->uniqueID());\n        canvas->drawString(id.c_str(), 30, blob->bounds().fBottom + 15, paint);\n        canvas->translate(blob->bounds().fRight + 10, 0);\n    }\n}",
     "width": 256,
     "height": 256,
-    "hash": "fdd9c8c4470cc4f725af779e9d6db6e4",
+    "hash": "6e12cceca981ddabc0fc18c380543f34",
     "file": "SkTextBlob_Reference",
     "name": "SkTextBlob::uniqueID"
 },
diff --git a/site/user/build.md b/site/user/build.md
index 31216fd..c44519c 100644
--- a/site/user/build.md
+++ b/site/user/build.md
@@ -257,8 +257,8 @@
 
 You can then pass the VC and SDK paths to GN by setting your GN args:
 
-    win_vc = "C:\toolchain\depot_tools\win_toolchain\vs_files\5454e45bf3764c03d3fc1024b3bf5bc41e3ab62c\VC"
-    win_sdk = "C:\toolchain\depot_tools\win_toolchain\vs_files\5454e45bf3764c03d3fc1024b3bf5bc41e3ab62c\win_sdk"
+    win_vc = "C:\toolchain\VC"
+    win_sdk = "C:\toolchain\win_sdk"
 
 This toolchain is the only way we support 32-bit builds, by also setting `target_cpu="x86"`.
 There is also a corresponding 2015 toolchain, downloaded via `infra/bots/assets/win_toolchain_2015`.
@@ -297,6 +297,23 @@
 of inactive code blocks based on preprocessor definitions from the selected
 solution configuration.
 
+Windows ARM64
+-------------
+
+There is early, experimental support for [Windows 10 on ARM](https://docs.microsoft.com/en-us/windows/arm/).
+This currently requires (a recent version of) MSVC, and the `Visual C++ compilers and libraries for ARM64`
+individual component in the Visual Studio Installer. For Googlers, the win_toolchain asset includes the
+ARM64 compiler.
+
+To use that toolchain, set the `target_cpu` GN argument to `"arm64"`. Note that OpenGL is not supported
+by Windows 10 on ARM, so Skia's GL backends are stubbed out, and will not work. ANGLE is supported:
+
+    bin/gn gen out/win-arm64 --args='target_cpu="arm64" skia_use_angle=true'
+
+This will produce a build of Skia that can use the software or ANGLE backends, in DM. Viewer only works
+when launched with `--backend angle`, because the software backend tries to use OpenGL to display the
+window contents.
+
 CMake
 -----
 
diff --git a/site/user/modules/canvaskit.md b/site/user/modules/canvaskit.md
index 09d831e..c079729 100644
--- a/site/user/modules/canvaskit.md
+++ b/site/user/modules/canvaskit.md
@@ -55,7 +55,7 @@
   <figure>
     <canvas id=patheffect width=400 height=400></canvas>
     <figcaption>
-      <a href="https://jsfiddle.skia.org/canvaskit/bb98ad306a0c826b6dc8e8b8f709fca75fee95210c529679def945f0be7ed90c"
+      <a href="https://jsfiddle.skia.org/canvaskit/28004d8841e7e497013263598241a3c1edc21dc1cf87a679abba307f39fa5fe6"
           target=_blank rel=noopener>
         Star JSFiddle</a>
     </figcaption>
@@ -96,7 +96,7 @@
   var locate_file = '';
   if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') {
     console.log('WebAssembly is supported!');
-    locate_file = 'https://storage.googleapis.com/skia-cdn/canvaskit-wasm/0.2.1/bin/';
+    locate_file = 'https://unpkg.com/canvaskit-wasm@0.3.0/bin/';
   } else {
     console.log('WebAssembly is not supported (yet) on this browser.');
     document.getElementById('demo').innerHTML = "<div>WASM not supported by your browser. Try a recent version of Chrome, Firefox, Edge, or Safari.</div>";
@@ -113,7 +113,6 @@
   CanvasKitInit({
     locateFile: (file) => locate_file + file,
   }).then((CK) => {
-    CK.initFonts();
     CanvasKit = CK;
     DrawingExample(CanvasKit);
     InkExample(CanvasKit);
@@ -354,4 +353,4 @@
 
 Download
 --------
-Work is underway on an npm download. Check back soon.
+Get [CanvasKit on NPM](https://www.npmjs.com/package/canvaskit-wasm)
diff --git a/site/user/modules/pathkit.md b/site/user/modules/pathkit.md
index 3859c95..b081176 100644
--- a/site/user/modules/pathkit.md
+++ b/site/user/modules/pathkit.md
@@ -40,10 +40,10 @@
   let s = document.createElement('script');
   if (window.WebAssembly && typeof window.WebAssembly.compile === 'function') {
     console.log('WebAssembly is supported! Using the wasm version of PathKit');
-    window.__pathkit_locate_file = 'https://unpkg.com/experimental-pathkit-wasm@0.3.1/bin/';
+    window.__pathkit_locate_file = 'https://unpkg.com/pathkit-wasm@0.5.0/bin/';
   } else {
     console.log('WebAssembly is not supported (yet) on this browser. Using the asmjs version of PathKit');
-    window.__pathkit_locate_file = 'https://unpkg.com/experimental-pathkit-asmjs@0.3.1/bin/';
+    window.__pathkit_locate_file = 'https://unpkg.com/pathkit-asmjs@0.5.0/bin/';
   }
   s.src = window.__pathkit_locate_file+'pathkit.js';
   s.onload = () => {
diff --git a/site/user/modules/skottie.md b/site/user/modules/skottie.md
index f7a2df2..a46578b 100644
--- a/site/user/modules/skottie.md
+++ b/site/user/modules/skottie.md
@@ -17,38 +17,30 @@
 
 Here are some test samples rendering with Skia's animation player:
 
-<a href="https://skottie.skia.org/e6741dda67629da1f80c254dad3df865"> <video
-id="video" muted="" autoplay="" title="lottie" loop=""
-src="https://skottie.skia.org/_/i/e6741dda67629da1f80c254dad3df865" width="200"
-height="200"> </video> </a> 
-<a href="https://skottie.skia.org/ffea72cf6be48fa061671c124ed7789c"> <video
-id="video" muted="" autoplay="" title="lottie" loop=""
-src="https://skottie.skia.org/_/i/ffea72cf6be48fa061671c124ed7789c" width="200"
-height="200"> </video> </a> 
-<a href="https://skottie.skia.org/00e850cdbed7304985eaefe98a4e8a9c"> <video
-id="video" muted="" autoplay="" title="lottie" loop=""
-src="https://skottie.skia.org/_/i/00e850cdbed7304985eaefe98a4e8a9c" width="200"
-height="200"> </video> </a> 
-<a href="https://skottie.skia.org/e1aca009d5ebec9bd122b87b018bb673"> <video
-id="video" muted="" autoplay="" title="lottie" loop=""
-src="https://skottie.skia.org/_/i/e1aca009d5ebec9bd122b87b018bb673" width="200"
-height="200"> </video> </a> 
-<a href="https://skottie.skia.org/821fd79dd7437b97ba891e7a00970a06"> <video
-id="video" muted="" autoplay="" title="lottie" loop=""
-src="https://skottie.skia.org/_/i/821fd79dd7437b97ba891e7a00970a06" width="200"
-height="200"> </video> </a> 
-<a href="https://skottie.skia.org/ad63f250084685c96edd9b52ae2f436b"> <video
-id="video" muted="" autoplay="" title="lottie" loop=""
-src="https://skottie.skia.org/_/i/ad63f250084685c96edd9b52ae2f436b" width="200"
-height="200"> </video> </a>
-<a href="https://skottie.skia.org/40f78ddc751c16348a08e1d61d3e78b1"> <video
-id="video" muted="" autoplay="" title="lottie" loop=""
-src="https://skottie.skia.org/_/i/40f78ddc751c16348a08e1d61d3e78b1" width="200"
-height="200"> </video> </a>
-<a href="https://skottie.skia.org/fc42db7c75741437b5cb0e90b3febc65"> <video
-id="video" muted="" autoplay="" title="lottie" loop=""
-src="https://skottie.skia.org/_/i/fc42db7c75741437b5cb0e90b3febc65" width="200"
-height="200"> </video> </a>
+<a href="https://skottie.skia.org/e6741dda67629da1f80c254dad3df865">
+  <skottie-inline-sk src="https://skottie.skia.org/_/j/e6741dda67629da1f80c254dad3df865" width=200 height=200></skottie-inline-sk>
+</a>
+<a href="https://skottie.skia.org/ffea72cf6be48fa061671c124ed7789c">
+  <skottie-inline-sk src="https://skottie.skia.org/_/j/ffea72cf6be48fa061671c124ed7789c" width=200 height=200></skottie-inline-sk>
+</a>
+<a href="https://skottie.skia.org/00e850cdbed7304985eaefe98a4e8a9c">
+  <skottie-inline-sk src="https://skottie.skia.org/_/j/00e850cdbed7304985eaefe98a4e8a9c" width=200 height=200></skottie-inline-sk>
+</a>
+<a href="https://skottie.skia.org/e1aca009d5ebec9bd122b87b018bb673">
+  <skottie-inline-sk src="https://skottie.skia.org/_/j/e1aca009d5ebec9bd122b87b018bb673" width=200 height=200></skottie-inline-sk>
+</a>
+<a href="https://skottie.skia.org/821fd79dd7437b97ba891e7a00970a06">
+  <skottie-inline-sk src="https://skottie.skia.org/_/j/821fd79dd7437b97ba891e7a00970a06" width=200 height=200></skottie-inline-sk>
+</a>
+<a href="https://skottie.skia.org/ad63f250084685c96edd9b52ae2f436b">
+  <skottie-inline-sk src="https://skottie.skia.org/_/j/ad63f250084685c96edd9b52ae2f436b" width=200 height=200></skottie-inline-sk>
+</a>
+<a href="https://skottie.skia.org/40f78ddc751c16348a08e1d61d3e78b1">
+  <skottie-inline-sk src="https://skottie.skia.org/_/j/40f78ddc751c16348a08e1d61d3e78b1" width=200 height=200></skottie-inline-sk>
+</a>
+<a href="https://skottie.skia.org/fc42db7c75741437b5cb0e90b3febc65">
+  <skottie-inline-sk src="https://skottie.skia.org/_/j/fc42db7c75741437b5cb0e90b3febc65" width=200 height=200></skottie-inline-sk>
+</a>
 
 *Sample animations courtesy of the lottiefiles.com community
 
diff --git a/site/user/sample/architecture.png b/site/user/sample/architecture.png
deleted file mode 100644
index a2b74cf..0000000
--- a/site/user/sample/architecture.png
+++ /dev/null
Binary files differ
diff --git a/site/user/sample/color.md b/site/user/sample/color.md
deleted file mode 100644
index eb9df66..0000000
--- a/site/user/sample/color.md
+++ /dev/null
@@ -1,189 +0,0 @@
-Color Correct Skia
-==================
-
-Why is Skia Color Correct?
---------------------------
-
-A color space is a **gamut** and a **transfer function**.
-
-Gamut refers to the **available range of colors** in an image or on a display device.  Being
-gamut correct means that we will display colors as the designer intended and consistently across
-display devices.  A common problem with new “wide gamut” devices and uncorrected colors is 
-illustrated below.
-
-Device Dependent Color (Wrong)
-
-<img src='gamut_wrong.png'>
-
-Gamut Corrected Color
-
-<img src='gamut_correct.png'>
-
-Transfer function refers to **a non-linear encoding of pixel values**.  A common transfer function
-is shown below.
-
-<img src='transfer_fn.png'>
-
-If we ignore the transfer function and treat non-linear values as if they are linear (when
-filtering, blending, anti-aliasing, multiplying), everything gets “too dark”.
-
-For example, we should see yellow (not brown) as the average of red and green light.
-
-Ignore Transfer Function
-
-<img src='gradient_wrong.png'>
-
-Apply Transfer Function
-
-<img src='gradient_correct.png'>
-
-Also, we should maintain fine detail when anti-aliasing (or downscaling).
-
-Ignore Transfer Function
-
-<img src='detail_wrong.png'>
-
-Apply Transfer Function
-
-<img src='detail_correct.png'>
-
-Skia Architecture for Color Correctness
----------------------------------------
-
-<img src='architecture.png'>
-
-The major stages of the Skia drawing pipeline (premultiplication, filtering, blending) all assume
-linear inputs and linear outputs.  Also, because they are linear operations, they are
-interchangeable.
-
-The gamut transform is a new operation (3x3 matrix) in the pipeline, but with similar properties:
-it is a linear operation with linear inputs and linear outputs.
-
-The important shift in logic from the legacy pipeline is that we actually apply the transfer
-function to transform the pixels to linear values before performing the linear operations.
-
-The most common transfer function, sRGB, is actually free on GPU!  GPU hardware can transform sRGB
-to linear on reads and linear to sRGB on writes.
-
-Best Practices for Color Correct Skia
--------------------------------------
-
-In order to perform color correct rendering, Skia needs to know the **SkColorSpace** of the content
-that you draw and the **SkColorSpace** of the surface that you draw to.  There are useful factories
-to make color spaces.
-
-<!--?prettify lang=cc?-->
-
-	// Really common color spaces
-	sk_sp<SkColorSpace> MakeSRGB();
-	sk_sp<SkColorSpace> MakeSRGBLinear();
-	
-	// Choose a common gamut and a common transfer function
-	sk_sp<SkColorSpace> MakeRGB(RenderTargetGamma, Gamut);
-
-Starting with **sources** (the things that you draw), there are a number of ways to make sure
-that they are tagged with a color space.
-
-**SkColor** (stored on **SkPaint**) is assumed to be in the sRGB color space - meaning that it
-is in the sRGB gamut and encoded with the sRGB transfer function.
-
-**SkShaders** (also stored on **SkPaint**) can be used to create more complex colors.  Color and
-gradient shaders typically accept **SkColor4f** (float colors).  These high precision colors
-can be in any gamut, but must have a linear transfer function.
-
-<!--?prettify lang=cc?-->
-
-	// Create a high precision color in a particular color space
-	sk_sp<SkShader> MakeColorShader(const SkColor4f&, sk_sp<SkColorSpace>);
-	
-	// Create a gradient shader in a particular color space
-	sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const SkColor4f colors[2],
-	                           sk_sp<SkColorSpace>, ...);
-	
-	// Many more variations of shaders...
-	// Remember that SkColor is always assumed to be sRGB as a convenience
-
-**SkImage** is the preferred representation for image sources.  It is easy to create **SkImages**
- that are tagged with color spaces.
-
-<!--?prettify lang=cc?-->
-	
-	// Create an image from encoded data (jpeg, png, etc.)
-	// Will be tagged with the color space of the encoded data
-	sk_sp<SkImage> MakeFromEncoded(sk_sp<SkData> encoded);
-	
-	// Create an image from a texture in a particular color space
-	sk_sp<SkImage> MakeFromTexture(GrContext*, const GrBackendTexture&,
-                                       GrSurfaceOrigin, SkAlphaType, sk_sp<SkColorSpace>,
-                                       ...);
-
-**SkBitmap** is another (not preferred) representation for image sources.  Be careful to not forget
-the color space.
-
-<!--?prettify lang=cc?-->
-
-	SkBitmap bitmap;
-	bitmap.allocN32Pixels(); // Bad: What is the color space?
-	
-	SkBitmap bitmap;
-	SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
-	bitmap.allocPixels(info); // Bad: N32 is shorthand for 8888, no color space
-	
-	SkBitmap bitmap;
-	SkImageInfo info = SkImageInfo::MakeS32(width, height, kPremul_SkAlphaType);
-	bitmap.allocPixels(info); // Good: S32 is shorthand for 8888, sRGB
-
-**SkImageInfo** is a useful struct for providing information about pixel buffers.  Remember to use
-the color correct variants.
-
-<!--?prettify lang=cc?-->
-
-	// sRGB, 8888
-	SkImageInfo MakeS32(int width, int height, SkAlphaType);
-	
-	// Create an SkImageInfo in a particular color space
-	SkImageInfo Make(int width, int height, SkColorType, SkAlphaType,
-	                 sk_sp<SkColorSpace>);
-
-Moving to **destinations** (the surfaces that you draw to), there are also constructors that allow
-them to be tagged with color spaces.
-
-<!--?prettify lang=cc?-->
-
-	// Raster backed: Make sure |info| has a non-null color space
-	sk_sp<SkSurface> MakeRaster(const SkImageInfo& info);
-	
-	// Gpu backed: Make sure |info| has a non-null color space
-	sk_sp<SkSurface> SkSurface::MakeRenderTarget(GrContext, SkBudgeted,
-	                                             const SkImageInfo& info);
-
-Opting In To Color Correct Skia
--------------------------------
-
-By itself, **adding a color space tag to a source will not change draw behavior**.  In fact,
-tagging sources with color spaces is always a best practice, regardless of whether or not we want
-Skia's color correct behavior.
-
-Adding a color space tag to the **destination is the trigger that turns on Skia color correct
-behavior**.
-
-Drawing a source without a color space to a destination with a color space is undefined.  Skia
-cannot know how to draw without knowing the color space of the source.
-
-<style scoped><!--
-#colortable {border-collapse:collapse;}
-#colortable tr th, #colortable tr td {border:#888888 2px solid;padding: 5px;}
---></style>
-<table id="colortable">
-<tr><th>Source SkColorSpace</th> <th>Destination SkColorSpace</th>  <th>Behavior</th></tr>
-<tr><td>Non-null</td>            <td>Non-null</td>                  <td>Color Correct Skia</td></tr>
-<tr><td>Null</td>                <td>Non-null</td>                  <td>Undefined</td></tr>
-<tr><td>Non-null</td>            <td>Null</td>                      <td>Legacy Skia</td></tr>
-<tr><td>Null</td>                <td>Null</td>                      <td>Legacy Skia</td></tr>
-</table>
-
-It is possible to create **an object that is both a source and destination**, if Skia will both
-draw into it and then draw it somewhere else.  The same rules from above still apply, but it is
-subtle that the color space tag could have an effect (or no effect) depending on how the object is
-used.
-
diff --git a/site/user/sample/detail_correct.png b/site/user/sample/detail_correct.png
deleted file mode 100644
index 3f14885e..0000000
--- a/site/user/sample/detail_correct.png
+++ /dev/null
Binary files differ
diff --git a/site/user/sample/detail_wrong.png b/site/user/sample/detail_wrong.png
deleted file mode 100644
index bd3c836..0000000
--- a/site/user/sample/detail_wrong.png
+++ /dev/null
Binary files differ
diff --git a/site/user/sample/gamut_correct.png b/site/user/sample/gamut_correct.png
deleted file mode 100644
index bb94caf..0000000
--- a/site/user/sample/gamut_correct.png
+++ /dev/null
Binary files differ
diff --git a/site/user/sample/gamut_wrong.png b/site/user/sample/gamut_wrong.png
deleted file mode 100644
index a5e584d..0000000
--- a/site/user/sample/gamut_wrong.png
+++ /dev/null
Binary files differ
diff --git a/site/user/sample/gradient_correct.png b/site/user/sample/gradient_correct.png
deleted file mode 100644
index 5649995..0000000
--- a/site/user/sample/gradient_correct.png
+++ /dev/null
Binary files differ
diff --git a/site/user/sample/gradient_wrong.png b/site/user/sample/gradient_wrong.png
deleted file mode 100644
index 8b70450..0000000
--- a/site/user/sample/gradient_wrong.png
+++ /dev/null
Binary files differ
diff --git a/site/user/sample/transfer_fn.png b/site/user/sample/transfer_fn.png
deleted file mode 100644
index 14d38bd..0000000
--- a/site/user/sample/transfer_fn.png
+++ /dev/null
Binary files differ
diff --git a/site/user/tips.md b/site/user/tips.md
index 8954cef..0e417d5 100644
--- a/site/user/tips.md
+++ b/site/user/tips.md
@@ -133,25 +133,22 @@
 <!--?prettify lang=cc?-->
 
     void draw(SkCanvas* canvas) {
-        const char text[] = "Skia";
-        const SkScalar radius = 2.0f;
+        const SkScalar sigma = 1.65f;
         const SkScalar xDrop = 2.0f;
         const SkScalar yDrop = 2.0f;
         const SkScalar x = 8.0f;
         const SkScalar y = 52.0f;
         const SkScalar textSize = 48.0f;
         const uint8_t blurAlpha = 127;
-        canvas->drawColor(SK_ColorWHITE);
+        auto blob = SkTextBlob::MakeFromString("Skia", SkFont(nullptr, textSize));
         SkPaint paint;
         paint.setAntiAlias(true);
-        paint.setTextSize(textSize);
         SkPaint blur(paint);
         blur.setAlpha(blurAlpha);
-        blur.setMaskFilter(SkBlurMaskFilter::Make(
-            kNormal_SkBlurStyle,
-            SkBlurMaskFilter::ConvertRadiusToSigma(radius), 0));
-        canvas->drawText(text, strlen(text), x + xDrop, y + yDrop, blur);
-        canvas->drawText(text, strlen(text), x, y, paint);
+        blur.setMaskFilter(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, sigma, 0));
+        canvas->drawColor(SK_ColorWHITE);
+        canvas->drawTextBlob(blob.get(), x + xDrop, y + yDrop, blur);
+        canvas->drawTextBlob(blob.get(), x,         y,         paint);
     }
 
 <a href='https://fiddle.skia.org/c/@text_shadow'><img src='https://fiddle.skia.org/i/@text_shadow_raster.png'></a>
diff --git a/src/android/SkAndroidFrameworkUtils.cpp b/src/android/SkAndroidFrameworkUtils.cpp
index f0a7b8a..d0f4499 100644
--- a/src/android/SkAndroidFrameworkUtils.cpp
+++ b/src/android/SkAndroidFrameworkUtils.cpp
@@ -8,6 +8,7 @@
 #include "SkAndroidFrameworkUtils.h"
 #include "SkCanvas.h"
 #include "SkDevice.h"
+#include "SkSurface_Base.h"
 
 #if SK_SUPPORT_GPU
 #include "GrStyle.h"
@@ -58,5 +59,13 @@
     android_errorWriteLog(0x534e4554, bugNumber);
 }
 
+sk_sp<SkSurface> SkAndroidFrameworkUtils::getSurfaceFromCanvas(SkCanvas* canvas) {
+    sk_sp<SkSurface> surface(SkSafeRef(canvas->getSurfaceBase()));
+    return surface;
+}
+
+int SkAndroidFrameworkUtils::SaveBehind(SkCanvas* canvas, const SkRect* subset) {
+    return canvas->only_axis_aligned_saveBehind(subset);
+}
 #endif // SK_BUILD_FOR_ANDROID_FRAMEWORK
 
diff --git a/src/atlastext/SkAtlasTextTarget.cpp b/src/atlastext/SkAtlasTextTarget.cpp
index 68b19ed..29ea081 100644
--- a/src/atlastext/SkAtlasTextTarget.cpp
+++ b/src/atlastext/SkAtlasTextTarget.cpp
@@ -148,10 +148,6 @@
                                          const SkAtlasTextFont& font) {
     SkPaint paint;
     paint.setAntiAlias(true);
-    paint.setTypeface(font.refTypeface());
-    paint.setTextSize(font.size());
-    paint.setStyle(SkPaint::kFill_Style);
-    paint.setTextEncoding(kGlyphID_SkTextEncoding);
 
     // The atlas text context does munging of the paint color. We store the client's color here
     // and then overwrite the generated op's color when addDrawOp() is called.
@@ -161,7 +157,9 @@
     auto* grContext = this->context()->internal().grContext();
     auto atlasTextContext = grContext->contextPriv().drawingManager()->getTextContext();
     SkGlyphRunBuilder builder;
-    builder.drawGlyphPos(paint, SkSpan<const SkGlyphID>{glyphs, SkTo<size_t>(glyphCnt)}, positions);
+    builder.drawGlyphsWithPositions(paint, font.makeFont(),
+                                    SkSpan<const SkGlyphID>{glyphs, SkTo<size_t>(glyphCnt)},
+                                    positions);
     auto glyphRunList = builder.useGlyphRunList();
     if (!glyphRunList.empty()) {
         atlasTextContext->drawGlyphRunList(grContext, this, GrNoClip(), this->ctm(), props,
diff --git a/src/codec/SkAndroidCodec.cpp b/src/codec/SkAndroidCodec.cpp
index 581640f..d741d6d 100644
--- a/src/codec/SkAndroidCodec.cpp
+++ b/src/codec/SkAndroidCodec.cpp
@@ -164,9 +164,8 @@
     switch (outputColorType) {
         case kRGBA_8888_SkColorType:
         case kBGRA_8888_SkColorType: {
-            // If |prefColorSpace| is supported, choose it.
-            SkColorSpaceTransferFn fn;
-            if (prefColorSpace && prefColorSpace->isNumericalTransferFn(&fn)) {
+            // If |prefColorSpace| is supplied, choose it.
+            if (prefColorSpace) {
                 return prefColorSpace;
             }
 
@@ -179,8 +178,7 @@
                 }
 
                 if (is_wide_gamut(*encodedProfile)) {
-                    return SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                                 SkColorSpace::kDCIP3_D65_Gamut);
+                    return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
                 }
             }
 
diff --git a/src/codec/SkCodec.cpp b/src/codec/SkCodec.cpp
index 626de9f..449812d 100644
--- a/src/codec/SkCodec.cpp
+++ b/src/codec/SkCodec.cpp
@@ -585,8 +585,8 @@
     SkSampler::Fill(fillInfo, fillDst, rowBytes, kNo_ZeroInitialized);
 }
 
-static inline bool select_xform_format(SkColorType colorType, bool forColorTable,
-                                       skcms_PixelFormat* outFormat) {
+bool sk_select_xform_format(SkColorType colorType, bool forColorTable,
+                            skcms_PixelFormat* outFormat) {
     SkASSERT(outFormat);
 
     switch (colorType) {
@@ -646,8 +646,8 @@
         fXformTime = SkEncodedInfo::kPalette_Color != fEncodedInfo.color()
                           || kRGBA_F16_SkColorType == dstInfo.colorType()
                 ? kDecodeRow_XformTime : kPalette_XformTime;
-        if (!select_xform_format(dstInfo.colorType(), fXformTime == kPalette_XformTime,
-                                 &fDstXformFormat)) {
+        if (!sk_select_xform_format(dstInfo.colorType(), fXformTime == kPalette_XformTime,
+                                    &fDstXformFormat)) {
             return false;
         }
         if (encodedAlpha == SkEncodedInfo::kUnpremul_Alpha
diff --git a/src/codec/SkCodecPriv.h b/src/codec/SkCodecPriv.h
index 10bd064..1602130 100644
--- a/src/codec/SkCodecPriv.h
+++ b/src/codec/SkCodecPriv.h
@@ -21,6 +21,10 @@
     #define SkCodecPrintf(...)
 #endif
 
+// Defined in SkCodec.cpp
+bool sk_select_xform_format(SkColorType colorType, bool forColorTable,
+                            skcms_PixelFormat* outFormat);
+
 // FIXME: Consider sharing with dm, nanbench, and tools.
 static inline float get_scale_from_sample_size(int sampleSize) {
     return 1.0f / ((float) sampleSize);
diff --git a/src/codec/SkOrientationMarker.cpp b/src/codec/SkOrientationMarker.cpp
new file mode 100644
index 0000000..e56b842
--- /dev/null
+++ b/src/codec/SkOrientationMarker.cpp
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+// Dummy file so Chromium can start compiling this and be ready for
+// https://skia-review.googlesource.com/c/skia/+/175428
diff --git a/src/codec/SkRawCodec.cpp b/src/codec/SkRawCodec.cpp
index c9a78db..a8ec40b 100644
--- a/src/codec/SkRawCodec.cpp
+++ b/src/codec/SkRawCodec.cpp
@@ -17,7 +17,6 @@
 #include "SkRefCnt.h"
 #include "SkStream.h"
 #include "SkStreamPriv.h"
-#include "SkSwizzler.h"
 #include "SkTArray.h"
 #include "SkTaskGroup.h"
 #include "SkTemplates.h"
@@ -613,17 +612,6 @@
     bool fIsXtransImage;
 };
 
-static constexpr skcms_Matrix3x3 gAdobe_RGB_to_XYZD50 = {{
-    // ICC fixed-point (16.16) repesentation of:
-    // 0.60974, 0.20528, 0.14919,
-    // 0.31111, 0.62567, 0.06322,
-    // 0.01947, 0.06087, 0.74457,
-    { SkFixedToFloat(0x9c18), SkFixedToFloat(0x348d), SkFixedToFloat(0x2631) }, // Rx, Gx, Bx
-    { SkFixedToFloat(0x4fa5), SkFixedToFloat(0xa02c), SkFixedToFloat(0x102f) }, // Ry, Gy, By
-    { SkFixedToFloat(0x04fc), SkFixedToFloat(0x0f95), SkFixedToFloat(0xbe9c) }, // Rz, Gz, Bz
-}};
-
-
 /*
  * Tries to handle the image with PIEX. If PIEX returns kOk and finds the preview image, create a
  * SkJpegCodec. If PIEX returns kFail, then the file is invalid, return nullptr. In other cases,
@@ -650,12 +638,10 @@
 
         std::unique_ptr<SkEncodedInfo::ICCProfile> profile;
         if (imageData.color_space == ::piex::PreviewImageData::kAdobeRgb) {
-            constexpr skcms_TransferFunction twoDotTwo =
-                    { 2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
             skcms_ICCProfile skcmsProfile;
             skcms_Init(&skcmsProfile);
-            skcms_SetTransferFunction(&skcmsProfile, &twoDotTwo);
-            skcms_SetXYZD50(&skcmsProfile, &gAdobe_RGB_to_XYZD50);
+            skcms_SetTransferFunction(&skcmsProfile, &SkNamedTransferFn::k2Dot2);
+            skcms_SetXYZD50(&skcmsProfile, &SkNamedGamut::kAdobeRGB);
             profile = SkEncodedInfo::ICCProfile::Make(skcmsProfile);
         }
 
@@ -697,17 +683,6 @@
 SkCodec::Result SkRawCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst,
                                         size_t dstRowBytes, const Options& options,
                                         int* rowsDecoded) {
-    SkImageInfo swizzlerInfo = dstInfo;
-    std::unique_ptr<uint32_t[]> xformBuffer = nullptr;
-    if (this->colorXform()) {
-        swizzlerInfo = swizzlerInfo.makeColorType(kRGBA_8888_SkColorType);
-        xformBuffer.reset(new uint32_t[dstInfo.width()]);
-    }
-
-    std::unique_ptr<SkSwizzler> swizzler = SkSwizzler::Make(
-            this->getEncodedInfo(), nullptr, swizzlerInfo, options);
-    SkASSERT(swizzler);
-
     const int width = dstInfo.width();
     const int height = dstInfo.height();
     std::unique_ptr<dng_image> image(fDngImage->render(width, height));
@@ -737,6 +712,20 @@
     buffer.fPixelSize = sizeof(uint8_t);
     buffer.fRowStep = width * 3;
 
+    constexpr auto srcFormat = skcms_PixelFormat_RGB_888;
+    skcms_PixelFormat dstFormat;
+    if (!sk_select_xform_format(dstInfo.colorType(), false, &dstFormat)) {
+        return kInvalidConversion;
+    }
+
+    const skcms_ICCProfile* const srcProfile = this->getEncodedInfo().profile();
+    skcms_ICCProfile dstProfileStorage;
+    const skcms_ICCProfile* dstProfile = nullptr;
+    if (auto cs = dstInfo.colorSpace()) {
+        cs->toProfile(&dstProfileStorage);
+        dstProfile = &dstProfileStorage;
+    }
+
     for (int i = 0; i < height; ++i) {
         buffer.fArea = dng_rect(i, 0, i + 1, width);
 
@@ -747,13 +736,14 @@
             return kIncompleteInput;
         }
 
-        if (this->colorXform()) {
-            swizzler->swizzle(xformBuffer.get(), &srcRow[0]);
-
-            this->applyColorXform(dstRow, xformBuffer.get(), dstInfo.width());
-        } else {
-            swizzler->swizzle(dstRow, &srcRow[0]);
+        if (!skcms_Transform(&srcRow[0], srcFormat, skcms_AlphaFormat_Unpremul, srcProfile,
+                             dstRow,     dstFormat, skcms_AlphaFormat_Unpremul, dstProfile,
+                             dstInfo.width())) {
+            SkDebugf("failed to transform\n");
+            *rowsDecoded = i;
+            return kInternalError;
         }
+
         dstRow = SkTAddOffset<void>(dstRow, dstRowBytes);
     }
     return kSuccess;
diff --git a/src/codec/SkRawCodec.h b/src/codec/SkRawCodec.h
index c258ac1..fb8e338 100644
--- a/src/codec/SkRawCodec.h
+++ b/src/codec/SkRawCodec.h
@@ -45,6 +45,10 @@
 
     bool onDimensionsSupported(const SkISize&) override;
 
+    // SkCodec only applies the colorXform if it's necessary for color space
+    // conversion. SkRawCodec will always convert, so tell SkCodec not to.
+    bool usesColorXform() const override { return false; }
+
 private:
 
     /*
diff --git a/src/codec/SkSwizzler.cpp b/src/codec/SkSwizzler.cpp
index 8e9fec3..e74b72d 100644
--- a/src/codec/SkSwizzler.cpp
+++ b/src/codec/SkSwizzler.cpp
@@ -1213,7 +1213,7 @@
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
             SkAndroidFrameworkUtils::SafetyNetLog("118143775");
 #endif
-            SkASSERT(dstSwizzleBytes < dstAllocatedBytes);
+            SkASSERT(dstSwizzleBytes <= dstAllocatedBytes);
             fDstOffsetBytes = dstAllocatedBytes - dstSwizzleBytes;
         }
     }
diff --git a/src/codec/SkWuffsCodec.cpp b/src/codec/SkWuffsCodec.cpp
index 090c234..5540672 100644
--- a/src/codec/SkWuffsCodec.cpp
+++ b/src/codec/SkWuffsCodec.cpp
@@ -12,7 +12,16 @@
 #include "SkSampler.h"
 #include "SkSwizzler.h"
 #include "SkUtils.h"
-#include "wuffs-v0.2.h"
+
+// Wuffs ships as a "single file C library" or "header file library" as per
+// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
+//
+// As we have not #define'd WUFFS_IMPLEMENTATION, the #include here is
+// including a header file, even though that file name ends in ".c".
+#include "wuffs-v0.2.c"
+#if WUFFS_VERSION_BUILD_METADATA_COMMIT_COUNT < 1535
+#error "Wuffs version is too old. Upgrade to the latest version."
+#endif
 
 #define SK_WUFFS_CODEC_BUFFER_SIZE 4096
 
@@ -198,11 +207,11 @@
 
     // Incremental decoding state.
     uint8_t* fIncrDecDst;
-    bool     fIncrDecHaveFrameConfig;
     size_t   fIncrDecRowBytes;
 
     std::unique_ptr<SkSwizzler> fSwizzler;
     SkPMColor                   fColorTable[256];
+    bool                        fColorTableFilled;
 
     uint64_t                  fNumFullyReceivedFrames;
     std::vector<SkWuffsFrame> fFrames;
@@ -319,9 +328,9 @@
       fPixelBuffer(pixbuf),
       fIOBuffer((wuffs_base__io_buffer){}),
       fIncrDecDst(nullptr),
-      fIncrDecHaveFrameConfig(false),
       fIncrDecRowBytes(0),
       fSwizzler(nullptr),
+      fColorTableFilled(false),
       fNumFullyReceivedFrames(0),
       fFramesComplete(false),
       fDecoderIsSuspended(false) {
@@ -382,12 +391,20 @@
     }
 
     fSpySampler.reset();
-    fIncrDecDst = static_cast<uint8_t*>(dst);
-    fIncrDecHaveFrameConfig = false;
-    fIncrDecRowBytes = rowBytes;
     fSwizzler = nullptr;
+    fColorTableFilled = false;
 
-    return SkCodec::kSuccess;
+    const char* status = this->decodeFrameConfig();
+    if (status == nullptr) {
+        fIncrDecDst = static_cast<uint8_t*>(dst);
+        fIncrDecRowBytes = rowBytes;
+        return SkCodec::kSuccess;
+    } else if (status == wuffs_base__suspension__short_read) {
+        return SkCodec::kIncompleteInput;
+    } else {
+        SkCodecPrintf("decodeFrameConfig: %s", status);
+        return SkCodec::kErrorInInput;
+    }
 }
 
 static bool independent_frame(SkCodec* codec, int frameIndex) {
@@ -415,100 +432,37 @@
         return SkCodec::kInternalError;
     }
 
-    if (!fIncrDecHaveFrameConfig) {
-        const char* status = this->decodeFrameConfig();
-        if (status == nullptr) {
-            // No-op.
-        } else if (status == wuffs_base__suspension__short_read) {
-            return SkCodec::kIncompleteInput;
-        } else {
-            SkCodecPrintf("decodeFrameConfig: %s", status);
-            return SkCodec::kErrorInInput;
-        }
-        fIncrDecHaveFrameConfig = true;
-    }
-
-    SkCodec::Result result = SkCodec::kSuccess;
-    const char*     status = this->decodeFrame();
-    if (status == nullptr) {
-        // No-op.
-    } else if (status == wuffs_base__suspension__short_read) {
-        result = SkCodec::kIncompleteInput;
-    } else {
-        SkCodecPrintf("decodeFrame: %s", status);
-        return SkCodec::kErrorInInput;
-    }
-
+    // In Wuffs, a paletted image is always 1 byte per pixel.
+    static constexpr size_t src_bpp = 1;
+    wuffs_base__table_u8 pixels = fPixelBuffer.plane(0);
+    int scaledHeight = dstInfo().height();
     const bool independent = independent_frame(this, options().fFrameIndex);
-    wuffs_base__rect_ie_u32 r = fFrameConfig.bounds();
+    wuffs_base__rect_ie_u32 frame_rect = fFrameConfig.bounds();
     if (!fSwizzler) {
-        SkIRect swizzleRect = SkIRect::MakeLTRB(r.min_incl_x, 0, r.max_excl_x, 1);
-        fSwizzler = SkSwizzler::Make(this->getEncodedInfo(), fColorTable, dstInfo(), Options(),
-                                     &swizzleRect);
+        auto bounds = SkIRect::MakeLTRB(frame_rect.min_incl_x, frame_rect.min_incl_y,
+                                        frame_rect.max_excl_x, frame_rect.max_excl_y);
+        fSwizzler = SkSwizzler::Make(this->getEncodedInfo(), fColorTable, dstInfo(),
+                                     this->options(), &bounds);
         fSwizzler->setSampleX(fSpySampler.sampleX());
         fSwizzler->setSampleY(fSpySampler.sampleY());
+        scaledHeight = get_scaled_dimension(dstInfo().height(), fSpySampler.sampleY());
 
-        if (independent) {
-            auto fillInfo = dstInfo().makeWH(fSwizzler->fillWidth(),
-                                             get_scaled_dimension(this->dstInfo().height(),
-                                                                  fSwizzler->sampleY()));
+        // Zero-initialize wuffs' buffer covering the frame rect. This will later be used to
+        // determine how we write to the output, even if the image was incomplete. This ensures
+        // that we do not swizzle uninitialized memory.
+        for (uint32_t y = frame_rect.min_incl_y; y < frame_rect.max_excl_y; y++) {
+            uint8_t* s = pixels.ptr + (y * pixels.stride) + (frame_rect.min_incl_x * src_bpp);
+            sk_bzero(s, frame_rect.width() * src_bpp);
+        }
+
+        // If the frame rect does not fill the output, ensure that those pixels are not
+        // left uninitialized either.
+        if (independent && bounds != this->bounds()) {
+            auto fillInfo = dstInfo().makeWH(fSwizzler->fillWidth(), scaledHeight);
             SkSampler::Fill(fillInfo, fIncrDecDst, fIncrDecRowBytes, options().fZeroInitialized);
         }
     }
 
-    wuffs_base__slice_u8 palette = fPixelBuffer.palette();
-    SkASSERT(palette.len == 4 * 256);
-    auto proc = choose_pack_color_proc(false, dstInfo().colorType());
-    for (int i = 0; i < 256; i++) {
-        uint8_t* p = palette.ptr + 4 * i;
-        fColorTable[i] = proc(p[3], p[2], p[1], p[0]);
-    }
-
-    std::unique_ptr<uint8_t[]> tmpBuffer;
-    if (!independent) {
-        tmpBuffer.reset(new uint8_t[dstInfo().minRowBytes()]);
-    }
-    wuffs_base__table_u8 pixels = fPixelBuffer.plane(0);
-    const int            sampleY = fSwizzler->sampleY();
-    const int            scaledHeight = get_scaled_dimension(dstInfo().height(), sampleY);
-    for (uint32_t y = r.min_incl_y; y < r.max_excl_y; y++) {
-        // In Wuffs, a paletted image is always 1 byte per pixel.
-        static constexpr size_t src_bpp = 1;
-
-        int dstY = y;
-        if (sampleY != 1) {
-            if (!fSwizzler->rowNeeded(y)) {
-                continue;
-            }
-            dstY /= sampleY;
-            if (dstY >= scaledHeight) {
-                break;
-            }
-        }
-
-        // We don't adjust d by (r.min_incl_x * dst_bpp) as we have already
-        // accounted for that in swizzleRect, above.
-        uint8_t* d = fIncrDecDst + (dstY * fIncrDecRowBytes);
-
-        // The Wuffs model is that the dst buffer is the image, not the frame.
-        // The expectation is that you allocate the buffer once, but re-use it
-        // for the N frames, regardless of each frame's top-left co-ordinate.
-        //
-        // To get from the start (in the X-direction) of the image to the start
-        // of the frame, we adjust s by (r.min_incl_x * src_bpp).
-        uint8_t* s = pixels.ptr + (y * pixels.stride) + (r.min_incl_x * src_bpp);
-        if (independent) {
-            fSwizzler->swizzle(d, s);
-        } else {
-            SkASSERT(tmpBuffer.get());
-            fSwizzler->swizzle(tmpBuffer.get(), s);
-            d = SkTAddOffset<uint8_t>(d, fSwizzler->swizzleOffsetBytes());
-            const auto* swizzled = SkTAddOffset<uint32_t>(tmpBuffer.get(),
-                                                          fSwizzler->swizzleOffsetBytes());
-            blend(reinterpret_cast<uint32_t*>(d), swizzled, fSwizzler->swizzleWidth());
-        }
-    }
-
     // The semantics of *rowsDecoded is: say you have a 10 pixel high image
     // (both the frame and the image). If you only decoded the first 3 rows,
     // set this to 3, and then SkCodec (or the caller of incrementalDecode)
@@ -530,12 +484,89 @@
         *rowsDecoded = scaledHeight;
     }
 
+    SkCodec::Result result = SkCodec::kSuccess;
+    const char*     status = this->decodeFrame();
+    if (status != nullptr) {
+        if (status == wuffs_base__suspension__short_read) {
+            result = SkCodec::kIncompleteInput;
+        } else {
+            SkCodecPrintf("decodeFrame: %s", status);
+            result = SkCodec::kErrorInInput;
+        }
+
+        if (!independent) {
+            // For a dependent frame, we cannot blend the partial result, since
+            // that will overwrite the contribution from prior frames with all
+            // zeroes that were written to |pixels| above.
+            return result;
+        }
+    }
+
+    // If the frame's dirty rect is empty, no need to swizzle.
+    wuffs_base__rect_ie_u32 dirty_rect = wuffs_gif__decoder__frame_dirty_rect(fDecoder.get());
+    if (!dirty_rect.is_empty()) {
+        if (!fColorTableFilled) {
+            fColorTableFilled = true;
+            wuffs_base__slice_u8 palette = fPixelBuffer.palette();
+            SkASSERT(palette.len == 4 * 256);
+            auto proc = choose_pack_color_proc(false, dstInfo().colorType());
+            for (int i = 0; i < 256; i++) {
+                uint8_t* p = palette.ptr + 4 * i;
+                fColorTable[i] = proc(p[3], p[2], p[1], p[0]);
+            }
+        }
+
+        std::unique_ptr<uint8_t[]> tmpBuffer;
+        if (!independent) {
+            tmpBuffer.reset(new uint8_t[dstInfo().minRowBytes()]);
+        }
+        const int sampleY = fSwizzler->sampleY();
+        for (uint32_t y = dirty_rect.min_incl_y; y < dirty_rect.max_excl_y; y++) {
+            int dstY = y;
+            if (sampleY != 1) {
+                if (!fSwizzler->rowNeeded(y)) {
+                    continue;
+                }
+                dstY /= sampleY;
+                if (dstY >= scaledHeight) {
+                    break;
+                }
+            }
+
+            // We don't adjust d by (frame_rect.min_incl_x * dst_bpp) as we
+            // have already accounted for that in swizzleRect, above.
+            uint8_t* d = fIncrDecDst + (dstY * fIncrDecRowBytes);
+
+            // The Wuffs model is that the dst buffer is the image, not the frame.
+            // The expectation is that you allocate the buffer once, but re-use it
+            // for the N frames, regardless of each frame's top-left co-ordinate.
+            //
+            // To get from the start (in the X-direction) of the image to the start
+            // of the frame, we adjust s by (frame_rect.min_incl_x * src_bpp).
+            //
+            // We adjust (in the X-direction) by the frame rect, not the dirty
+            // rect, because the swizzler (which operates on rows) was
+            // configured with the frame rect's X range.
+            uint8_t* s = pixels.ptr + (y * pixels.stride) + (frame_rect.min_incl_x * src_bpp);
+            if (independent) {
+                fSwizzler->swizzle(d, s);
+            } else {
+                SkASSERT(tmpBuffer.get());
+                fSwizzler->swizzle(tmpBuffer.get(), s);
+                d = SkTAddOffset<uint8_t>(d, fSwizzler->swizzleOffsetBytes());
+                const auto* swizzled = SkTAddOffset<uint32_t>(tmpBuffer.get(),
+                                                              fSwizzler->swizzleOffsetBytes());
+                blend(reinterpret_cast<uint32_t*>(d), swizzled, fSwizzler->swizzleWidth());
+            }
+        }
+    }
+
     if (result == SkCodec::kSuccess) {
         fSpySampler.reset();
         fIncrDecDst = nullptr;
-        fIncrDecHaveFrameConfig = false;
         fIncrDecRowBytes = 0;
         fSwizzler = nullptr;
+        fColorTableFilled = false;
     } else {
         // Make fSpySampler return whatever fSwizzler would have for fillWidth.
         fSpySampler.fFillWidth = fSwizzler->fillWidth();
diff --git a/src/compute/sk/SkSurface_Compute.cpp b/src/compute/sk/SkSurface_Compute.cpp
index 5dac0f0..6451c19 100644
--- a/src/compute/sk/SkSurface_Compute.cpp
+++ b/src/compute/sk/SkSurface_Compute.cpp
@@ -57,7 +57,7 @@
 
   // skc_interop_size_get(compute->interop,&w,&h); TODO skc.h
 
-  SkDevice_Compute * const device_compute = new SkDevice_Compute(compute,w,h);;
+  SkDevice_Compute * const device_compute = new SkDevice_Compute(compute,w,h);
   SkCanvas         * const canvas         = new SkCanvas(device_compute,SkCanvas::kConservativeRasterClip_InitFlag);
 
   //
diff --git a/src/core/Sk4px.h b/src/core/Sk4px.h
index bc95596..e2c30f7 100644
--- a/src/core/Sk4px.h
+++ b/src/core/Sk4px.h
@@ -17,36 +17,48 @@
 // in Debug modes the compilers may not inline everything.  So wrap everything in an
 // anonymous namespace to give each includer their own silo of this code (or the linker
 // will probably pick one randomly for us, which is rarely correct).
-namespace {
+namespace {  // NOLINT(google-build-namespaces)
 
 // 1, 2 or 4 SkPMColors, generally vectorized.
 class Sk4px : public Sk16b {
 public:
-    static Sk4px DupAlpha(SkAlpha a) { return Sk16b(a); }  //    a -> aaaa aaaa aaaa aaaa
-    static Sk4px DupPMColor(SkPMColor c);                  // argb -> argb argb argb argb
-
     Sk4px(const Sk16b& v) : INHERITED(v) {}
 
+    static Sk4px DupPMColor(SkPMColor c) {
+        Sk4u splat(c);
+
+        Sk4px v;
+        memcpy(&v, &splat, 16);
+        return v;
+    }
+
     Sk4px alphas() const;  // ARGB argb XYZW xyzw -> AAAA aaaa XXXX xxxx
-
-    // Mask away color or alpha lanes.
-    Sk4px zeroColors() const;  // ARGB argb XYZW xyzw -> A000 a000 X000 x000
-    Sk4px zeroAlphas() const;  // ARGB argb XYZW xyzw -> 0RGB 0rgb 0YZW 0yzw
-
     Sk4px inv() const { return Sk16b(255) - *this; }
 
     // When loading or storing fewer than 4 SkPMColors, we use the low lanes.
-    static Sk4px Load4(const SkPMColor[4]);  // PMColor[4] -> ARGB argb XYZW xyzw
-    static Sk4px Load2(const SkPMColor[2]);  // PMColor[2] -> ARGB argb ???? ????
-    static Sk4px Load1(const SkPMColor[1]);  // PMColor[1] -> ARGB ???? ???? ????
+    static Sk4px Load4(const SkPMColor px[4]) {
+        Sk4px v;
+        memcpy(&v, px, 16);
+        return v;
+    }
+    static Sk4px Load2(const SkPMColor px[2]) {
+        Sk4px v;
+        memcpy(&v, px, 8);
+        return v;
+    }
+    static Sk4px Load1(const SkPMColor px[1]) {
+        Sk4px v;
+        memcpy(&v, px, 4);
+        return v;
+    }
 
     // Ditto for Alphas... Load2Alphas fills the low two lanes of Sk4px.
     static Sk4px Load4Alphas(const SkAlpha[4]);  // AaXx -> AAAA aaaa XXXX xxxx
     static Sk4px Load2Alphas(const SkAlpha[2]);  // Aa   -> AAAA aaaa ???? ????
 
-    void store4(SkPMColor[4]) const;
-    void store2(SkPMColor[2]) const;
-    void store1(SkPMColor[1]) const;
+    void store4(SkPMColor px[4]) const { memcpy(px, this, 16); }
+    void store2(SkPMColor px[2]) const { memcpy(px, this,  8); }
+    void store1(SkPMColor px[1]) const { memcpy(px, this,  4); }
 
     // 1, 2, or 4 SkPMColors with 16-bit components.
     // This is most useful as the result of a multiply, e.g. from mulWiden().
@@ -54,7 +66,7 @@
     public:
         Wide(const Sk16h& v) : Sk16h(v) {}
 
-        // Pack the top byte of each component back down into 4 SkPMColors.
+        // Add, then pack the top byte of each component back down into 4 SkPMColors.
         Sk4px addNarrowHi(const Sk16h&) const;
 
         // Rounds, i.e. (x+127) / 255.
@@ -66,16 +78,12 @@
         Wide operator - (const Wide& o) const { return INHERITED::operator-(o); }
         Wide operator >> (int bits) const { return INHERITED::operator>>(bits); }
         Wide operator << (int bits) const { return INHERITED::operator<<(bits); }
-        static Wide Min(const Wide& a, const Wide& b) { return INHERITED::Min(a,b); }
-        Wide thenElse(const Wide& t, const Wide& e) const { return INHERITED::thenElse(t,e); }
 
     private:
         typedef Sk16h INHERITED;
     };
 
-    Wide widenLo() const;               // ARGB -> 0A 0R 0G 0B
-    Wide widenHi() const;               // ARGB -> A0 R0 G0 B0
-    Wide widenLoHi() const;             // ARGB -> AA RR GG BB
+    Wide widen() const;               // Widen 8-bit values to low 8-bits of 16-bit lanes.
     Wide mulWiden(const Sk16b&) const;  // 8-bit x 8-bit -> 16-bit components.
 
     // The only 8-bit multiply we use is 8-bit x 8-bit -> 16-bit.  Might as well make it pithy.
@@ -92,7 +100,7 @@
     Sk4px approxMulDiv255(const Sk16b& o) const {
         // (x*y + x) / 256 meets these criteria.  (As of course does (x*y + y) / 256 by symmetry.)
         // FYI: (x*y + 255) / 256 also meets these criteria.  In my brief testing, it was slower.
-        return this->widenLo().addNarrowHi(*this * o);
+        return this->widen().addNarrowHi(*this * o);
     }
 
     // A generic driver that maps fn over a src array into a dst array.
@@ -182,7 +190,7 @@
                 dst += 2; a += 2; n -= 2;
             }
             if (n >= 1) {
-                fn(Load1(dst), DupAlpha(*a)).store1(dst);
+                fn(Load1(dst), Sk16b(*a)).store1(dst);
             }
             break;
         }
@@ -214,13 +222,15 @@
                 dst += 2; src += 2; a += 2; n -= 2;
             }
             if (n >= 1) {
-                fn(Load1(dst), Load1(src), DupAlpha(*a)).store1(dst);
+                fn(Load1(dst), Load1(src), Sk16b(*a)).store1(dst);
             }
             break;
         }
     }
 
 private:
+    Sk4px() = default;
+
     typedef Sk16b INHERITED;
 };
 
diff --git a/src/core/Sk4x4f.h b/src/core/Sk4x4f.h
index 0fd6b38..bcfd241 100644
--- a/src/core/Sk4x4f.h
+++ b/src/core/Sk4x4f.h
@@ -10,7 +10,7 @@
 
 #include "SkNx.h"
 
-namespace {
+namespace {  // NOLINT(google-build-namespaces)
 
 struct Sk4x4f {
     Sk4f r,g,b,a;
diff --git a/src/core/SkAAClip.cpp b/src/core/SkAAClip.cpp
index 3394738..baf8277 100644
--- a/src/core/SkAAClip.cpp
+++ b/src/core/SkAAClip.cpp
@@ -7,7 +7,6 @@
 
 #include "SkAAClip.h"
 
-#include "SkAtomics.h"
 #include "SkBlitter.h"
 #include "SkColorData.h"
 #include "SkMacros.h"
@@ -16,7 +15,7 @@
 #include "SkScan.h"
 #include "SkTo.h"
 #include "SkUTF.h"
-
+#include <atomic>
 #include <utility>
 
 class AutoAAClipValidate {
@@ -61,7 +60,7 @@
 };
 
 struct SkAAClip::RunHead {
-    int32_t fRefCnt;
+    std::atomic<int32_t> fRefCnt;
     int32_t fRowCount;
     size_t  fDataSize;
 
@@ -81,7 +80,7 @@
     static RunHead* Alloc(int rowCount, size_t dataSize) {
         size_t size = sizeof(RunHead) + rowCount * sizeof(YOffset) + dataSize;
         RunHead* head = (RunHead*)sk_malloc_throw(size);
-        head->fRefCnt = 1;
+        head->fRefCnt.store(1);
         head->fRowCount = rowCount;
         head->fDataSize = dataSize;
         return head;
@@ -200,7 +199,7 @@
     SkASSERT(!fBounds.isEmpty());
 
     const RunHead* head = fRunHead;
-    SkASSERT(head->fRefCnt > 0);
+    SkASSERT(head->fRefCnt.load() > 0);
     SkASSERT(head->fRowCount > 0);
 
     const YOffset* yoff = head->yoffsets();
@@ -535,8 +534,8 @@
 
 void SkAAClip::freeRuns() {
     if (fRunHead) {
-        SkASSERT(fRunHead->fRefCnt >= 1);
-        if (1 == sk_atomic_dec(&fRunHead->fRefCnt)) {
+        SkASSERT(fRunHead->fRefCnt.load() >= 1);
+        if (1 == fRunHead->fRefCnt--) {
             sk_free(fRunHead);
         }
     }
@@ -566,7 +565,7 @@
         fBounds = src.fBounds;
         fRunHead = src.fRunHead;
         if (fRunHead) {
-            sk_atomic_inc(&fRunHead->fRefCnt);
+            fRunHead->fRefCnt++;
         }
     }
     return *this;
@@ -1755,7 +1754,7 @@
     }
 
     if (this != dst) {
-        sk_atomic_inc(&fRunHead->fRefCnt);
+        fRunHead->fRefCnt++;
         dst->freeRuns();
         dst->fRunHead = fRunHead;
         dst->fBounds = fBounds;
diff --git a/src/core/SkBitmap.cpp b/src/core/SkBitmap.cpp
index 8aba794..5b70168 100644
--- a/src/core/SkBitmap.cpp
+++ b/src/core/SkBitmap.cpp
@@ -7,7 +7,6 @@
 
 #include "SkBitmap.h"
 
-#include "SkAtomics.h"
 #include "SkColorData.h"
 #include "SkConvertPixels.h"
 #include "SkData.h"
diff --git a/src/core/SkBitmapCache.cpp b/src/core/SkBitmapCache.cpp
index e8cdead..75a3f20 100644
--- a/src/core/SkBitmapCache.cpp
+++ b/src/core/SkBitmapCache.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "SkAtomics.h"
 #include "SkBitmapCache.h"
 #include "SkBitmapProvider.h"
 #include "SkImage.h"
@@ -61,12 +60,6 @@
     pr->setImmutableWithID(id);
 }
 
-//#define REC_TRACE   SkDebugf
-static void REC_TRACE(const char format[], ...) {}
-
-// for diagnostics
-static int32_t gRecCounter;
-
 class SkBitmapCache::Rec : public SkResourceCache::Rec {
 public:
     Rec(const SkBitmapCacheDesc& desc, const SkImageInfo& info, size_t rowBytes,
@@ -76,26 +69,20 @@
         , fMalloc(block)
         , fInfo(info)
         , fRowBytes(rowBytes)
-        , fExternalCounter(kBeforeFirstInstall_ExternalCounter)
     {
         SkASSERT(!(fDM && fMalloc));    // can't have both
 
         // We need an ID to return with the bitmap/pixelref. We can't necessarily use the key/desc
         // ID - lazy images cache the same ID with multiple keys (in different color types).
         fPrUniqueID = SkNextID::ImageID();
-        REC_TRACE(" Rec(%d): [%d %d] %d\n",
-                  sk_atomic_inc(&gRecCounter), fInfo.width(), fInfo.height(), fPrUniqueID);
     }
 
     ~Rec() override {
-        SkASSERT(0 == fExternalCounter || kBeforeFirstInstall_ExternalCounter == fExternalCounter);
-        if (fDM && kBeforeFirstInstall_ExternalCounter == fExternalCounter) {
-            // we never installed, so we need to unlock before we destroy the DM
+        SkASSERT(0 == fExternalCounter);
+        if (fDM && fDiscardableIsLocked) {
             SkASSERT(fDM->data());
             fDM->unlock();
         }
-        REC_TRACE("~Rec(%d): [%d %d] %d\n",
-                  sk_atomic_dec(&gRecCounter) - 1, fInfo.width(), fInfo.height(), fPrUniqueID);
         sk_free(fMalloc);   // may be null
     }
 
@@ -120,15 +107,13 @@
         Rec* rec = static_cast<Rec*>(ctx);
         SkAutoMutexAcquire ama(rec->fMutex);
 
-        REC_TRACE(" Rec: [%d] releaseproc\n", rec->fPrUniqueID);
-
         SkASSERT(rec->fExternalCounter > 0);
         rec->fExternalCounter -= 1;
         if (rec->fDM) {
             SkASSERT(rec->fMalloc == nullptr);
             if (rec->fExternalCounter == 0) {
-                REC_TRACE(" Rec [%d] unlock\n", rec->fPrUniqueID);
                 rec->fDM->unlock();
+                rec->fDiscardableIsLocked = false;
             }
         } else {
             SkASSERT(rec->fMalloc != nullptr);
@@ -138,52 +123,32 @@
     bool install(SkBitmap* bitmap) {
         SkAutoMutexAcquire ama(fMutex);
 
-        // are we still valid
         if (!fDM && !fMalloc) {
-            REC_TRACE(" Rec: [%d] invalid\n", fPrUniqueID);
             return false;
         }
 
-        /*
-            constructor      fExternalCount < 0     fDM->data()
-            after install    fExternalCount > 0     fDM->data()
-            after Release    fExternalCount == 0    !fDM->data()
-        */
         if (fDM) {
-            if (kBeforeFirstInstall_ExternalCounter == fExternalCounter) {
-                SkASSERT(fDM->data());
-            } else if (fExternalCounter > 0) {
-                SkASSERT(fDM->data());
-            } else {
+            if (!fDiscardableIsLocked) {
                 SkASSERT(fExternalCounter == 0);
                 if (!fDM->lock()) {
-                    REC_TRACE(" Rec [%d] re-lock failed\n", fPrUniqueID);
                     fDM.reset(nullptr);
                     return false;
                 }
-                REC_TRACE(" Rec [%d] re-lock succeeded\n", fPrUniqueID);
+                fDiscardableIsLocked = true;
             }
             SkASSERT(fDM->data());
         }
 
         bitmap->installPixels(fInfo, fDM ? fDM->data() : fMalloc, fRowBytes, ReleaseProc, this);
         SkBitmapCache_setImmutableWithID(bitmap->pixelRef(), fPrUniqueID);
+        fExternalCounter++;
 
-        REC_TRACE(" Rec: [%d] install new pr\n", fPrUniqueID);
-
-        if (kBeforeFirstInstall_ExternalCounter == fExternalCounter) {
-            fExternalCounter = 1;
-        } else {
-            fExternalCounter += 1;
-        }
-        SkASSERT(fExternalCounter > 0);
         return true;
     }
 
     static bool Finder(const SkResourceCache::Rec& baseRec, void* contextBitmap) {
         Rec* rec = (Rec*)&baseRec;
         SkBitmap* result = (SkBitmap*)contextBitmap;
-        REC_TRACE(" Rec: [%d] found\n", rec->fPrUniqueID);
         return rec->install(result);
     }
 
@@ -200,18 +165,10 @@
     size_t      fRowBytes;
     uint32_t    fPrUniqueID;
 
-    // This field counts the number of external pixelrefs we have created. They notify us when
-    // they are destroyed so we can decrement this.
-    //
-    //  > 0     we have outstanding pixelrefs
-    // == 0     we have no outstanding pixelrefs, and can be safely purged
-    //  < 0     we have been created, but not yet "installed" the first time.
-    //
-    int         fExternalCounter;
-
-    enum {
-        kBeforeFirstInstall_ExternalCounter = -1
-    };
+    // This field counts the number of external pixelrefs we have created.
+    // They notify us when they are destroyed so we can decrement this.
+    int  fExternalCounter     = 0;
+    bool fDiscardableIsLocked = true;
 };
 
 void SkBitmapCache::PrivateDeleteRec(Rec* rec) { delete rec; }
diff --git a/src/core/SkBitmapDevice.cpp b/src/core/SkBitmapDevice.cpp
index b51f28a..4f38b56 100644
--- a/src/core/SkBitmapDevice.cpp
+++ b/src/core/SkBitmapDevice.cpp
@@ -710,6 +710,10 @@
     return this->makeSpecial(fBitmap);
 }
 
+sk_sp<SkSpecialImage> SkBitmapDevice::snapBackImage(const SkIRect& bounds) {
+    return SkSpecialImage::CopyFromRaster(bounds, fBitmap, &this->surfaceProps());
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkSurface> SkBitmapDevice::makeSurface(const SkImageInfo& info, const SkSurfaceProps& props) {
diff --git a/src/core/SkBitmapDevice.h b/src/core/SkBitmapDevice.h
index 7a0d947..e6f64b6 100644
--- a/src/core/SkBitmapDevice.h
+++ b/src/core/SkBitmapDevice.h
@@ -113,6 +113,8 @@
     sk_sp<SkSpecialImage> snapSpecial() override;
     void setImmutable() override { fBitmap.setImmutable(); }
 
+    sk_sp<SkSpecialImage> snapBackImage(const SkIRect&) override;
+
     ///////////////////////////////////////////////////////////////////////////
 
     bool onReadPixels(const SkPixmap&, int x, int y) override;
diff --git a/src/core/SkBitmapProcState_matrix.h b/src/core/SkBitmapProcState_matrix.h
deleted file mode 100644
index ce1685d..0000000
--- a/src/core/SkBitmapProcState_matrix.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright 2011 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkMath.h"
-#include "SkMathPriv.h"
-
-#define SCALE_FILTER_NAME MAKENAME(_filter_scale)
-
-static void SCALE_FILTER_NAME(const SkBitmapProcState& s,
-                              uint32_t xy[], int count, int x, int y) {
-    SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
-                             SkMatrix::kScale_Mask)) == 0);
-    SkASSERT(s.fInvKy == 0);
-
-    auto pack = [](SkFixed f, unsigned max, SkFixed one) {
-        unsigned i = TILE_PROCF(f, max);
-        i = (i << 4) | EXTRACT_LOW_BITS(f, max);
-        return (i << 14) | (TILE_PROCF((f + one), max));
-    };
-
-    const unsigned maxX = s.fPixmap.width() - 1;
-    const SkFractionalInt dx = s.fInvSxFractionalInt;
-    SkFractionalInt fx;
-    {
-        const SkBitmapProcStateAutoMapper mapper(s, x, y);
-        const SkFixed fy = mapper.fixedY();
-        const unsigned maxY = s.fPixmap.height() - 1;
-        // compute our two Y values up front
-        *xy++ = pack(fy, maxY, s.fFilterOneY);
-        // now initialize fx
-        fx = mapper.fractionalIntX();
-    }
-
-#ifdef CHECK_FOR_DECAL
-    // For historical reasons we check both ends are < maxX rather than <= maxX.
-    // TODO: try changing this?  See also can_truncate_to_fixed_for_decal().
-    if ((unsigned)SkFractionalIntToInt(fx               ) < maxX &&
-        (unsigned)SkFractionalIntToInt(fx + dx*(count-1)) < maxX) {
-        while (count --> 0) {
-            SkFixed fixedFx = SkFractionalIntToFixed(fx);
-            SkASSERT((fixedFx >> (16 + 14)) == 0);
-            *xy++ = (fixedFx >> 12 << 14) | ((fixedFx >> 16) + 1);
-            fx += dx;
-        }
-        return;
-    }
-#endif
-    while (count --> 0) {
-        SkFixed fixedFx = SkFractionalIntToFixed(fx);
-        *xy++ = pack(fixedFx, maxX, s.fFilterOneX);
-        fx += dx;
-    }
-}
-
-#undef MAKENAME
-#undef TILE_PROCF
-#undef EXTRACT_LOW_BITS
-#ifdef CHECK_FOR_DECAL
-    #undef CHECK_FOR_DECAL
-#endif
-
-#undef SCALE_FILTER_NAME
diff --git a/src/core/SkBitmapProcState_matrixProcs.cpp b/src/core/SkBitmapProcState_matrixProcs.cpp
index c7ef7f4..24e3b18 100644
--- a/src/core/SkBitmapProcState_matrixProcs.cpp
+++ b/src/core/SkBitmapProcState_matrixProcs.cpp
@@ -14,11 +14,49 @@
  */
 
 #include "SkBitmapProcState.h"
-#include "SkBitmapProcState_utils.h"
 #include "SkShader.h"
 #include "SkTo.h"
 #include "SkUtils.h"
 
+/*
+ *  The decal_ functions require that
+ *  1. dx > 0
+ *  2. [fx, fx+dx, fx+2dx, fx+3dx, ... fx+(count-1)dx] are all <= maxX
+ *
+ *  In addition, we use SkFractionalInt to keep more fractional precision than
+ *  just SkFixed, so we will abort the decal_ call if dx is very small, since
+ *  the decal_ function just operates on SkFixed. If that were changed, we could
+ *  skip the very_small test here.
+ */
+static inline bool can_truncate_to_fixed_for_decal(SkFixed fx,
+                                                   SkFixed dx,
+                                                   int count, unsigned max) {
+    SkASSERT(count > 0);
+
+    // if decal_ kept SkFractionalInt precision, this would just be dx <= 0
+    // I just made up the 1/256. Just don't want to perceive accumulated error
+    // if we truncate frDx and lose its low bits.
+    if (dx <= SK_Fixed1 / 256) {
+        return false;
+    }
+
+    // Note: it seems the test should be (fx <= max && lastFx <= max); but
+    // historically it's been a strict inequality check, and changing produces
+    // unexpected diffs.  Further investigation is needed.
+
+    // We cast to unsigned so we don't have to check for negative values, which
+    // will now appear as very large positive values, and thus fail our test!
+    if ((unsigned)SkFixedFloorToInt(fx) >= max) {
+        return false;
+    }
+
+    // Promote to 64bit (48.16) to avoid overflow.
+    const uint64_t lastFx = fx + sk_64_mul(dx, count - 1);
+
+    return SkTFitsIn<int32_t>(lastFx) && (unsigned)SkFixedFloorToInt(SkTo<int32_t>(lastFx)) < max;
+}
+
+
 // When not filtering, we store 32-bit y, 16-bit x, 16-bit x, 16-bit x, ...
 // When filtering we write out 32-bit encodings, pairing 14.4 x0 with 14-bit x1.
 
@@ -86,9 +124,94 @@
     }
 }
 
-// Clamp/Clamp and Repeat/Repeat have NEON or portable implementations.
-// TODO: switch SkBitmapProcState_matrix.h to templates instead of #defines and multiple includes?
+// Extract the high four fractional bits from fx, the lerp parameter when filtering.
+static unsigned extract_low_bits_clamp(SkFixed fx, int /*max*/) {
+    // If we're already scaled up to by max like clamp/decal,
+    // just grab the high four fractional bits.
+    return (fx >> 12) & 0xf;
+}
+static unsigned extract_low_bits_repeat_mirror(SkFixed fx, int max) {
+    // In repeat or mirror fx is in [0,1], so scale up by max first.
+    // TODO: remove the +1 here and the -1 at the call sites...
+    return extract_low_bits_clamp((fx & 0xffff) * (max+1), max);
+}
 
+template <unsigned (*tile)(SkFixed, int), unsigned (*extract_low_bits)(SkFixed, int), bool tryDecal>
+static void filter_scale(const SkBitmapProcState& s,
+                         uint32_t xy[], int count, int x, int y) {
+    SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+                             SkMatrix::kScale_Mask)) == 0);
+    SkASSERT(s.fInvKy == 0);
+
+    auto pack = [](SkFixed f, unsigned max, SkFixed one) {
+        unsigned i = tile(f, max);
+        i = (i << 4) | extract_low_bits(f, max);
+        return (i << 14) | (tile((f + one), max));
+    };
+
+    const unsigned maxX = s.fPixmap.width() - 1;
+    const SkFractionalInt dx = s.fInvSxFractionalInt;
+    SkFractionalInt fx;
+    {
+        const SkBitmapProcStateAutoMapper mapper(s, x, y);
+        const SkFixed fy = mapper.fixedY();
+        const unsigned maxY = s.fPixmap.height() - 1;
+        // compute our two Y values up front
+        *xy++ = pack(fy, maxY, s.fFilterOneY);
+        // now initialize fx
+        fx = mapper.fractionalIntX();
+    }
+
+    // For historical reasons we check both ends are < maxX rather than <= maxX.
+    // TODO: try changing this?  See also can_truncate_to_fixed_for_decal().
+    if (tryDecal &&
+        (unsigned)SkFractionalIntToInt(fx               ) < maxX &&
+        (unsigned)SkFractionalIntToInt(fx + dx*(count-1)) < maxX) {
+        while (count --> 0) {
+            SkFixed fixedFx = SkFractionalIntToFixed(fx);
+            SkASSERT((fixedFx >> (16 + 14)) == 0);
+            *xy++ = (fixedFx >> 12 << 14) | ((fixedFx >> 16) + 1);
+            fx += dx;
+        }
+        return;
+    }
+
+    while (count --> 0) {
+        SkFixed fixedFx = SkFractionalIntToFixed(fx);
+        *xy++ = pack(fixedFx, maxX, s.fFilterOneX);
+        fx += dx;
+    }
+}
+
+// Helper to ensure that when we shift down, we do it w/o sign-extension
+// so the caller doesn't have to manually mask off the top 16 bits.
+static inline unsigned SK_USHIFT16(unsigned x) {
+    return x >> 16;
+}
+
+static unsigned clamp(SkFixed fx, int max) {
+    return SkClampMax(fx >> 16, max);
+}
+static unsigned repeat(SkFixed fx, int max) {
+    SkASSERT(max < 65535);
+    return SK_USHIFT16((unsigned)(fx & 0xFFFF) * (max + 1));
+}
+static unsigned mirror(SkFixed fx, int max) {
+    SkASSERT(max < 65535);
+    // s is 0xFFFFFFFF if we're on an odd interval, or 0 if an even interval
+    SkFixed s = SkLeftShift(fx, 15) >> 31;
+
+    // This should be exactly the same as repeat(fx ^ s, max) from here on.
+    return SK_USHIFT16( ((fx ^ s) & 0xFFFF) * (max + 1) );
+}
+
+// Mirror/Mirror's always just portable code.
+static const SkBitmapProcState::MatrixProc MirrorX_MirrorY_Procs[] = {
+    nofilter_scale<mirror, false>,
+    filter_scale<mirror, extract_low_bits_repeat_mirror, false>,
+};
+
+// Clamp/Clamp and Repeat/Repeat have NEON or portable implementations.
 #if defined(SK_ARM_HAS_NEON)
     #include <arm_neon.h>
 
@@ -179,8 +302,7 @@
         }
     }
 
-    // TILEX_PROCF(fx, max)    SkClampMax((fx) >> 16, max)
-    static inline int16x8_t sbpsm_clamp_tile8(int32x4_t low, int32x4_t high, unsigned max) {
+    static inline int16x8_t clamp8(int32x4_t low, int32x4_t high, unsigned max) {
         int16x8_t res;
 
         // get the hi 16s of all those 32s
@@ -193,8 +315,7 @@
         return res;
     }
 
-    // TILEX_PROCF(fx, max)    SkClampMax((fx) >> 16, max)
-    static inline int32x4_t sbpsm_clamp_tile4(int32x4_t f, unsigned max) {
+    static inline int32x4_t clamp4(int32x4_t f, unsigned max) {
         int32x4_t res;
 
         // get the hi 16s of all those 32s
@@ -207,8 +328,7 @@
         return res;
     }
 
-    // EXTRACT_LOW_BITS(fy, max)         (((fy) >> 12) & 0xF)
-    static inline int32x4_t sbpsm_clamp_tile4_low_bits(int32x4_t fx) {
+    static inline int32x4_t extract_low_bits_clamp4(int32x4_t fx, unsigned) {
         int32x4_t ret;
 
         ret = vshrq_n_s32(fx, 12);
@@ -221,8 +341,7 @@
         return ret;
     }
 
-    // TILEX_PROCF(fx, max) (((fx)&0xFFFF)*((max)+1)>> 16)
-    static inline int16x8_t sbpsm_repeat_tile8(int32x4_t low, int32x4_t high, unsigned max) {
+    static inline int16x8_t repeat8(int32x4_t low, int32x4_t high, unsigned max) {
         uint16x8_t res;
         uint32x4_t tmpl, tmph;
 
@@ -239,8 +358,7 @@
         return vreinterpretq_s16_u16(res);
     }
 
-    // TILEX_PROCF(fx, max) (((fx)&0xFFFF)*((max)+1)>> 16)
-    static inline int32x4_t sbpsm_repeat_tile4(int32x4_t f, unsigned max) {
+    static inline int32x4_t repeat4(int32x4_t f, unsigned max) {
         uint16x4_t res;
         uint32x4_t tmp;
 
@@ -256,8 +374,7 @@
         return vreinterpretq_s32_u32(tmp);
     }
 
-    // EXTRACT_LOW_BITS(fx, max)         ((((fx) & 0xFFFF) * ((max) + 1) >> 12) & 0xF)
-    static inline int32x4_t sbpsm_repeat_tile4_low_bits(int32x4_t fx, unsigned max) {
+    static inline int32x4_t extract_low_bits_repeat_mirror4(int32x4_t fx, unsigned max) {
         uint16x4_t res;
         uint32x4_t tmp;
         int32x4_t ret;
@@ -279,81 +396,198 @@
         return ret;
     }
 
-    #define MAKENAME(suffix)                ClampX_ClampY ## suffix ## _neon
-    #define TILEX_PROCF(fx, max)            SkClampMax((fx) >> 16, max)
-    #define TILEY_PROCF(fy, max)            SkClampMax((fy) >> 16, max)
-    #define TILEX_PROCF_NEON8(l, h, max)    sbpsm_clamp_tile8(l, h, max)
-    #define TILEY_PROCF_NEON8(l, h, max)    sbpsm_clamp_tile8(l, h, max)
-    #define TILEX_PROCF_NEON4(fx, max)      sbpsm_clamp_tile4(fx, max)
-    #define TILEY_PROCF_NEON4(fy, max)      sbpsm_clamp_tile4(fy, max)
-    #define EXTRACT_LOW_BITS(v, max)        (((v) >> 12) & 0xF)
-    #define EXTRACT_LOW_BITS_NEON4(v, max)  sbpsm_clamp_tile4_low_bits(v)
-    #define CHECK_FOR_DECAL
-    #include "SkBitmapProcState_matrix_neon.h"
+    template <unsigned   (*tile)(SkFixed, int),
+              int16x8_t (*tile8)(int32x4_t, int32x4_t, unsigned),
+             bool tryDecal>
+    static void nofilter_scale_neon(const SkBitmapProcState& s,
+                                    uint32_t xy[], int count, int x, int y) {
+        SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+                                 SkMatrix::kScale_Mask)) == 0);
 
-    #define MAKENAME(suffix)                RepeatX_RepeatY ## suffix ## _neon
-    #define TILEX_PROCF(fx, max)            SK_USHIFT16(((fx) & 0xFFFF) * ((max) + 1))
-    #define TILEY_PROCF(fy, max)            SK_USHIFT16(((fy) & 0xFFFF) * ((max) + 1))
-    #define TILEX_PROCF_NEON8(l, h, max)    sbpsm_repeat_tile8(l, h, max)
-    #define TILEY_PROCF_NEON8(l, h, max)    sbpsm_repeat_tile8(l, h, max)
-    #define TILEX_PROCF_NEON4(fx, max)      sbpsm_repeat_tile4(fx, max)
-    #define TILEY_PROCF_NEON4(fy, max)      sbpsm_repeat_tile4(fy, max)
-    #define EXTRACT_LOW_BITS(v, max)        ((((v) & 0xFFFF) * ((max) + 1) >> 12) & 0xF)
-    #define EXTRACT_LOW_BITS_NEON4(v, max)  sbpsm_repeat_tile4_low_bits(v, max)
-    #include "SkBitmapProcState_matrix_neon.h"
+        // we store y, x, x, x, x, x
+        const unsigned maxX = s.fPixmap.width() - 1;
+        SkFractionalInt fx;
+        {
+            const SkBitmapProcStateAutoMapper mapper(s, x, y);
+            const unsigned maxY = s.fPixmap.height() - 1;
+            *xy++ = tile(mapper.fixedY(), maxY);
+            fx = mapper.fractionalIntX();
+        }
 
-#else
-    static unsigned clamp(SkFixed fx, int max) {
-        return SkClampMax(fx >> 16, max);
+        if (0 == maxX) {
+            // all of the following X values must be 0
+            memset(xy, 0, count * sizeof(uint16_t));
+            return;
+        }
+
+        const SkFractionalInt dx = s.fInvSxFractionalInt;
+
+        // test if we don't need to apply the tile proc
+        const SkFixed fixedFx = SkFractionalIntToFixed(fx);
+        const SkFixed fixedDx = SkFractionalIntToFixed(dx);
+        if (tryDecal && can_truncate_to_fixed_for_decal(fixedFx, fixedDx, count, maxX)) {
+            decal_nofilter_scale_neon(xy, fixedFx, fixedDx, count);
+            return;
+        }
+
+        if (count >= 8) {
+            SkFractionalInt dx2 = dx+dx;
+            SkFractionalInt dx4 = dx2+dx2;
+            SkFractionalInt dx8 = dx4+dx4;
+
+            // now build fx/fx+dx/fx+2dx/fx+3dx
+            SkFractionalInt fx1, fx2, fx3;
+            int32x4_t lbase, hbase;
+            int16_t *dst16 = (int16_t *)xy;
+
+            fx1 = fx+dx;
+            fx2 = fx1+dx;
+            fx3 = fx2+dx;
+
+            lbase = vdupq_n_s32(SkFractionalIntToFixed(fx));
+            lbase = vsetq_lane_s32(SkFractionalIntToFixed(fx1), lbase, 1);
+            lbase = vsetq_lane_s32(SkFractionalIntToFixed(fx2), lbase, 2);
+            lbase = vsetq_lane_s32(SkFractionalIntToFixed(fx3), lbase, 3);
+            hbase = vaddq_s32(lbase, vdupq_n_s32(SkFractionalIntToFixed(dx4)));
+
+            // store & bump
+            while (count >= 8) {
+
+                int16x8_t fx8;
+
+                fx8 = tile8(lbase, hbase, maxX);
+
+                vst1q_s16(dst16, fx8);
+
+                // but preserving base & on to the next
+                lbase = vaddq_s32 (lbase, vdupq_n_s32(SkFractionalIntToFixed(dx8)));
+                hbase = vaddq_s32 (hbase, vdupq_n_s32(SkFractionalIntToFixed(dx8)));
+                dst16 += 8;
+                count -= 8;
+                fx += dx8;
+            };
+            xy = (uint32_t *) dst16;
+        }
+
+        uint16_t* xx = (uint16_t*)xy;
+        for (int i = count; i > 0; --i) {
+            *xx++ = tile(SkFractionalIntToFixed(fx), maxX);
+            fx += dx;
+        }
     }
 
-    #define MAKENAME(suffix)         ClampX_ClampY ## suffix
-    #define TILE_PROCF               clamp
-    #define EXTRACT_LOW_BITS(v, max) (((v) >> 12) & 0xF)
-    #define CHECK_FOR_DECAL
-    #include "SkBitmapProcState_matrix.h"  // will create ClampX_ClampY_filter_scale.
+    template <unsigned              (*tile )(SkFixed, int),
+              int32x4_t             (*tile4)(int32x4_t, unsigned),
+              unsigned  (*extract_low_bits )(SkFixed, int),
+              int32x4_t (*extract_low_bits4)(int32x4_t, unsigned),
+              bool tryDecal>
+    static void filter_scale_neon(const SkBitmapProcState& s,
+                                  uint32_t xy[], int count, int x, int y) {
+        SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
+                                 SkMatrix::kScale_Mask)) == 0);
+        SkASSERT(s.fInvKy == 0);
+
+        auto pack = [&](SkFixed f, unsigned max, SkFixed one) {
+            unsigned i = tile(f, max);
+            i = (i << 4) | extract_low_bits(f, max);
+            return (i << 14) | (tile((f + one), max));
+        };
+
+        auto pack4 = [&](int32x4_t f, unsigned max, SkFixed one) {
+            int32x4_t ret, res;
+
+            res = tile4(f, max);
+
+            ret = extract_low_bits4(f, max);
+            ret = vsliq_n_s32(ret, res, 4);
+
+            res = tile4(f + vdupq_n_s32(one), max);
+            ret = vorrq_s32(vshlq_n_s32(ret, 14), res);
+
+            return ret;
+        };
+
+        const unsigned maxX = s.fPixmap.width() - 1;
+        const SkFixed one = s.fFilterOneX;
+        const SkFractionalInt dx = s.fInvSxFractionalInt;
+        SkFractionalInt fx;
+
+        {
+            const SkBitmapProcStateAutoMapper mapper(s, x, y);
+            const SkFixed fy = mapper.fixedY();
+            const unsigned maxY = s.fPixmap.height() - 1;
+            // compute our two Y values up front
+            *xy++ = pack(fy, maxY, s.fFilterOneY);
+            // now initialize fx
+            fx = mapper.fractionalIntX();
+        }
+
+        // test if we don't need to apply the tile proc
+        const SkFixed fixedFx = SkFractionalIntToFixed(fx);
+        const SkFixed fixedDx = SkFractionalIntToFixed(dx);
+        if (tryDecal && can_truncate_to_fixed_for_decal(fixedFx, fixedDx, count, maxX)) {
+            decal_filter_scale_neon(xy, fixedFx, fixedDx, count);
+            return;
+        }
+
+        if (count >= 4) {
+            int32x4_t wide_fx;
+
+            wide_fx = vdupq_n_s32(SkFractionalIntToFixed(fx));
+            wide_fx = vsetq_lane_s32(SkFractionalIntToFixed(fx+dx), wide_fx, 1);
+            wide_fx = vsetq_lane_s32(SkFractionalIntToFixed(fx+dx+dx), wide_fx, 2);
+            wide_fx = vsetq_lane_s32(SkFractionalIntToFixed(fx+dx+dx+dx), wide_fx, 3);
+
+            while (count >= 4) {
+                int32x4_t res;
+
+                res = pack4(wide_fx, maxX, one);
+
+                vst1q_u32(xy, vreinterpretq_u32_s32(res));
+
+                wide_fx += vdupq_n_s32(SkFractionalIntToFixed(dx+dx+dx+dx));
+                fx += dx+dx+dx+dx;
+                xy += 4;
+                count -= 4;
+            }
+        }
+
+        while (--count >= 0) {
+            *xy++ = pack(SkFractionalIntToFixed(fx), maxX, one);
+            fx += dx;
+        }
+    }
 
     static const SkBitmapProcState::MatrixProc ClampX_ClampY_Procs[] = {
-        nofilter_scale<clamp, true>,
-        ClampX_ClampY_filter_scale,
+        nofilter_scale_neon<clamp, clamp8, true>,
+        filter_scale_neon<clamp,
+                          clamp4,
+                          extract_low_bits_clamp,
+                          extract_low_bits_clamp4,
+                          true>,
     };
 
+    static const SkBitmapProcState::MatrixProc RepeatX_RepeatY_Procs[] = {
+        nofilter_scale_neon<repeat, repeat8, false>,
+        filter_scale_neon<repeat,
+                          repeat4,
+                          extract_low_bits_repeat_mirror,
+                          extract_low_bits_repeat_mirror4,
+                          false>,
+    };
 
-    static unsigned repeat(SkFixed fx, int max) {
-        SkASSERT(max < 65535);
-        return SK_USHIFT16((unsigned)(fx & 0xFFFF) * (max + 1));
-    }
-
-    #define MAKENAME(suffix)         RepeatX_RepeatY ## suffix
-    #define TILE_PROCF               repeat
-    #define EXTRACT_LOW_BITS(v, max) (((v * (max + 1)) >> 12) & 0xF)
-    #include "SkBitmapProcState_matrix.h"  // will create RepeatX_RepeatY_filter_scale.
+#else
+    static const SkBitmapProcState::MatrixProc ClampX_ClampY_Procs[] = {
+        nofilter_scale<clamp, true>,
+        filter_scale<clamp, extract_low_bits_clamp, true>,
+    };
 
     static const SkBitmapProcState::MatrixProc RepeatX_RepeatY_Procs[] = {
         nofilter_scale<repeat, false>,
-        RepeatX_RepeatY_filter_scale,
+        filter_scale<repeat, extract_low_bits_repeat_mirror, false>,
     };
 #endif
 
-static unsigned mirror(SkFixed fx, int max) {
-    SkASSERT(max < 65535);
-    // s is 0xFFFFFFFF if we're on an odd interval, or 0 if an even interval
-    SkFixed s = SkLeftShift(fx, 15) >> 31;
-
-    // This should be exactly the same as repeat(fx ^ s, max) from here on.
-    return SK_USHIFT16( ((fx ^ s) & 0xFFFF) * (max + 1) );
-}
-
-#define MAKENAME(suffix)         MirrorX_MirrorY ## suffix
-#define TILE_PROCF               mirror
-#define EXTRACT_LOW_BITS(v, max) (((v * (max + 1)) >> 12) & 0xF)
-#include "SkBitmapProcState_matrix.h"  // will create MirrorX_MirrorY_filter_scale.
-
-static const SkBitmapProcState::MatrixProc MirrorX_MirrorY_Procs[] = {
-    nofilter_scale<mirror, false>,
-    MirrorX_MirrorY_filter_scale,
-};
-
 
 ///////////////////////////////////////////////////////////////////////////////
 // This next chunk has some specializations for unfiltered translate-only matrices.
@@ -580,11 +814,7 @@
         // clamp gets special version of filterOne, working in non-normalized space (allowing decal)
         fFilterOneX = SK_Fixed1;
         fFilterOneY = SK_Fixed1;
-    #if defined(SK_ARM_HAS_NEON)
-        return ClampX_ClampY_Procs_neon[index];
-    #else
         return ClampX_ClampY_Procs[index];
-    #endif
     }
 
     // all remaining procs use this form for filterOne, putting them into normalized space.
@@ -592,11 +822,7 @@
     fFilterOneY = SK_Fixed1 / fPixmap.height();
 
     if (fTileModeX == SkShader::kRepeat_TileMode) {
-    #if defined(SK_ARM_HAS_NEON)
-        return RepeatX_RepeatY_Procs_neon[index];
-    #else
         return RepeatX_RepeatY_Procs[index];
-    #endif
     }
 
     return MirrorX_MirrorY_Procs[index];
diff --git a/src/core/SkBitmapProcState_matrix_neon.h b/src/core/SkBitmapProcState_matrix_neon.h
deleted file mode 100644
index faa7a56..0000000
--- a/src/core/SkBitmapProcState_matrix_neon.h
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
- * Copyright 2014 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include <arm_neon.h>
-
-#define SCALE_NOFILTER_NAME     MAKENAME(_nofilter_scale)
-#define SCALE_FILTER_NAME       MAKENAME(_filter_scale)
-
-#define PACK_FILTER_X_NAME  MAKENAME(_pack_filter_x)
-#define PACK_FILTER_Y_NAME  MAKENAME(_pack_filter_y)
-#define PACK_FILTER_X4_NAME MAKENAME(_pack_filter_x4)
-#define PACK_FILTER_Y4_NAME MAKENAME(_pack_filter_y4)
-
-#ifndef PREAMBLE
-    #define PREAMBLE(state)
-    #define PREAMBLE_PARAM_X
-    #define PREAMBLE_PARAM_Y
-    #define PREAMBLE_ARG_X
-    #define PREAMBLE_ARG_Y
-#endif
-
-static void SCALE_NOFILTER_NAME(const SkBitmapProcState& s,
-                                uint32_t xy[], int count, int x, int y) {
-    SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
-                             SkMatrix::kScale_Mask)) == 0);
-
-    PREAMBLE(s);
-
-    // we store y, x, x, x, x, x
-    const unsigned maxX = s.fPixmap.width() - 1;
-    SkFractionalInt fx;
-    {
-        const SkBitmapProcStateAutoMapper mapper(s, x, y);
-        const unsigned maxY = s.fPixmap.height() - 1;
-        *xy++ = TILEY_PROCF(mapper.fixedY(), maxY);
-        fx = mapper.fractionalIntX();
-    }
-
-    if (0 == maxX) {
-        // all of the following X values must be 0
-        memset(xy, 0, count * sizeof(uint16_t));
-        return;
-    }
-
-    const SkFractionalInt dx = s.fInvSxFractionalInt;
-
-#ifdef CHECK_FOR_DECAL
-    // test if we don't need to apply the tile proc
-    const SkFixed fixedFx = SkFractionalIntToFixed(fx);
-    const SkFixed fixedDx = SkFractionalIntToFixed(dx);
-    if (can_truncate_to_fixed_for_decal(fixedFx, fixedDx, count, maxX)) {
-        decal_nofilter_scale_neon(xy, fixedFx, fixedDx, count);
-        return;
-    }
-#endif
-
-    if (count >= 8) {
-        SkFractionalInt dx2 = dx+dx;
-        SkFractionalInt dx4 = dx2+dx2;
-        SkFractionalInt dx8 = dx4+dx4;
-
-        // now build fx/fx+dx/fx+2dx/fx+3dx
-        SkFractionalInt fx1, fx2, fx3;
-        int32x4_t lbase, hbase;
-        int16_t *dst16 = (int16_t *)xy;
-
-        fx1 = fx+dx;
-        fx2 = fx1+dx;
-        fx3 = fx2+dx;
-
-        lbase = vdupq_n_s32(SkFractionalIntToFixed(fx));
-        lbase = vsetq_lane_s32(SkFractionalIntToFixed(fx1), lbase, 1);
-        lbase = vsetq_lane_s32(SkFractionalIntToFixed(fx2), lbase, 2);
-        lbase = vsetq_lane_s32(SkFractionalIntToFixed(fx3), lbase, 3);
-        hbase = vaddq_s32(lbase, vdupq_n_s32(SkFractionalIntToFixed(dx4)));
-
-        // store & bump
-        while (count >= 8) {
-
-            int16x8_t fx8;
-
-            fx8 = TILEX_PROCF_NEON8(lbase, hbase, maxX);
-
-            vst1q_s16(dst16, fx8);
-
-            // but preserving base & on to the next
-            lbase = vaddq_s32 (lbase, vdupq_n_s32(SkFractionalIntToFixed(dx8)));
-            hbase = vaddq_s32 (hbase, vdupq_n_s32(SkFractionalIntToFixed(dx8)));
-            dst16 += 8;
-            count -= 8;
-            fx += dx8;
-        };
-        xy = (uint32_t *) dst16;
-    }
-
-    uint16_t* xx = (uint16_t*)xy;
-    for (int i = count; i > 0; --i) {
-        *xx++ = TILEX_PROCF(SkFractionalIntToFixed(fx), maxX);
-        fx += dx;
-    }
-}
-
-static inline uint32_t PACK_FILTER_Y_NAME(SkFixed f, unsigned max,
-                                          SkFixed one PREAMBLE_PARAM_Y) {
-    unsigned i = TILEY_PROCF(f, max);
-    i = (i << 4) | EXTRACT_LOW_BITS(f, max);
-    return (i << 14) | (TILEY_PROCF((f + one), max));
-}
-
-static inline uint32_t PACK_FILTER_X_NAME(SkFixed f, unsigned max,
-                                          SkFixed one PREAMBLE_PARAM_X) {
-    unsigned i = TILEX_PROCF(f, max);
-    i = (i << 4) | EXTRACT_LOW_BITS(f, max);
-    return (i << 14) | (TILEX_PROCF((f + one), max));
-}
-
-static inline int32x4_t PACK_FILTER_X4_NAME(int32x4_t f, unsigned max,
-                                          SkFixed one PREAMBLE_PARAM_X) {
-    int32x4_t ret, res, wide_one;
-
-    // Prepare constants
-    wide_one = vdupq_n_s32(one);
-
-    // Step 1
-    res = TILEX_PROCF_NEON4(f, max);
-
-    // Step 2
-    ret = EXTRACT_LOW_BITS_NEON4(f, max);
-    ret = vsliq_n_s32(ret, res, 4);
-
-    // Step 3
-    res = TILEX_PROCF_NEON4(f + wide_one, max);
-    ret = vorrq_s32(vshlq_n_s32(ret, 14), res);
-
-    return ret;
-}
-
-static inline int32x4_t PACK_FILTER_Y4_NAME(int32x4_t f, unsigned max,
-                                          SkFixed one PREAMBLE_PARAM_X) {
-    int32x4_t ret, res, wide_one;
-
-    // Prepare constants
-    wide_one = vdupq_n_s32(one);
-
-    // Step 1
-    res = TILEY_PROCF_NEON4(f, max);
-
-    // Step 2
-    ret = EXTRACT_LOW_BITS_NEON4(f, max);
-    ret = vsliq_n_s32(ret, res, 4);
-
-    // Step 3
-    res = TILEY_PROCF_NEON4(f + wide_one, max);
-    ret = vorrq_s32(vshlq_n_s32(ret, 14), res);
-
-    return ret;
-}
-
-static void SCALE_FILTER_NAME(const SkBitmapProcState& s,
-                              uint32_t xy[], int count, int x, int y) {
-    SkASSERT((s.fInvType & ~(SkMatrix::kTranslate_Mask |
-                             SkMatrix::kScale_Mask)) == 0);
-    SkASSERT(s.fInvKy == 0);
-
-    PREAMBLE(s);
-
-    const unsigned maxX = s.fPixmap.width() - 1;
-    const SkFixed one = s.fFilterOneX;
-    const SkFractionalInt dx = s.fInvSxFractionalInt;
-    SkFractionalInt fx;
-
-    {
-        const SkBitmapProcStateAutoMapper mapper(s, x, y);
-        const SkFixed fy = mapper.fixedY();
-        const unsigned maxY = s.fPixmap.height() - 1;
-        // compute our two Y values up front
-        *xy++ = PACK_FILTER_Y_NAME(fy, maxY, s.fFilterOneY PREAMBLE_ARG_Y);
-        // now initialize fx
-        fx = mapper.fractionalIntX();
-    }
-
-#ifdef CHECK_FOR_DECAL
-    // test if we don't need to apply the tile proc
-    const SkFixed fixedFx = SkFractionalIntToFixed(fx);
-    const SkFixed fixedDx = SkFractionalIntToFixed(dx);
-    if (can_truncate_to_fixed_for_decal(fixedFx, fixedDx, count, maxX)) {
-        decal_filter_scale_neon(xy, fixedFx, fixedDx, count);
-        return;
-    }
-#endif
-    {
-
-    if (count >= 4) {
-        int32x4_t wide_fx;
-
-        wide_fx = vdupq_n_s32(SkFractionalIntToFixed(fx));
-        wide_fx = vsetq_lane_s32(SkFractionalIntToFixed(fx+dx), wide_fx, 1);
-        wide_fx = vsetq_lane_s32(SkFractionalIntToFixed(fx+dx+dx), wide_fx, 2);
-        wide_fx = vsetq_lane_s32(SkFractionalIntToFixed(fx+dx+dx+dx), wide_fx, 3);
-
-        while (count >= 4) {
-            int32x4_t res;
-
-            res = PACK_FILTER_X4_NAME(wide_fx, maxX, one PREAMBLE_ARG_X);
-
-            vst1q_u32(xy, vreinterpretq_u32_s32(res));
-
-            wide_fx += vdupq_n_s32(SkFractionalIntToFixed(dx+dx+dx+dx));
-            fx += dx+dx+dx+dx;
-            xy += 4;
-            count -= 4;
-        }
-    }
-
-    while (--count >= 0) {
-        *xy++ = PACK_FILTER_X_NAME(SkFractionalIntToFixed(fx), maxX, one PREAMBLE_ARG_X);
-        fx += dx;
-    }
-
-    }
-}
-
-static const SkBitmapProcState::MatrixProc MAKENAME(_Procs)[] = {
-    SCALE_NOFILTER_NAME,
-    SCALE_FILTER_NAME,
-};
-
-#undef TILEX_PROCF_NEON8
-#undef TILEY_PROCF_NEON8
-#undef TILEX_PROCF_NEON4
-#undef TILEY_PROCF_NEON4
-#undef EXTRACT_LOW_BITS_NEON4
-
-#undef MAKENAME
-#undef TILEX_PROCF
-#undef TILEY_PROCF
-#ifdef CHECK_FOR_DECAL
-    #undef CHECK_FOR_DECAL
-#endif
-
-#undef SCALE_NOFILTER_NAME
-#undef SCALE_FILTER_NAME
-
-#undef PREAMBLE
-#undef PREAMBLE_PARAM_X
-#undef PREAMBLE_PARAM_Y
-#undef PREAMBLE_ARG_X
-#undef PREAMBLE_ARG_Y
-
-#undef EXTRACT_LOW_BITS
diff --git a/src/core/SkBitmapProcState_utils.h b/src/core/SkBitmapProcState_utils.h
deleted file mode 100644
index b40b45c..0000000
--- a/src/core/SkBitmapProcState_utils.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkBitmapProcState_utils_DEFINED
-#define SkBitmapProcState_utils_DEFINED
-
-#include "SkTo.h"
-
-// Helper to ensure that when we shift down, we do it w/o sign-extension
-// so the caller doesn't have to manually mask off the top 16 bits
-//
-static inline unsigned SK_USHIFT16(unsigned x) {
-    return x >> 16;
-}
-
-/*
- *  The decal_ functions require that
- *  1. dx > 0
- *  2. [fx, fx+dx, fx+2dx, fx+3dx, ... fx+(count-1)dx] are all <= maxX
- *
- *  In addition, we use SkFractionalInt to keep more fractional precision than
- *  just SkFixed, so we will abort the decal_ call if dx is very small, since
- *  the decal_ function just operates on SkFixed. If that were changed, we could
- *  skip the very_small test here.
- */
-static inline bool can_truncate_to_fixed_for_decal(SkFixed fx,
-                                                   SkFixed dx,
-                                                   int count, unsigned max) {
-    SkASSERT(count > 0);
-
-    // if decal_ kept SkFractionalInt precision, this would just be dx <= 0
-    // I just made up the 1/256. Just don't want to perceive accumulated error
-    // if we truncate frDx and lose its low bits.
-    if (dx <= SK_Fixed1 / 256) {
-        return false;
-    }
-
-    // Note: it seems the test should be (fx <= max && lastFx <= max); but
-    // historically it's been a strict inequality check, and changing produces
-    // unexpected diffs.  Further investigation is needed.
-
-    // We cast to unsigned so we don't have to check for negative values, which
-    // will now appear as very large positive values, and thus fail our test!
-    if ((unsigned)SkFixedFloorToInt(fx) >= max) {
-        return false;
-    }
-
-    // Promote to 64bit (48.16) to avoid overflow.
-    const uint64_t lastFx = fx + sk_64_mul(dx, count - 1);
-
-    return SkTFitsIn<int32_t>(lastFx) && (unsigned)SkFixedFloorToInt(SkTo<int32_t>(lastFx)) < max;
-}
-
-#endif /* #ifndef SkBitmapProcState_utils_DEFINED */
diff --git a/src/core/SkBlitRow_D32.cpp b/src/core/SkBlitRow_D32.cpp
index b8095d4..94f9680 100644
--- a/src/core/SkBlitRow_D32.cpp
+++ b/src/core/SkBlitRow_D32.cpp
@@ -27,7 +27,35 @@
 
 #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2
     #include <emmintrin.h>
-    #include "SkColor_opts_SSE2.h"
+
+    static inline __m128i SkPMLerp_SSE2(const __m128i& src,
+                                        const __m128i& dst,
+                                        const unsigned src_scale) {
+        // Computes dst + (((src - dst)*src_scale)>>8)
+        const __m128i mask = _mm_set1_epi32(0x00FF00FF);
+
+        // Unpack the 16x8-bit source into 2 8x16-bit splayed halves.
+        __m128i src_rb = _mm_and_si128(mask, src);
+        __m128i src_ag = _mm_srli_epi16(src, 8);
+        __m128i dst_rb = _mm_and_si128(mask, dst);
+        __m128i dst_ag = _mm_srli_epi16(dst, 8);
+
+        // Compute scaled differences.
+        __m128i diff_rb = _mm_sub_epi16(src_rb, dst_rb);
+        __m128i diff_ag = _mm_sub_epi16(src_ag, dst_ag);
+        __m128i s = _mm_set1_epi16(src_scale);
+        diff_rb = _mm_mullo_epi16(diff_rb, s);
+        diff_ag = _mm_mullo_epi16(diff_ag, s);
+
+        // Pack the differences back together.
+        diff_rb = _mm_srli_epi16(diff_rb, 8);
+        diff_ag = _mm_andnot_si128(mask, diff_ag);
+        __m128i diff = _mm_or_si128(diff_rb, diff_ag);
+
+        // Add difference to destination.
+        return _mm_add_epi8(dst, diff);
+    }
+
 
     static void blit_row_s32_blend(SkPMColor* dst, const SkPMColor* src, int count, U8CPU alpha) {
         SkASSERT(alpha <= 255);
@@ -54,6 +82,46 @@
         }
     }
 
+    static inline __m128i SkBlendARGB32_SSE2(const __m128i& src,
+                                             const __m128i& dst,
+                                             const unsigned aa) {
+        unsigned alpha = SkAlpha255To256(aa);
+        __m128i src_scale = _mm_set1_epi16(alpha);
+        // SkAlphaMulInv256(SkGetPackedA32(src), src_scale)
+        __m128i dst_scale = _mm_srli_epi32(src, 24);
+        // High words in dst_scale are 0, so it's safe to multiply with 16-bit src_scale.
+        dst_scale = _mm_mullo_epi16(dst_scale, src_scale);
+        dst_scale = _mm_sub_epi32(_mm_set1_epi32(0xFFFF), dst_scale);
+        dst_scale = _mm_add_epi32(dst_scale, _mm_srli_epi32(dst_scale, 8));
+        dst_scale = _mm_srli_epi32(dst_scale, 8);
+        // Duplicate scales into 2x16-bit pattern per pixel.
+        dst_scale = _mm_shufflelo_epi16(dst_scale, _MM_SHUFFLE(2, 2, 0, 0));
+        dst_scale = _mm_shufflehi_epi16(dst_scale, _MM_SHUFFLE(2, 2, 0, 0));
+
+        const __m128i mask = _mm_set1_epi32(0x00FF00FF);
+
+        // Unpack the 16x8-bit source/destination into 2 8x16-bit splayed halves.
+        __m128i src_rb = _mm_and_si128(mask, src);
+        __m128i src_ag = _mm_srli_epi16(src, 8);
+        __m128i dst_rb = _mm_and_si128(mask, dst);
+        __m128i dst_ag = _mm_srli_epi16(dst, 8);
+
+        // Scale them.
+        src_rb = _mm_mullo_epi16(src_rb, src_scale);
+        src_ag = _mm_mullo_epi16(src_ag, src_scale);
+        dst_rb = _mm_mullo_epi16(dst_rb, dst_scale);
+        dst_ag = _mm_mullo_epi16(dst_ag, dst_scale);
+
+        // Add the scaled source and destination.
+        dst_rb = _mm_add_epi16(src_rb, dst_rb);
+        dst_ag = _mm_add_epi16(src_ag, dst_ag);
+
+        // Unsplay the halves back together.
+        dst_rb = _mm_srli_epi16(dst_rb, 8);
+        dst_ag = _mm_andnot_si128(mask, dst_ag);
+        return _mm_or_si128(dst_rb, dst_ag);
+    }
+
     static void blit_row_s32a_blend(SkPMColor* dst, const SkPMColor* src, int count, U8CPU alpha) {
         SkASSERT(alpha <= 255);
 
@@ -80,8 +148,6 @@
     }
 
 #elif defined(SK_ARM_HAS_NEON)
-
-    #include "SkColor_opts_neon.h"
     #include <arm_neon.h>
 
     static void blit_row_s32_blend(SkPMColor* dst, const SkPMColor* src, int count, U8CPU alpha) {
@@ -250,7 +316,7 @@
     invA += invA >> 7;
     SkASSERT(invA < 256);  // We've should have already handled alpha == 0 externally.
 
-    Sk16h colorHighAndRound = Sk4px::DupPMColor(color).widenHi() + Sk16h(128);
+    Sk16h colorHighAndRound = (Sk4px::DupPMColor(color).widen() << 8) + Sk16h(128);
     Sk16b invA_16x(invA);
 
     Sk4px::MapSrc(count, dst, src, [&](const Sk4px& src4) -> Sk4px {
diff --git a/src/core/SkBlitter_ARGB32.cpp b/src/core/SkBlitter_ARGB32.cpp
index dac1cfe..abd63b7 100644
--- a/src/core/SkBlitter_ARGB32.cpp
+++ b/src/core/SkBlitter_ARGB32.cpp
@@ -435,7 +435,12 @@
     }
 
 #elif defined(SK_ARM_HAS_NEON)
-    #include "SkColor_opts_neon.h"
+    #include <arm_neon.h>
+
+    #define NEON_A (SK_A32_SHIFT / 8)
+    #define NEON_R (SK_R32_SHIFT / 8)
+    #define NEON_G (SK_G32_SHIFT / 8)
+    #define NEON_B (SK_B32_SHIFT / 8)
 
     static inline uint8x8_t blend_32_neon(uint8x8_t src, uint8x8_t dst, uint16x8_t scale) {
         int16x8_t src_wide, dst_wide;
diff --git a/src/core/SkCachedData.cpp b/src/core/SkCachedData.cpp
index 0f3ca64..3a40cc9 100644
--- a/src/core/SkCachedData.cpp
+++ b/src/core/SkCachedData.cpp
@@ -9,25 +9,6 @@
 #include "SkDiscardableMemory.h"
 #include "SkMalloc.h"
 
-//#define TRACK_CACHEDDATA_LIFETIME
-
-#ifdef TRACK_CACHEDDATA_LIFETIME
-static int32_t gCachedDataCounter;
-
-static void inc() {
-    int32_t oldCount = sk_atomic_inc(&gCachedDataCounter);
-    SkDebugf("SkCachedData inc %d\n", oldCount + 1);
-}
-
-static void dec() {
-    int32_t oldCount = sk_atomic_dec(&gCachedDataCounter);
-    SkDebugf("SkCachedData dec %d\n", oldCount - 1);
-}
-#else
-static void inc() {}
-static void dec() {}
-#endif
-
 SkCachedData::SkCachedData(void* data, size_t size)
     : fData(data)
     , fSize(size)
@@ -37,7 +18,6 @@
     , fIsLocked(true)
 {
     fStorage.fMalloc = data;
-    inc();
 }
 
 SkCachedData::SkCachedData(size_t size, SkDiscardableMemory* dm)
@@ -49,7 +29,6 @@
     , fIsLocked(true)
 {
     fStorage.fDM = dm;
-    inc();
 }
 
 SkCachedData::~SkCachedData() {
@@ -61,7 +40,6 @@
             delete fStorage.fDM;
             break;
     }
-    dec();
 }
 
 class SkCachedData::AutoMutexWritable {
diff --git a/src/core/SkCanvas.cpp b/src/core/SkCanvas.cpp
index efa75a9..ee0960c 100644
--- a/src/core/SkCanvas.cpp
+++ b/src/core/SkCanvas.cpp
@@ -208,6 +208,14 @@
     }
 };
 
+namespace {
+// Encapsulate state needed to restore from saveBehind()
+struct BackImage {
+    sk_sp<SkSpecialImage> fImage;
+    SkIPoint              fLoc;
+};
+}
+
 /*  This is the record we keep for each save/restore level in the stack.
     Since a level optionally copies the matrix and/or stack, we have pointers
     for these fields. If the value is copied for this level, the copy is
@@ -217,17 +225,18 @@
 */
 class SkCanvas::MCRec {
 public:
-    DeviceCM*       fLayer;
+    DeviceCM* fLayer;
     /*  If there are any layers in the stack, this points to the top-most
         one that is at or below this level in the stack (so we know what
         bitmap/device to draw into from this level. This value is NOT
         reference counted, since the real owner is either our fLayer field,
         or a previous one in a lower level.)
     */
-    DeviceCM*           fTopLayer;
-    SkConservativeClip  fRasterClip;
-    SkMatrix            fMatrix;
-    int                 fDeferredSaveCount;
+    DeviceCM* fTopLayer;
+    std::unique_ptr<BackImage> fBackImage;
+    SkConservativeClip fRasterClip;
+    SkMatrix fMatrix;
+    int fDeferredSaveCount;
 
     MCRec() {
         fLayer      = nullptr;
@@ -901,10 +910,6 @@
     return this->saveLayer(SaveLayerRec(bounds, paint, 0));
 }
 
-int SkCanvas::saveLayerPreserveLCDTextRequests(const SkRect* bounds, const SkPaint* paint) {
-    return this->saveLayer(SaveLayerRec(bounds, paint, kPreserveLCDText_SaveLayerFlag));
-}
-
 int SkCanvas::saveLayer(const SaveLayerRec& rec) {
     TRACE_EVENT0("skia", TRACE_FUNC);
     if (rec.fPaint && rec.fPaint->nothingToDraw()) {
@@ -919,6 +924,22 @@
     return this->getSaveCount() - 1;
 }
 
+int SkCanvas::only_axis_aligned_saveBehind(const SkRect* bounds) {
+    if (bounds && !this->getLocalClipBounds().intersects(*bounds)) {
+        // Assuming clips never expand, if the request bounds is outside of the current clip
+        // there is no need to copy/restore the area, so just devolve back to a regular save.
+        this->save();
+    } else {
+        bool doTheWork = this->onDoSaveBehind(bounds);
+        fSaveCount += 1;
+        this->internalSave();
+        if (doTheWork) {
+            this->internalSaveBehind(bounds);
+        }
+    }
+    return this->getSaveCount() - 1;
+}
+
 void SkCanvas::DrawDeviceWithFilter(SkBaseDevice* src, const SkImageFilter* filter,
                                     SkBaseDevice* dst, const SkIPoint& dstOrigin,
                                     const SkMatrix& ctm) {
@@ -1040,8 +1061,7 @@
 
     sk_sp<SkBaseDevice> newDevice;
     {
-        const bool preserveLCDText = kOpaque_SkAlphaType == info.alphaType() ||
-                                     (saveLayerFlags & kPreserveLCDText_SaveLayerFlag);
+        const bool preserveLCDText = kOpaque_SkAlphaType == info.alphaType();
         const SkBaseDevice::TileUsage usage = SkBaseDevice::kNever_TileUsage;
         const bool trackCoverage =
                 SkToBool(saveLayerFlags & kMaskAgainstCoverage_EXPERIMENTAL_DONT_USE_SaveLayerFlag);
@@ -1090,6 +1110,48 @@
     }
 }
 
+void SkCanvas::internalSaveBehind(const SkRect* localBounds) {
+    SkIRect devBounds;
+    if (localBounds) {
+        SkRect tmp;
+        fMCRec->fMatrix.mapRect(&tmp, *localBounds);
+        if (!devBounds.intersect(tmp.round(), this->getDeviceClipBounds())) {
+            devBounds.setEmpty();
+        }
+    } else {
+        devBounds = this->getDeviceClipBounds();
+    }
+    if (devBounds.isEmpty()) {
+        return;
+    }
+
+    SkBaseDevice* device = this->getTopDevice();
+    if (nullptr == device) {   // Do we still need this check???
+        return;
+    }
+
+    // need the bounds relative to the device itself
+    devBounds.offset(-device->fOrigin.fX, -device->fOrigin.fY);
+
+    auto backImage = device->snapBackImage(devBounds);
+    if (!backImage) {
+        return;
+    }
+
+    // we really need the save, so we can wack the fMCRec
+    this->checkForDeferredSave();
+
+    fMCRec->fBackImage.reset(new BackImage{std::move(backImage), devBounds.topLeft()});
+
+    SkPaint paint;
+    paint.setBlendMode(SkBlendMode::kClear);
+    if (localBounds) {
+        this->drawRect(*localBounds, paint);
+    } else {
+        this->drawPaint(paint);
+    }
+}
+
 void SkCanvas::internalRestore() {
     SkASSERT(fMCStack.count() != 0);
 
@@ -1098,6 +1160,9 @@
     // now detach it from fMCRec so we can pop(). Gets freed after its drawn
     fMCRec->fLayer = nullptr;
 
+    // move this out before we do the actual restore
+    auto backImage = std::move(fMCRec->fBackImage);
+
     // now do the normal restore()
     fMCRec->~MCRec();       // balanced in save()
     fMCStack.pop_back();
@@ -1107,6 +1172,15 @@
         FOR_EACH_TOP_DEVICE(device->restore(fMCRec->fMatrix));
     }
 
+    if (backImage) {
+        SkPaint paint;
+        paint.setBlendMode(SkBlendMode::kDstOver);
+        const int x = backImage->fLoc.x();
+        const int y = backImage->fLoc.y();
+        this->getTopDevice()->drawSpecial(backImage->fImage.get(), x, y, paint,
+                                          nullptr, SkMatrix::I());
+    }
+
     /*  Time to draw the layer's offscreen. We can't call the public drawSprite,
         since if we're being recorded, we don't want to record this (the
         recorder will have already recorded the restore).
@@ -2436,70 +2510,6 @@
     LOOPER_END
 }
 
-void SkCanvas::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                          const SkPaint& paint) {
-
-    LOOPER_BEGIN(paint, nullptr)
-
-    while (iter.next()) {
-        fScratchGlyphRunBuilder->drawText(
-                looper.paint(), text, byteLength, SkPoint::Make(x, y));
-        auto glyphRunList = fScratchGlyphRunBuilder->useGlyphRunList();
-        iter.fDevice->drawGlyphRunList(glyphRunList);
-    }
-
-    LOOPER_END
-}
-
-void SkCanvas::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                             const SkPaint& paint) {
-
-    LOOPER_BEGIN(paint, nullptr)
-
-    while (iter.next()) {
-        fScratchGlyphRunBuilder->drawPosText(looper.paint(), text, byteLength, pos);
-        auto glyphRunList = fScratchGlyphRunBuilder->useGlyphRunList();
-        iter.fDevice->drawGlyphRunList(glyphRunList);
-    }
-
-    LOOPER_END
-}
-
-void SkCanvas::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                              SkScalar constY, const SkPaint& paint) {
-
-    LOOPER_BEGIN(paint, nullptr)
-
-    while (iter.next()) {
-        fScratchGlyphRunBuilder->drawPosTextH(
-                looper.paint(), text, byteLength, xpos, constY);
-        auto glyphRunList = fScratchGlyphRunBuilder->useGlyphRunList();
-        iter.fDevice->drawGlyphRunList(glyphRunList);
-    }
-
-    LOOPER_END
-}
-
-void SkCanvas::onDrawTextRSXform(const void* text, size_t len, const SkRSXform xform[],
-                                 const SkRect* cullRect, const SkPaint& paint) {
-    if (cullRect && this->quickReject(*cullRect)) {
-        return;
-    }
-
-    LOOPER_BEGIN(paint, nullptr)
-
-    while (iter.next()) {
-        fScratchGlyphRunBuilder->drawTextAtOrigin(paint, text, len);
-        auto list = fScratchGlyphRunBuilder->useGlyphRunList();
-        if (!list.empty()) {
-            auto glyphRun = list[0];
-            iter.fDevice->drawGlyphRunRSXform(&glyphRun, xform);
-        }
-    }
-
-    LOOPER_END
-}
-
 void SkCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                               const SkPaint& paint) {
     SkRect storage;
@@ -2518,26 +2528,25 @@
     LOOPER_BEGIN(paint, bounds)
 
     while (iter.next()) {
-        fScratchGlyphRunBuilder->drawTextBlob(looper.paint(), *blob, SkPoint::Make(x, y));
-        iter.fDevice->drawGlyphRunList(fScratchGlyphRunBuilder->useGlyphRunList());
+        fScratchGlyphRunBuilder->drawTextBlob(looper.paint(), *blob, {x, y}, iter.fDevice);
     }
 
     LOOPER_END
 }
 
+#ifdef SK_SUPPORT_LEGACY_DRAWSTRING
 void SkCanvas::drawString(const SkString& string, SkScalar x, SkScalar y, const SkPaint& paint) {
     this->drawText(string.c_str(), string.size(), x, y, paint);
 }
+#endif
 
 // These call the (virtual) onDraw... method
-void SkCanvas::drawSimpleText(const void* text, size_t byteLength, SkTextEncoding,
+void SkCanvas::drawSimpleText(const void* text, size_t byteLength, SkTextEncoding encoding,
                               SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint) {
     TRACE_EVENT0("skia", TRACE_FUNC);
     if (byteLength) {
         sk_msan_assert_initialized(text, SkTAddOffset<const void>(text, byteLength));
-        SkPaint tmp(paint);
-        font.LEGACY_applyToPaint(&tmp);
-        this->onDrawText(text, byteLength, x, y, tmp);
+        this->drawTextBlob(SkTextBlob::MakeFromText(text, byteLength, font, encoding), x, y, paint);
     }
 }
 void SkCanvas::drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
@@ -2545,32 +2554,9 @@
     TRACE_EVENT0("skia", TRACE_FUNC);
     if (byteLength) {
         sk_msan_assert_initialized(text, SkTAddOffset<const void>(text, byteLength));
-        this->onDrawText(text, byteLength, x, y, paint);
-    }
-}
-void SkCanvas::drawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                           const SkPaint& paint) {
-    TRACE_EVENT0("skia", TRACE_FUNC);
-    if (byteLength) {
-        sk_msan_assert_initialized(text, SkTAddOffset<const void>(text, byteLength));
-        this->onDrawPosText(text, byteLength, pos, paint);
-    }
-}
-void SkCanvas::drawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                            SkScalar constY, const SkPaint& paint) {
-    TRACE_EVENT0("skia", TRACE_FUNC);
-    if (byteLength) {
-        sk_msan_assert_initialized(text, SkTAddOffset<const void>(text, byteLength));
-        this->onDrawPosTextH(text, byteLength, xpos, constY, paint);
-    }
-}
-
-void SkCanvas::drawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                               const SkRect* cullRect, const SkPaint& paint) {
-    TRACE_EVENT0("skia", TRACE_FUNC);
-    if (byteLength) {
-        sk_msan_assert_initialized(text, SkTAddOffset<const void>(text, byteLength));
-        this->onDrawTextRSXform(text, byteLength, xform, cullRect, paint);
+        const SkFont font = SkFont::LEGACY_ExtractFromPaint(paint);
+        const SkTextEncoding encoding = paint.private_internal_getTextEncoding();
+        this->drawTextBlob(SkTextBlob::MakeFromText(text, byteLength, font, encoding), x, y, paint);
     }
 }
 void SkCanvas::drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
@@ -2868,6 +2854,10 @@
     return kNoLayer_SaveLayerStrategy;
 }
 
+bool SkNoDrawCanvas::onDoSaveBehind(const SkRect*) {
+    return false;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 static_assert((int)SkRegion::kDifference_Op         == (int)kDifference_SkClipOp, "");
diff --git a/src/core/SkCanvasPriv.h b/src/core/SkCanvasPriv.h
index 3e1ca16..cb25401 100644
--- a/src/core/SkCanvasPriv.h
+++ b/src/core/SkCanvasPriv.h
@@ -40,6 +40,9 @@
 
     static SkCanvas::SaveLayerFlags LegacySaveFlagsToSaveLayerFlags(uint32_t legacySaveFlags);
 
+    static int SaveBehind(SkCanvas* canvas, const SkRect* subset) {
+        return canvas->only_axis_aligned_saveBehind(subset);
+    }
 };
 
 #endif
diff --git a/src/core/SkClipStack.cpp b/src/core/SkClipStack.cpp
index 97c0af2..7cbdf07 100644
--- a/src/core/SkClipStack.cpp
+++ b/src/core/SkClipStack.cpp
@@ -5,19 +5,17 @@
  * found in the LICENSE file.
  */
 
-#include "SkAtomics.h"
 #include "SkCanvas.h"
 #include "SkClipStack.h"
 #include "SkPath.h"
 #include "SkPathOps.h"
 #include "SkClipOpPriv.h"
-
+#include <atomic>
 #include <new>
 
-
-// 0-2 are reserved for invalid, empty & wide-open
-static const int32_t kFirstUnreservedGenID = 3;
-int32_t SkClipStack::gGenID = kFirstUnreservedGenID;
+#if SK_SUPPORT_GPU
+#include "GrProxyProvider.h"
+#endif
 
 SkClipStack::Element::Element(const Element& that) {
     switch (that.getDeviceSpaceType()) {
@@ -45,6 +43,15 @@
     fGenID = that.fGenID;
 }
 
+SkClipStack::Element::~Element() {
+#if SK_SUPPORT_GPU
+    for (int i = 0; i < fKeysToInvalidate.count(); ++i) {
+        fProxyProvider->processInvalidUniqueKey(fKeysToInvalidate[i], nullptr,
+                                                GrProxyProvider::InvalidateGPUResource::kYes);
+    }
+#endif
+}
+
 bool SkClipStack::Element::operator== (const Element& element) const {
     if (this == &element) {
         return true;
@@ -1004,9 +1011,13 @@
 }
 
 uint32_t SkClipStack::GetNextGenID() {
+    // 0-2 are reserved for invalid, empty & wide-open
+    static const uint32_t kFirstUnreservedGenID = 3;
+    static std::atomic<uint32_t> nextID{kFirstUnreservedGenID};
+
     uint32_t id;
     do {
-        id = static_cast<uint32_t>(sk_atomic_inc(&gGenID));
+        id = nextID++;
     } while (id < kFirstUnreservedGenID);
     return id;
 }
diff --git a/src/core/SkClipStack.h b/src/core/SkClipStack.h
index 050f30ed..87288d8 100644
--- a/src/core/SkClipStack.h
+++ b/src/core/SkClipStack.h
@@ -19,6 +19,8 @@
 #include "SkTLazy.h"
 
 #if SK_SUPPORT_GPU
+class GrProxyProvider;
+
 #include "GrResourceKey.h"
 #endif
 
@@ -81,13 +83,7 @@
             this->initPath(0, path, m, op, doAA);
         }
 
-        ~Element() {
-#if SK_SUPPORT_GPU
-            for (int i = 0; i < fMessages.count(); ++i) {
-                SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(*fMessages[i]);
-            }
-#endif
-        }
+        ~Element();
 
         bool operator== (const Element& element) const;
         bool operator!= (const Element& element) const { return !(*this == element); }
@@ -181,9 +177,16 @@
          * This is used to purge any GPU resource cache items that become unreachable when
          * the element is destroyed because their key is based on this element's gen ID.
          */
-        void addResourceInvalidationMessage(
-                std::unique_ptr<GrUniqueKeyInvalidatedMessage> msg) const {
-            fMessages.emplace_back(std::move(msg));
+        void addResourceInvalidationMessage(GrProxyProvider* proxyProvider,
+                                            const GrUniqueKey& key) const {
+            SkASSERT(proxyProvider);
+
+            if (!fProxyProvider) {
+                fProxyProvider = proxyProvider;
+            }
+            SkASSERT(fProxyProvider == proxyProvider);
+
+            fKeysToInvalidate.push_back(key);
         }
 #endif
 
@@ -216,7 +219,8 @@
 
         uint32_t fGenID;
 #if SK_SUPPORT_GPU
-        mutable SkTArray<std::unique_ptr<GrUniqueKeyInvalidatedMessage>> fMessages;
+        mutable GrProxyProvider*      fProxyProvider = nullptr;
+        mutable SkTArray<GrUniqueKey> fKeysToInvalidate;
 #endif
         Element(int saveCount) {
             this->initCommon(saveCount, kReplace_SkClipOp, false);
@@ -489,10 +493,6 @@
     SkDeque fDeque;
     int     fSaveCount;
 
-    // Generation ID for the clip stack. This is incremented for each
-    // clipDevRect and clipDevPath call. 0 is reserved to indicate an
-    // invalid ID.
-    static int32_t     gGenID;
     SkRect fClipRestrictionRect = SkRect::MakeEmpty();
 
     bool internalQuickContains(const SkRect& devRect) const;
@@ -519,3 +519,4 @@
 };
 
 #endif
+
diff --git a/src/core/SkColorSpace.cpp b/src/core/SkColorSpace.cpp
index 66ddaf4..f54f2ec 100644
--- a/src/core/SkColorSpace.cpp
+++ b/src/core/SkColorSpace.cpp
@@ -20,6 +20,10 @@
     return true;
 }
 
+bool SkColorSpacePrimaries::toXYZD50(skcms_Matrix3x3* toXYZ_D50) const {
+    return skcms_PrimariesToXYZD50(fRX, fRY, fGX, fGY, fBX, fBY, fWX, fWY, toXYZ_D50);
+}
+
 static bool is_3x3(const SkMatrix44& m44) {
     return m44.getFloat(0, 3) == 0.0f
         && m44.getFloat(1, 3) == 0.0f
@@ -57,9 +61,9 @@
     fToXYZD50Hash = SkOpts::hash_fn(fToXYZD50_3x3, 9*sizeof(float), 0);
 
     switch (fGammaNamed) {
-        case kSRGB_SkGammaNamed:        transferFn = &  gSRGB_TransferFn.fG; break;
-        case k2Dot2Curve_SkGammaNamed:  transferFn = & g2Dot2_TransferFn.fG; break;
-        case kLinear_SkGammaNamed:      transferFn = &gLinear_TransferFn.fG; break;
+        case kSRGB_SkGammaNamed:        transferFn = &  SkNamedTransferFn::kSRGB.g; break;
+        case k2Dot2Curve_SkGammaNamed:  transferFn = & SkNamedTransferFn::k2Dot2.g; break;
+        case kLinear_SkGammaNamed:      transferFn = &SkNamedTransferFn::kLinear.g; break;
         case kNonStandard_SkGammaNamed:                                      break;
     }
     memcpy(fTransferFn, transferFn, 7*sizeof(float));
@@ -73,12 +77,12 @@
     }
     switch (gammaNamed) {
         case kSRGB_SkGammaNamed:
-            if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
+            if (xyz_almost_equal(toXYZD50, &SkNamedGamut::kSRGB.vals[0][0])) {
                 return SkColorSpace::MakeSRGB();
             }
             break;
         case kLinear_SkGammaNamed:
-            if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) {
+            if (xyz_almost_equal(toXYZD50, &SkNamedGamut::kSRGB.vals[0][0])) {
                 return SkColorSpace::MakeSRGBLinear();
             }
             break;
@@ -136,6 +140,16 @@
     return SkColorSpace::MakeRGB(coeffs, toXYZD50);
 }
 
+sk_sp<SkColorSpace> SkColorSpace::MakeRGB(const skcms_TransferFunction& transferFn,
+                                          const skcms_Matrix3x3& toXYZ) {
+    SkMatrix44 toXYZD50;
+    toXYZD50.set3x3RowMajorf(&toXYZ.vals[0][0]);
+    SkColorSpaceTransferFn tf;
+    memcpy(&tf, &transferFn, sizeof(tf));
+    // Going through this old path makes sure we classify transferFn as an SkGammaNamed
+    return SkColorSpace::MakeRGB(tf, toXYZD50);
+}
+
 class SkColorSpaceSingletonFactory {
 public:
     static SkColorSpace* Make(SkGammaNamed gamma, const float to_xyz[9]) {
@@ -148,12 +162,12 @@
 
 SkColorSpace* sk_srgb_singleton() {
     static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(kSRGB_SkGammaNamed,
-                                                                 gSRGB_toXYZD50);
+                                                                 &SkNamedGamut::kSRGB.vals[0][0]);
     return cs;
 }
 SkColorSpace* sk_srgb_linear_singleton() {
     static SkColorSpace* cs = SkColorSpaceSingletonFactory::Make(kLinear_SkGammaNamed,
-                                                                 gSRGB_toXYZD50);
+                                                                 &SkNamedGamut::kSRGB.vals[0][0]);
     return cs;
 }
 
@@ -196,6 +210,11 @@
     return true;
 }
 
+bool SkColorSpace::isNumericalTransferFn(skcms_TransferFunction* coeffs) const {
+    this->transferFn(&coeffs->g);
+    return true;
+}
+
 void SkColorSpace::transferFn(float gabcdef[7]) const {
     memcpy(gabcdef, &fTransferFn, 7*sizeof(float));
 }
@@ -210,6 +229,10 @@
     return true;
 }
 
+bool SkColorSpace::toXYZD50(skcms_Matrix3x3* toXYZD50) const {
+    memcpy(toXYZD50, fToXYZD50_3x3, 9*sizeof(float));
+    return true;
+}
 
 void SkColorSpace::gamutTransformTo(const SkColorSpace* dst, float src_to_dst[9]) const {
     dst->computeLazyDstFields();
@@ -314,134 +337,47 @@
 
 enum Version {
     k0_Version, // Initial version, header + flags for matrix and profile
+    k1_Version, // Simple header (version tag) + 16 floats
+
+    kCurrent_Version = k1_Version,
 };
 
 enum NamedColorSpace {
     kSRGB_NamedColorSpace,
-    // No longer a singleton, preserved to support reading data from branches m65 and older
     kAdobeRGB_NamedColorSpace,
     kSRGBLinear_NamedColorSpace,
 };
 
 struct ColorSpaceHeader {
-    /**
-     *  It is only valid to set zero or one flags.
-     *  Setting multiple flags is invalid.
-     */
-
-    /**
-     *  If kMatrix_Flag is set, we will write 12 floats after the header.
-     */
+    // Flag values, only used by old (k0_Version) serialization
     static constexpr uint8_t kMatrix_Flag     = 1 << 0;
-
-    /**
-     *  If kICC_Flag is set, we will write an ICC profile after the header.
-     *  The ICC profile will be written as a uint32 size, followed immediately
-     *  by the data (padded to 4 bytes).
-     *  DEPRECATED / UNUSED
-     */
     static constexpr uint8_t kICC_Flag        = 1 << 1;
-
-    /**
-     *  If kTransferFn_Flag is set, we will write 19 floats after the header.
-     *  The first seven represent the transfer fn, and the next twelve are the
-     *  matrix.
-     */
     static constexpr uint8_t kTransferFn_Flag = 1 << 3;
 
-    static ColorSpaceHeader Pack(Version version, uint8_t named, uint8_t gammaNamed, uint8_t flags)
-    {
-        ColorSpaceHeader header;
+    uint8_t fVersion = kCurrent_Version;
 
-        SkASSERT(k0_Version == version);
-        header.fVersion = (uint8_t) version;
-
-        SkASSERT(named <= kSRGBLinear_NamedColorSpace);
-        header.fNamed = (uint8_t) named;
-
-        SkASSERT(gammaNamed <= kNonStandard_SkGammaNamed);
-        header.fGammaNamed = (uint8_t) gammaNamed;
-
-        SkASSERT(flags <= kTransferFn_Flag);
-        header.fFlags = flags;
-        return header;
-    }
-
-    uint8_t fVersion;            // Always zero
-    uint8_t fNamed;              // Must be a SkColorSpace::Named
-    uint8_t fGammaNamed;         // Must be a SkGammaNamed
-    uint8_t fFlags;
+    // Other fields are only used by k0_Version. Could be re-purposed in future versions.
+    uint8_t fNamed      = 0;
+    uint8_t fGammaNamed = 0;
+    uint8_t fFlags      = 0;
 };
 
 size_t SkColorSpace::writeToMemory(void* memory) const {
-    // If we have a named profile, only write the enum.
-    const SkGammaNamed gammaNamed = this->gammaNamed();
-    if (this == sk_srgb_singleton()) {
-        if (memory) {
-            *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
-                    k0_Version, kSRGB_NamedColorSpace, gammaNamed, 0);
-        }
-        return sizeof(ColorSpaceHeader);
-    } else if (this == sk_srgb_linear_singleton()) {
-        if (memory) {
-            *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(
-                    k0_Version, kSRGBLinear_NamedColorSpace, gammaNamed, 0);
-        }
-        return sizeof(ColorSpaceHeader);
+    if (memory) {
+        *((ColorSpaceHeader*) memory) = ColorSpaceHeader();
+        memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
+
+        memcpy(memory, fTransferFn, 7 * sizeof(float));
+        memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
+
+        memcpy(memory, fToXYZD50_3x3, 9 * sizeof(float));
     }
 
-    // If we have a named gamma, write the enum and the matrix.
-    switch (gammaNamed) {
-        case kSRGB_SkGammaNamed:
-        case k2Dot2Curve_SkGammaNamed:
-        case kLinear_SkGammaNamed: {
-            if (memory) {
-                *((ColorSpaceHeader*) memory) =
-                        ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed,
-                                                ColorSpaceHeader::kMatrix_Flag);
-                memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
-                SkMatrix44 m44;
-                this->toXYZD50(&m44);
-                m44.as3x4RowMajorf((float*) memory);
-            }
-            return sizeof(ColorSpaceHeader) + 12 * sizeof(float);
-        }
-        default: {
-            SkColorSpaceTransferFn transferFn;
-            SkAssertResult(this->isNumericalTransferFn(&transferFn));
-
-            if (memory) {
-                *((ColorSpaceHeader*) memory) =
-                        ColorSpaceHeader::Pack(k0_Version, 0, gammaNamed,
-                                                ColorSpaceHeader::kTransferFn_Flag);
-                memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader));
-
-                *(((float*) memory) + 0) = transferFn.fA;
-                *(((float*) memory) + 1) = transferFn.fB;
-                *(((float*) memory) + 2) = transferFn.fC;
-                *(((float*) memory) + 3) = transferFn.fD;
-                *(((float*) memory) + 4) = transferFn.fE;
-                *(((float*) memory) + 5) = transferFn.fF;
-                *(((float*) memory) + 6) = transferFn.fG;
-                memory = SkTAddOffset<void>(memory, 7 * sizeof(float));
-
-                SkMatrix44 m44;
-                this->toXYZD50(&m44);
-                m44.as3x4RowMajorf((float*) memory);
-            }
-
-            return sizeof(ColorSpaceHeader) + 19 * sizeof(float);
-        }
-    }
+    return sizeof(ColorSpaceHeader) + 16 * sizeof(float);
 }
 
 sk_sp<SkData> SkColorSpace::serialize() const {
-    size_t size = this->writeToMemory(nullptr);
-    if (0 == size) {
-        return nullptr;
-    }
-
-    sk_sp<SkData> data = SkData::MakeUninitialized(size);
+    sk_sp<SkData> data = SkData::MakeUninitialized(this->writeToMemory(nullptr));
     this->writeToMemory(data->writable_data());
     return data;
 }
@@ -454,59 +390,81 @@
     ColorSpaceHeader header = *((const ColorSpaceHeader*) data);
     data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader));
     length -= sizeof(ColorSpaceHeader);
-    if (0 == header.fFlags) {
-        switch ((NamedColorSpace)header.fNamed) {
-            case kSRGB_NamedColorSpace:
-                return SkColorSpace::MakeSRGB();
-            case kSRGBLinear_NamedColorSpace:
-                return SkColorSpace::MakeSRGBLinear();
-            case kAdobeRGB_NamedColorSpace:
-                return SkColorSpace::MakeRGB(g2Dot2_TransferFn, SkColorSpace::kAdobeRGB_Gamut);
-        }
-    }
-
-    switch ((SkGammaNamed) header.fGammaNamed) {
-        case kSRGB_SkGammaNamed:
-        case k2Dot2Curve_SkGammaNamed:
-        case kLinear_SkGammaNamed: {
-            if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) {
-                return nullptr;
-            }
-
-            SkMatrix44 toXYZ;
-            toXYZ.set3x4RowMajorf((const float*) data);
-            return SkColorSpace::MakeRGB((SkGammaNamed) header.fGammaNamed, toXYZ);
-        }
-        default:
-            break;
-    }
-
-    switch (header.fFlags) {
-        case ColorSpaceHeader::kICC_Flag: {
-            // Deprecated and unsupported code path
+    if (k1_Version == header.fVersion) {
+        if (length < 16 * sizeof(float)) {
             return nullptr;
         }
-        case ColorSpaceHeader::kTransferFn_Flag: {
-            if (length < 19 * sizeof(float)) {
+
+        skcms_TransferFunction transferFn;
+        memcpy(&transferFn, data, 7 * sizeof(float));
+        data = SkTAddOffset<const void>(data, 7 * sizeof(float));
+
+        skcms_Matrix3x3 toXYZ;
+        memcpy(&toXYZ, data, 9 * sizeof(float));
+        return SkColorSpace::MakeRGB(transferFn, toXYZ);
+    } else if (k0_Version == header.fVersion) {
+        if (0 == header.fFlags) {
+            switch ((NamedColorSpace)header.fNamed) {
+                case kSRGB_NamedColorSpace:
+                    return SkColorSpace::MakeSRGB();
+                case kSRGBLinear_NamedColorSpace:
+                    return SkColorSpace::MakeSRGBLinear();
+                case kAdobeRGB_NamedColorSpace:
+                    return SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2,
+                                                 SkNamedGamut::kAdobeRGB);
+            }
+        }
+
+        switch ((SkGammaNamed) header.fGammaNamed) {
+            case kSRGB_SkGammaNamed:
+            case k2Dot2Curve_SkGammaNamed:
+            case kLinear_SkGammaNamed: {
+                if (ColorSpaceHeader::kMatrix_Flag != header.fFlags ||
+                    length < 12 * sizeof(float)) {
+                    return nullptr;
+                }
+
+                SkMatrix44 toXYZ;
+                toXYZ.set3x4RowMajorf((const float*) data);
+                return SkColorSpace::MakeRGB((SkGammaNamed) header.fGammaNamed, toXYZ);
+            }
+            default:
+                break;
+        }
+
+        switch (header.fFlags) {
+            case ColorSpaceHeader::kICC_Flag: {
+                // Deprecated and unsupported code path
                 return nullptr;
             }
+            case ColorSpaceHeader::kTransferFn_Flag: {
+                if (length < 19 * sizeof(float)) {
+                    return nullptr;
+                }
 
-            SkColorSpaceTransferFn transferFn;
-            transferFn.fA = *(((const float*) data) + 0);
-            transferFn.fB = *(((const float*) data) + 1);
-            transferFn.fC = *(((const float*) data) + 2);
-            transferFn.fD = *(((const float*) data) + 3);
-            transferFn.fE = *(((const float*) data) + 4);
-            transferFn.fF = *(((const float*) data) + 5);
-            transferFn.fG = *(((const float*) data) + 6);
-            data = SkTAddOffset<const void>(data, 7 * sizeof(float));
+                // Version 0 TF is in abcdefg order
+                skcms_TransferFunction transferFn;
+                transferFn.a = *(((const float*) data) + 0);
+                transferFn.b = *(((const float*) data) + 1);
+                transferFn.c = *(((const float*) data) + 2);
+                transferFn.d = *(((const float*) data) + 3);
+                transferFn.e = *(((const float*) data) + 4);
+                transferFn.f = *(((const float*) data) + 5);
+                transferFn.g = *(((const float*) data) + 6);
+                data = SkTAddOffset<const void>(data, 7 * sizeof(float));
 
-            SkMatrix44 toXYZ;
-            toXYZ.set3x4RowMajorf((const float*) data);
-            return SkColorSpace::MakeRGB(transferFn, toXYZ);
+                // Version 0 matrix is row-major 3x4
+                skcms_Matrix3x3 toXYZ;
+                memcpy(&toXYZ.vals[0][0], (const float*)data + 0, 3 * sizeof(float));
+                memcpy(&toXYZ.vals[1][0], (const float*)data + 4, 3 * sizeof(float));
+                memcpy(&toXYZ.vals[2][0], (const float*)data + 8, 3 * sizeof(float));
+                return SkColorSpace::MakeRGB(transferFn, toXYZ);
+            }
+            default:
+                return nullptr;
         }
-        default:
-            return nullptr;
+    } else {
+        return nullptr;
     }
 }
 
diff --git a/src/core/SkColorSpacePriv.h b/src/core/SkColorSpacePriv.h
index de45dc2..298b932 100644
--- a/src/core/SkColorSpacePriv.h
+++ b/src/core/SkColorSpacePriv.h
@@ -14,73 +14,26 @@
 
 #define SkColorSpacePrintf(...)
 
-static constexpr float gSRGB_toXYZD50[] {
-    // These are taken from skcms, and there originally from 16-bit fixed point.
-    // For best results, please keep them exactly in sync with skcms.
-    0.436065674f, 0.385147095f, 0.143066406f,
-    0.222488403f, 0.716873169f, 0.060607910f,
-    0.013916016f, 0.097076416f, 0.714096069f,
-};
-
-static constexpr float gAdobeRGB_toXYZD50[] {
-    // ICC fixed-point (16.16) repesentation of:
-    // 0.60974, 0.20528, 0.14919,
-    // 0.31111, 0.62567, 0.06322,
-    // 0.01947, 0.06087, 0.74457,
-    SkFixedToFloat(0x9c18), SkFixedToFloat(0x348d), SkFixedToFloat(0x2631), // Rx, Gx, Bx
-    SkFixedToFloat(0x4fa5), SkFixedToFloat(0xa02c), SkFixedToFloat(0x102f), // Ry, Gy, By
-    SkFixedToFloat(0x04fc), SkFixedToFloat(0x0f95), SkFixedToFloat(0xbe9c), // Rz, Gz, Bz
-};
-
-static constexpr float gDCIP3_toXYZD50[] {
-    0.515102f,   0.291965f,  0.157153f,  // Rx, Gx, Bx
-    0.241182f,   0.692236f,  0.0665819f, // Ry, Gy, By
-   -0.00104941f, 0.0418818f, 0.784378f,  // Rz, Gz, Bz
-};
-
-static constexpr float gRec2020_toXYZD50[] {
-    0.673459f,   0.165661f,  0.125100f,  // Rx, Gx, Bx
-    0.279033f,   0.675338f,  0.0456288f, // Ry, Gy, By
-   -0.00193139f, 0.0299794f, 0.797162f,  // Rz, Gz, Bz
-};
-
 // A gamut narrower than sRGB, useful for testing.
-static constexpr float gNarrow_toXYZD50[] {
-    0.190974f,  0.404865f,  0.368380f,
-    0.114746f,  0.582937f,  0.302318f,
-    0.032925f,  0.153615f,  0.638669f,
-};
-
-// Like gSRGB_toXYZD50, keeping this bitwise exactly the same as skcms makes things fastest.
-static constexpr SkColorSpaceTransferFn gSRGB_TransferFn =
-#ifdef SK_LEGACY_SRGB_TRANSFER_FUNCTION
-        { 2.4f, 1.0f / 1.055f, 0.055f / 1.055f, 1.0f / 12.92f, 0.04045f, 0.0f, 0.0f };
-#else
-        { 2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0.0f, 0.0f };
-#endif
-
-static constexpr SkColorSpaceTransferFn g2Dot2_TransferFn =
-        { 2.2f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
-
-static constexpr SkColorSpaceTransferFn gLinear_TransferFn =
-        { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
-
-static constexpr SkColorSpaceTransferFn gDCIP3_TransferFn =
-    { 2.399994f, 0.947998047f, 0.0520019531f, 0.0769958496f, 0.0390014648f, 0.0f, 0.0f };
+static constexpr skcms_Matrix3x3 gNarrow_toXYZD50 = {{
+    { 0.190974f,  0.404865f,  0.368380f },
+    { 0.114746f,  0.582937f,  0.302318f },
+    { 0.032925f,  0.153615f,  0.638669f },
+}};
 
 static inline void to_xyz_d50(SkMatrix44* toXYZD50, SkColorSpace::Gamut gamut) {
     switch (gamut) {
         case SkColorSpace::kSRGB_Gamut:
-            toXYZD50->set3x3RowMajorf(gSRGB_toXYZD50);
+            toXYZD50->set3x3RowMajorf(&SkNamedGamut::kSRGB.vals[0][0]);
             break;
         case SkColorSpace::kAdobeRGB_Gamut:
-            toXYZD50->set3x3RowMajorf(gAdobeRGB_toXYZD50);
+            toXYZD50->set3x3RowMajorf(&SkNamedGamut::kAdobeRGB.vals[0][0]);
             break;
         case SkColorSpace::kDCIP3_D65_Gamut:
-            toXYZD50->set3x3RowMajorf(gDCIP3_toXYZD50);
+            toXYZD50->set3x3RowMajorf(&SkNamedGamut::kDCIP3.vals[0][0]);
             break;
         case SkColorSpace::kRec2020_Gamut:
-            toXYZD50->set3x3RowMajorf(gRec2020_toXYZD50);
+            toXYZD50->set3x3RowMajorf(&SkNamedGamut::kRec2020.vals[0][0]);
             break;
     }
 }
@@ -152,13 +105,13 @@
 }
 
 static inline bool is_almost_srgb(const SkColorSpaceTransferFn& coeffs) {
-    return transfer_fn_almost_equal(gSRGB_TransferFn.fA, coeffs.fA) &&
-           transfer_fn_almost_equal(gSRGB_TransferFn.fB, coeffs.fB) &&
-           transfer_fn_almost_equal(gSRGB_TransferFn.fC, coeffs.fC) &&
-           transfer_fn_almost_equal(gSRGB_TransferFn.fD, coeffs.fD) &&
-           transfer_fn_almost_equal(gSRGB_TransferFn.fE, coeffs.fE) &&
-           transfer_fn_almost_equal(gSRGB_TransferFn.fF, coeffs.fF) &&
-           transfer_fn_almost_equal(gSRGB_TransferFn.fG, coeffs.fG);
+    return transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.a, coeffs.fA) &&
+           transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.b, coeffs.fB) &&
+           transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.c, coeffs.fC) &&
+           transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.d, coeffs.fD) &&
+           transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.e, coeffs.fE) &&
+           transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.f, coeffs.fF) &&
+           transfer_fn_almost_equal(SkNamedTransferFn::kSRGB.g, coeffs.fG);
 }
 
 static inline bool is_almost_2dot2(const SkColorSpaceTransferFn& coeffs) {
diff --git a/src/core/SkColorSpaceXformCanvas.cpp b/src/core/SkColorSpaceXformCanvas.cpp
index 34e7c55..63df992 100644
--- a/src/core/SkColorSpaceXformCanvas.cpp
+++ b/src/core/SkColorSpaceXformCanvas.cpp
@@ -108,26 +108,6 @@
         fTarget->drawVertices(vertices, bones, boneCount, mode, fXformer->apply(paint));
     }
 
-    void onDrawText(const void* ptr, size_t len,
-                    SkScalar x, SkScalar y,
-                    const SkPaint& paint) override {
-        fTarget->drawText(ptr, len, x, y, fXformer->apply(paint));
-    }
-    void onDrawPosText(const void* ptr, size_t len,
-                       const SkPoint* xys,
-                       const SkPaint& paint) override {
-        fTarget->drawPosText(ptr, len, xys, fXformer->apply(paint));
-    }
-    void onDrawPosTextH(const void* ptr, size_t len,
-                        const SkScalar* xs, SkScalar y,
-                        const SkPaint& paint) override {
-        fTarget->drawPosTextH(ptr, len, xs, y, fXformer->apply(paint));
-    }
-    void onDrawTextRSXform(const void* ptr, size_t len,
-                           const SkRSXform* xforms, const SkRect* cull,
-                           const SkPaint& paint) override {
-        fTarget->drawTextRSXform(ptr, len, xforms, cull, fXformer->apply(paint));
-    }
     void onDrawTextBlob(const SkTextBlob* blob,
                         SkScalar x, SkScalar y,
                         const SkPaint& paint) override {
diff --git a/src/core/SkColorSpaceXformSteps.cpp b/src/core/SkColorSpaceXformSteps.cpp
index a2f73fc..5fff910 100644
--- a/src/core/SkColorSpaceXformSteps.cpp
+++ b/src/core/SkColorSpaceXformSteps.cpp
@@ -53,10 +53,10 @@
         this->src_to_dst_matrix[8] = row_major[8];
     } else {
     #ifdef SK_DEBUG
-        SkMatrix44 srcM, dstM;
+        skcms_Matrix3x3 srcM, dstM;
         src->toXYZD50(&srcM);
         dst->toXYZD50(&dstM);
-        SkASSERT(0 == memcmp(&srcM, &dstM, 16*sizeof(SkMScalar)) && "Hash collision");
+        SkASSERT(0 == memcmp(&srcM, &dstM, 9*sizeof(float)) && "Hash collision");
     #endif
     }
 
diff --git a/src/core/SkDeferredDisplayListPriv.h b/src/core/SkDeferredDisplayListPriv.h
index 12f0319..4675284 100644
--- a/src/core/SkDeferredDisplayListPriv.h
+++ b/src/core/SkDeferredDisplayListPriv.h
@@ -23,6 +23,14 @@
 #endif
     }
 
+    const SkDeferredDisplayList::LazyProxyData* lazyProxyData() const {
+#if SK_SUPPORT_GPU
+        return fDDL->fLazyProxyData.get();
+#else
+        return nullptr;
+#endif
+    }
+
 private:
     explicit SkDeferredDisplayListPriv(SkDeferredDisplayList* ddl) : fDDL(ddl) {}
     SkDeferredDisplayListPriv(const SkDeferredDisplayListPriv&);            // unimpl
diff --git a/src/core/SkDeferredDisplayListRecorder.cpp b/src/core/SkDeferredDisplayListRecorder.cpp
index 6cb7126..da02727 100644
--- a/src/core/SkDeferredDisplayListRecorder.cpp
+++ b/src/core/SkDeferredDisplayListRecorder.cpp
@@ -41,6 +41,7 @@
 sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
                                                         SkYUVColorSpace yuvColorSpace,
                                                         const GrBackendFormat yuvaFormats[],
+                                                        const SkISize yuvaSizes[],
                                                         const SkYUVAIndex yuvaIndices[4],
                                                         int imageWidth,
                                                         int imageHeight,
@@ -131,10 +132,6 @@
         // In GL, FBO 0 never supports mixed samples
         surfaceFlags |= GrInternalSurfaceFlags::kMixedSampled;
     }
-    if (fContext->contextPriv().caps()->maxWindowRectangles() > 0 && !usesGLFBO0) {
-        // In GL, FBO 0 never supports window rectangles
-        surfaceFlags |= GrInternalSurfaceFlags::kWindowRectsSupport;
-    }
     if (usesGLFBO0) {
         surfaceFlags |= GrInternalSurfaceFlags::kGLRTFBOIDIs0;
     }
@@ -193,10 +190,20 @@
         return nullptr;
     }
 
+    if (fSurface) {
+        SkCanvas* canvas = fSurface->getCanvas();
+
+        canvas->restoreToCount(0);
+    }
+
     auto ddl = std::unique_ptr<SkDeferredDisplayList>(
                            new SkDeferredDisplayList(fCharacterization, std::move(fLazyProxyData)));
 
     fContext->contextPriv().moveOpListsToDDL(ddl.get());
+
+    // We want a new lazy proxy target for each recorded DDL so force the (lazy proxy-backed)
+    // SkSurface to be regenerated for each DDL.
+    fSurface = nullptr;
     return ddl;
 }
 
@@ -264,34 +271,4 @@
                                                    textureContexts);
 }
 
-sk_sp<SkImage> SkDeferredDisplayListRecorder::makeYUVAPromiseTexture(
-    SkYUVColorSpace yuvColorSpace,
-    const GrBackendFormat yuvaFormats[],
-    const SkYUVAIndex yuvaIndices[4],
-    int imageWidth,
-    int imageHeight,
-    GrSurfaceOrigin imageOrigin,
-    sk_sp<SkColorSpace> imageColorSpace,
-    TextureFulfillProc textureFulfillProc,
-    TextureReleaseProc textureReleaseProc,
-    PromiseDoneProc promiseDoneProc,
-    TextureContext textureContexts[]) {
-    if (!fContext) {
-        return nullptr;
-    }
-
-    return SkImage_Gpu::MakePromiseYUVATexture(fContext.get(),
-                                               yuvColorSpace,
-                                               yuvaFormats,
-                                               yuvaIndices,
-                                               imageWidth,
-                                               imageHeight,
-                                               imageOrigin,
-                                               std::move(imageColorSpace),
-                                               textureFulfillProc,
-                                               textureReleaseProc,
-                                               promiseDoneProc,
-                                               textureContexts);
-}
-
 #endif
diff --git a/src/core/SkDescriptor.cpp b/src/core/SkDescriptor.cpp
new file mode 100644
index 0000000..572b9be
--- /dev/null
+++ b/src/core/SkDescriptor.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDescriptor.h"
+
+#include <new>
+
+#include "SkOpts.h"
+#include "SkTo.h"
+#include "SkTypes.h"
+
+std::unique_ptr<SkDescriptor> SkDescriptor::Alloc(size_t length) {
+    SkASSERT(SkAlign4(length) == length);
+    return std::unique_ptr<SkDescriptor>(static_cast<SkDescriptor*>(::operator new (length)));
+}
+
+void SkDescriptor::operator delete(void* p) { ::operator delete(p); }
+
+void* SkDescriptor::addEntry(uint32_t tag, size_t length, const void* data) {
+    SkASSERT(tag);
+    SkASSERT(SkAlign4(length) == length);
+    SkASSERT(this->findEntry(tag, nullptr) == nullptr);
+
+    Entry* entry = (Entry*)((char*)this + fLength);
+    entry->fTag = tag;
+    entry->fLen = SkToU32(length);
+    if (data) {
+        memcpy(entry + 1, data, length);
+    }
+
+    fCount += 1;
+    fLength = SkToU32(fLength + sizeof(Entry) + length);
+    return (entry + 1); // return its data
+}
+
+void SkDescriptor::computeChecksum() {
+    fChecksum = SkDescriptor::ComputeChecksum(this);
+}
+
+const void* SkDescriptor::findEntry(uint32_t tag, uint32_t* length) const {
+    const Entry* entry = (const Entry*)(this + 1);
+    int          count = fCount;
+
+    while (--count >= 0) {
+        if (entry->fTag == tag) {
+            if (length) {
+                *length = entry->fLen;
+            }
+            return entry + 1;
+        }
+        entry = (const Entry*)((const char*)(entry + 1) + entry->fLen);
+    }
+    return nullptr;
+}
+
+std::unique_ptr<SkDescriptor> SkDescriptor::copy() const {
+    std::unique_ptr<SkDescriptor> desc = SkDescriptor::Alloc(fLength);
+    memcpy(desc.get(), this, fLength);
+    return desc;
+}
+
+bool SkDescriptor::operator==(const SkDescriptor& other) const {
+
+    // the first value we should look at is the checksum, so this loop
+    // should terminate early if they descriptors are different.
+    // NOTE: if we wrote a sentinel value at the end of each, we could
+    //       remove the aa < stop test in the loop...
+    const uint32_t* aa = (const uint32_t*)this;
+    const uint32_t* bb = (const uint32_t*)&other;
+    const uint32_t* stop = (const uint32_t*)((const char*)aa + fLength);
+    do {
+        if (*aa++ != *bb++)
+            return false;
+    } while (aa < stop);
+    return true;
+}
+
+uint32_t SkDescriptor::ComputeChecksum(const SkDescriptor* desc) {
+    const uint32_t* ptr = (const uint32_t*)desc + 1; // skip the checksum field
+    size_t len = desc->fLength - sizeof(uint32_t);
+    return SkOpts::hash(ptr, len);
+}
+
+SkAutoDescriptor::SkAutoDescriptor() = default;
+SkAutoDescriptor::SkAutoDescriptor(size_t size) { this->reset(size); }
+SkAutoDescriptor::SkAutoDescriptor(const SkDescriptor& desc) { this->reset(desc); }
+SkAutoDescriptor::~SkAutoDescriptor() { this->free(); }
+
+void SkAutoDescriptor::reset(size_t size) {
+    this->free();
+    if (size <= sizeof(fStorage)) {
+        fDesc = reinterpret_cast<SkDescriptor*>(&fStorage);
+    } else {
+        fDesc = SkDescriptor::Alloc(size).release();
+    }
+}
+
+void SkAutoDescriptor::reset(const SkDescriptor& desc) {
+    size_t size = desc.getLength();
+    this->reset(size);
+    memcpy(fDesc, &desc, size);
+}
+
+void SkAutoDescriptor::free() {
+    if (fDesc != (SkDescriptor*)&fStorage) {
+        delete fDesc;
+    }
+}
+
+
diff --git a/src/core/SkDescriptor.h b/src/core/SkDescriptor.h
index 639efb5..58b82e5 100644
--- a/src/core/SkDescriptor.h
+++ b/src/core/SkDescriptor.h
@@ -8,14 +8,11 @@
 #ifndef SkDescriptor_DEFINED
 #define SkDescriptor_DEFINED
 
+#include <memory>
+
 #include "SkMacros.h"
 #include "SkNoncopyable.h"
-#include "SkOpts.h"
-#include "SkTo.h"
-#include "SkTypes.h"
-
-#include <memory>
-#include <new>
+#include "SkScalerContext.h"
 
 class SkDescriptor : SkNoncopyable {
 public:
@@ -24,41 +21,17 @@
         return sizeof(SkDescriptor) + entryCount * sizeof(Entry);
     }
 
-    static std::unique_ptr<SkDescriptor> Alloc(size_t length) {
-        SkASSERT(SkAlign4(length) == length);
-        return std::unique_ptr<SkDescriptor>(static_cast<SkDescriptor*>(::operator new (length)));
-    }
+    static std::unique_ptr<SkDescriptor> Alloc(size_t length);
 
     // Ensure the unsized delete is called.
-    void operator delete(void* p) { ::operator delete(p); }
-
+    void operator delete(void* p);
     void init() {
         fLength = sizeof(SkDescriptor);
         fCount  = 0;
     }
-
     uint32_t getLength() const { return fLength; }
-
-    void* addEntry(uint32_t tag, size_t length, const void* data = nullptr) {
-        SkASSERT(tag);
-        SkASSERT(SkAlign4(length) == length);
-        SkASSERT(this->findEntry(tag, nullptr) == nullptr);
-
-        Entry* entry = (Entry*)((char*)this + fLength);
-        entry->fTag = tag;
-        entry->fLen = SkToU32(length);
-        if (data) {
-            memcpy(entry + 1, data, length);
-        }
-
-        fCount += 1;
-        fLength = SkToU32(fLength + sizeof(Entry) + length);
-        return (entry + 1); // return its data
-    }
-
-    void computeChecksum() {
-        fChecksum = SkDescriptor::ComputeChecksum(this);
-    }
+    void* addEntry(uint32_t tag, size_t length, const void* data = nullptr);
+    void computeChecksum();
 
 #ifdef SK_DEBUG
     void assertChecksum() const {
@@ -66,45 +39,13 @@
     }
 #endif
 
-    const void* findEntry(uint32_t tag, uint32_t* length) const {
-        const Entry* entry = (const Entry*)(this + 1);
-        int          count = fCount;
+    const void* findEntry(uint32_t tag, uint32_t* length) const;
 
-        while (--count >= 0) {
-            if (entry->fTag == tag) {
-                if (length) {
-                    *length = entry->fLen;
-                }
-                return entry + 1;
-            }
-            entry = (const Entry*)((const char*)(entry + 1) + entry->fLen);
-        }
-        return nullptr;
-    }
+    std::unique_ptr<SkDescriptor> copy() const;
 
-    std::unique_ptr<SkDescriptor> copy() const {
-        std::unique_ptr<SkDescriptor> desc = SkDescriptor::Alloc(fLength);
-        memcpy(desc.get(), this, fLength);
-        return desc;
-    }
-
-    bool operator==(const SkDescriptor& other) const {
-        // probe to see if we have a good checksum algo
-//        SkASSERT(a.fChecksum != b.fChecksum || memcmp(&a, &b, a.fLength) == 0);
-
-        // the first value we should look at is the checksum, so this loop
-        // should terminate early if they descriptors are different.
-        // NOTE: if we wrote a sentinel value at the end of each, we chould
-        //       remove the aa < stop test in the loop...
-        const uint32_t* aa = (const uint32_t*)this;
-        const uint32_t* bb = (const uint32_t*)&other;
-        const uint32_t* stop = (const uint32_t*)((const char*)aa + fLength);
-        do {
-            if (*aa++ != *bb++)
-                return false;
-        } while (aa < stop);
-        return true;
-    }
+    // This assumes that all memory added has a length that is a multiple of 4. This is checked
+    // by the assert in addEntry.
+    bool operator==(const SkDescriptor& other) const;
     bool operator!=(const SkDescriptor& other) const { return !(*this == other); }
 
     uint32_t getChecksum() const { return fChecksum; }
@@ -119,63 +60,40 @@
 #endif
 
 private:
+    // private so no one can create one except our factories
+    SkDescriptor() = default;
+
+    static uint32_t ComputeChecksum(const SkDescriptor* desc);
+
     uint32_t fChecksum;  // must be first
     uint32_t fLength;    // must be second
     uint32_t fCount;
-
-    static uint32_t ComputeChecksum(const SkDescriptor* desc) {
-        const uint32_t* ptr = (const uint32_t*)desc + 1; // skip the checksum field
-        size_t len = desc->fLength - sizeof(uint32_t);
-        return SkOpts::hash(ptr, len);
-    }
-
-    // private so no one can create one except our factories
-    SkDescriptor() {}
 };
 
-#include "SkScalerContext.h"
-
 class SkAutoDescriptor : SkNoncopyable {
 public:
-    SkAutoDescriptor() : fDesc(nullptr) {}
-    SkAutoDescriptor(size_t size) : fDesc(nullptr) { this->reset(size); }
-    SkAutoDescriptor(const SkDescriptor& desc) : fDesc(nullptr) {
-        size_t size = desc.getLength();
-        this->reset(size);
-        memcpy(fDesc, &desc, size);
-    }
-
-    ~SkAutoDescriptor() { this->free(); }
-
-    void reset(size_t size) {
-        this->free();
-        if (size <= sizeof(fStorage)) {
-            fDesc = (SkDescriptor*)(void*)fStorage;
-        } else {
-            fDesc = SkDescriptor::Alloc(size).release();
-        }
-    }
-
-    SkDescriptor* getDesc() const { SkASSERT(fDesc); return fDesc; }
-private:
+    SkAutoDescriptor();
+    SkAutoDescriptor(size_t size);
+    SkAutoDescriptor(const SkDescriptor& desc);
     SkAutoDescriptor(SkAutoDescriptor&&) = delete;
     SkAutoDescriptor& operator =(SkAutoDescriptor&&) = delete;
-    void free() {
-        if (fDesc != (SkDescriptor*)(void*)fStorage) {
-            delete fDesc;
-        }
-    }
 
-    enum {
-        kStorageSize =  sizeof(SkDescriptor)
-                        + sizeof(SkDescriptor::Entry) + sizeof(SkScalerContextRec) // for rec
-                        + sizeof(SkDescriptor::Entry) + sizeof(void*)              // for typeface
-                        + 32   // slop for occational small extras
-    };
-    SkDescriptor*   fDesc;
-    uint32_t        fStorage[(kStorageSize + 3) >> 2];
+    ~SkAutoDescriptor();
+
+    void reset(size_t size);
+    void reset(const SkDescriptor& desc);
+    SkDescriptor* getDesc() const { SkASSERT(fDesc); return fDesc; }
+
+private:
+    void free();
+    static constexpr size_t kStorageSize
+            = sizeof(SkDescriptor)
+              + sizeof(SkDescriptor::Entry) + sizeof(SkScalerContextRec) // for rec
+              + sizeof(SkDescriptor::Entry) + sizeof(void*)              // for typeface
+              + 32;   // slop for occasional small extras
+
+    SkDescriptor*   fDesc{nullptr};
+    std::aligned_storage<kStorageSize, alignof(uint32_t)>::type fStorage;
 };
-#define SkAutoDescriptor(...) SK_REQUIRE_LOCAL_VAR(SkAutoDescriptor)
 
-
-#endif
+#endif  //SkDescriptor_DEFINED
diff --git a/src/core/SkDevice.cpp b/src/core/SkDevice.cpp
index 847b5a5..3580888 100644
--- a/src/core/SkDevice.cpp
+++ b/src/core/SkDevice.cpp
@@ -326,23 +326,37 @@
 
 #include "SkUtils.h"
 
-void SkBaseDevice::drawGlyphRunRSXform(
-        SkGlyphRun* run, const SkRSXform* xform) {
+void SkBaseDevice::drawGlyphRunRSXform(const SkFont& font, const SkGlyphID glyphs[],
+                                       const SkRSXform xform[], int count, SkPoint origin,
+                                       const SkPaint& paint) {
     const SkMatrix originalCTM = this->ctm();
-    if (!originalCTM.isFinite() || !SkScalarIsFinite(run->paint().getTextSize()) ||
-        !SkScalarIsFinite(run->paint().getTextScaleX()) ||
-        !SkScalarIsFinite(run->paint().getTextSkewX())) {
+    if (!originalCTM.isFinite() || !SkScalarIsFinite(font.getSize()) ||
+        !SkScalarIsFinite(font.getScaleX()) ||
+        !SkScalarIsFinite(font.getSkewX())) {
         return;
     }
 
-    auto perGlyph = [this, &xform, &originalCTM] (const SkGlyphRun& glyphRun) {
+    SkPoint sharedPos{0, 0};    // we're at the origin
+    SkGlyphID glyphID;
+    SkGlyphRun glyphRun{
+        font,
+        SkSpan<const SkPoint>{&sharedPos, 1},
+        SkSpan<const SkGlyphID>{&glyphID, 1},
+        SkSpan<const char>{},
+        SkSpan<const uint32_t>{}
+    };
+
+    for (int i = 0; i < count; i++) {
+        glyphID = glyphs[i];
+        // now "glyphRun" is pointing at the current glyphID
+
         SkMatrix ctm;
-        ctm.setRSXform(*xform++);
+        ctm.setRSXform(xform[i]).postTranslate(origin.fX, origin.fY);
 
         // We want to rotate each glyph by the rsxform, but we don't want to rotate "space"
         // (i.e. the shader that cares about the ctm) so we have to undo our little ctm trick
         // with a localmatrixshader so that the shader draws as if there was no change to the ctm.
-        SkPaint transformingPaint = glyphRun.paint();
+        SkPaint transformingPaint{paint};
         auto shader = transformingPaint.getShader();
         if (shader) {
             SkMatrix inverse;
@@ -356,10 +370,8 @@
         ctm.setConcat(originalCTM, ctm);
         this->setCTM(ctm);
 
-        SkGlyphRun transformedGlyphRun{glyphRun, transformingPaint};
-        this->drawGlyphRunList(SkGlyphRunList{transformedGlyphRun});
-    };
-    run->eachGlyphToGlyphRun(perGlyph);
+        this->drawGlyphRunList(SkGlyphRunList{glyphRun, transformingPaint});
+    }
     this->setCTM(originalCTM);
 }
 
@@ -369,6 +381,10 @@
     return nullptr;
 }
 
+sk_sp<SkSpecialImage> SkBaseDevice::snapBackImage(const SkIRect&) {
+    return nullptr;
+}
+
 //////////////////////////////////////////////////////////////////////////////////////////
 
 void SkBaseDevice::LogDrawScaleFactor(const SkMatrix& matrix, SkFilterQuality filterQuality) {
diff --git a/src/core/SkDevice.h b/src/core/SkDevice.h
index f6de6bd..d8621c4 100644
--- a/src/core/SkDevice.h
+++ b/src/core/SkDevice.h
@@ -235,7 +235,8 @@
      */
     virtual void drawDevice(SkBaseDevice*, int x, int y, const SkPaint&) = 0;
 
-    void drawGlyphRunRSXform(SkGlyphRun* run, const SkRSXform* xform);
+    void drawGlyphRunRSXform(const SkFont&, const SkGlyphID[], const SkRSXform[], int count,
+                             SkPoint origin, const SkPaint& paint);
 
     virtual void drawDrawable(SkDrawable*, const SkMatrix*, SkCanvas*);
 
@@ -248,6 +249,8 @@
 
     bool readPixels(const SkPixmap&, int x, int y);
 
+    virtual sk_sp<SkSpecialImage> snapBackImage(const SkIRect&);    // default returns null
+
     ///////////////////////////////////////////////////////////////////////////
 
     virtual GrContext* context() const { return nullptr; }
@@ -336,6 +339,7 @@
     // Temporarily friend the SkGlyphRunBuilder until drawPosText is gone.
     friend class SkGlyphRun;
     friend class SkGlyphRunList;
+    friend class SkGlyphRunBuilder;
 
     // used to change the backend's pixels (and possibly config/rowbytes)
     // but cannot change the width/height, so there should be no change to
diff --git a/src/core/SkDiscardableMemory.h b/src/core/SkDiscardableMemory.h
index 8952b8d..7bd8695 100644
--- a/src/core/SkDiscardableMemory.h
+++ b/src/core/SkDiscardableMemory.h
@@ -47,7 +47,7 @@
      *
      * Nested calls to lock are not allowed.
      */
-    virtual bool lock() = 0;
+    virtual bool SK_WARN_UNUSED_RESULT lock() = 0;
 
     /**
      * Returns the current pointer for the discardable memory. This call is ONLY
diff --git a/src/core/SkDrawable.cpp b/src/core/SkDrawable.cpp
index bf6d39c..034bacc 100644
--- a/src/core/SkDrawable.cpp
+++ b/src/core/SkDrawable.cpp
@@ -5,20 +5,18 @@
  * found in the LICENSE file.
  */
 
-#include "SkAtomics.h"
 #include "SkCanvas.h"
 #include "SkDrawable.h"
+#include <atomic>
 
 static int32_t next_generation_id() {
-    static int32_t gCanvasDrawableGenerationID;
+    static std::atomic<int32_t> nextID{1};
 
-    // do a loop in case our global wraps around, as we never want to
-    // return a 0
-    int32_t genID;
+    int32_t id;
     do {
-        genID = sk_atomic_inc(&gCanvasDrawableGenerationID) + 1;
-    } while (0 == genID);
-    return genID;
+        id = nextID++;
+    } while (id == 0);
+    return id;
 }
 
 SkDrawable::SkDrawable() : fGenerationID(0) {}
diff --git a/src/core/SkFindAndPlaceGlyph.h b/src/core/SkFindAndPlaceGlyph.h
index 8aa3da9..d4afcad 100644
--- a/src/core/SkFindAndPlaceGlyph.h
+++ b/src/core/SkFindAndPlaceGlyph.h
@@ -37,7 +37,7 @@
     // This routine handles all of them using inline polymorphic variable (no heap allocation).
     template<typename ProcessOneGlyph>
     static void ProcessPosText(
-        SkTextEncoding, const char text[], size_t byteLength,
+        const SkGlyphID[], int count,
         SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition,
         SkGlyphCache* cache, ProcessOneGlyph&& processOneGlyph);
 
@@ -108,118 +108,6 @@
     }
 
 private:
-    // GlyphFinderInterface is the polymorphic base for classes that parse a stream of chars into
-    // the right UniChar (or GlyphID) and lookup up the glyph on the cache. The concrete
-    // implementations are: Utf8GlyphFinder, Utf16GlyphFinder, Utf32GlyphFinder,
-    // and GlyphIdGlyphFinder.
-    class GlyphFinderInterface {
-    public:
-        virtual ~GlyphFinderInterface() {}
-        virtual const SkGlyph& lookupGlyph(const char** text, const char* stop) = 0;
-        virtual const SkGlyph& lookupGlyphXY(const char** text, const char* stop,
-                                             SkFixed x, SkFixed y) = 0;
-    };
-
-    class UtfNGlyphFinder : public GlyphFinderInterface {
-    public:
-        explicit UtfNGlyphFinder(SkGlyphCache* cache)
-            : fCache(cache) {
-            SkASSERT(cache != nullptr);
-        }
-
-        const SkGlyph& lookupGlyph(const char** text, const char* stop) override {
-            SkASSERT(text != nullptr);
-            return fCache->getUnicharMetrics(nextUnichar(text, stop));
-        }
-        const SkGlyph& lookupGlyphXY(const char** text, const char* stop, SkFixed x, SkFixed y) override {
-            SkASSERT(text != nullptr);
-            return fCache->getUnicharMetrics(nextUnichar(text, stop), x, y);
-        }
-
-    private:
-        virtual SkUnichar nextUnichar(const char** text, const char* stop) = 0;
-        SkGlyphCache* fCache;
-    };
-
-    class Utf8GlyphFinder final : public UtfNGlyphFinder {
-    public:
-        explicit Utf8GlyphFinder(SkGlyphCache* cache) : UtfNGlyphFinder(cache) { }
-
-    private:
-        SkUnichar nextUnichar(const char** text, const char* stop) override {
-            return SkUTF::NextUTF8(text, stop);
-        }
-    };
-
-    class Utf16GlyphFinder final : public UtfNGlyphFinder {
-    public:
-        explicit Utf16GlyphFinder(SkGlyphCache* cache) : UtfNGlyphFinder(cache) { }
-
-    private:
-        SkUnichar nextUnichar(const char** text, const char* stop) override {
-            return SkUTF::NextUTF16((const uint16_t**)text, (const uint16_t*)stop);
-        }
-    };
-
-    class Utf32GlyphFinder final : public UtfNGlyphFinder {
-    public:
-        explicit Utf32GlyphFinder(SkGlyphCache* cache) : UtfNGlyphFinder(cache) { }
-
-    private:
-        SkUnichar nextUnichar(const char** text, const char* stop) override {
-            return SkUTF::NextUTF32((const int32_t**)text, (const int32_t*)stop);
-        }
-    };
-
-    class GlyphIdGlyphFinder final : public GlyphFinderInterface {
-    public:
-        explicit GlyphIdGlyphFinder(SkGlyphCache* cache)
-            : fCache(cache) {
-            SkASSERT(cache != nullptr);
-        }
-
-        const SkGlyph& lookupGlyph(const char** text, const char* stop) override {
-            return fCache->getGlyphIDMetrics(nextGlyphId(text, stop));
-        }
-        const SkGlyph& lookupGlyphXY(const char** text, const char* stop,
-                                     SkFixed x, SkFixed y) override {
-            return fCache->getGlyphIDMetrics(nextGlyphId(text, stop), x, y);
-        }
-
-    private:
-        uint16_t nextGlyphId(const char** text, const char* stop) {
-            SkASSERT(text != nullptr);
-
-            const uint16_t* ptr = *(const uint16_t**)text;
-            SkASSERT(ptr);
-            if (ptr + 1 > (const uint16_t*)stop) {
-                *text = stop;
-                return 0;
-            }
-            uint16_t glyphID = *ptr;
-            ptr += 1;
-            *text = (const char*)ptr;
-            return glyphID;
-        }
-        SkGlyphCache* fCache;
-    };
-
-    static GlyphFinderInterface* getGlyphFinder(
-        SkArenaAlloc* arena, SkTextEncoding encoding, SkGlyphCache* cache) {
-        switch(encoding) {
-            case kUTF8_SkTextEncoding:
-                return arena->make<Utf8GlyphFinder>(cache);
-            case kUTF16_SkTextEncoding:
-                return arena->make<Utf16GlyphFinder>(cache);
-            case kUTF32_SkTextEncoding:
-                return arena->make<Utf32GlyphFinder>(cache);
-            case kGlyphID_SkTextEncoding:
-                return arena->make<GlyphIdGlyphFinder>(cache);
-        }
-        SK_ABORT("Should not get here.");
-        return nullptr;
-    }
-
     // PositionReaderInterface reads a point from the pos vector.
     // * HorizontalPositions - assumes a common Y for many X values.
     // * ArbitraryPositions - a list of (X,Y) pairs.
@@ -325,7 +213,7 @@
         // compile error.
         // See GCC bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60277
         virtual SkPoint findAndPositionGlyph(
-            const char** text, const char* stop, SkPoint position,
+            SkGlyphID, SkPoint position,
             ProcessOneGlyph&& processOneGlyph) {
             SK_ABORT("Should never get here.");
             return {0.0f, 0.0f};
@@ -338,17 +226,12 @@
     template<typename ProcessOneGlyph, SkAxisAlignment kAxisAlignment>
     class GlyphFindAndPlaceSubpixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
     public:
-        explicit GlyphFindAndPlaceSubpixel(GlyphFinderInterface* glyphFinder)
-            : fGlyphFinder(glyphFinder) { }
+        explicit GlyphFindAndPlaceSubpixel(SkGlyphCache* cache) : fCache(cache) {}
 
-        SkPoint findAndPositionGlyph(
-            const char** text, const char* stop, SkPoint position,
-            ProcessOneGlyph&& processOneGlyph) override {
-
+        SkPoint findAndPositionGlyph(SkGlyphID glyphID, SkPoint position, ProcessOneGlyph&& processOneGlyph) override {
             // Find the glyph.
             SkIPoint lookupPosition = SubpixelAlignment(kAxisAlignment, position);
-            const SkGlyph& renderGlyph =
-                fGlyphFinder->lookupGlyphXY(text, stop, lookupPosition.fX, lookupPosition.fY);
+            const SkGlyph& renderGlyph = fCache->getGlyphIDMetrics(glyphID, lookupPosition.fX, lookupPosition.fY);
 
             // If the glyph has no width (no pixels) then don't bother processing it.
             if (renderGlyph.fWidth > 0) {
@@ -360,7 +243,7 @@
         }
 
     private:
-        GlyphFinderInterface* fGlyphFinder;
+        SkGlyphCache* fCache;
     };
 
     // GlyphFindAndPlaceFullPixel handles finding and placing glyphs when no sub-pixel
@@ -368,15 +251,13 @@
     template<typename ProcessOneGlyph>
     class GlyphFindAndPlaceFullPixel final : public GlyphFindAndPlaceInterface<ProcessOneGlyph> {
     public:
-        explicit GlyphFindAndPlaceFullPixel(GlyphFinderInterface* glyphFinder)
-            : fGlyphFinder(glyphFinder) {
-        }
+        explicit GlyphFindAndPlaceFullPixel(SkGlyphCache* cache) : fCache(cache) {}
 
         SkPoint findAndPositionGlyph(
-            const char** text, const char* stop, SkPoint position,
+            SkGlyphID glyphID, SkPoint position,
             ProcessOneGlyph&& processOneGlyph) override {
             SkPoint finalPosition = position;
-            const SkGlyph& glyph = fGlyphFinder->lookupGlyph(text, stop);
+            const SkGlyph& glyph = fCache->getGlyphIDMetrics(glyphID);
 
             if (glyph.fWidth > 0) {
                 processOneGlyph(glyph, finalPosition, {SK_ScalarHalf, SK_ScalarHalf});
@@ -386,49 +267,32 @@
         }
 
     private:
-        GlyphFinderInterface* fGlyphFinder;
+        SkGlyphCache* fCache;
     };
 
     template <typename ProcessOneGlyph>
     static GlyphFindAndPlaceInterface<ProcessOneGlyph>* getSubpixel(
-        SkArenaAlloc* arena, SkAxisAlignment axisAlignment, GlyphFinderInterface* glyphFinder)
+        SkArenaAlloc* arena, SkAxisAlignment axisAlignment, SkGlyphCache* cache)
     {
         switch (axisAlignment) {
             case kX_SkAxisAlignment:
                 return arena->make<GlyphFindAndPlaceSubpixel<
-                    ProcessOneGlyph, kX_SkAxisAlignment>>(glyphFinder);
+                    ProcessOneGlyph, kX_SkAxisAlignment>>(cache);
             case kNone_SkAxisAlignment:
                 return arena->make<GlyphFindAndPlaceSubpixel<
-                    ProcessOneGlyph, kNone_SkAxisAlignment>>(glyphFinder);
+                    ProcessOneGlyph, kNone_SkAxisAlignment>>(cache);
             case kY_SkAxisAlignment:
                 return arena->make<GlyphFindAndPlaceSubpixel<
-                    ProcessOneGlyph, kY_SkAxisAlignment>>(glyphFinder);
+                    ProcessOneGlyph, kY_SkAxisAlignment>>(cache);
         }
         SK_ABORT("Should never get here.");
         return nullptr;
     }
-
-    static SkPoint MeasureText(
-        GlyphFinderInterface* glyphFinder, const char text[], size_t byteLength) {
-        SkScalar    x = 0, y = 0;
-        const char* stop = text + byteLength;
-
-        while (text < stop) {
-            // don't need x, y here, since all subpixel variants will have the
-            // same advance
-            const SkGlyph& glyph = glyphFinder->lookupGlyph(&text, stop);
-
-            x += SkFloatToScalar(glyph.fAdvanceX);
-            y += SkFloatToScalar(glyph.fAdvanceY);
-        }
-        SkASSERT(text == stop);
-        return {x, y};
-    }
 };
 
 template<typename ProcessOneGlyph>
 inline void SkFindAndPlaceGlyph::ProcessPosText(
-    SkTextEncoding textEncoding, const char text[], size_t byteLength,
+    const SkGlyphID glyphs[], int count,
     SkPoint offset, const SkMatrix& matrix, const SkScalar pos[], int scalarsPerPosition,
     SkGlyphCache* cache, ProcessOneGlyph&& processOneGlyph) {
 
@@ -436,12 +300,10 @@
     uint32_t mtype = matrix.getType();
 
     // Specialized code for handling the most common case for blink.
-    if (textEncoding == kGlyphID_SkTextEncoding
-        && axisAlignment == kX_SkAxisAlignment
+    if (axisAlignment == kX_SkAxisAlignment
         && cache->isSubpixel()
         && mtype <= SkMatrix::kTranslate_Mask)
     {
-        GlyphIdGlyphFinder glyphFinder(cache);
         using Positioner =
             GlyphFindAndPlaceSubpixel <
                 ProcessOneGlyph, kX_SkAxisAlignment>;
@@ -454,21 +316,17 @@
             positions = &hPositions;
         }
         TranslationMapper mapper{matrix, offset};
-        Positioner positioner(&glyphFinder);
-        const char* cursor = text;
-        const char* stop = text + byteLength;
-        while (cursor < stop) {
+        Positioner positioner(cache);
+        for (int i = 0; i < count; ++i) {
             SkPoint mappedPoint = mapper.TranslationMapper::map(positions->nextPoint());
             positioner.Positioner::findAndPositionGlyph(
-                &cursor, stop, mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
+                glyphs[i], mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
         }
         return;
     }
 
     SkSTArenaAlloc<120> arena;
 
-    GlyphFinderInterface* glyphFinder = getGlyphFinder(&arena, textEncoding, cache);
-
     PositionReaderInterface* positionReader = nullptr;
     if (2 == scalarsPerPosition) {
         positionReader = arena.make<ArbitraryPositions>(pos);
@@ -479,16 +337,15 @@
     MapperInterface* mapper = CreateMapper(matrix, offset, scalarsPerPosition, &arena);
     GlyphFindAndPlaceInterface<ProcessOneGlyph>* findAndPosition = nullptr;
     if (cache->isSubpixel()) {
-        findAndPosition = getSubpixel<ProcessOneGlyph>(&arena, axisAlignment, glyphFinder);
+        findAndPosition = getSubpixel<ProcessOneGlyph>(&arena, axisAlignment, cache);
     } else {
-        findAndPosition = arena.make<GlyphFindAndPlaceFullPixel<ProcessOneGlyph>>(glyphFinder);
+        findAndPosition = arena.make<GlyphFindAndPlaceFullPixel<ProcessOneGlyph>>(cache);
     }
 
-    const char* stop = text + byteLength;
-    while (text < stop) {
+    for (int i = 0; i < count; ++i) {
         SkPoint mappedPoint = mapper->map(positionReader->nextPoint());
         findAndPosition->findAndPositionGlyph(
-            &text, stop, mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
+            glyphs[i], mappedPoint, std::forward<ProcessOneGlyph>(processOneGlyph));
     }
 }
 
diff --git a/src/core/SkFont.cpp b/src/core/SkFont.cpp
index f19ba59..956f832 100644
--- a/src/core/SkFont.cpp
+++ b/src/core/SkFont.cpp
@@ -28,7 +28,7 @@
 }
 
 SkFont::SkFont(sk_sp<SkTypeface> face, SkScalar size, SkScalar scaleX, SkScalar skewX)
-    : fTypeface(face ? std::move(face) : SkTypeface::MakeDefault())
+    : fTypeface(std::move(face))
     , fSize(valid_size(size))
     , fScaleX(scaleX)
     , fSkewX(skewX)
@@ -39,6 +39,8 @@
 
 SkFont::SkFont(sk_sp<SkTypeface> face, SkScalar size) : SkFont(std::move(face), size, 1, 0) {}
 
+SkFont::SkFont(sk_sp<SkTypeface> face) : SkFont(std::move(face), kDefault_Size, 1, 0) {}
+
 SkFont::SkFont() : SkFont(nullptr, kDefault_Size) {}
 
 bool SkFont::operator==(const SkFont& b) const {
@@ -51,6 +53,16 @@
             fHinting        == b.fHinting;
 }
 
+void SkFont::dump() const {
+    SkDebugf("typeface %p\n", fTypeface.get());
+    SkDebugf("size %g\n", fSize);
+    SkDebugf("skewx %g\n", fSkewX);
+    SkDebugf("scalex %g\n", fScaleX);
+    SkDebugf("flags 0x%X\n", fFlags);
+    SkDebugf("edging %d\n", (unsigned)fEdging);
+    SkDebugf("hinting %d\n", (unsigned)fHinting);
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 static inline uint32_t set_clear_mask(uint32_t bits, bool cond, uint32_t mask) {
@@ -120,25 +132,37 @@
     return textSize / SkPaint::kCanonicalTextSizeForPaths;
 }
 
+bool SkFont::hasSomeAntiAliasing() const {
+    Edging edging = this->getEdging();
+    return edging == SkFont::Edging::kAntiAlias
+        || edging == SkFont::Edging::kSubpixelAntiAlias;
+}
+
 class SkCanonicalizeFont {
 public:
-    SkCanonicalizeFont(const SkFont& font) : fFont(&font), fScale(0) {
+    SkCanonicalizeFont(const SkFont& font, const SkPaint* paint) : fFont(&font) {
+        if (paint) {
+            fPaint = *paint;
+        }
         if (font.isLinearMetrics() ||
-            SkDraw::ShouldDrawTextAsPaths(font, SkPaint(), SkMatrix::I()))
+            SkDraw::ShouldDrawTextAsPaths(font, fPaint, SkMatrix::I()))
         {
-            SkFont* f = fLazy.set(font);
+            SkFont* f = fLazyFont.set(font);
             fScale = f->setupForAsPaths(nullptr);
             fFont = f;
+            fPaint.reset();
         }
     }
 
     const SkFont& getFont() const { return *fFont; }
+    const SkPaint& getPaint() const { return fPaint; }
     SkScalar getScale() const { return fScale; }
 
 private:
-    const SkFont*   fFont;
-    SkTLazy<SkFont> fLazy;
-    SkScalar        fScale;
+    const SkFont*    fFont;
+    SkTLazy<SkFont>  fLazyFont;
+    SkPaint          fPaint;
+    SkScalar         fScale = 0;
 };
 
 
@@ -150,22 +174,7 @@
 
     SkASSERT(text);
 
-    int count = 0;  // fix uninitialized warning (even though the switch is complete!)
-
-    switch (encoding) {
-        case kUTF8_SkTextEncoding:
-            count = SkUTF::CountUTF8((const char*)text, byteLength);
-            break;
-        case kUTF16_SkTextEncoding:
-            count = SkUTF::CountUTF16((const uint16_t*)text, byteLength);
-            break;
-        case kUTF32_SkTextEncoding:
-            count = SkToInt(byteLength >> 2);
-            break;
-        case kGlyphID_SkTextEncoding:
-            count = SkToInt(byteLength >> 1);
-            break;
-    }
+    int count = SkFontPriv::CountTextElements(text, byteLength, encoding);
     if (!glyphs || count > maxGlyphCount) {
         return count;
     }
@@ -194,6 +203,22 @@
     return count;
 }
 
+void SkFont::glyphsToUnichars(const SkGlyphID glyphs[], int count, SkUnichar text[]) const {
+    if (count <= 0) {
+        return;
+    }
+
+    auto typeface = SkFontPriv::GetTypefaceOrDefault(*this);
+    const unsigned numGlyphsInTypeface = typeface->countGlyphs();
+    SkAutoTArray<SkUnichar> unichars(numGlyphsInTypeface);
+    typeface->getGlyphToUnicodeMap(unichars.get());
+
+    for (int i = 0; i < count; ++i) {
+        unsigned id = glyphs[i];
+        text[i] = (id < numGlyphsInTypeface) ? unichars[id] : 0xFFFD;
+    }
+}
+
 static SkTypeface::Encoding to_encoding(SkTextEncoding e) {
     static_assert((int)SkTypeface::kUTF8_Encoding  == (int)kUTF8_SkTextEncoding,  "");
     static_assert((int)SkTypeface::kUTF16_Encoding == (int)kUTF16_SkTextEncoding, "");
@@ -246,7 +271,7 @@
         return length;
     }
 
-    SkCanonicalizeFont canon(*this);
+    SkCanonicalizeFont canon(*this, nullptr);
     const SkFont& font = canon.getFont();
     SkScalar scale = canon.getScale();
 
@@ -295,7 +320,7 @@
 }
 
 SkScalar SkFont::measureText(const void* textD, size_t length, SkTextEncoding encoding,
-                             SkRect* bounds) const {
+                             SkRect* bounds, const SkPaint* paint) const {
     if (length == 0) {
         if (bounds) {
             bounds->setEmpty();
@@ -303,11 +328,11 @@
         return 0;
     }
 
-    SkCanonicalizeFont canon(*this);
+    SkCanonicalizeFont canon(*this, paint);
     const SkFont& font = canon.getFont();
     SkScalar scale = canon.getScale();
 
-    auto cache = SkStrikeCache::FindOrCreateStrikeWithNoDeviceExclusive(font);
+    auto cache = SkStrikeCache::FindOrCreateStrikeWithNoDeviceExclusive(font, canon.getPaint());
 
     const char* text = static_cast<const char*>(textD);
     const char* stop = text + length;
@@ -357,15 +382,14 @@
         return;
     }
 
-    SkCanonicalizeFont canon(origFont);
+    SkCanonicalizeFont canon(origFont, paint);
     const SkFont& font = canon.getFont();
     SkScalar scale = canon.getScale();
     if (!scale) {
         scale = 1;
     }
 
-    auto cache = SkStrikeCache::FindOrCreateStrikeWithNoDeviceExclusive(font,
-                                                                        paint ? *paint : SkPaint());
+    auto cache = SkStrikeCache::FindOrCreateStrikeWithNoDeviceExclusive(font, canon.getPaint());
     handler(cache.get(), glyphs, count, scale);
 }
 
@@ -444,7 +468,7 @@
 }
 
 SkScalar SkFont::getMetrics(SkFontMetrics* metrics) const {
-    SkCanonicalizeFont canon(*this);
+    SkCanonicalizeFont canon(*this, nullptr);
     const SkFont& font = canon.getFont();
     SkScalar scale = canon.getScale();
 
@@ -527,3 +551,145 @@
     }
     this->setEdging(edging);
 }
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+SkRect SkFontPriv::GetFontBounds(const SkFont& font) {
+    SkMatrix m;
+    m.setScale(font.getSize() * font.getScaleX(), font.getSize());
+    m.postSkew(font.getSkewX(), 0);
+
+    SkTypeface* typeface = SkFontPriv::GetTypefaceOrDefault(font);
+
+    SkRect bounds;
+    m.mapRect(&bounds, typeface->getBounds());
+    return bounds;
+}
+
+int SkFontPriv::CountTextElements(const void* text, size_t byteLength, SkTextEncoding encoding) {
+    switch (encoding) {
+        case kUTF8_SkTextEncoding:
+            return SkUTF::CountUTF8(reinterpret_cast<const char*>(text), byteLength);
+        case kUTF16_SkTextEncoding:
+            return SkUTF::CountUTF16(reinterpret_cast<const uint16_t*>(text), byteLength);
+        case kUTF32_SkTextEncoding:
+            return byteLength >> 2;
+        case kGlyphID_SkTextEncoding:
+            return byteLength >> 1;
+    }
+    SkASSERT(false);
+    return 0;
+}
+
+void SkFontPriv::GlyphsToUnichars(const SkFont& font, const uint16_t glyphs[], int count,
+                                  SkUnichar uni[]) {
+    font.glyphsToUnichars(glyphs, count, uni);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+#include "SkReadBuffer.h"
+#include "SkWriteBuffer.h"
+
+// packed int at the beginning of the serialized font:
+//
+//  control_bits:8 size_as_byte:8 flags:12 edging:2 hinting:2
+
+enum {
+    kSize_Is_Byte_Bit   = 1 << 31,
+    kHas_ScaleX_Bit     = 1 << 30,
+    kHas_SkewX_Bit      = 1 << 29,
+    kHas_Typeface_Bit   = 1 << 28,
+
+    kShift_for_Size     = 16,
+    kMask_For_Size      = 0xFF,
+
+    kShift_For_Flags    = 4,
+    kMask_For_Flags     = 0xFFF,
+
+    kShift_For_Edging   = 2,
+    kMask_For_Edging    = 0x3,
+
+    kShift_For_Hinting  = 0,
+    kMask_For_Hinting   = 0x3
+};
+
+static bool scalar_is_byte(SkScalar x) {
+    int ix = (int)x;
+    return ix == x && ix >= 0 && ix <= kMask_For_Size;
+}
+
+void SkFontPriv::Flatten(const SkFont& font, SkWriteBuffer& buffer) {
+    SkASSERT((font.fFlags & ~kMask_For_Flags) == 0);
+    SkASSERT((font.fEdging & ~kMask_For_Edging) == 0);
+    SkASSERT((font.fHinting & ~kMask_For_Hinting) == 0);
+
+    uint32_t packed = 0;
+    packed |= font.fFlags << kShift_For_Flags;
+    packed |= font.fEdging << kShift_For_Edging;
+    packed |= font.fHinting << kShift_For_Hinting;
+
+    if (scalar_is_byte(font.fSize)) {
+        packed |= kSize_Is_Byte_Bit;
+        packed |= (int)font.fSize << kShift_for_Size;
+    }
+    if (font.fScaleX != 1) {
+        packed |= kHas_ScaleX_Bit;
+    }
+    if (font.fSkewX != 0) {
+        packed |= kHas_SkewX_Bit;
+    }
+    if (font.fTypeface) {
+        packed |= kHas_Typeface_Bit;
+    }
+
+    buffer.write32(packed);
+    if (!(packed & kSize_Is_Byte_Bit)) {
+        buffer.writeScalar(font.fSize);
+    }
+    if (packed & kHas_ScaleX_Bit) {
+        buffer.writeScalar(font.fScaleX);
+    }
+    if (packed & kHas_SkewX_Bit) {
+        buffer.writeScalar(font.fSkewX);
+    }
+    if (packed & kHas_Typeface_Bit) {
+        buffer.writeTypeface(font.fTypeface.get());
+    }
+}
+
+bool SkFontPriv::Unflatten(SkFont* font, SkReadBuffer& buffer) {
+    const uint32_t packed = buffer.read32();
+
+    if (packed & kSize_Is_Byte_Bit) {
+        font->fSize = (packed >> kShift_for_Size) & kMask_For_Size;
+    } else {
+        font->fSize = buffer.readScalar();
+    }
+    if (packed & kHas_ScaleX_Bit) {
+        font->fScaleX = buffer.readScalar();
+    }
+    if (packed & kHas_SkewX_Bit) {
+        font->fSkewX = buffer.readScalar();
+    }
+    if (packed & kHas_Typeface_Bit) {
+        font->fTypeface = buffer.readTypeface();
+    }
+
+    SkASSERT(SkFont::kAllFlags <= kMask_For_Flags);
+    // we & with kAllFlags, to clear out any unknown flag bits
+    font->fFlags = SkToU8((packed >> kShift_For_Flags) & SkFont::kAllFlags);
+
+    unsigned edging = (packed >> kShift_For_Edging) & kMask_For_Edging;
+    if (edging > (unsigned)SkFont::Edging::kSubpixelAntiAlias) {
+        edging = 0;
+    }
+    font->fEdging = SkToU8(edging);
+
+    unsigned hinting = (packed >> kShift_For_Hinting) & kMask_For_Hinting;
+    if (hinting > (unsigned)kFull_SkFontHinting) {
+        hinting = 0;
+    }
+    font->fHinting = SkToU8(hinting);
+
+    return buffer.isValid();
+}
diff --git a/src/core/SkFontPriv.h b/src/core/SkFontPriv.h
index eeeb930..8d78069 100644
--- a/src/core/SkFontPriv.h
+++ b/src/core/SkFontPriv.h
@@ -12,6 +12,11 @@
 #include "SkMatrix.h"
 #include "SkTypeface.h"
 
+class SkGlyph;
+class SkGlyphCache;
+class SkReadBuffer;
+class SkWriteBuffer;
+
 class SkFontPriv {
 public:
     /**
@@ -45,6 +50,33 @@
     typedef const SkGlyph& (*GlyphCacheProc)(SkGlyphCache*, const char**, const char*);
 
     static GlyphCacheProc GetGlyphCacheProc(SkTextEncoding encoding, bool needFullMetrics);
+
+    /**
+        Returns the union of bounds of all glyphs.
+        Returned dimensions are computed by font manager from font data,
+        ignoring SkPaint::Hinting. Includes font metrics, but not fake bold or SkPathEffect.
+
+        If text size is large, text scale is one, and text skew is zero,
+        returns the bounds as:
+        { SkFontMetrics::fXMin, SkFontMetrics::fTop, SkFontMetrics::fXMax, SkFontMetrics::fBottom }.
+
+        @return  union of bounds of all glyphs
+     */
+    static SkRect GetFontBounds(const SkFont&);
+
+    static bool IsFinite(const SkFont& font) {
+        return SkScalarIsFinite(font.fSize) &&
+               SkScalarIsFinite(font.fScaleX) &&
+               SkScalarIsFinite(font.fSkewX);
+    }
+
+    // Returns the number of elements (characters or glyphs) in the array.
+    static int CountTextElements(const void* text, size_t byteLength, SkTextEncoding);
+
+    static void GlyphsToUnichars(const SkFont&, const uint16_t glyphs[], int count, SkUnichar[]);
+
+    static void Flatten(const SkFont&, SkWriteBuffer& buffer);
+    static bool Unflatten(SkFont*, SkReadBuffer& buffer);
 };
 
 class SkAutoToGlyphs {
diff --git a/src/core/SkGeometry.h b/src/core/SkGeometry.h
index 315103d..f7828dc 100644
--- a/src/core/SkGeometry.h
+++ b/src/core/SkGeometry.h
@@ -310,7 +310,7 @@
 };
 
 // inline helpers are contained in a namespace to avoid external leakage to fragile SkNx members
-namespace {
+namespace {  // NOLINT(google-build-namespaces)
 
 /**
  *  use for : eval(t) == A * t^2 + B * t + C
diff --git a/src/core/SkGlyph.cpp b/src/core/SkGlyph.cpp
index 0206afc..69a4082 100644
--- a/src/core/SkGlyph.cpp
+++ b/src/core/SkGlyph.cpp
@@ -117,14 +117,11 @@
 SkPath* SkGlyph::addPath(SkScalerContext* scalerContext, SkArenaAlloc* alloc) {
     if (!this->isEmpty()) {
         if (fPathData == nullptr) {
-            SkGlyph::PathData* pathData = alloc->make<SkGlyph::PathData>();
-            fPathData = pathData;
-            pathData->fIntercept = nullptr;
-            auto path = skstd::make_unique<SkPath>();
-            if (scalerContext->getPath(this->getPackedID(), path.get())) {
-                path->updateBoundsCache();
-                path->getGenerationID();
-                pathData->fPath = std::move(path);
+            fPathData = alloc->make<SkGlyph::PathData>();
+            if (scalerContext->getPath(this->getPackedID(), &fPathData->fPath)) {
+                fPathData->fPath.updateBoundsCache();
+                fPathData->fPath.getGenerationID();
+                fPathData->fHasPath = true;
             }
         }
     }
diff --git a/src/core/SkGlyph.h b/src/core/SkGlyph.h
index 7ce0a7c..c615126 100644
--- a/src/core/SkGlyph.h
+++ b/src/core/SkGlyph.h
@@ -8,16 +8,14 @@
 #ifndef SkGlyph_DEFINED
 #define SkGlyph_DEFINED
 
-#include <memory>
-
 #include "SkChecksum.h"
 #include "SkFixed.h"
 #include "SkMask.h"
+#include "SkPath.h"
 #include "SkTo.h"
 #include "SkTypes.h"
 
 class SkArenaAlloc;
-class SkPath;
 class SkGlyphCache;
 class SkScalerContext;
 
@@ -167,7 +165,7 @@
     SkPath* addPath(SkScalerContext*, SkArenaAlloc*);
 
     SkPath* path() const {
-        return fPathData ? fPathData->fPath.get() : nullptr;
+        return fPathData != nullptr && fPathData->fHasPath ? &fPathData->fPath : nullptr;
     }
 
     // Returns the size allocated on the arena.
@@ -223,8 +221,9 @@
     };
 
     struct PathData {
-        Intercept*              fIntercept;
-        std::unique_ptr<SkPath> fPath;
+        Intercept* fIntercept{nullptr};
+        SkPath     fPath;
+        bool       fHasPath{false};
     };
 
     // TODO(herb) remove friend statement after SkGlyphCache cleanup.
diff --git a/src/core/SkGlyphCache.cpp b/src/core/SkGlyphCache.cpp
index 6b64284..266d207 100644
--- a/src/core/SkGlyphCache.cpp
+++ b/src/core/SkGlyphCache.cpp
@@ -71,10 +71,6 @@
     }
 }
 
-SkUnichar SkGlyphCache::glyphToUnichar(SkGlyphID glyphID) {
-    return fScalerContext->glyphIDToChar(glyphID);
-}
-
 unsigned SkGlyphCache::getGlyphCount() const {
     return fScalerContext->getGlyphCount();
 }
@@ -181,6 +177,7 @@
 const void* SkGlyphCache::findImage(const SkGlyph& glyph) {
     if (glyph.fWidth > 0 && glyph.fWidth < kMaxGlyphWidth) {
         if (nullptr == glyph.fImage) {
+            SkDEBUGCODE(SkMask::Format oldFormat = (SkMask::Format)glyph.fMaskFormat);
             size_t  size = const_cast<SkGlyph&>(glyph).allocImage(&fAlloc);
             // check that alloc() actually succeeded
             if (glyph.fImage) {
@@ -191,6 +188,7 @@
                 // is smaller, and if so, strink the alloc size in fImageAlloc.
                 fMemoryUsed += size;
             }
+            SkASSERT(oldFormat == glyph.fMaskFormat);
         }
     }
     return glyph.fImage;
@@ -205,7 +203,7 @@
         size_t allocSize = glyph->allocImage(&fAlloc);
         // check that alloc() actually succeeded
         if (glyph->fImage) {
-            SkAssertResult(size == allocSize);
+            SkASSERT(size == allocSize);
             memcpy(glyph->fImage, const_cast<const void*>(data), allocSize);
             fMemoryUsed += size;
         }
@@ -217,14 +215,18 @@
     if (!glyph.isEmpty()) {
         // If the path already exists, return it.
         if (glyph.fPathData != nullptr) {
-            return glyph.fPathData->fPath.get();
+            if (glyph.fPathData->fHasPath) {
+                return &glyph.fPathData->fPath;
+            }
+            return nullptr;
         }
 
-        // Add new path to the glyph, and add it's size to the glyph cache size.
-        if (SkPath* path = const_cast<SkGlyph&>(glyph).addPath(fScalerContext.get(), &fAlloc)) {
-            fMemoryUsed += compute_path_size(*path);
-            return path;
+        const_cast<SkGlyph&>(glyph).addPath(fScalerContext.get(), &fAlloc);
+        if (glyph.fPathData != nullptr) {
+            fMemoryUsed += compute_path_size(glyph.fPathData->fPath);
         }
+
+        return glyph.path();
     }
 
     return nullptr;
@@ -238,13 +240,12 @@
     if (glyph->fWidth) {
         SkGlyph::PathData* pathData = fAlloc.make<SkGlyph::PathData>();
         glyph->fPathData = pathData;
-        pathData->fIntercept = nullptr;
         auto path = skstd::make_unique<SkPath>();
-        if (!path->readFromMemory(const_cast<const void*>(data), size)) {
+        if (!pathData->fPath.readFromMemory(const_cast<const void*>(data), size)) {
             return false;
         }
-        fMemoryUsed += compute_path_size(*path);
-        pathData->fPath = std::move(path);
+        fMemoryUsed += compute_path_size(glyph->fPathData->fPath);
+        pathData->fHasPath = true;
     }
 
     return true;
@@ -403,7 +404,7 @@
     intercept->fInterval[0] = SK_ScalarMax;
     intercept->fInterval[1] = SK_ScalarMin;
     glyph->fPathData->fIntercept = intercept;
-    const SkPath* path = glyph->fPathData->fPath.get();
+    const SkPath* path = &(glyph->fPathData->fPath);
     const SkRect& pathBounds = path->getBounds();
     if (*(&pathBounds.fBottom - yAxis) < bounds[0] || bounds[1] < *(&pathBounds.fTop - yAxis)) {
         return;
@@ -487,8 +488,8 @@
         if (glyph.fImage) {
             memoryUsed += glyph.computeImageSize();
         }
-        if (glyph.fPathData && glyph.fPathData->fPath) {
-            memoryUsed += compute_path_size(*glyph.fPathData->fPath);
+        if (glyph.fPathData) {
+            memoryUsed += compute_path_size(glyph.fPathData->fPath);
         }
     });
     SkASSERT(fMemoryUsed == memoryUsed);
diff --git a/src/core/SkGlyphCache.h b/src/core/SkGlyphCache.h
index a4c3e85..9a24b33 100644
--- a/src/core/SkGlyphCache.h
+++ b/src/core/SkGlyphCache.h
@@ -74,10 +74,6 @@
     */
     SkGlyphID unicharToGlyph(SkUnichar);
 
-    /** Map the glyph to its Unicode equivalent. Unmappable glyphs map to a character code of zero.
-    */
-    SkUnichar glyphToUnichar(SkGlyphID);
-
     /** Returns the number of glyphs for this strike.
     */
     unsigned getGlyphCount() const;
diff --git a/src/core/SkGlyphRun.cpp b/src/core/SkGlyphRun.cpp
index 1e487e3..f613f5b 100644
--- a/src/core/SkGlyphRun.cpp
+++ b/src/core/SkGlyphRun.cpp
@@ -7,29 +7,19 @@
 
 #include "SkGlyphRun.h"
 
+#include "SkDevice.h"
+#include "SkFont.h"
+#include "SkFontPriv.h"
 #include "SkGlyphCache.h"
 #include "SkPaint.h"
-#include "SkPaintPriv.h"
 #include "SkStrikeCache.h"
 #include "SkTextBlob.h"
 #include "SkTextBlobPriv.h"
 #include "SkTo.h"
 #include "SkUtils.h"
 
-namespace {
-static SkTypeface::Encoding convert_encoding(SkTextEncoding encoding) {
-    switch (encoding) {
-        case  kUTF8_SkTextEncoding: return SkTypeface::kUTF8_Encoding;
-        case kUTF16_SkTextEncoding: return SkTypeface::kUTF16_Encoding;
-        case kUTF32_SkTextEncoding: return SkTypeface::kUTF32_Encoding;
-        default: return SkTypeface::kUTF32_Encoding;
-    }
-}
-}  // namespace
-
 // -- SkGlyphRun -----------------------------------------------------------------------------------
-SkGlyphRun::SkGlyphRun(const SkPaint& basePaint,
-                       const SkRunFont& runFont,
+SkGlyphRun::SkGlyphRun(const SkFont& font,
                        SkSpan<const SkPoint> positions,
                        SkSpan<const SkGlyphID> glyphIDs,
                        SkSpan<const char> text,
@@ -38,34 +28,14 @@
         , fGlyphIDs{glyphIDs}
         , fText{text}
         , fClusters{clusters}
-        , fRunPaint{basePaint, runFont} {}
+        , fFont{font} {}
 
-SkGlyphRun::SkGlyphRun(const SkGlyphRun& that, const SkPaint& paint)
+SkGlyphRun::SkGlyphRun(const SkGlyphRun& that, const SkFont& font)
     : fPositions{that.fPositions}
     , fGlyphIDs{that.fGlyphIDs}
     , fText{that.fText}
     , fClusters{that.fClusters}
-    , fRunPaint{paint} {}
-
-void SkGlyphRun::eachGlyphToGlyphRun(SkGlyphRun::PerGlyph perGlyph) {
-    SkPoint point;
-    SkGlyphID glyphID;
-    SkGlyphRun run{
-        fRunPaint,
-        SkRunFont{fRunPaint},
-        SkSpan<const SkPoint>{&point, 1},
-        SkSpan<const SkGlyphID>{&glyphID, 1},
-        SkSpan<const char>{},
-        SkSpan<const uint32_t>{}
-    };
-
-    auto runSize = fGlyphIDs.size();
-    for (size_t i = 0; i < runSize; i++) {
-        glyphID = fGlyphIDs[i];
-        point = fPositions[i];
-        perGlyph(run);
-    }
-}
+    , fFont{font} {}
 
 void SkGlyphRun::filloutGlyphsAndPositions(SkGlyphID* glyphIDs, SkPoint* positions) {
     memcpy(glyphIDs, fGlyphIDs.data(), fGlyphIDs.size_bytes());
@@ -84,8 +54,8 @@
         , fOrigin{origin}
         , fGlyphRuns{glyphRunList} { }
 
-SkGlyphRunList::SkGlyphRunList(const SkGlyphRun& glyphRun)
-        : fOriginalPaint{&glyphRun.paint()}
+SkGlyphRunList::SkGlyphRunList(const SkGlyphRun& glyphRun, const SkPaint& paint)
+        : fOriginalPaint{&paint}
         , fOriginalTextBlob{nullptr}
         , fOrigin{SkPoint::Make(0, 0)}
         , fGlyphRuns{SkSpan<const SkGlyphRun>{&glyphRun, 1}} {}
@@ -97,7 +67,7 @@
 
 bool SkGlyphRunList::anyRunsLCD() const {
     for (const auto& r : fGlyphRuns) {
-        if (r.paint().isLCDRenderText()) {
+        if (r.font().getEdging() == SkFont::Edging::kSubpixelAntiAlias) {
             return true;
         }
     }
@@ -106,13 +76,22 @@
 
 bool SkGlyphRunList::anyRunsSubpixelPositioned() const {
     for (const auto& r : fGlyphRuns) {
-        if (r.paint().isSubpixelText()) {
+        if (r.font().isSubpixel()) {
             return true;
         }
     }
     return false;
 }
 
+bool SkGlyphRunList::allFontsFinite() const {
+    for (const auto& r : fGlyphRuns) {
+        if (!SkFontPriv::IsFinite(r.font())) {
+            return false;
+        }
+    }
+    return true;
+}
+
 void SkGlyphRunList::temporaryShuntBlobNotifyAddedToCache(uint32_t cacheID) const {
     SkASSERT(fOriginalTextBlob != nullptr);
     fOriginalTextBlob->notifyAddedToCache(cacheID);
@@ -181,64 +160,20 @@
 }
 
 // -- SkGlyphRunBuilder ----------------------------------------------------------------------------
-void SkGlyphRunBuilder::drawTextAtOrigin(
-        const SkPaint& paint, const void* bytes, size_t byteLength) {
-    auto glyphIDs = textToGlyphIDs(paint, bytes, byteLength);
+void SkGlyphRunBuilder::drawTextUTF8(const SkPaint& paint, const SkFont& font, const void* bytes,
+                                     size_t byteLength, SkPoint origin) {
+    auto glyphIDs = textToGlyphIDs(font, bytes, byteLength, kUTF8_SkTextEncoding);
     if (!glyphIDs.empty()) {
         this->initialize(glyphIDs.size());
-    }
-
-    auto positions = SkSpan<const SkPoint>{fPositions, glyphIDs.size()};
-
-    // Every glyph is at the origin.
-    sk_bzero((void *)positions.data(), positions.size_bytes());
-
-    this->makeGlyphRun(
-            paint,
-            SkRunFont{paint},
-            glyphIDs,
-            positions,
-            SkSpan<const char>{},
-            SkSpan<const uint32_t>{});
-    this->makeGlyphRunList(paint, nullptr, SkPoint::Make(0, 0));
-}
-
-void SkGlyphRunBuilder::drawText(
-        const SkPaint& paint, const void* bytes, size_t byteLength, SkPoint origin) {
-    auto glyphIDs = textToGlyphIDs(paint, bytes, byteLength);
-    if (!glyphIDs.empty()) {
-        this->initialize(glyphIDs.size());
-        this->simplifyDrawText(paint, SkRunFont{paint}, glyphIDs, origin, fPositions);
+        this->simplifyDrawText(SkFont::LEGACY_ExtractFromPaint(paint),
+                               glyphIDs, origin, fPositions);
     }
 
     this->makeGlyphRunList(paint, nullptr, SkPoint::Make(0, 0));
 }
 
-void SkGlyphRunBuilder::drawPosTextH(const SkPaint& paint, const void* bytes,
-                                     size_t byteLength, const SkScalar* xpos,
-                                     SkScalar constY) {
-    auto glyphIDs = textToGlyphIDs(paint, bytes, byteLength);
-    if (!glyphIDs.empty()) {
-        this->initialize(glyphIDs.size());
-        this->simplifyDrawPosTextH(
-                paint, SkRunFont{paint}, glyphIDs, xpos, constY, fPositions);
-    }
-
-    this->makeGlyphRunList(paint, nullptr, SkPoint::Make(0, 0));
-}
-
-void SkGlyphRunBuilder::drawPosText(const SkPaint& paint, const void* bytes,
-                                    size_t byteLength, const SkPoint* pos) {
-    auto glyphIDs = textToGlyphIDs(paint, bytes, byteLength);
-    if (!glyphIDs.empty()) {
-        this->initialize(glyphIDs.size());
-        this->simplifyDrawPosText(paint, SkRunFont{paint}, glyphIDs, pos);
-    }
-
-    this->makeGlyphRunList(paint, nullptr, SkPoint::Make(0, 0));
-}
-
-void SkGlyphRunBuilder::drawTextBlob(const SkPaint& paint, const SkTextBlob& blob, SkPoint origin) {
+void SkGlyphRunBuilder::drawTextBlob(const SkPaint& paint, const SkTextBlob& blob, SkPoint origin,
+                                     SkBaseDevice* device) {
     // Figure out all the storage needed to pre-size everything below.
     size_t totalGlyphs = 0;
     for (SkTextBlobRunIterator it(&blob); !it.done(); it.next()) {
@@ -263,32 +198,47 @@
         switch (it.positioning()) {
             case SkTextBlobRunIterator::kDefault_Positioning: {
                 this->simplifyDrawText(
-                        paint, it.runFont(), glyphIDs, offset, positions, text, clusters);
+                        it.font(), glyphIDs, offset, positions, text, clusters);
             }
                 break;
             case SkTextBlobRunIterator::kHorizontal_Positioning: {
                 auto constY = offset.y();
                 this->simplifyDrawPosTextH(
-                        paint, it.runFont(), glyphIDs, it.pos(), constY, positions, text, clusters);
+                        it.font(), glyphIDs, it.pos(), constY, positions, text, clusters);
             }
                 break;
             case SkTextBlobRunIterator::kFull_Positioning:
                 this->simplifyDrawPosText(
-                        paint, it.runFont(), glyphIDs, (const SkPoint*)it.pos(), text, clusters);
+                        it.font(), glyphIDs, (const SkPoint*)it.pos(), text, clusters);
                 break;
+            case SkTextBlobRunIterator::kRSXform_Positioning: {
+                if (!this->empty()) {
+                    this->makeGlyphRunList(paint, &blob, origin);
+                    device->drawGlyphRunList(this->useGlyphRunList());
+                }
+
+                device->drawGlyphRunRSXform(it.font(), it.glyphs(), (const SkRSXform*)it.pos(),
+                                            runSize, origin, paint);
+
+                // re-init in case we keep looping and need the builder again
+                this->initialize(totalGlyphs);
+            } break;
         }
 
         positions += runSize;
     }
 
-    this->makeGlyphRunList(paint, &blob, origin);
+    if (!this->empty()) {
+        this->makeGlyphRunList(paint, &blob, origin);
+        device->drawGlyphRunList(this->useGlyphRunList());
+    }
 }
 
-void SkGlyphRunBuilder::drawGlyphPos(
-        const SkPaint& paint, SkSpan<const SkGlyphID> glyphIDs, const SkPoint* pos) {
+void SkGlyphRunBuilder::drawGlyphsWithPositions(const SkPaint& paint, const SkFont& font,
+                                            SkSpan<const SkGlyphID> glyphIDs, const SkPoint* pos) {
     if (!glyphIDs.empty()) {
         this->initialize(glyphIDs.size());
-        this->simplifyDrawPosText(paint, SkRunFont{paint}, glyphIDs, pos);
+        this->simplifyDrawPosText(font, glyphIDs, pos);
         this->makeGlyphRunList(paint, nullptr, SkPoint::Make(0, 0));
     }
 }
@@ -308,16 +258,12 @@
 }
 
 SkSpan<const SkGlyphID> SkGlyphRunBuilder::textToGlyphIDs(
-        const SkPaint& paint, const void* bytes, size_t byteLength) {
-    SkTextEncoding encoding = (SkTextEncoding)paint.getTextEncoding();
+        const SkFont& font, const void* bytes, size_t byteLength, SkTextEncoding encoding) {
     if (encoding != kGlyphID_SkTextEncoding) {
-        auto tfEncoding = convert_encoding(encoding);
-        int utfSize = SkUTFN_CountUnichars(tfEncoding, bytes, byteLength);
-        if (utfSize > 0) {
-            size_t runSize = SkTo<size_t>(utfSize);
-            fScratchGlyphIDs.resize(runSize);
-            auto typeface = SkPaintPriv::GetTypefaceOrDefault(paint);
-            typeface->charsToGlyphs(bytes, tfEncoding, fScratchGlyphIDs.data(), runSize);
+        int count = font.countText(bytes, byteLength, encoding);
+        if (count > 0) {
+            fScratchGlyphIDs.resize(count);
+            font.textToGlyphs(bytes, byteLength, encoding, fScratchGlyphIDs.data(), count);
             return SkSpan<const SkGlyphID>{fScratchGlyphIDs};
         } else {
             return SkSpan<const SkGlyphID>();
@@ -328,8 +274,7 @@
 }
 
 void SkGlyphRunBuilder::makeGlyphRun(
-        const SkPaint& basePaint,
-        const SkRunFont& runFont,
+        const SkFont& font,
         SkSpan<const SkGlyphID> glyphIDs,
         SkSpan<const SkPoint> positions,
         SkSpan<const char> text,
@@ -338,8 +283,7 @@
     // Ignore empty runs.
     if (!glyphIDs.empty()) {
         fGlyphRunListStorage.emplace_back(
-                basePaint,
-                runFont,
+                font,
                 positions,
                 glyphIDs,
                 text,
@@ -356,20 +300,17 @@
 }
 
 void SkGlyphRunBuilder::simplifyDrawText(
-        const SkPaint& paint, const SkRunFont& runFont, SkSpan<const SkGlyphID> glyphIDs,
+        const SkFont& font, SkSpan<const SkGlyphID> glyphIDs,
         SkPoint origin, SkPoint* positions,
         SkSpan<const char> text, SkSpan<const uint32_t> clusters) {
     SkASSERT(!glyphIDs.empty());
 
     auto runSize = glyphIDs.size();
 
-    SkPaint runPaint{paint, runFont};
-
     if (!glyphIDs.empty()) {
         fScratchAdvances.resize(runSize);
         {
-            const SkFont font = SkFont::LEGACY_ExtractFromPaint(runPaint);
-            auto cache = SkStrikeCache::FindOrCreateStrikeWithNoDeviceExclusive(font, runPaint);
+            auto cache = SkStrikeCache::FindOrCreateStrikeWithNoDeviceExclusive(font);
             cache->getAdvances(glyphIDs, fScratchAdvances.data());
         }
 
@@ -381,8 +322,7 @@
         }
 
         this->makeGlyphRun(
-                paint,
-                runFont,
+                font,
                 glyphIDs,
                 SkSpan<const SkPoint>{positions, runSize},
                 text,
@@ -391,7 +331,7 @@
 }
 
 void SkGlyphRunBuilder::simplifyDrawPosTextH(
-        const SkPaint& paint, const SkRunFont& runFont, SkSpan<const SkGlyphID> glyphIDs,
+        const SkFont& font, SkSpan<const SkGlyphID> glyphIDs,
         const SkScalar* xpos, SkScalar constY, SkPoint* positions,
         SkSpan<const char> text, SkSpan<const uint32_t> clusters) {
 
@@ -400,18 +340,17 @@
         *posCursor++ = SkPoint::Make(x, constY);
     }
 
-    simplifyDrawPosText(paint, runFont, glyphIDs, positions, text, clusters);
+    simplifyDrawPosText(font, glyphIDs, positions, text, clusters);
 }
 
 void SkGlyphRunBuilder::simplifyDrawPosText(
-        const SkPaint& paint, const SkRunFont& runFont, SkSpan<const SkGlyphID> glyphIDs,
+        const SkFont& font, SkSpan<const SkGlyphID> glyphIDs,
         const SkPoint* pos,
         SkSpan<const char> text, SkSpan<const uint32_t> clusters) {
     auto runSize = glyphIDs.size();
 
     this->makeGlyphRun(
-            paint,
-            runFont,
+            font,
             glyphIDs,
             SkSpan<const SkPoint>{pos, runSize},
             text,
diff --git a/src/core/SkGlyphRun.h b/src/core/SkGlyphRun.h
index 703b1d0..eb6b664 100644
--- a/src/core/SkGlyphRun.h
+++ b/src/core/SkGlyphRun.h
@@ -11,36 +11,32 @@
 #include <functional>
 #include <vector>
 
+#include "SkFont.h"
 #include "SkPaint.h"
 #include "SkPoint.h"
 #include "SkSpan.h"
 #include "SkTemplates.h"
 #include "SkTypes.h"
 
+class SkBaseDevice;
 class SkGlyph;
-class SkRunFont;
 
 class SkGlyphRun {
 public:
     SkGlyphRun() = default;
-    SkGlyphRun(const SkPaint& basePaint,
-               const SkRunFont& runFont,
+    SkGlyphRun(const SkFont& font,
                SkSpan<const SkPoint> positions,
                SkSpan<const SkGlyphID> glyphIDs,
                SkSpan<const char> text,
                SkSpan<const uint32_t> clusters);
-    SkGlyphRun(const SkGlyphRun& glyphRun, const SkPaint& paint);
-
-    // A function that turns an SkGlyphRun into an SkGlyphRun for each glyph.
-    using PerGlyph = std::function<void (const SkGlyphRun&)>;
-    void eachGlyphToGlyphRun(PerGlyph perGlyph);
+    SkGlyphRun(const SkGlyphRun& glyphRun, const SkFont& font);
 
     void filloutGlyphsAndPositions(SkGlyphID* glyphIDs, SkPoint* positions);
 
     size_t runSize() const { return fGlyphIDs.size(); }
     SkSpan<const SkPoint> positions() const { return fPositions.toConst(); }
     SkSpan<const SkGlyphID> glyphsIDs() const { return fGlyphIDs; }
-    const SkPaint& paint() const { return fRunPaint; }
+    const SkFont& font() const { return fFont; }
     SkSpan<const uint32_t> clusters() const { return fClusters; }
     SkSpan<const char> text() const { return fText; }
 
@@ -54,7 +50,7 @@
     // Original clusters from SkTextBlob if present. Will be empty if not present.
     const SkSpan<const uint32_t>   fClusters;
     // Paint for this run modified to have glyph encoding and left alignment.
-    SkPaint fRunPaint;
+    SkFont fFont;
 };
 
 class SkGlyphRunList {
@@ -74,7 +70,7 @@
             SkPoint origin,
             SkSpan<const SkGlyphRun> glyphRunList);
 
-    SkGlyphRunList(const SkGlyphRun& glyphRun);
+    SkGlyphRunList(const SkGlyphRun& glyphRun, const SkPaint& paint);
 
     uint64_t uniqueID() const;
     bool anyRunsLCD() const;
@@ -90,6 +86,7 @@
         }
         return glyphCount;
     }
+    bool allFontsFinite() const;
 
     SkPoint origin() const { return fOrigin; }
     const SkPaint& paint() const { return *fOriginalPaint; }
@@ -116,28 +113,26 @@
 
 class SkGlyphRunBuilder {
 public:
-    void drawTextAtOrigin(const SkPaint& paint, const void* bytes, size_t byteLength);
-    void drawText(
-            const SkPaint& paint, const void* bytes, size_t byteLength, SkPoint origin);
-    void drawPosTextH(
-            const SkPaint& paint, const void* bytes, size_t byteLength,
-            const SkScalar* xpos, SkScalar constY);
-    void drawPosText(
-            const SkPaint& paint, const void* bytes, size_t byteLength, const SkPoint* pos);
-    void drawTextBlob(const SkPaint& paint, const SkTextBlob& blob, SkPoint origin);
-    void drawGlyphPos(
-            const SkPaint& paint, SkSpan<const SkGlyphID> glyphIDs, const SkPoint* pos);
+    void drawTextUTF8(
+        const SkPaint& paint, const SkFont&, const void* bytes, size_t byteLength, SkPoint origin);
+    void drawGlyphsWithPositions(
+            const SkPaint&, const SkFont&, SkSpan<const SkGlyphID> glyphIDs, const SkPoint* pos);
+    void drawTextBlob(const SkPaint& paint, const SkTextBlob& blob, SkPoint origin, SkBaseDevice*);
 
     const SkGlyphRunList& useGlyphRunList();
 
+    bool empty() const { return fGlyphRunListStorage.size() == 0; }
+
+    static void DispatchBlob(SkGlyphRunBuilder* builder, const SkPaint& paint,
+                             const SkTextBlob& blob, SkPoint origin, SkBaseDevice* device);
+
 private:
     void initialize(size_t totalRunSize);
     SkSpan<const SkGlyphID> textToGlyphIDs(
-            const SkPaint& paint, const void* bytes, size_t byteLength);
+            const SkFont& font, const void* bytes, size_t byteLength, SkTextEncoding);
 
     void makeGlyphRun(
-            const SkPaint& basePaint,
-            const SkRunFont& runFont,
+            const SkFont& font,
             SkSpan<const SkGlyphID> glyphIDs,
             SkSpan<const SkPoint> positions,
             SkSpan<const char> text,
@@ -146,17 +141,17 @@
     void makeGlyphRunList(const SkPaint& paint, const SkTextBlob* blob, SkPoint origin);
 
     void simplifyDrawText(
-            const SkPaint& paint, const SkRunFont& runFont, SkSpan<const SkGlyphID> glyphIDs,
+            const SkFont& font, SkSpan<const SkGlyphID> glyphIDs,
             SkPoint origin, SkPoint* positions,
             SkSpan<const char> text = SkSpan<const char>{},
             SkSpan<const uint32_t> clusters = SkSpan<const uint32_t>{});
     void simplifyDrawPosTextH(
-            const SkPaint& paint, const SkRunFont& runFont, SkSpan<const SkGlyphID> glyphIDs,
+            const SkFont& font, SkSpan<const SkGlyphID> glyphIDs,
             const SkScalar* xpos, SkScalar constY, SkPoint* positions,
             SkSpan<const char> text = SkSpan<const char>{},
             SkSpan<const uint32_t> clusters = SkSpan<const uint32_t>{});
     void simplifyDrawPosText(
-            const SkPaint& paint, const SkRunFont& runFont, SkSpan<const SkGlyphID> glyphIDs,
+            const SkFont& font, SkSpan<const SkGlyphID> glyphIDs,
             const SkPoint* pos,
             SkSpan<const char> text = SkSpan<const char>{},
             SkSpan<const uint32_t> clusters = SkSpan<const uint32_t>{});
diff --git a/src/core/SkGlyphRunPainter.cpp b/src/core/SkGlyphRunPainter.cpp
index 00a4f6c..ce24fa0 100644
--- a/src/core/SkGlyphRunPainter.cpp
+++ b/src/core/SkGlyphRunPainter.cpp
@@ -19,6 +19,7 @@
 #include "SkDevice.h"
 #include "SkDistanceFieldGen.h"
 #include "SkDraw.h"
+#include "SkFontPriv.h"
 #include "SkGlyphCache.h"
 #include "SkMaskFilter.h"
 #include "SkPaintPriv.h"
@@ -101,7 +102,8 @@
 
 #endif
 
-bool SkGlyphRunListPainter::ShouldDrawAsPath(const SkPaint& paint, const SkMatrix& matrix) {
+bool SkGlyphRunListPainter::ShouldDrawAsPath(
+        const SkPaint& paint, const SkFont& font, const SkMatrix& matrix) {
     // hairline glyphs are fast enough so we don't need to cache them
     if (SkPaint::kStroke_Style == paint.getStyle() && 0 == paint.getStrokeWidth()) {
         return true;
@@ -112,9 +114,7 @@
         return true;
     }
 
-    SkMatrix textM;
-    SkPaintPriv::MakeTextMatrix(&textM, paint);
-    return SkPaint::TooBigToUseCache(matrix, textM, 1024);
+    return SkPaint::TooBigToUseCache(matrix, SkFontPriv::MakeTextMatrix(font), 1024);
 }
 
 void SkGlyphRunListPainter::ensureBitmapBuffers(size_t runSize) {
@@ -159,28 +159,29 @@
 void SkGlyphRunListPainter::drawForBitmapDevice(
         const SkGlyphRunList& glyphRunList, const SkMatrix& deviceMatrix,
         const BitmapDevicePainter* bitmapDevice) {
+    const SkPaint& runPaint = glyphRunList.paint();
+    // The bitmap blitters can only draw lcd text to a N32 bitmap in srcOver. Otherwise,
+    // convert the lcd text into A8 text. The props communicates this to the scaler.
+    auto& props = (kN32_SkColorType == fColorType && runPaint.isSrcOver())
+                  ? fDeviceProps
+                  : fBitmapFallbackProps;
 
     SkPoint origin = glyphRunList.origin();
     for (auto& glyphRun : glyphRunList) {
-        // The bitmap blitters can only draw lcd text to a N32 bitmap in srcOver. Otherwise,
-        // convert the lcd text into A8 text. The props communicates this to the scaler.
-        auto& props = (kN32_SkColorType == fColorType && glyphRun.paint().isSrcOver())
-                      ? fDeviceProps
-                      : fBitmapFallbackProps;
-
-        const SkPaint& paint = glyphRun.paint();
+        const SkFont& runFont = glyphRun.font();
         auto runSize = glyphRun.runSize();
         this->ensureBitmapBuffers(runSize);
 
-        if (ShouldDrawAsPath(paint, deviceMatrix)) {
+        if (ShouldDrawAsPath(runPaint, runFont, deviceMatrix)) {
             SkMatrix::MakeTrans(origin.x(), origin.y()).mapPoints(
                     fPositions, glyphRun.positions().data(), runSize);
             // setup our std pathPaint, in hopes of getting hits in the cache
-            SkPaint pathPaint(paint);
-            SkScalar textScale = pathPaint.setupForAsPaths();
+            SkPaint pathPaint(runPaint);
+            SkFont  pathFont{runFont};
+            SkScalar textScale = pathFont.setupForAsPaths(&pathPaint);
 
             auto pathCache = SkStrikeCache::FindOrCreateStrikeExclusive(
-                                SkFont::LEGACY_ExtractFromPaint(pathPaint), pathPaint, props,
+                                pathFont, pathPaint, props,
                                 fScalerContextFlags, SkMatrix::I());
 
             SkTDArray<PathAndPos> pathsAndPositions;
@@ -199,13 +200,17 @@
                 }
             }
 
+            // The paint we draw paths with must have the same anti-aliasing state as the runFont
+            // allowing the paths to have the same edging as the glyph masks.
+            pathPaint = runPaint;
+            pathPaint.setAntiAlias(runFont.hasSomeAntiAliasing());
+
             bitmapDevice->paintPaths(
                     SkSpan<const PathAndPos>{pathsAndPositions.begin(), pathsAndPositions.size()},
-                    textScale,
-                    paint);
+                    textScale, pathPaint);
         } else {
             auto cache = SkStrikeCache::FindOrCreateStrikeExclusive(
-                                        SkFont::LEGACY_ExtractFromPaint(paint), paint, props,
+                                        runFont, runPaint, props,
                                         fScalerContextFlags, deviceMatrix);
 
             // Add rounding and origin.
@@ -228,7 +233,7 @@
                     }
                 }
             }
-            bitmapDevice->paintMasks(SkSpan<const SkMask>{masks.begin(), masks.size()}, paint);
+            bitmapDevice->paintMasks(SkSpan<const SkMask>{masks.begin(), masks.size()}, runPaint);
         }
     }
 }
@@ -255,7 +260,7 @@
 //   scale factor is used to increase the size of the destination rectangles. The destination
 //   rectangles are then scaled, rotated, etc. by the GPU using the view matrix.
 void SkGlyphRunListPainter::processARGBFallback(
-        SkScalar maxGlyphDimension, const SkPaint& runPaint,
+        SkScalar maxGlyphDimension, const SkPaint& runPaint, const SkFont& runFont,
         const SkMatrix& viewMatrix, SkScalar textScale, ARGBFallback argbFallback) {
     SkASSERT(!fARGBGlyphsIDs.empty());
 
@@ -284,7 +289,13 @@
         }
 
         auto positions = SkSpan<const SkPoint>{fARGBPositions};
-        argbFallback(runPaint, glyphIDs, positions, SK_Scalar1, viewMatrix, kTransformDone);
+        argbFallback(runPaint,
+                     runFont,
+                     glyphIDs,
+                     positions,
+                     SK_Scalar1,
+                     viewMatrix,
+                     kTransformDone);
 
     } else {
         // If the matrix is complicated or if scaling is used to fit the glyphs in the cache,
@@ -293,29 +304,34 @@
         // Subtract 2 to account for the bilerp pad around the glyph
         SkScalar maxAtlasDimension = SkGlyphCacheCommon::kSkSideTooBigForAtlas - 2;
 
-        SkScalar runPaintTextSize = runPaint.getTextSize();
+        SkScalar runFontTextSize = runFont.getSize();
 
         // Scale the text size down so the long side of all the glyphs will fit in the atlas.
         SkScalar reducedTextSize =
-                (maxAtlasDimension / conservativeMaxGlyphDimension) * runPaintTextSize;
+                (maxAtlasDimension / conservativeMaxGlyphDimension) * runFontTextSize;
 
         // If there's a glyph in the font that's particularly large, it's possible
         // that fScaledFallbackTextSize may end up minimizing too much. We'd rather skip
         // that glyph than make the others blurry, so we set a minimum size of half the
         // maximum text size to avoid this case.
         SkScalar fallbackTextSize =
-                SkScalarFloorToScalar(std::max(reducedTextSize, 0.5f * runPaintTextSize));
+                SkScalarFloorToScalar(std::max(reducedTextSize, 0.5f * runFontTextSize));
 
         // Don't allow the text size to get too big. This will also improve glyph cache hit rate
         // for larger text sizes.
         fallbackTextSize = std::min(fallbackTextSize, 256.0f);
 
-        SkPaint fallbackPaint{runPaint};
-        fallbackPaint.setTextSize(fallbackTextSize);
-        SkScalar fallbackTextScale = runPaintTextSize / fallbackTextSize;
+        SkFont fallbackFont{runFont};
+        fallbackFont.setSize(fallbackTextSize);
+        SkScalar fallbackTextScale = runFontTextSize / fallbackTextSize;
         auto positions = SkSpan<const SkPoint>{fARGBPositions};
-        argbFallback(
-                fallbackPaint, glyphIDs, positions, fallbackTextScale, SkMatrix::I(), kDoTransform);
+        argbFallback(runPaint,
+                     fallbackFont,
+                     glyphIDs,
+                     positions,
+                     fallbackTextScale,
+                     SkMatrix::I(),
+                     kDoTransform);
     }
 }
 
@@ -324,7 +340,7 @@
 template <typename PerEmptyT, typename PerPathT>
 void SkGlyphRunListPainter::drawGlyphRunAsPathWithARGBFallback(
         SkGlyphCacheInterface* pathCache, const SkGlyphRun& glyphRun,
-        SkPoint origin, const SkMatrix& viewMatrix, SkScalar textScale,
+        SkPoint origin, const SkPaint& runPaint, const SkMatrix& viewMatrix, SkScalar textScale,
         PerEmptyT&& perEmpty, PerPathT&& perPath, ARGBFallback&& argbFallback) {
     fARGBGlyphsIDs.clear();
     fARGBPositions.clear();
@@ -352,7 +368,7 @@
 
     if (!fARGBGlyphsIDs.empty()) {
         this->processARGBFallback(
-                maxFallbackDimension, glyphRun.paint(), viewMatrix, textScale,
+                maxFallbackDimension, runPaint, glyphRun.font(), viewMatrix, textScale,
                 std::move(argbFallback));
 
     }
@@ -400,7 +416,7 @@
 template <typename PerEmptyT, typename PerSDFT, typename PerPathT>
 void SkGlyphRunListPainter::drawGlyphRunAsSDFWithARGBFallback(
         SkGlyphCacheInterface* cache, const SkGlyphRun& glyphRun,
-        SkPoint origin, const SkMatrix& viewMatrix, SkScalar textScale,
+        SkPoint origin, const SkPaint& runPaint, const SkMatrix& viewMatrix, SkScalar textScale,
         PerEmptyT&& perEmpty, PerSDFT&& perSDF, PerPathT&& perPath, ARGBFallback&& argbFallback) {
     fARGBGlyphsIDs.clear();
     fARGBPositions.clear();
@@ -438,7 +454,7 @@
 
     if (!fARGBGlyphsIDs.empty()) {
         this->processARGBFallback(
-                maxFallbackDimension, glyphRun.paint(), viewMatrix, textScale,
+                maxFallbackDimension, runPaint, glyphRun.font(), viewMatrix, textScale,
                 std::move(argbFallback));
     }
 }
@@ -465,7 +481,9 @@
 
     // Get the first paint to use as the key paint.
     const SkPaint& listPaint = glyphRunList.paint();
+
     SkPMColor4f filteredColor = generate_filtered_color(listPaint, target->colorSpaceInfo());
+    GrColor color = generate_filtered_color(listPaint, target->colorSpaceInfo()).toBytes_RGBA();
 
     // If we have been abandoned, then don't draw
     if (context->abandoned()) {
@@ -514,35 +532,33 @@
             // TODO we could probably get away reuse most of the time if the pointer is unique,
             // but we'd have to clear the subrun information
             textBlobCache->remove(cacheBlob.get());
-            cacheBlob = textBlobCache->makeCachedBlob(glyphRunList, key, blurRec, listPaint);
+            cacheBlob = textBlobCache->makeCachedBlob(glyphRunList, key, blurRec, listPaint, color);
             cacheBlob->generateFromGlyphRunList(
                     glyphCache, *context->contextPriv().caps()->shaderCaps(), fOptions,
-                    listPaint, filteredColor, scalerContextFlags, viewMatrix, props,
+                    listPaint, scalerContextFlags, viewMatrix, props,
                     glyphRunList, target->glyphPainter());
         } else {
             textBlobCache->makeMRU(cacheBlob.get());
 
             if (CACHE_SANITY_CHECK) {
-                int glyphCount = glyphRunList.totalGlyphCount();
-                int runCount = glyphRunList.runCount();
-                sk_sp<GrTextBlob> sanityBlob(textBlobCache->makeBlob(glyphCount, runCount));
+                sk_sp<GrTextBlob> sanityBlob(textBlobCache->makeBlob(glyphRunList, color));
                 sanityBlob->setupKey(key, blurRec, listPaint);
                 cacheBlob->generateFromGlyphRunList(
                         glyphCache, *context->contextPriv().caps()->shaderCaps(), fOptions,
-                        listPaint, filteredColor, scalerContextFlags, viewMatrix, props, glyphRunList,
+                        listPaint, scalerContextFlags, viewMatrix, props, glyphRunList,
                         target->glyphPainter());
                 GrTextBlob::AssertEqual(*sanityBlob, *cacheBlob);
             }
         }
     } else {
         if (canCache) {
-            cacheBlob = textBlobCache->makeCachedBlob(glyphRunList, key, blurRec, listPaint);
+            cacheBlob = textBlobCache->makeCachedBlob(glyphRunList, key, blurRec, listPaint, color);
         } else {
-            cacheBlob = textBlobCache->makeBlob(glyphRunList);
+            cacheBlob = textBlobCache->makeBlob(glyphRunList, color);
         }
         cacheBlob->generateFromGlyphRunList(
                 glyphCache, *context->contextPriv().caps()->shaderCaps(), fOptions, listPaint,
-                filteredColor, scalerContextFlags, viewMatrix, props, glyphRunList,
+                scalerContextFlags, viewMatrix, props, glyphRunList,
                 target->glyphPainter());
     }
 
@@ -550,10 +566,12 @@
                      clip, viewMatrix, origin.x(), origin.y());
 }
 
-void GrTextBlob::SubRun::appendGlyph(GrTextBlob* blob, GrGlyph* glyph, SkRect dstRect) {
+void GrTextBlob::SubRun::appendGlyph(GrGlyph* glyph, SkRect dstRect) {
 
     this->joinGlyphBounds(dstRect);
 
+    GrTextBlob* blob = fRun->fBlob;
+
     bool hasW = this->hasWCoord();
     // glyphs drawn in perspective must always have a w coord.
     SkASSERT(hasW || !blob->fInitialViewMatrix.hasPerspective());
@@ -589,106 +607,88 @@
     blob->fGlyphs[fGlyphEndIndex++] = glyph;
 }
 
-static SkRect rect_to_draw(
-        const SkGlyph& glyph, SkPoint origin, SkScalar textScale, bool isDFT) {
+void GrTextBlob::Run::switchSubRunIfNeededAndAppendGlyph(GrGlyph* glyph,
+                                                         const sk_sp<GrTextStrike>& strike,
+                                                         const SkRect& destRect,
+                                                         bool needsTransform) {
+    GrMaskFormat format = glyph->fMaskFormat;
 
-    SkScalar dx = SkIntToScalar(glyph.fLeft);
-    SkScalar dy = SkIntToScalar(glyph.fTop);
-    SkScalar width = SkIntToScalar(glyph.fWidth);
-    SkScalar height = SkIntToScalar(glyph.fHeight);
-
-    if (isDFT) {
-        dx += SK_DistanceFieldInset;
-        dy += SK_DistanceFieldInset;
-        width -= 2 * SK_DistanceFieldInset;
-        height -= 2 * SK_DistanceFieldInset;
+    SubRun* subRun = &fSubRunInfo.back();
+    if (fInitialized && subRun->maskFormat() != format) {
+        subRun = pushBackSubRun(fDescriptor, fColor);
+        subRun->setStrike(strike);
+    } else if (!fInitialized) {
+        subRun->setStrike(strike);
     }
 
-    dx *= textScale;
-    dy *= textScale;
-    width *= textScale;
-    height *= textScale;
-
-    return SkRect::MakeXYWH(origin.x() + dx, origin.y() + dy, width, height);
+    fInitialized = true;
+    subRun->setMaskFormat(format);
+    subRun->setNeedsTransform(needsTransform);
+    subRun->appendGlyph(glyph, destRect);
 }
 
-void GrTextBlob::Run::appendGlyph(GrTextBlob* blob,
-                                  const sk_sp<GrTextStrike>& strike,
-                                  const SkGlyph& skGlyph, GrGlyph::MaskStyle maskStyle,
-                                  SkPoint origin,
-                                  const SkPMColor4f& color4f, SkGlyphCache* skGlyphCache,
-                                  SkScalar textRatio, bool needsTransform) {
+void GrTextBlob::Run::appendDeviceSpaceGlyph(const sk_sp<GrTextStrike>& strike,
+                                             const SkGlyph& skGlyph, SkPoint origin) {
+    if (GrGlyph* glyph = strike->getGlyph(skGlyph)) {
 
-    GrGlyph::PackedID id = GrGlyph::Pack(skGlyph.getGlyphID(),
-                                         skGlyph.getSubXFixed(),
-                                         skGlyph.getSubYFixed(),
-                                         maskStyle);
-    GrGlyph* glyph = strike->getGlyph(skGlyph, id, skGlyphCache);
-    if (!glyph) {
-        return;
-    }
+        SkRect glyphRect = glyph->destRect(origin);
 
-    SkASSERT(skGlyph.fWidth == glyph->width());
-    SkASSERT(skGlyph.fHeight == glyph->height());
-
-    bool isDFT = maskStyle == GrGlyph::kDistance_MaskStyle;
-
-    SkRect glyphRect = rect_to_draw(skGlyph, origin, textRatio, isDFT);
-    if (!glyphRect.isEmpty()) {
-        // TODO4F: Preserve float colors
-        GrColor color = color4f.toBytes_RGBA();
-
-        GrMaskFormat format = glyph->fMaskFormat;
-
-        SubRun* subRun = &fSubRunInfo.back();
-        if (fInitialized && subRun->maskFormat() != format) {
-            subRun = pushBackSubRun(fDescriptor);
-            subRun->setStrike(strike);
-        } else if (!fInitialized) {
-            subRun->setStrike(strike);
+        if (!glyphRect.isEmpty()) {
+            this->switchSubRunIfNeededAndAppendGlyph(glyph, strike, glyphRect, false);
         }
-
-        fInitialized = true;
-        subRun->setMaskFormat(format);
-        subRun->setColor(color);
-        subRun->setNeedsTransform(needsTransform);
-
-        subRun->appendGlyph(blob, glyph, glyphRect);
     }
 }
 
+void GrTextBlob::Run::appendSourceSpaceGlyph(const sk_sp<GrTextStrike>& strike,
+                                             const SkGlyph& skGlyph,
+                                             SkPoint origin,
+                                             SkScalar textScale) {
+    if (GrGlyph* glyph = strike->getGlyph(skGlyph)) {
+
+        SkRect glyphRect = glyph->destRect(origin, textScale);
+
+        if (!glyphRect.isEmpty()) {
+            this->switchSubRunIfNeededAndAppendGlyph(glyph, strike, glyphRect, true);
+        }
+    }
+}
 
 void GrTextBlob::generateFromGlyphRunList(GrGlyphCache* glyphCache,
                                           const GrShaderCaps& shaderCaps,
                                           const GrTextContext::Options& options,
                                           const SkPaint& paint,
-                                          const SkPMColor4f& filteredColor,
                                           SkScalerContextFlags scalerContextFlags,
                                           const SkMatrix& viewMatrix,
                                           const SkSurfaceProps& props,
                                           const SkGlyphRunList& glyphRunList,
                                           SkGlyphRunListPainter* glyphPainter) {
     struct ARGBFallbackHelper {
-        void operator ()(const SkPaint& fallbackPaint, SkSpan<const SkGlyphID> glyphIDs,
-                    SkSpan<const SkPoint> positions, SkScalar textScale,
-                    const SkMatrix& glyphCacheMatrix,
-                    SkGlyphRunListPainter::NeedsTransform needsTransform) const {
+        void operator()(const SkPaint& fallbackPaint, const SkFont& fallbackFont,
+                        SkSpan<const SkGlyphID> glyphIDs,
+                        SkSpan<const SkPoint> positions, SkScalar textScale,
+                        const SkMatrix& glyphCacheMatrix,
+                        SkGlyphRunListPainter::NeedsTransform needsTransform) const {
             fBlob->setHasBitmap();
             fRun->setSubRunHasW(glyphCacheMatrix.hasPerspective());
             auto subRun = fRun->initARGBFallback();
-            SkExclusiveStrikePtr fallbackCache =
-                    fRun->setupCache(fallbackPaint, fProps, fScalerContextFlags, glyphCacheMatrix);
+            SkExclusiveStrikePtr fallbackCache = SkStrikeCache::FindOrCreateStrikeExclusive(
+                    fallbackFont, fallbackPaint, fProps, fScalerContextFlags, glyphCacheMatrix);
             sk_sp<GrTextStrike> strike = fGlyphCache->getStrike(fallbackCache.get());
+            fRun->setupFont(fallbackPaint, fallbackFont, fallbackCache->getDescriptor());
+
             SkASSERT(strike != nullptr);
             subRun->setStrike(strike);
             const SkPoint* glyphPos = positions.data();
-            for (auto glyphID : glyphIDs) {
-                const SkGlyph& glyph = fallbackCache->getGlyphIDMetrics(glyphID);
-                fRun->appendGlyph(fBlob, strike, glyph,
-                                 GrGlyph::kCoverage_MaskStyle,
-                                 *glyphPos, fFilteredColor,
-                                 fallbackCache.get(), textScale, needsTransform);
-                glyphPos++;
+            if (needsTransform == SkGlyphRunListPainter::kTransformDone) {
+                for (auto glyphID : glyphIDs) {
+                    const SkGlyph& glyph = fallbackCache->getGlyphIDMetrics(glyphID);
+                    fRun->appendDeviceSpaceGlyph(strike, glyph, *glyphPos++);
+                }
+            } else {
+                for (auto glyphID : glyphIDs) {
+                    const SkGlyph& glyph = fallbackCache->getGlyphIDMetrics(glyphID);
+                    fRun->appendSourceSpaceGlyph(strike, glyph, *glyphPos++, textScale);
+                }
             }
         }
 
@@ -697,21 +697,20 @@
         const SkSurfaceProps& fProps;
         const SkScalerContextFlags fScalerContextFlags;
         GrGlyphCache* const fGlyphCache;
-        SkPMColor4f fFilteredColor;
     };
 
     SkPoint origin = glyphRunList.origin();
-    this->initReusableBlob(
-            glyphRunList.paint().computeLuminanceColor(), viewMatrix, origin.x(), origin.y());
+    const SkPaint& runPaint = glyphRunList.paint();
+    this->initReusableBlob(runPaint.computeLuminanceColor(), viewMatrix, origin.x(), origin.y());
 
     for (const auto& glyphRun : glyphRunList) {
-        const SkPaint& runPaint = glyphRun.paint();
+        const SkFont& runFont = glyphRun.font();
+
         Run* run = this->pushBackRun();
 
-        run->setRunFontAntiAlias(runPaint.isAntiAlias());
+        run->setRunFontAntiAlias(runFont.hasSomeAntiAliasing());
 
-        if (GrTextContext::CanDrawAsDistanceFields(runPaint,
-                                   SkFont::LEGACY_ExtractFromPaint(runPaint), viewMatrix, props,
+        if (GrTextContext::CanDrawAsDistanceFields(runPaint, runFont, viewMatrix, props,
                                     shaderCaps.supportsDistanceFieldText(), options)) {
             bool hasWCoord = viewMatrix.hasPerspective()
                              || options.fDistanceFieldVerticesAlwaysHaveW;
@@ -719,27 +718,34 @@
             // Setup distance field runPaint and text ratio
             SkScalar textScale;
             SkPaint distanceFieldPaint{runPaint};
+            SkFont distanceFieldFont{runFont};
             SkScalerContextFlags flags;
-            GrTextContext::InitDistanceFieldPaint(this, &distanceFieldPaint, viewMatrix,
-                                                  options, &textScale, &flags);
+            GrTextContext::InitDistanceFieldPaint(runFont.getSize(),
+                                                  viewMatrix,
+                                                  options,
+                                                  this,
+                                                  &distanceFieldPaint,
+                                                  &distanceFieldFont,
+                                                  &textScale,
+                                                  &flags);
             this->setHasDistanceField();
-            run->setSubRunHasDistanceFields(runPaint.isLCDRenderText(),
-                                            runPaint.isAntiAlias(), hasWCoord);
+            run->setSubRunHasDistanceFields(
+                    runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias,
+                    runFont.hasSomeAntiAliasing(),
+                    hasWCoord);
 
             {
-                auto cache = run->setupCache(distanceFieldPaint, props, flags, SkMatrix::I());
-
+                SkExclusiveStrikePtr cache =SkStrikeCache::FindOrCreateStrikeExclusive(
+                        distanceFieldFont, distanceFieldPaint, props, flags, SkMatrix::I());
                 sk_sp<GrTextStrike> currStrike = glyphCache->getStrike(cache.get());
+                run->setupFont(distanceFieldPaint, distanceFieldFont, cache->getDescriptor());
 
                 auto perEmpty = [](const SkGlyph&, SkPoint) {};
 
                 auto perSDF =
-                    [this, run, &currStrike, &filteredColor, cache{cache.get()}, textScale]
+                    [run, &currStrike, textScale]
                     (const SkGlyph& glyph, SkPoint position) {
-                        run->appendGlyph(this, currStrike,
-                                    glyph, GrGlyph::kDistance_MaskStyle, position,
-                                    filteredColor,
-                                    cache, textScale, true);
+                        run->appendSourceSpaceGlyph(currStrike, glyph, position, textScale);
                     };
 
                 auto perPath =
@@ -752,30 +758,31 @@
                     };
 
                 ARGBFallbackHelper argbFallback{this, run, props, scalerContextFlags,
-                                                glyphCache, filteredColor};
+                                                glyphCache};
 
                 glyphPainter->drawGlyphRunAsSDFWithARGBFallback(
-                    cache.get(), glyphRun, origin, viewMatrix, textScale,
+                    cache.get(), glyphRun, origin, runPaint, viewMatrix, textScale,
                     std::move(perEmpty), std::move(perSDF), std::move(perPath),
                     std::move(argbFallback));
             }
 
-        } else if (SkDraw::ShouldDrawTextAsPaths(runPaint, viewMatrix)) {
+        } else if (SkGlyphRunListPainter::ShouldDrawAsPath(runPaint, runFont, viewMatrix)) {
             // The glyphs are big, so use paths to draw them.
 
             // Ensure the blob is set for bitmaptext
             this->setHasBitmap();
 
             // setup our std runPaint, in hopes of getting hits in the cache
-            SkPaint pathPaint(runPaint);
+            SkPaint pathPaint{runPaint};
+            SkFont pathFont{runFont};
+            SkScalar textScale = pathFont.setupForAsPaths(&pathPaint);
+
+            auto pathCache = SkStrikeCache::FindOrCreateStrikeExclusive(
+                                pathFont, pathPaint, props,
+                                scalerContextFlags, SkMatrix::I());
 
             auto perEmpty = [](const SkGlyph&, SkPoint) {};
 
-            SkScalar textScale = pathPaint.setupForAsPaths();
-            auto pathCache = SkStrikeCache::FindOrCreateStrikeExclusive(
-                                SkFont::LEGACY_ExtractFromPaint(pathPaint), pathPaint, props,
-                                scalerContextFlags, SkMatrix::I());
-
             // Given a glyph that is not ARGB, draw it.
             auto perPath = [textScale, run]
                            (const SkGlyph& glyph, SkPoint position) {
@@ -785,30 +792,28 @@
                 }
             };
 
-            ARGBFallbackHelper argbFallback{this, run, props, scalerContextFlags,
-                                            glyphCache, filteredColor};
+            ARGBFallbackHelper argbFallback{this, run, props, scalerContextFlags, glyphCache};
 
             glyphPainter->drawGlyphRunAsPathWithARGBFallback(
-                pathCache.get(), glyphRun, origin, viewMatrix, textScale,
+                pathCache.get(), glyphRun, origin, runPaint, viewMatrix, textScale,
                 std::move(perEmpty), std::move(perPath), std::move(argbFallback));
         } else {
             // Ensure the blob is set for bitmaptext
             this->setHasBitmap();
 
-            auto cache = run->setupCache(runPaint, props, scalerContextFlags, viewMatrix);
-
+            auto cache = SkStrikeCache::FindOrCreateStrikeExclusive(
+                    runFont, runPaint, props, scalerContextFlags, viewMatrix);
             sk_sp<GrTextStrike> currStrike = glyphCache->getStrike(cache.get());
+            run->setupFont(runPaint, runFont, cache->getDescriptor());
 
             auto perEmpty = [](const SkGlyph&, SkPoint) {};
 
             auto perGlyph =
-                [this, run, &currStrike, &filteredColor, cache{cache.get()}]
+                [run, &currStrike]
                 (const SkGlyph& glyph, SkPoint mappedPt) {
                     SkPoint pt{SkScalarFloorToScalar(mappedPt.fX),
                                SkScalarFloorToScalar(mappedPt.fY)};
-                    run->appendGlyph(this, currStrike,
-                                     glyph, GrGlyph::kCoverage_MaskStyle, pt,
-                                     filteredColor, cache, SK_Scalar1, false);
+                    run->appendDeviceSpaceGlyph(currStrike, glyph, pt);
                 };
 
             auto perPath =
@@ -837,6 +842,7 @@
                                                               GrTextContext* textContext,
                                                               GrRenderTargetContext* rtc,
                                                               const SkPaint& skPaint,
+                                                              const SkFont& font,
                                                               const SkMatrix& viewMatrix,
                                                               const char* text,
                                                               int x,
@@ -848,22 +854,22 @@
     size_t textLen = (int)strlen(text);
 
     SkPMColor4f filteredColor = generate_filtered_color(skPaint, rtc->colorSpaceInfo());
+    GrColor color = filteredColor.toBytes_RGBA();
 
     auto origin = SkPoint::Make(x, y);
     SkGlyphRunBuilder builder;
-    builder.drawText(skPaint, text, textLen, origin);
-
+    builder.drawTextUTF8(skPaint, font, text, textLen, origin);
 
     auto glyphRunList = builder.useGlyphRunList();
     sk_sp<GrTextBlob> blob;
     if (!glyphRunList.empty()) {
-        blob = context->contextPriv().getTextBlobCache()->makeBlob(glyphRunList);
+        blob = context->contextPriv().getTextBlobCache()->makeBlob(glyphRunList, color);
         // Use the text and textLen below, because we don't want to mess with the paint.
         SkScalerContextFlags scalerContextFlags =
                 ComputeScalerContextFlags(rtc->colorSpaceInfo());
         blob->generateFromGlyphRunList(
                 glyphCache, *context->contextPriv().caps()->shaderCaps(), textContext->fOptions,
-                skPaint, filteredColor, scalerContextFlags, viewMatrix, surfaceProps,
+                skPaint, scalerContextFlags, viewMatrix, surfaceProps,
                 glyphRunList, rtc->textTarget()->glyphPainter());
     }
 
@@ -877,33 +883,32 @@
 // -- SkTextBlobCacheDiffCanvas::TrackLayerDevice --------------------------------------------------
 
 void SkTextBlobCacheDiffCanvas::TrackLayerDevice::processGlyphRun(
-        const SkPoint& origin, const SkGlyphRun& glyphRun) {
+        const SkPoint& origin, const SkGlyphRun& glyphRun, const SkPaint& runPaint) {
     TRACE_EVENT0("skia", "SkTextBlobCacheDiffCanvas::processGlyphRun");
 
-    const SkPaint& runPaint = glyphRun.paint();
     const SkMatrix& runMatrix = this->ctm();
 
     // If the matrix has perspective, we fall back to using distance field text or paths.
 #if SK_SUPPORT_GPU
-    if (this->maybeProcessGlyphRunForDFT(glyphRun, runMatrix, origin)) {
+    if (this->maybeProcessGlyphRunForDFT(glyphRun, runMatrix, origin, runPaint)) {
         return;
     } else
 #endif
-    if (SkDraw::ShouldDrawTextAsPaths(runPaint, runMatrix)) {
-        this->processGlyphRunForPaths(glyphRun, runMatrix, origin);
+    if (SkGlyphRunListPainter::ShouldDrawAsPath(runPaint, glyphRun.font(), runMatrix)) {
+        this->processGlyphRunForPaths(glyphRun, runMatrix, origin, runPaint);
     } else {
-        this->processGlyphRunForMask(glyphRun, runMatrix, origin);
+        this->processGlyphRunForMask(glyphRun, runMatrix, origin, runPaint);
     }
 }
 
 void SkTextBlobCacheDiffCanvas::TrackLayerDevice::processGlyphRunForMask(
-        const SkGlyphRun& glyphRun, const SkMatrix& runMatrix, SkPoint origin) {
+        const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
+        SkPoint origin, const SkPaint& runPaint) {
     TRACE_EVENT0("skia", "SkTextBlobCacheDiffCanvas::processGlyphRunForMask");
-    const SkPaint& runPaint = glyphRun.paint();
 
     SkScalerContextEffects effects;
     auto* glyphCacheState = fStrikeServer->getOrCreateCache(
-            runPaint, this->surfaceProps(), runMatrix,
+            runPaint, glyphRun.font(), this->surfaceProps(), runMatrix,
             SkScalerContextFlags::kFakeGammaAndBoostContrast, &effects);
     SkASSERT(glyphCacheState);
 
@@ -929,16 +934,19 @@
 }
 
 struct ARGBHelper {
-    void operator () (const SkPaint& fallbackPaint, SkSpan<const SkGlyphID> glyphIDs,
-                      SkSpan<const SkPoint> positions, SkScalar textScale,
-                      const SkMatrix& glyphCacheMatrix,
-                      SkGlyphRunListPainter::NeedsTransform needsTransform) {
+    void operator()(const SkPaint& fallbackPaint,
+                    const SkFont& fallbackFont,
+                    SkSpan<const SkGlyphID> glyphIDs,
+                    SkSpan<const SkPoint> positions,
+                    SkScalar textScale,
+                    const SkMatrix& glyphCacheMatrix,
+                    SkGlyphRunListPainter::NeedsTransform needsTransform) {
         TRACE_EVENT0("skia", "argbFallback");
 
         SkScalerContextEffects effects;
         auto* fallbackCache =
                 fStrikeServer->getOrCreateCache(
-                        fallbackPaint, fSurfaceProps, fFallbackMatrix,
+                        fallbackPaint, fallbackFont, fSurfaceProps, fFallbackMatrix,
                         SkScalerContextFlags::kFakeGammaAndBoostContrast, &effects);
 
         for (auto glyphID : glyphIDs) {
@@ -951,17 +959,22 @@
     SkStrikeServer* const fStrikeServer;
 };
 
-void SkTextBlobCacheDiffCanvas::TrackLayerDevice::processGlyphRunForPaths(
-        const SkGlyphRun& glyphRun, const SkMatrix& runMatrix, SkPoint origin) {
-    TRACE_EVENT0("skia", "SkTextBlobCacheDiffCanvas::processGlyphRunForPaths");
-    const SkPaint& runPaint = glyphRun.paint();
-    SkPaint pathPaint{runPaint};
+SkScalar SkTextBlobCacheDiffCanvas::SetupForPath(SkPaint* paint, SkFont* font) {
+    return font->setupForAsPaths(paint);
+}
 
-    SkScalar textScale = pathPaint.setupForAsPaths();
+void SkTextBlobCacheDiffCanvas::TrackLayerDevice::processGlyphRunForPaths(
+        const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
+        SkPoint origin, const SkPaint& runPaint) {
+    TRACE_EVENT0("skia", "SkTextBlobCacheDiffCanvas::processGlyphRunForPaths");
+
+    SkPaint pathPaint{runPaint};
+    SkFont pathFont{glyphRun.font()};
+    SkScalar textScale = SetupForPath(&pathPaint, &pathFont);
 
     SkScalerContextEffects effects;
     auto* glyphCacheState = fStrikeServer->getOrCreateCache(
-            pathPaint, this->surfaceProps(), SkMatrix::I(),
+            pathPaint, pathFont, this->surfaceProps(), SkMatrix::I(),
             SkScalerContextFlags::kFakeGammaAndBoostContrast, &effects);
 
     auto perEmpty = [glyphCacheState] (const SkGlyph& glyph, SkPoint mappedPt) {
@@ -976,22 +989,23 @@
     ARGBHelper argbFallback{runMatrix, surfaceProps(), fStrikeServer};
 
     fPainter.drawGlyphRunAsPathWithARGBFallback(
-            glyphCacheState, glyphRun, origin, runMatrix, textScale,
+            glyphCacheState, glyphRun, origin, runPaint, runMatrix, textScale,
             std::move(perEmpty), std::move(perPath), std::move(argbFallback));
 }
 
 #if SK_SUPPORT_GPU
 bool SkTextBlobCacheDiffCanvas::TrackLayerDevice::maybeProcessGlyphRunForDFT(
-        const SkGlyphRun& glyphRun, const SkMatrix& runMatrix, SkPoint origin) {
+        const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
+        SkPoint origin, const SkPaint& runPaint) {
     TRACE_EVENT0("skia", "SkTextBlobCacheDiffCanvas::maybeProcessGlyphRunForDFT");
 
-    const SkPaint& runPaint = glyphRun.paint();
+    const SkFont& runFont = glyphRun.font();
 
     GrTextContext::Options options;
     options.fMinDistanceFieldFontSize = fSettings.fMinDistanceFieldFontSize;
     options.fMaxDistanceFieldFontSize = fSettings.fMaxDistanceFieldFontSize;
     GrTextContext::SanitizeOptions(&options);
-    if (!GrTextContext::CanDrawAsDistanceFields(runPaint, SkFont::LEGACY_ExtractFromPaint(runPaint),
+    if (!GrTextContext::CanDrawAsDistanceFields(runPaint, runFont,
                                                 runMatrix, this->surfaceProps(),
                                                 fSettings.fContextSupportsDistanceFieldText,
                                                 options)) {
@@ -999,12 +1013,19 @@
     }
 
     SkScalar textRatio;
-    SkPaint dfPaint(runPaint);
+    SkPaint dfPaint{runPaint};
+    SkFont dfFont{runFont};
     SkScalerContextFlags flags;
-    GrTextContext::InitDistanceFieldPaint(nullptr, &dfPaint, runMatrix, options, &textRatio,
+    GrTextContext::InitDistanceFieldPaint(runFont.getSize(),
+                                          runMatrix,
+                                          options,
+                                          nullptr,
+                                          &dfPaint,
+                                          &dfFont,
+                                          &textRatio,
                                           &flags);
     SkScalerContextEffects effects;
-    auto* sdfCache = fStrikeServer->getOrCreateCache(dfPaint, this->surfaceProps(),
+    auto* sdfCache = fStrikeServer->getOrCreateCache(dfPaint, dfFont, this->surfaceProps(),
                                                      SkMatrix::I(), flags, &effects);
 
     ARGBHelper argbFallback{runMatrix, surfaceProps(), fStrikeServer};
@@ -1024,7 +1045,7 @@
     };
 
     fPainter.drawGlyphRunAsSDFWithARGBFallback(
-            sdfCache, glyphRun, origin, runMatrix, textRatio,
+            sdfCache, glyphRun, origin, runPaint, runMatrix, textRatio,
             std::move(perEmpty), std::move(perSDF), std::move(perPath),
             std::move(argbFallback));
 
diff --git a/src/core/SkGlyphRunPainter.h b/src/core/SkGlyphRunPainter.h
index 77c1e08..7a464f8 100644
--- a/src/core/SkGlyphRunPainter.h
+++ b/src/core/SkGlyphRunPainter.h
@@ -82,6 +82,7 @@
 
     using ARGBFallback =
     std::function<void(const SkPaint& fallbackPaint, // The run paint maybe with a new text size
+                       const SkFont& fallbackFont,
                        SkSpan<const SkGlyphID> fallbackGlyphIDs, // Colored glyphs
                        SkSpan<const SkPoint> fallbackPositions,  // Positions of above glyphs
                        SkScalar fallbackTextScale,               // Scale factor for glyph
@@ -97,21 +98,23 @@
     template <typename PerEmptyT, typename PerPath>
     void drawGlyphRunAsPathWithARGBFallback(
             SkGlyphCacheInterface* cache, const SkGlyphRun& glyphRun,
-            SkPoint origin, const SkMatrix& viewMatrix, SkScalar textScale,
+            SkPoint origin, const SkPaint& paint, const SkMatrix& viewMatrix, SkScalar textScale,
             PerEmptyT&& perEmpty, PerPath&& perPath, ARGBFallback&& fallbackARGB);
 
     template <typename PerEmptyT, typename PerSDFT, typename PerPathT>
     void drawGlyphRunAsSDFWithARGBFallback(
             SkGlyphCacheInterface* cache, const SkGlyphRun& glyphRun,
-            SkPoint origin, const SkMatrix& viewMatrix, SkScalar textRatio,
+            SkPoint origin, const SkPaint& runPaint, const SkMatrix& viewMatrix, SkScalar textRatio,
             PerEmptyT&& perEmpty, PerSDFT&& perSDF, PerPathT&& perPath, ARGBFallback&& perFallback);
 
+    // TODO: Make this the canonical check for Skia.
+    static bool ShouldDrawAsPath(const SkPaint& paint, const SkFont& font, const SkMatrix& matrix);
+
 private:
-    static bool ShouldDrawAsPath(const SkPaint& paint, const SkMatrix& matrix);
     void ensureBitmapBuffers(size_t runSize);
 
     void processARGBFallback(
-            SkScalar maxGlyphDimension, const SkPaint& runPaint,
+            SkScalar maxGlyphDimension, const SkPaint& fallbackPaint, const SkFont& fallbackFont,
             const SkMatrix& viewMatrix, SkScalar textScale, ARGBFallback argbFallback);
 
     // The props as on the actual device.
diff --git a/src/core/SkICC.cpp b/src/core/SkICC.cpp
index b27ee10..d1db49e 100644
--- a/src/core/SkICC.cpp
+++ b/src/core/SkICC.cpp
@@ -206,14 +206,14 @@
 }
 
 static bool nearly_equal(const SkColorSpaceTransferFn& u,
-                         const SkColorSpaceTransferFn& v) {
-    return nearly_equal(u.fG, v.fG)
-        && nearly_equal(u.fA, v.fA)
-        && nearly_equal(u.fB, v.fB)
-        && nearly_equal(u.fC, v.fC)
-        && nearly_equal(u.fD, v.fD)
-        && nearly_equal(u.fE, v.fE)
-        && nearly_equal(u.fF, v.fF);
+                         const skcms_TransferFunction& v) {
+    return nearly_equal(u.fG, v.g)
+        && nearly_equal(u.fA, v.a)
+        && nearly_equal(u.fB, v.b)
+        && nearly_equal(u.fC, v.c)
+        && nearly_equal(u.fD, v.d)
+        && nearly_equal(u.fE, v.e)
+        && nearly_equal(u.fF, v.f);
 }
 
 static bool nearly_equal(const float u[9], const float v[9]) {
@@ -228,23 +228,23 @@
 // Return nullptr if the color profile doen't have a special name.
 const char* get_color_profile_description(const SkColorSpaceTransferFn& fn,
                                           const float toXYZD50[9]) {
-    bool srgb_xfer = nearly_equal(fn, gSRGB_TransferFn);
-    bool srgb_gamut = nearly_equal(toXYZD50, gSRGB_toXYZD50);
+    bool srgb_xfer = nearly_equal(fn, SkNamedTransferFn::kSRGB);
+    bool srgb_gamut = nearly_equal(toXYZD50, &SkNamedGamut::kSRGB.vals[0][0]);
     if (srgb_xfer && srgb_gamut) {
         return "sRGB";
     }
-    bool line_xfer = nearly_equal(fn, gLinear_TransferFn);
+    bool line_xfer = nearly_equal(fn, SkNamedTransferFn::kLinear);
     if (line_xfer && srgb_gamut) {
         return "Linear Transfer with sRGB Gamut";
     }
-    bool twoDotTwo = nearly_equal(fn, g2Dot2_TransferFn);
+    bool twoDotTwo = nearly_equal(fn, SkNamedTransferFn::k2Dot2);
     if (twoDotTwo && srgb_gamut) {
         return "2.2 Transfer with sRGB Gamut";
     }
-    if (twoDotTwo && nearly_equal(toXYZD50, gAdobeRGB_toXYZD50)) {
+    if (twoDotTwo && nearly_equal(toXYZD50, &SkNamedGamut::kAdobeRGB.vals[0][0])) {
         return "AdobeRGB";
     }
-    bool dcip3_gamut = nearly_equal(toXYZD50, gDCIP3_toXYZD50);
+    bool dcip3_gamut = nearly_equal(toXYZD50, &SkNamedGamut::kDCIP3.vals[0][0]);
     if (srgb_xfer || line_xfer) {
         if (srgb_xfer && dcip3_gamut) {
             return "sRGB Transfer with DCI-P3 Gamut";
@@ -252,7 +252,7 @@
         if (line_xfer && dcip3_gamut) {
             return "Linear Transfer with DCI-P3 Gamut";
         }
-        bool rec2020 = nearly_equal(toXYZD50, gRec2020_toXYZD50);
+        bool rec2020 = nearly_equal(toXYZD50, &SkNamedGamut::kRec2020.vals[0][0]);
         if (srgb_xfer && rec2020) {
             return "sRGB Transfer with Rec-BT-2020 Gamut";
         }
@@ -260,9 +260,6 @@
             return "Linear Transfer with Rec-BT-2020 Gamut";
         }
     }
-    if (dcip3_gamut && nearly_equal(fn, gDCIP3_TransferFn)) {
-        return "DCI-P3";
-    }
     return nullptr;
 }
 
diff --git a/src/core/SkImageFilter.cpp b/src/core/SkImageFilter.cpp
index 710e1ff..4de7124 100644
--- a/src/core/SkImageFilter.cpp
+++ b/src/core/SkImageFilter.cpp
@@ -27,6 +27,7 @@
 #include "GrTextureProxy.h"
 #include "SkGr.h"
 #endif
+#include <atomic>
 
 void SkImageFilter::CropRect::applyTo(const SkIRect& imageBounds,
                                       const SkMatrix& ctm,
@@ -69,13 +70,12 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 static int32_t next_image_filter_unique_id() {
-    static int32_t gImageFilterUniqueID;
+    static std::atomic<int32_t> nextID{1};
 
-    // Never return 0.
     int32_t id;
     do {
-        id = sk_atomic_inc(&gImageFilterUniqueID) + 1;
-    } while (0 == id);
+        id = nextID++;
+    } while (id == 0);
     return id;
 }
 
diff --git a/src/core/SkLiteDL.cpp b/src/core/SkLiteDL.cpp
index ea5e49b..831544c 100644
--- a/src/core/SkLiteDL.cpp
+++ b/src/core/SkLiteDL.cpp
@@ -8,6 +8,7 @@
 #include "SkLiteDL.h"
 #include <algorithm>
 #include "SkCanvas.h"
+#include "SkCanvasPriv.h"
 #include "SkData.h"
 #include "SkDrawShadowInfo.h"
 #include "SkImage.h"
@@ -48,14 +49,13 @@
 
 namespace {
 #define TYPES(M)                                                                       \
-    M(Flush) M(Save) M(Restore) M(SaveLayer)                                           \
+    M(Flush) M(Save) M(Restore) M(SaveLayer) M(SaveBehind)                             \
     M(Concat) M(SetMatrix) M(Translate)                                                \
     M(ClipPath) M(ClipRect) M(ClipRRect) M(ClipRegion)                                 \
     M(DrawPaint) M(DrawPath) M(DrawRect) M(DrawRegion) M(DrawOval) M(DrawArc)          \
     M(DrawRRect) M(DrawDRRect) M(DrawAnnotation) M(DrawDrawable) M(DrawPicture)        \
     M(DrawImage) M(DrawImageNine) M(DrawImageRect) M(DrawImageLattice) M(DrawImageSet) \
-    M(DrawText) M(DrawPosText) M(DrawPosTextH)                                         \
-    M(DrawTextRSXform) M(DrawTextBlob)                                                 \
+    M(DrawTextBlob)                                                                    \
     M(DrawPatch) M(DrawPoints) M(DrawVertices) M(DrawAtlas) M(DrawShadowRec)
 
 #define M(T) T,
@@ -104,7 +104,16 @@
                            clipMatrix.isIdentity() ? nullptr : &clipMatrix, flags });
         }
     };
-
+    struct SaveBehind final : Op {
+        static const auto kType = Type::SaveBehind;
+        SaveBehind(const SkRect* subset) {
+            if (subset) { this->subset = *subset; }
+        }
+        SkRect  subset = kUnset;
+        void draw(SkCanvas* c, const SkMatrix&) const {
+            SkCanvasPriv::SaveBehind(c, maybe_unset(subset));
+        }
+    };
     struct Concat final : Op {
         static const auto kType = Type::Concat;
         Concat(const SkMatrix& matrix) : matrix(matrix) {}
@@ -341,63 +350,6 @@
             c->experimental_DrawImageSetV1(set.get(), count, quality, xfermode);
         }
     };
-    struct DrawText final : Op {
-        static const auto kType = Type::DrawText;
-        DrawText(size_t bytes, SkScalar x, SkScalar y, const SkPaint& paint)
-            : bytes(bytes), x(x), y(y), paint(paint) {}
-        size_t bytes;
-        SkScalar x,y;
-        SkPaint paint;
-        void draw(SkCanvas* c, const SkMatrix&) const {
-            c->drawText(pod<void>(this), bytes, x,y, paint);
-        }
-    };
-    struct DrawPosText final : Op {
-        static const auto kType = Type::DrawPosText;
-        DrawPosText(size_t bytes, const SkPaint& paint, int n)
-            : bytes(bytes), paint(paint), n(n) {}
-        size_t bytes;
-        SkPaint paint;
-        int n;
-        void draw(SkCanvas* c, const SkMatrix&) const {
-            auto points = pod<SkPoint>(this);
-            auto text   = pod<void>(this, n*sizeof(SkPoint));
-            c->drawPosText(text, bytes, points, paint);
-        }
-    };
-    struct DrawPosTextH final : Op {
-        static const auto kType = Type::DrawPosTextH;
-        DrawPosTextH(size_t bytes, SkScalar y, const SkPaint& paint, int n)
-            : bytes(bytes), y(y), paint(paint), n(n) {}
-        size_t   bytes;
-        SkScalar y;
-        SkPaint  paint;
-        int n;
-        void draw(SkCanvas* c, const SkMatrix&) const {
-            auto xs   = pod<SkScalar>(this);
-            auto text = pod<void>(this, n*sizeof(SkScalar));
-            c->drawPosTextH(text, bytes, xs, y, paint);
-        }
-    };
-    struct DrawTextRSXform final : Op {
-        static const auto kType = Type::DrawTextRSXform;
-        DrawTextRSXform(size_t bytes, int xforms, const SkRect* cull, const SkPaint& paint)
-            : bytes(bytes), xforms(xforms), paint(paint) {
-            if (cull) { this->cull = *cull; }
-        }
-        size_t  bytes;
-        int     xforms;
-        SkRect  cull = kUnset;
-        SkPaint paint;
-        void draw(SkCanvas* c, const SkMatrix&) const {
-            // For alignment, the SkRSXforms are first in the pod section, followed by the text.
-            c->drawTextRSXform(pod<void>(this,xforms*sizeof(SkRSXform)),
-                               bytes,
-                               pod<SkRSXform>(this),
-                               maybe_unset(cull),
-                               paint);
-        }
-    };
     struct DrawTextBlob final : Op {
         static const auto kType = Type::DrawTextBlob;
         DrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint)
@@ -537,6 +489,9 @@
                          const SkMatrix* clipMatrix, SkCanvas::SaveLayerFlags flags) {
     this->push<SaveLayer>(0, bounds, paint, backdrop, clipMask, clipMatrix, flags);
 }
+void SkLiteDL::saveBehind(const SkRect* subset) {
+    this->push<SaveBehind>(0, subset);
+}
 
 void SkLiteDL::   concat(const SkMatrix& matrix)   { this->push   <Concat>(0, matrix); }
 void SkLiteDL::setMatrix(const SkMatrix& matrix)   { this->push<SetMatrix>(0, matrix); }
@@ -624,29 +579,6 @@
     this->push<DrawImageSet>(0, set, count, filterQuality, mode);
 }
 
-void SkLiteDL::drawText(const void* text, size_t bytes,
-                        SkScalar x, SkScalar y, const SkPaint& paint) {
-    void* pod = this->push<DrawText>(bytes, bytes, x, y, paint);
-    copy_v(pod, (const char*)text,bytes);
-}
-void SkLiteDL::drawPosText(const void* text, size_t bytes,
-                           const SkPoint pos[], const SkPaint& paint) {
-    int n = paint.countText(text, bytes);
-    void* pod = this->push<DrawPosText>(n*sizeof(SkPoint)+bytes, bytes, paint, n);
-    copy_v(pod, pos,n, (const char*)text,bytes);
-}
-void SkLiteDL::drawPosTextH(const void* text, size_t bytes,
-                           const SkScalar xs[], SkScalar y, const SkPaint& paint) {
-    int n = paint.countText(text, bytes);
-    void* pod = this->push<DrawPosTextH>(n*sizeof(SkScalar)+bytes, bytes, y, paint, n);
-    copy_v(pod, xs,n, (const char*)text,bytes);
-}
-void SkLiteDL::drawTextRSXform(const void* text, size_t bytes,
-                               const SkRSXform xforms[], const SkRect* cull, const SkPaint& paint) {
-    int n = paint.countText(text, bytes);
-    void* pod = this->push<DrawTextRSXform>(bytes+n*sizeof(SkRSXform), bytes, n, cull, paint);
-    copy_v(pod, xforms,n, (const char*)text,bytes);
-}
 void SkLiteDL::drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint) {
     this->push<DrawTextBlob>(0, blob, x,y, paint);
 }
diff --git a/src/core/SkLiteDL.h b/src/core/SkLiteDL.h
index 079094b..5c4c519 100644
--- a/src/core/SkLiteDL.h
+++ b/src/core/SkLiteDL.h
@@ -30,6 +30,7 @@
     void save();
     void saveLayer(const SkRect*, const SkPaint*, const SkImageFilter*, const SkImage*,
                    const SkMatrix*, SkCanvas::SaveLayerFlags);
+    void saveBehind(const SkRect*);
     void restore();
 
     void    concat (const SkMatrix&);
@@ -55,10 +56,6 @@
     void drawDrawable       (SkDrawable*, const SkMatrix*);
     void drawPicture        (const SkPicture*, const SkMatrix*, const SkPaint*);
 
-    void drawText       (const void*, size_t, SkScalar, SkScalar, const SkPaint&);
-    void drawPosText    (const void*, size_t, const SkPoint[], const SkPaint&);
-    void drawPosTextH   (const void*, size_t, const SkScalar[], SkScalar, const SkPaint&);
-    void drawTextRSXform(const void*, size_t, const SkRSXform[], const SkRect*, const SkPaint&);
     void drawTextBlob   (const SkTextBlob*, SkScalar,SkScalar, const SkPaint&);
 
     void drawImage    (sk_sp<const SkImage>, SkScalar,SkScalar,             const SkPaint*);
diff --git a/src/core/SkLiteRecorder.cpp b/src/core/SkLiteRecorder.cpp
index 5d5c98b..50cece8 100644
--- a/src/core/SkLiteRecorder.cpp
+++ b/src/core/SkLiteRecorder.cpp
@@ -30,6 +30,10 @@
                    rec.fSaveLayerFlags);
     return SkCanvas::kNoLayer_SaveLayerStrategy;
 }
+bool SkLiteRecorder::onDoSaveBehind(const SkRect* subset) {
+    fDL->saveBehind(subset);
+    return false;
+}
 void SkLiteRecorder::willRestore() { fDL->restore(); }
 
 void SkLiteRecorder::didConcat   (const SkMatrix& matrix)   { fDL->   concat(matrix); }
@@ -91,26 +95,6 @@
     fDL->drawAnnotation(rect, key, val);
 }
 
-void SkLiteRecorder::onDrawText(const void* text, size_t bytes,
-                                SkScalar x, SkScalar y,
-                                const SkPaint& paint) {
-    fDL->drawText(text, bytes, x, y, paint);
-}
-void SkLiteRecorder::onDrawPosText(const void* text, size_t bytes,
-                                   const SkPoint pos[],
-                                   const SkPaint& paint) {
-    fDL->drawPosText(text, bytes, pos, paint);
-}
-void SkLiteRecorder::onDrawPosTextH(const void* text, size_t bytes,
-                                    const SkScalar xs[], SkScalar y,
-                                    const SkPaint& paint) {
-    fDL->drawPosTextH(text, bytes, xs, y, paint);
-}
-void SkLiteRecorder::onDrawTextRSXform(const void* text, size_t bytes,
-                                       const SkRSXform xform[], const SkRect* cull,
-                                       const SkPaint& paint) {
-    fDL->drawTextRSXform(text, bytes, xform, cull, paint);
-}
 void SkLiteRecorder::onDrawTextBlob(const SkTextBlob* blob,
                                     SkScalar x, SkScalar y,
                                     const SkPaint& paint) {
diff --git a/src/core/SkLiteRecorder.h b/src/core/SkLiteRecorder.h
index 2cc78f9..805b460 100644
--- a/src/core/SkLiteRecorder.h
+++ b/src/core/SkLiteRecorder.h
@@ -22,6 +22,7 @@
 
     void willSave() override;
     SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override;
+    bool onDoSaveBehind(const SkRect*) override;
     void willRestore() override;
 
     void onFlush() override;
@@ -48,11 +49,6 @@
     void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override;
     void onDrawAnnotation(const SkRect&, const char[], SkData*) override;
 
-    void onDrawText      (const void*, size_t, SkScalar x, SkScalar y, const SkPaint&) override;
-    void onDrawPosText   (const void*, size_t, const SkPoint[], const SkPaint&) override;
-    void onDrawPosTextH  (const void*, size_t, const SkScalar[], SkScalar, const SkPaint&) override;
-    void onDrawTextRSXform(const void*, size_t,
-                           const SkRSXform[], const SkRect*, const SkPaint&) override;
     void onDrawTextBlob(const SkTextBlob*, SkScalar, SkScalar, const SkPaint&) override;
 
     void onDrawBitmap(const SkBitmap&, SkScalar, SkScalar, const SkPaint*) override;
diff --git a/src/core/SkMathPriv.h b/src/core/SkMathPriv.h
index f9ff27a..90b1dab 100644
--- a/src/core/SkMathPriv.h
+++ b/src/core/SkMathPriv.h
@@ -129,7 +129,7 @@
  * Swap byte order of a 4-byte value, e.g. 0xaarrggbb -> 0xbbggrraa.
  */
 #if defined(_MSC_VER)
-    #include <intrin.h>
+    #include <stdlib.h>
     static inline uint32_t SkBSwap32(uint32_t v) { return _byteswap_ulong(v); }
 #else
     static inline uint32_t SkBSwap32(uint32_t v) { return __builtin_bswap32(v); }
diff --git a/src/core/SkOpts.cpp b/src/core/SkOpts.cpp
index 25486f9..878b114 100644
--- a/src/core/SkOpts.cpp
+++ b/src/core/SkOpts.cpp
@@ -75,7 +75,6 @@
     DEFINE_DEFAULT(hash_fn);
 
     DEFINE_DEFAULT(S32_alpha_D32_filter_DX);
-
 #undef DEFINE_DEFAULT
 
 #define M(st) (StageFn)SK_OPTS_NS::st,
diff --git a/src/core/SkOverdrawCanvas.cpp b/src/core/SkOverdrawCanvas.cpp
index 0d3596e..d1f247e 100644
--- a/src/core/SkOverdrawCanvas.cpp
+++ b/src/core/SkOverdrawCanvas.cpp
@@ -61,107 +61,39 @@
     fPaint.setColorFilter(SkColorFilter::MakeMatrixFilterRowMajor255(kIncrementAlpha));
 }
 
-void SkOverdrawCanvas::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                                  const SkPaint& paint) {
-    SK_ABORT("This canvas does not support draw text.");
-}
-
-void SkOverdrawCanvas::drawPosTextCommon(const void* text, size_t byteLength, const SkScalar pos[],
+void SkOverdrawCanvas::drawPosTextCommon(const SkGlyphID glyphs[], int count, const SkScalar pos[],
                                          int scalarsPerPos, const SkPoint& offset,
-                                         const SkPaint& paint) {
+                                         const SkFont& font, const SkPaint& paint) {
     ProcessOneGlyphBounds processBounds(this);
     SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
     this->getProps(&props);
     auto cache = SkStrikeCache::FindOrCreateStrikeExclusive(
-                                SkFont::LEGACY_ExtractFromPaint(paint), paint, props,
+                                font, paint, props,
                                 SkScalerContextFlags::kNone, this->getTotalMatrix());
-    SkFindAndPlaceGlyph::ProcessPosText((SkTextEncoding)paint.getTextEncoding(),
-                                        (const char*)text, byteLength,
+    SkFindAndPlaceGlyph::ProcessPosText(glyphs, count,
                                         SkPoint::Make(0, 0), SkMatrix(), (const SkScalar*) pos, 2,
                                         cache.get(), processBounds);
 }
 
-void SkOverdrawCanvas::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                                     const SkPaint& paint) {
-    this->drawPosTextCommon(text, byteLength, (SkScalar*) pos, 2, SkPoint::Make(0, 0), paint);
-}
-
-void SkOverdrawCanvas::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xs[],
-                                      SkScalar y, const SkPaint& paint) {
-    this->drawPosTextCommon(text, byteLength, (SkScalar*) xs, 1, SkPoint::Make(0, y), paint);
-}
-
-typedef int (*CountTextProc)(const char* text, const char* stop);
-static int count_utf16(const char* text, const char* stop) {
-    const uint16_t* prev = (const uint16_t*)text;
-    (void)SkUTF::NextUTF16(&prev, (const uint16_t*)stop);
-    return SkToInt((const char*)prev - text);
-}
-static int return_4(const char* text, const char* stop) { return 4; }
-static int return_2(const char* text, const char* stop) { return 2; }
-static int count_utf8(const char* text, const char* stop) {
-    const char* ptr = text;
-    (void)SkUTF::NextUTF8(&ptr, stop);
-    return SkToInt(ptr - text);
-}
-
-void SkOverdrawCanvas::onDrawTextRSXform(const void* text, size_t byteLength,
-                                         const SkRSXform xform[], const SkRect*,
-                                         const SkPaint& paint) {
-    const char* stop = (const char*)text + byteLength;
-    CountTextProc proc = nullptr;
-    switch ((SkTextEncoding)paint.getTextEncoding()) {
-        case kUTF8_SkTextEncoding:
-            proc = count_utf8;
-            break;
-        case kUTF16_SkTextEncoding:
-            proc = count_utf16;
-            break;
-        case kUTF32_SkTextEncoding:
-            proc = return_4;
-            break;
-        case kGlyphID_SkTextEncoding:
-            proc = return_2;
-            break;
-    }
-    SkASSERT(proc);
-
-    SkMatrix matrix;
-    const void* stopText = (const char*)text + byteLength;
-    while ((const char*)text < (const char*)stopText) {
-        matrix.setRSXform(*xform++);
-        matrix.setConcat(this->getTotalMatrix(), matrix);
-        int subLen = proc((const char*)text, stop);
-        SkASSERT(subLen > 0);
-
-        this->save();
-        this->concat(matrix);
-        this->drawText(text, subLen, 0, 0, paint);
-        this->restore();
-
-        text = (const char*)text + subLen;
-    }
-}
-
 void SkOverdrawCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                       const SkPaint& paint) {
-    SkPaint runPaint = paint;
     SkTextBlobRunIterator it(blob);
     for (;!it.done(); it.next()) {
-        size_t textLen = it.glyphCount() * sizeof(uint16_t);
         const SkPoint& offset = it.offset();
-        it.applyFontToPaint(&runPaint);
         switch (it.positioning()) {
             case SkTextBlobRunIterator::kDefault_Positioning:
-                this->onDrawText(it.glyphs(), textLen, x + offset.x(), y + offset.y(), runPaint);
+                SK_ABORT("This canvas does not support draw text.");
                 break;
             case SkTextBlobRunIterator::kHorizontal_Positioning:
-                this->drawPosTextCommon(it.glyphs(), textLen, it.pos(), 1,
-                                        SkPoint::Make(x, y + offset.y()), runPaint);
+                this->drawPosTextCommon(it.glyphs(), it.glyphCount(), it.pos(), 1,
+                                        SkPoint::Make(x, y + offset.y()), it.font(), paint);
                 break;
             case SkTextBlobRunIterator::kFull_Positioning:
-                this->drawPosTextCommon(it.glyphs(), textLen, it.pos(), 2, SkPoint::Make(x, y),
-                                        runPaint);
+                this->drawPosTextCommon(it.glyphs(), it.glyphCount(), it.pos(), 2, {x, y},
+                                        it.font(), paint);
+                break;
+            case SkTextBlobRunIterator::kRSXform_Positioning:
+                // unimplemented ...
                 break;
         }
     }
diff --git a/src/core/SkPaint.cpp b/src/core/SkPaint.cpp
index 2b39c38..5f1add0 100644
--- a/src/core/SkPaint.cpp
+++ b/src/core/SkPaint.cpp
@@ -326,6 +326,7 @@
     fTextSkewX = skewX;
 }
 
+#ifdef SK_SUPPORT_LEGACY_PAINTTEXTENCODING
 void SkPaint::setTextEncoding(SkTextEncoding encoding) {
     if ((unsigned)encoding <= (unsigned)kGlyphID_SkTextEncoding) {
         fBitfields.fTextEncoding = (unsigned)encoding;
@@ -335,6 +336,7 @@
 #endif
     }
 }
+#endif
 
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -357,14 +359,6 @@
     return reinterpret_cast<uintptr_t>(p);
 }
 
-static uint32_t pack_4(unsigned a, unsigned b, unsigned c, unsigned d) {
-    SkASSERT(a == (uint8_t)a);
-    SkASSERT(b == (uint8_t)b);
-    SkASSERT(c == (uint8_t)c);
-    SkASSERT(d == (uint8_t)d);
-    return (a << 24) | (b << 16) | (c << 8) | d;
-}
-
 #ifdef SK_DEBUG
     static void ASSERT_FITS_IN(uint32_t value, int bitCount) {
         SkASSERT(bitCount > 0 && bitCount <= 32);
@@ -394,20 +388,6 @@
     return (1 << bits) - 1;
 }
 
-static uint32_t pack_paint_flags(unsigned flags, unsigned hint, unsigned filter,
-                                 unsigned flatFlags) {
-    ASSERT_FITS_IN(flags, kFlags_BPF);
-    ASSERT_FITS_IN(hint, kHint_BPF);
-    ASSERT_FITS_IN(filter, kFilter_BPF);
-    ASSERT_FITS_IN(flatFlags, kFlatFlags_BPF);
-
-    unsigned was_align = 0; // used to be textalign [0..2]
-
-    // left-align the fields of "known" size, and right-align the last (flatFlags) so it can easly
-    // add more bits in the future.
-    return (flags << 16) | (hint << 14) | (was_align << 12) | (filter << 10) | flatFlags;
-}
-
 static FlatFlags unpack_paint_flags(SkPaint* paint, uint32_t packed) {
     paint->setFlags(packed >> 16);
     paint->setHinting((SkFontHinting)((packed >> 14) & BPF_Mask(kHint_BPF)));
@@ -415,16 +395,58 @@
     return (FlatFlags)(packed & kFlatFlagMask);
 }
 
+template <typename T> uint32_t shift_bits(T value, unsigned shift, unsigned bits) {
+    SkASSERT(shift + bits <= 32);
+    uint32_t v = static_cast<uint32_t>(value);
+    ASSERT_FITS_IN(v, bits);
+    return v << shift;
+}
+
+/*  Packing the paint
+ flags :  8  // 2...
+ blend :  8  // 30+
+ cap   :  2  // 3
+ join  :  2  // 3
+ style :  2  // 3
+ filter:  2  // 4
+ flat  :  8  // 1...
+ total : 32
+ */
+static uint32_t pack_v68(const SkPaint& paint, unsigned flatFlags) {
+    uint32_t packed = 0;
+    packed |= shift_bits(((unsigned)paint.isDither() << 1) |
+                          (unsigned)paint.isAntiAlias(), 0, 8);
+    packed |= shift_bits(paint.getBlendMode(),      8, 8);
+    packed |= shift_bits(paint.getStrokeCap(),     16, 2);
+    packed |= shift_bits(paint.getStrokeJoin(),    18, 2);
+    packed |= shift_bits(paint.getStyle(),         20, 2);
+    packed |= shift_bits(paint.getFilterQuality(), 22, 2);
+    packed |= shift_bits(flatFlags,                24, 8);
+    return packed;
+}
+
+static uint32_t unpack_v68(SkPaint* paint, uint32_t packed, SkSafeRange& safe) {
+    paint->setAntiAlias((packed & 1) != 0);
+    paint->setDither((packed & 2) != 0);
+    packed >>= 8;
+    paint->setBlendMode(safe.checkLE(packed & 0xFF, SkBlendMode::kLastMode));
+    packed >>= 8;
+    paint->setStrokeCap(safe.checkLE(packed & 0x3, SkPaint::kLast_Cap));
+    packed >>= 2;
+    paint->setStrokeJoin(safe.checkLE(packed & 0x3, SkPaint::kLast_Join));
+    packed >>= 2;
+    paint->setStyle(safe.checkLE(packed & 0x3, SkPaint::kStrokeAndFill_Style));
+    packed >>= 2;
+    paint->setFilterQuality(safe.checkLE(packed & 0x3, kLast_SkFilterQuality));
+    packed >>= 2;
+    return packed;
+}
+
 /*  To save space/time, we analyze the paint, and write a truncated version of
     it if there are not tricky elements like shaders, etc.
  */
 void SkPaintPriv::Flatten(const SkPaint& paint, SkWriteBuffer& buffer) {
-    // We force recording our typeface, even if its "default" since the receiver process
-    // may have a different notion of default.
-    SkTypeface* tf = SkPaintPriv::GetTypefaceOrDefault(paint);
-    SkASSERT(tf);
-
-    uint8_t flatFlags = kHasTypeface_FlatFlag;
+    uint8_t flatFlags = 0;
 
     if (asint(paint.getPathEffect()) |
         asint(paint.getShader()) |
@@ -435,33 +457,23 @@
         flatFlags |= kHasEffects_FlatFlag;
     }
 
-    buffer.writeScalar(paint.getTextSize());
-    buffer.writeScalar(paint.getTextScaleX());
-    buffer.writeScalar(paint.getTextSkewX());
     buffer.writeScalar(paint.getStrokeWidth());
     buffer.writeScalar(paint.getStrokeMiter());
     buffer.writeColor4f(paint.getColor4f());
 
-    buffer.writeUInt(pack_paint_flags(paint.getFlags(), static_cast<unsigned>(paint.getHinting()),
-                                      paint.getFilterQuality(), flatFlags));
-    buffer.writeUInt(pack_4(paint.getStrokeCap(), paint.getStrokeJoin(),
-                            (paint.getStyle() << 4) | (unsigned)paint.getTextEncoding(),
-                            paint.fBlendMode));
-
-    buffer.writeTypeface(tf);
+    buffer.write32(pack_v68(paint, flatFlags));
 
     if (flatFlags & kHasEffects_FlatFlag) {
         buffer.writeFlattenable(paint.getPathEffect());
         buffer.writeFlattenable(paint.getShader());
         buffer.writeFlattenable(paint.getMaskFilter());
         buffer.writeFlattenable(paint.getColorFilter());
-        buffer.write32(0);  // use to be SkRasterizer
         buffer.writeFlattenable(paint.getLooper());
         buffer.writeFlattenable(paint.getImageFilter());
     }
 }
 
-bool SkPaintPriv::Unflatten(SkPaint* paint, SkReadBuffer& buffer) {
+bool SkPaintPriv::Unflatten_PreV68(SkPaint* paint, SkReadBuffer& buffer) {
     SkSafeRange safe;
 
     paint->setTextSize(buffer.readScalar());
@@ -483,7 +495,7 @@
     paint->setStrokeCap(safe.checkLE((tmp >> 24) & 0xFF, SkPaint::kLast_Cap));
     paint->setStrokeJoin(safe.checkLE((tmp >> 16) & 0xFF, SkPaint::kLast_Join));
     paint->setStyle(safe.checkLE((tmp >> 12) & 0xF, SkPaint::kStrokeAndFill_Style));
-    paint->setTextEncoding(safe.checkLE((tmp >> 8) & 0xF, kGlyphID_SkTextEncoding));
+    paint->private_internal_setTextEncoding(safe.checkLE((tmp >> 8) & 0xF, kGlyphID_SkTextEncoding));
     paint->setBlendMode(safe.checkLE(tmp & 0xFF, SkBlendMode::kLastMode));
 
     if (flatFlags & kHasTypeface_FlatFlag) {
@@ -516,6 +528,46 @@
     return true;
 }
 
+bool SkPaintPriv::Unflatten(SkPaint* paint, SkReadBuffer& buffer) {
+    if (buffer.isVersionLT(SkReadBuffer::kPaintDoesntSerializeFonts_Version)) {
+        return Unflatten_PreV68(paint, buffer);
+    }
+
+    SkSafeRange safe;
+
+    paint->setStrokeWidth(buffer.readScalar());
+    paint->setStrokeMiter(buffer.readScalar());
+    {
+        SkColor4f color;
+        buffer.readColor4f(&color);
+        paint->setColor4f(color, sk_srgb_singleton());
+    }
+
+    unsigned flatFlags = unpack_v68(paint, buffer.readUInt(), safe);
+
+    if (flatFlags & kHasEffects_FlatFlag) {
+        paint->setPathEffect(buffer.readPathEffect());
+        paint->setShader(buffer.readShader());
+        paint->setMaskFilter(buffer.readMaskFilter());
+        paint->setColorFilter(buffer.readColorFilter());
+        paint->setLooper(buffer.readDrawLooper());
+        paint->setImageFilter(buffer.readImageFilter());
+    } else {
+        paint->setPathEffect(nullptr);
+        paint->setShader(nullptr);
+        paint->setMaskFilter(nullptr);
+        paint->setColorFilter(nullptr);
+        paint->setLooper(nullptr);
+        paint->setImageFilter(nullptr);
+    }
+
+    if (!buffer.validate(safe)) {
+        paint->reset();
+        return false;
+    }
+    return true;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 bool SkPaint::getFillPath(const SkPath& src, SkPath* dst, const SkRect* cullRect,
diff --git a/src/core/SkPaintPriv.h b/src/core/SkPaintPriv.h
index d3a0c35..8ef79ae 100644
--- a/src/core/SkPaintPriv.h
+++ b/src/core/SkPaintPriv.h
@@ -79,6 +79,10 @@
         return paint.getTypeface() ? paint.refTypeface() : SkTypeface::MakeDefault();
     }
 
+    static SkTextEncoding GetEncoding(const SkPaint& paint) {
+        return paint.private_internal_getTextEncoding();
+    }
+
     /** Serializes SkPaint into a buffer. A companion unflatten() call
     can reconstitute the paint at a later time.
 
@@ -97,6 +101,8 @@
     */
     static bool Unflatten(SkPaint* paint, SkReadBuffer& buffer);
 
+private:
+    static bool Unflatten_PreV68(SkPaint* paint, SkReadBuffer& buffer);
 };
 
 #endif
diff --git a/src/core/SkPaint_text.cpp b/src/core/SkPaint_text.cpp
index 91e92f7..895beaa 100644
--- a/src/core/SkPaint_text.cpp
+++ b/src/core/SkPaint_text.cpp
@@ -61,38 +61,25 @@
 #include "SkGlyphCache.h"
 #include "SkUtils.h"
 
+#ifdef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
 int SkPaint::countText(const void* text, size_t length) const {
-    return SkFont::LEGACY_ExtractFromPaint(*this).countText(text, length,
-                                                        (SkTextEncoding)this->getTextEncoding());
+    return SkFont::LEGACY_ExtractFromPaint(*this).countText(text, length, this->getTextEncoding());
 }
 
 int SkPaint::textToGlyphs(const void* text, size_t length, uint16_t glyphs[]) const {
     return SkFont::LEGACY_ExtractFromPaint(*this).textToGlyphs(text, length,
-                                                           (SkTextEncoding)this->getTextEncoding(),
+                                                               this->getTextEncoding(),
                                                                glyphs, length);
 }
 
 bool SkPaint::containsText(const void* text, size_t length) const {
     return SkFont::LEGACY_ExtractFromPaint(*this).containsText(text, length,
-                                                           (SkTextEncoding)this->getTextEncoding());
+                                                               this->getTextEncoding());
 }
+#endif
 
 void SkPaint::glyphsToUnichars(const uint16_t glyphs[], int count, SkUnichar textData[]) const {
-    if (count <= 0) {
-        return;
-    }
-
-    SkASSERT(glyphs != nullptr);
-    SkASSERT(textData != nullptr);
-
-    SkFont font = SkFont::LEGACY_ExtractFromPaint(*this);
-    SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
-    auto cache = SkStrikeCache::FindOrCreateStrikeExclusive(
-            font, *this, props, SkScalerContextFlags::kFakeGammaAndBoostContrast, SkMatrix::I());
-
-    for (int index = 0; index < count; index++) {
-        textData[index] = cache->glyphToUnichar(glyphs[index]);
-    }
+    SkFont::LEGACY_ExtractFromPaint(*this).glyphsToUnichars(glyphs, count, textData);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -286,7 +273,7 @@
     }
 
     SkFontPriv::GlyphCacheProc glyphCacheProc = SkFontPriv::GetGlyphCacheProc(
-                    static_cast<SkTextEncoding>(this->getTextEncoding()), nullptr != bounds);
+                                      this->private_internal_getTextEncoding(), nullptr != bounds);
 
     int         n = 1;
     const char* stop = (const char*)text + byteLength;
@@ -312,6 +299,7 @@
     return x;
 }
 
+#ifdef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
 SkScalar SkPaint::measureText(const void* textData, size_t length, SkRect* bounds) const {
     const char* text = (const char*)textData;
     SkASSERT(text != nullptr || length == 0);
@@ -345,78 +333,22 @@
     return width;
 }
 
-size_t SkPaint::breakText(const void* textD, size_t length, SkScalar maxWidth,
-                          SkScalar* measuredWidth) const {
-    if (0 == length || 0 >= maxWidth) {
-        if (measuredWidth) {
-            *measuredWidth = 0;
-        }
-        return 0;
-    }
-
-    if (0 == fTextSize) {
-        if (measuredWidth) {
-            *measuredWidth = 0;
-        }
-        return length;
-    }
-
-    SkASSERT(textD != nullptr);
-    const char* text = (const char*)textD;
-    const char* stop = text + length;
-
-    SkCanonicalizePaint canon(*this);
-    const SkPaint& paint = canon.getPaint();
-    SkScalar scale = canon.getScale();
-
-    // adjust max in case we changed the textSize in paint
-    if (scale) {
-        maxWidth /= scale;
-    }
-
-    const SkFont font = SkFont::LEGACY_ExtractFromPaint(paint);
-    auto cache = SkStrikeCache::FindOrCreateStrikeWithNoDeviceExclusive(font, paint);
-
-    SkFontPriv::GlyphCacheProc glyphCacheProc = SkFontPriv::GetGlyphCacheProc(
-                                  static_cast<SkTextEncoding>(paint.getTextEncoding()), false);
-    SkScalar width = 0;
-
-    while (text < stop) {
-        const char* curr = text;
-        SkScalar x = advance(glyphCacheProc(cache.get(), &text, stop));
-        if ((width += x) > maxWidth) {
-            width -= x;
-            text = curr;
-            break;
-        }
-    }
-
-    if (measuredWidth) {
-        if (scale) {
-            width *= scale;
-        }
-        *measuredWidth = width;
-    }
-
-    // return the number of bytes measured
-    return text - stop + length;
-}
-
 SkScalar SkPaint::getFontMetrics(SkFontMetrics* metrics) const {
     return SkFont::LEGACY_ExtractFromPaint(*this).getMetrics(metrics);
 }
 
-///////////////////////////////////////////////////////////////////////////////
-
 int SkPaint::getTextWidths(const void* text, size_t len, SkScalar widths[], SkRect bounds[]) const {
     const SkFont font = SkFont::LEGACY_ExtractFromPaint(*this);
-    SkAutoToGlyphs gly(font, text, len, (SkTextEncoding)this->getTextEncoding());
+    SkAutoToGlyphs gly(font, text, len, this->getTextEncoding());
     font.getWidthsBounds(gly.glyphs(), gly.count(), widths, bounds, this);
     return gly.count();
 }
 
+#endif
+
 ///////////////////////////////////////////////////////////////////////////////
 
+#ifdef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
 #include "SkDraw.h"
 
 struct PathPosRec {
@@ -436,7 +368,7 @@
 void SkPaint::getTextPath(const void* text, size_t length,
                           SkScalar x, SkScalar y, SkPath* path) const {
     SkFont font = SkFont::LEGACY_ExtractFromPaint(*this);
-    SkAutoToGlyphs gly(font, text, length, (SkTextEncoding)this->getTextEncoding());
+    SkAutoToGlyphs gly(font, text, length, this->getTextEncoding());
     SkAutoSTArray<32, SkPoint> fPos(gly.count());
     font.getPos(gly.glyphs(), gly.count(), fPos.get(), {x, y});
 
@@ -448,109 +380,17 @@
 void SkPaint::getPosTextPath(const void* text, size_t length,
                              const SkPoint pos[], SkPath* path) const {
     SkFont font = SkFont::LEGACY_ExtractFromPaint(*this);
-    SkAutoToGlyphs gly(font, text, length, (SkTextEncoding)this->getTextEncoding());
+    SkAutoToGlyphs gly(font, text, length, this->getTextEncoding());
 
     path->reset();
     PathPosRec rec = { path, pos };
     font.getPaths(gly.glyphs(), gly.count(), PathPosProc, &rec);
 }
-
-template <SkTextInterceptsIter::TextType TextType, typename Func>
-int GetTextIntercepts(const SkPaint& paint, const void* text, size_t length,
-                      const SkScalar bounds[2], SkScalar* array, Func posMaker) {
-    SkASSERT(length == 0 || text != nullptr);
-    if (!length) {
-        return 0;
-    }
-
-    const SkPoint pos0 = posMaker(0);
-    SkTextInterceptsIter iter(static_cast<const char*>(text), length, paint, bounds,
-                              pos0.x(), pos0.y(), TextType);
-
-    int i = 0;
-    int count = 0;
-    while (iter.next(array, &count)) {
-        if (TextType == SkTextInterceptsIter::TextType::kPosText) {
-            const SkPoint pos = posMaker(++i);
-            iter.setPosition(pos.x(), pos.y());
-        }
-    }
-
-    return count;
-}
-
-int SkPaint::getTextIntercepts(const void* textData, size_t length,
-                               SkScalar x, SkScalar y, const SkScalar bounds[2],
-                               SkScalar* array) const {
-
-    return GetTextIntercepts<SkTextInterceptsIter::TextType::kText>(
-        *this, textData, length, bounds, array, [&x, &y] (int) -> SkPoint {
-            return SkPoint::Make(x, y);
-        });
-}
-
-int SkPaint::getPosTextIntercepts(const void* textData, size_t length, const SkPoint pos[],
-                                  const SkScalar bounds[2], SkScalar* array) const {
-
-    return GetTextIntercepts<SkTextInterceptsIter::TextType::kPosText>(
-        *this, textData, length, bounds, array, [&pos] (int i) -> SkPoint {
-            return pos[i];
-        });
-}
-
-int SkPaint::getPosTextHIntercepts(const void* textData, size_t length, const SkScalar xpos[],
-                                   SkScalar constY, const SkScalar bounds[2],
-                                   SkScalar* array) const {
-
-    return GetTextIntercepts<SkTextInterceptsIter::TextType::kPosText>(
-        *this, textData, length, bounds, array, [&xpos, &constY] (int i) -> SkPoint {
-            return SkPoint::Make(xpos[i], constY);
-        });
-}
+#endif
 
 int SkPaint::getTextBlobIntercepts(const SkTextBlob* blob, const SkScalar bounds[2],
                                    SkScalar* intervals) const {
-    int count = 0;
-    SkPaint runPaint(*this);
-
-    SkTextBlobRunIterator it(blob);
-    while (!it.done()) {
-        it.applyFontToPaint(&runPaint);
-        const size_t runByteCount = it.glyphCount() * sizeof(SkGlyphID);
-        SkScalar* runIntervals = intervals ? intervals + count : nullptr;
-
-        switch (it.positioning()) {
-        case SkTextBlobRunIterator::kDefault_Positioning:
-            count += runPaint.getTextIntercepts(it.glyphs(), runByteCount, it.offset().x(),
-                                                it.offset().y(), bounds, runIntervals);
-            break;
-        case SkTextBlobRunIterator::kHorizontal_Positioning:
-            count += runPaint.getPosTextHIntercepts(it.glyphs(), runByteCount, it.pos(),
-                                                    it.offset().y(), bounds, runIntervals);
-            break;
-        case SkTextBlobRunIterator::kFull_Positioning:
-            count += runPaint.getPosTextIntercepts(it.glyphs(), runByteCount,
-                                                   reinterpret_cast<const SkPoint*>(it.pos()),
-                                                   bounds, runIntervals);
-            break;
-        }
-
-        it.next();
-    }
-
-    return count;
-}
-
-SkRect SkPaint::getFontBounds() const {
-    SkMatrix m;
-    m.setScale(fTextSize * fTextScaleX, fTextSize);
-    m.postSkew(fTextSkewX, 0);
-
-    SkTypeface* typeface = SkPaintPriv::GetTypefaceOrDefault(*this);
-
-    SkRect bounds;
-    m.mapRect(&bounds, typeface->getBounds());
-    return bounds;
+    return blob->getIntercepts(bounds, intervals, this);
 }
 
 // return true if the paint is just a single color (i.e. not a shader). If its
@@ -586,24 +426,23 @@
             paint.getStyle() != SkPaint::kFill_Style;
 }
 
-SkTextBaseIter::SkTextBaseIter(const char text[], size_t length,
-                                   const SkPaint& paint,
-                                   bool applyStrokeAndPathEffects)
-    : fPaint(paint) {
-    fGlyphCacheProc = SkFontPriv::GetGlyphCacheProc(
-                                    static_cast<SkTextEncoding>(paint.getTextEncoding()), true);
+SkTextBaseIter::SkTextBaseIter(const SkGlyphID glyphs[], int count, const SkFont& font,
+                               const SkPaint* paint)
+        : fFont(font)
+{
+    SkAssertResult(count >= 0);
 
-    fPaint.setLinearText(true);
-    fPaint.setMaskFilter(nullptr);   // don't want this affecting our path-cache lookup
+    fFont.setLinearMetrics(true);
 
-    if (fPaint.getPathEffect() == nullptr && !has_thick_frame(fPaint)) {
-        applyStrokeAndPathEffects = false;
+    if (paint) {
+        fPaint = *paint;
     }
+    fPaint.setMaskFilter(nullptr);   // don't want this affecting our path-cache lookup
 
     // can't use our canonical size if we need to apply patheffects
     if (fPaint.getPathEffect() == nullptr) {
-        fPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
-        fScale = paint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
+        fScale = fFont.getSize() / SkPaint::kCanonicalTextSizeForPaths;
+        fFont.setSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
         // Note: fScale can be zero here (even if it wasn't before the divide). It can also
         // be very very small. We call sk_ieee_float_divide below to ensure IEEE divide behavior,
         // since downstream we will check for the resulting coordinates being non-finite anyway.
@@ -615,25 +454,17 @@
         fScale = SK_Scalar1;
     }
 
-    if (!applyStrokeAndPathEffects) {
-        fPaint.setStyle(SkPaint::kFill_Style);
-        fPaint.setPathEffect(nullptr);
-    }
+    SkPaint::Style prevStyle = fPaint.getStyle();
+    auto prevPE = fPaint.refPathEffect();
+    auto prevMF = fPaint.refMaskFilter();
+    fPaint.setStyle(SkPaint::kFill_Style);
+    fPaint.setPathEffect(nullptr);
 
-    // SRGBTODO: Is this correct?
-    const SkFont font = SkFont::LEGACY_ExtractFromPaint(fPaint);
-    fCache = SkStrikeCache::FindOrCreateStrikeWithNoDeviceExclusive(font, fPaint);
+    fCache = SkStrikeCache::FindOrCreateStrikeWithNoDeviceExclusive(fFont, fPaint);
 
-    SkPaint::Style  style = SkPaint::kFill_Style;
-    sk_sp<SkPathEffect> pe;
-
-    if (!applyStrokeAndPathEffects) {
-        style = paint.getStyle();       // restore
-        pe = paint.refPathEffect();     // restore
-    }
-    fPaint.setStyle(style);
-    fPaint.setPathEffect(pe);
-    fPaint.setMaskFilter(paint.refMaskFilter());    // restore
+    fPaint.setStyle(prevStyle);
+    fPaint.setPathEffect(std::move(prevPE));
+    fPaint.setMaskFilter(std::move(prevMF));
 
     // now compute fXOffset if needed
 
@@ -641,41 +472,18 @@
     fXPos = xOffset;
     fPrevAdvance = 0;
 
-    fText = text;
-    fStop = text + length;
-}
-
-bool SkTextToPathIter::next(const SkPath** path, SkScalar* xpos) {
-    if (fText < fStop) {
-        const SkGlyph& glyph = fGlyphCacheProc(fCache.get(), &fText, fStop);
-
-        fXPos += fPrevAdvance * fScale;
-        fPrevAdvance = advance(glyph);   // + fPaint.getTextTracking();
-
-        if (glyph.fWidth) {
-            if (path) {
-                *path = fCache->findPath(glyph);
-            }
-        } else {
-            if (path) {
-                *path = nullptr;
-            }
-        }
-        if (xpos) {
-            *xpos = fXPos;
-        }
-        return true;
-    }
-    return false;
+    fGlyphs = glyphs;
+    fStop = glyphs + count;
 }
 
 bool SkTextInterceptsIter::next(SkScalar* array, int* count) {
-    const SkGlyph& glyph = fGlyphCacheProc(fCache.get(), &fText, fStop);
+    SkASSERT(fGlyphs < fStop);
+    const SkGlyph& glyph = fCache->getGlyphIDMetrics(*fGlyphs++);
     fXPos += fPrevAdvance * fScale;
     fPrevAdvance = advance(glyph);   // + fPaint.getTextTracking();
     if (fCache->findPath(glyph)) {
         fCache->findIntercepts(fBounds, fScale, fXPos, false,
                 const_cast<SkGlyph*>(&glyph), array, count);
     }
-    return fText < fStop;
+    return fGlyphs < fStop;
 }
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 09badf5..6feafd8 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -1863,6 +1863,11 @@
         // convex after a transformation, so mark it as unknown here.
         // However, some transformations are thought to be safe:
         //    axis-aligned values under scale/translate.
+        //
+        // See skbug.com/8606
+        // If we can land a robust convex scan-converter, we may be able to relax/remove this
+        // check, and keep convex paths marked as such after a general transform...
+        //
         if (matrix.isScaleTranslate() && SkPathPriv::IsAxisAligned(*this)) {
             dst->setConvexity(convexity);
         } else {
@@ -2290,6 +2295,8 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+#ifdef SK_LEGACY_PATH_CONVEXITY  // for rebaselining Chrome
+
 static int sign(SkScalar x) { return x < 0; }
 #define kValueNeverReturnedBySign   2
 
@@ -2573,6 +2580,335 @@
     return this->getConvexityOrUnknown();
 }
 
+#else
+
+static int sign(SkScalar x1, SkScalar x2) { SkASSERT(x1 != x2); return x2 < x1; }
+static int sign(SkScalar x) { return x < 0; }
+#define kValueNeverReturnedBySign   2
+
+enum DirChange {
+    kUnknown_DirChange,
+    kLeft_DirChange,
+    kRight_DirChange,
+    kStraight_DirChange,
+    kConcave_DirChange,   // if cross on diagonal is too small, assume concave
+    kBackwards_DirChange, // if double back, allow simple lines to be convex
+    kInvalid_DirChange
+};
+
+
+static bool almost_equal(SkScalar compA, SkScalar compB) {
+    // The error epsilon was empirically derived; worse case round rects
+    // with a mid point outset by 2x float epsilon in tests had an error
+    // of 12.
+    const int epsilon = 16;
+    if (!SkScalarIsFinite(compA) || !SkScalarIsFinite(compB)) {
+        return false;
+    }
+    // no need to check for small numbers because SkPath::Iter has removed degenerate values
+    int aBits = SkFloatAs2sCompliment(compA);
+    int bBits = SkFloatAs2sCompliment(compB);
+    return aBits < bBits + epsilon && bBits < aBits + epsilon;
+}
+
+static DirChange same_sign(SkScalar curr, SkScalar last, SkScalar prior) {
+    return sign(curr, last) == sign(last, prior) ? kStraight_DirChange : kBackwards_DirChange;
+}
+
+// only valid for a single contour
+struct Convexicator {
+
+    /** The direction returned is only valid if the path is determined convex */
+    SkPathPriv::FirstDirection getFirstDirection() const { return fFirstDirection; }
+
+    void setMovePt(const SkPoint& pt) {
+        fPriorPt = fLastPt = fCurrPt = pt;
+    }
+
+    bool addPt(const SkPoint& pt) {
+        if (fCurrPt == pt) {
+            return true;
+        }
+        fCurrPt = pt;
+        if (fPriorPt == fLastPt) {  // should only be true for first non-zero vector
+            fFirstPt = pt;
+            fCurrAligned = pt.fX == fLastPt.fX || pt.fY == fLastPt.fY;
+        } else if (!this->addVec()) {
+            return false;
+        }
+        fPriorPt = fLastPt;
+        fLastPt = fCurrPt;
+        fLastAligned = fCurrAligned;
+        return true;
+    }
+
+    static SkPath::Convexity BySign(const SkPoint points[], int count) {
+        const SkPoint* last = points + count;
+        SkPoint currPt = *points++;
+        SkPoint firstPt = currPt;
+        int dxes = 0;
+        int dyes = 0;
+        int lastSx = kValueNeverReturnedBySign;
+        int lastSy = kValueNeverReturnedBySign;
+        for (int outerLoop = 0; outerLoop < 2; ++outerLoop ) {
+            while (points != last) {
+                SkVector vec = *points - currPt;
+                if (!vec.isZero()) {
+                    // give up if vector construction failed
+                    if (!vec.isFinite()) {
+                        return SkPath::kUnknown_Convexity;
+                    }
+                    int sx = sign(vec.fX);
+                    int sy = sign(vec.fY);
+                    dxes += (sx != lastSx);
+                    dyes += (sy != lastSy);
+                    if (dxes > 3 || dyes > 3) {
+                        return SkPath::kConcave_Convexity;
+                    }
+                    lastSx = sx;
+                    lastSy = sy;
+                }
+                currPt = *points++;
+                if (outerLoop) {
+                    break;
+                }
+            }
+            points = &firstPt;
+        }
+        return SkPath::kConvex_Convexity;  // that is, it may be convex, don't know yet
+    }
+
+    bool close() {
+        return this->addPt(fFirstPt);
+    }
+
+    bool isFinite() const {
+        return fIsFinite;
+    }
+
+    int reversals() const {
+        return fReversals;
+    }
+
+private:
+    DirChange directionChange() {
+        // if both vectors are axis-aligned, don't do cross product
+        fCurrAligned = fCurrPt.fX == fLastPt.fX || fCurrPt.fY == fLastPt.fY;
+        if (fLastAligned && fCurrAligned) {
+            bool noYChange = fCurrPt.fY == fLastPt.fY && fLastPt.fY == fPriorPt.fY;
+            if (fCurrPt.fX == fLastPt.fX && fLastPt.fX == fPriorPt.fX) {
+                if (noYChange) {
+                    return kStraight_DirChange;
+                }
+                return same_sign(fCurrPt.fY, fLastPt.fY, fPriorPt.fY);
+            }
+            if (!noYChange) { // must be turn to left or right
+                bool flip = fCurrPt.fX != fLastPt.fX;
+                SkASSERT(flip ? fCurrPt.fY == fLastPt.fY &&
+                        fLastPt.fY != fPriorPt.fY && fLastPt.fX == fPriorPt.fX :
+                        fCurrPt.fY != fLastPt.fY &&
+                        fLastPt.fY == fPriorPt.fY && fLastPt.fX != fPriorPt.fX);
+                bool product = flip ? (fCurrPt.fX > fLastPt.fX) != (fLastPt.fY > fPriorPt.fY) :
+                        (fCurrPt.fY > fLastPt.fY) == (fLastPt.fX > fPriorPt.fX);
+                SkDEBUGCODE(SkVector lastV = fLastPt - fPriorPt);
+                SkDEBUGCODE(SkVector curV = fCurrPt - fLastPt);
+                SkDEBUGCODE(SkScalar crossV = SkPoint::CrossProduct(lastV, curV));
+                SkDEBUGCODE(int signV = SkScalarSignAsInt(crossV));
+                SkASSERT(!signV || signV == (product ? 1 : -1));
+                return product ? kRight_DirChange : kLeft_DirChange;
+            }
+            return same_sign(fCurrPt.fX, fLastPt.fX, fPriorPt.fX);
+        }
+        // there are no subtractions above this line; axis aligned paths
+        // are robust and can handle arbitrary values
+        SkVector lastVec = fLastPt - fPriorPt;
+        SkVector curVec = fCurrPt - fLastPt;
+        SkScalar cross = SkPoint::CrossProduct(lastVec, curVec);
+        if (!SkScalarIsFinite(cross)) {
+                return kUnknown_DirChange;
+        }
+        SkScalar smallest = SkTMin(fCurrPt.fX, SkTMin(fCurrPt.fY, SkTMin(fLastPt.fX, fLastPt.fY)));
+        SkScalar largest = SkTMax(fCurrPt.fX, SkTMax(fCurrPt.fY, SkTMax(fLastPt.fX, fLastPt.fY)));
+        largest = SkTMax(largest, -smallest);
+
+        if (almost_equal(largest, largest + cross)) {
+#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE
+    // colinear diagonals are not allowed; they aren't numerically stable
+    #define COLINEAR_POINT_DIR_CHANGE kConcave_DirChange
+#else
+    // colinear diagonals are allowed; we can survive dealing with 'close enough'
+    #define COLINEAR_POINT_DIR_CHANGE kStraight_DirChange
+#endif
+
+            SkScalar dot = lastVec.dot(curVec);
+            return dot < 0 ? kBackwards_DirChange : COLINEAR_POINT_DIR_CHANGE;
+        }
+        return 1 == SkScalarSignAsInt(cross) ? kRight_DirChange : kLeft_DirChange;
+    }
+
+    bool addVec() {
+        DirChange dir = this->directionChange();
+        switch (dir) {
+            case kLeft_DirChange:       // fall through
+            case kRight_DirChange:
+                if (kInvalid_DirChange == fExpectedDir) {
+                    fExpectedDir = dir;
+                    fFirstDirection = (kRight_DirChange == dir) ? SkPathPriv::kCW_FirstDirection
+                                                                : SkPathPriv::kCCW_FirstDirection;
+                } else if (dir != fExpectedDir) {
+                    fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
+                    return false;
+                }
+                break;
+            case kStraight_DirChange:
+                break;
+            case kConcave_DirChange:
+                fFirstDirection = SkPathPriv::kUnknown_FirstDirection;
+                return false;
+            case kBackwards_DirChange:
+                //  allow path to reverse direction twice
+                //    Given path.moveTo(0, 0); path.lineTo(1, 1);
+                //    - 1st reversal: direction change formed by line (0,0 1,1), line (1,1 0,0)
+                //    - 2nd reversal: direction change formed by line (1,1 0,0), line (0,0 1,1)
+                return ++fReversals < 3;
+            case kUnknown_DirChange:
+                return (fIsFinite = false);
+            case kInvalid_DirChange:
+                SK_ABORT("Use of invalid direction change flag");
+                break;
+        }
+        return true;
+    }
+
+    SkPoint             fFirstPt {0, 0};
+    SkPoint             fPriorPt {0, 0};
+    SkPoint             fLastPt {0, 0};
+    SkPoint             fCurrPt {0, 0};
+    DirChange           fExpectedDir { kInvalid_DirChange };
+    SkPathPriv::FirstDirection   fFirstDirection { SkPathPriv::kUnknown_FirstDirection };
+    int                 fReversals { 0 };
+    bool                fIsFinite { true };
+    bool                fLastAligned { true };
+    bool                fCurrAligned { true };
+};
+
+SkPath::Convexity SkPath::internalGetConvexity() const {
+    SkPoint         pts[4];
+    SkPath::Verb    verb;
+    SkPath::Iter    iter(*this, true);
+    auto setComputedConvexity = [=](Convexity convexity){
+        SkASSERT(kUnknown_Convexity != convexity);
+        this->setConvexity(convexity);
+        return convexity;
+    };
+
+    // Check to see if path changes direction more than three times as quick concave test
+    int pointCount = this->countPoints();
+    // last moveTo index may exceed point count if data comes from fuzzer (via SkImageFilter)
+    if (0 < fLastMoveToIndex && fLastMoveToIndex < pointCount) {
+        pointCount = fLastMoveToIndex;
+    }
+    if (pointCount > 3) {
+        const SkPoint* points = fPathRef->points();
+        const SkPoint* last = &points[pointCount];
+        // only consider the last of the initial move tos
+        while (SkPath::kMove_Verb == iter.next(pts, false, false)) {
+            ++points;
+        }
+        --points;
+        SkPath::Convexity convexity = Convexicator::BySign(points, (int) (last - points));
+        if (SkPath::kConcave_Convexity == convexity) {
+            return setComputedConvexity(SkPath::kConcave_Convexity);
+        } else if (SkPath::kUnknown_Convexity == convexity) {
+            return SkPath::kUnknown_Convexity;
+        }
+        iter.setPath(*this, true);
+    } else if (!this->isFinite()) {
+        return kUnknown_Convexity;
+    }
+
+    int             contourCount = 0;
+    int             count;
+    Convexicator    state;
+    auto setFail = [=](){
+        if (!state.isFinite()) {
+            return SkPath::kUnknown_Convexity;
+        }
+        return setComputedConvexity(SkPath::kConcave_Convexity);
+    };
+
+    while ((verb = iter.next(pts, false, false)) != SkPath::kDone_Verb) {
+        switch (verb) {
+            case kMove_Verb:
+                if (++contourCount > 1) {
+                    return setComputedConvexity(kConcave_Convexity);
+                }
+                state.setMovePt(pts[0]);
+                count = 0;
+                break;
+            case kLine_Verb:
+                count = 1;
+                break;
+            case kQuad_Verb:
+                // fall through
+            case kConic_Verb:
+                count = 2;
+                break;
+            case kCubic_Verb:
+                count = 3;
+                break;
+            case kClose_Verb:
+                if (!state.close()) {
+                    return setFail();
+                }
+                count = 0;
+                break;
+            default:
+                SkDEBUGFAIL("bad verb");
+                return setComputedConvexity(kConcave_Convexity);
+        }
+        for (int i = 1; i <= count; i++) {
+            if (!state.addPt(pts[i])) {
+                return setFail();
+            }
+        }
+    }
+
+    if (this->getFirstDirection() == SkPathPriv::kUnknown_FirstDirection) {
+        if (state.getFirstDirection() == SkPathPriv::kUnknown_FirstDirection
+                && !this->getBounds().isEmpty()) {
+            return setComputedConvexity(state.reversals() < 3 ?
+                    kConvex_Convexity : kConcave_Convexity);
+        }
+        this->setFirstDirection(state.getFirstDirection());
+    }
+    return setComputedConvexity(kConvex_Convexity);
+}
+
+bool SkPathPriv::IsConvex(const SkPoint points[], int count) {
+    SkPath::Convexity convexity = Convexicator::BySign(points, count);
+    if (SkPath::kConvex_Convexity != convexity) {
+        return false;
+    }
+    Convexicator state;
+    state.setMovePt(points[0]);
+    for (int i = 1; i < count; i++) {
+        if (!state.addPt(points[i])) {
+            return false;
+        }
+    }
+    if (!state.addPt(points[0])) {
+        return false;
+    }
+    if (!state.close()) {
+        return false;
+    }
+    return state.getFirstDirection() != SkPathPriv::kUnknown_FirstDirection
+            || state.reversals() < 3;
+}
+
+#endif
+
 ///////////////////////////////////////////////////////////////////////////////
 
 class ContourIter {
diff --git a/src/core/SkPathPriv.h b/src/core/SkPathPriv.h
index 8962d0e..14de724 100644
--- a/src/core/SkPathPriv.h
+++ b/src/core/SkPathPriv.h
@@ -10,6 +10,14 @@
 
 #include "SkPath.h"
 
+#define SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE 0
+
+#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE
+    #define COLINEAR_DIAGONAL_CONVEXITY kConcave_Convexity
+#else
+    #define COLINEAR_DIAGONAL_CONVEXITY kConvex_Convexity
+#endif
+
 class SkPathPriv {
 public:
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
@@ -159,6 +167,17 @@
         return path.fPathRef->conicWeights();
     }
 
+#ifndef SK_LEGACY_PATH_CONVEXITY
+    /** Returns true if path formed by pts is convex.
+
+        @param pts    SkPoint array of path
+        @param count  number of entries in array
+
+        @return       true if pts represent a convex geometry
+    */
+    static bool IsConvex(const SkPoint pts[], int count);
+#endif
+
     /** Returns true if the underlying SkPathRef has one single owner. */
     static bool TestingOnly_unique(const SkPath& path) {
         return path.fPathRef->unique();
diff --git a/src/core/SkPathRef.cpp b/src/core/SkPathRef.cpp
index 9d49e32..fb2a70b 100644
--- a/src/core/SkPathRef.cpp
+++ b/src/core/SkPathRef.cpp
@@ -44,7 +44,7 @@
     fPathRef->callGenIDChangeListeners();
     fPathRef->fGenerationID = 0;
     fPathRef->fBoundsIsDirty = true;
-    SkDEBUGCODE(sk_atomic_inc(&fPathRef->fEditorsAttached);)
+    SkDEBUGCODE(fPathRef->fEditorsAttached++;)
 }
 
 // Sort of like makeSpace(0) but the the additional requirement that we actively shrink the
@@ -102,7 +102,7 @@
     SkDEBUGCODE(fPointCnt = 0xAAAAAAA;)
     SkDEBUGCODE(fPointCnt = 0xBBBBBBB;)
     SkDEBUGCODE(fGenerationID = 0xEEEEEEEE;)
-    SkDEBUGCODE(fEditorsAttached = 0x7777777;)
+    SkDEBUGCODE(fEditorsAttached.store(0x7777777);)
 }
 
 static SkPathRef* gEmpty = nullptr;
@@ -687,18 +687,17 @@
 }
 
 uint32_t SkPathRef::genID() const {
-    SkASSERT(!fEditorsAttached);
+    SkASSERT(fEditorsAttached.load() == 0);
     static const uint32_t kMask = (static_cast<int64_t>(1) << SkPathPriv::kPathRefGenIDBitCnt) - 1;
-    if (!fGenerationID) {
-        if (0 == fPointCnt && 0 == fVerbCnt) {
+
+    if (fGenerationID == 0) {
+        if (fPointCnt == 0 && fVerbCnt == 0) {
             fGenerationID = kEmptyGenID;
         } else {
-            static int32_t  gPathRefGenerationID;
-            // do a loop in case our global wraps around, as we never want to return a 0 or the
-            // empty ID
+            static std::atomic<uint32_t> nextID{kEmptyGenID + 1};
             do {
-                fGenerationID = (sk_atomic_inc(&gPathRefGenerationID) + 1) & kMask;
-            } while (fGenerationID <= kEmptyGenID);
+                fGenerationID = nextID.fetch_add(1, std::memory_order_relaxed) & kMask;
+            } while (fGenerationID == 0 || fGenerationID == kEmptyGenID);
         }
     }
     return fGenerationID;
diff --git a/src/core/SkPictureFlat.h b/src/core/SkPictureFlat.h
index ddf3b8d..5231b69 100644
--- a/src/core/SkPictureFlat.h
+++ b/src/core/SkPictureFlat.h
@@ -85,7 +85,7 @@
     DRAW_ANNOTATION,
     DRAW_DRAWABLE,
     DRAW_DRAWABLE_MATRIX,
-    DRAW_TEXT_RSXFORM,
+    DRAW_TEXT_RSXFORM_DEPRECATED_DEC_2018,
 
     TRANSLATE_Z, // deprecated (M60)
 
@@ -98,7 +98,9 @@
     FLUSH,
 
     DRAW_IMAGE_SET,
-    LAST_DRAWTYPE_ENUM = DRAW_IMAGE_SET
+
+    SAVE_BEHIND,
+    LAST_DRAWTYPE_ENUM = SAVE_BEHIND,
 };
 
 enum DrawVertexFlags {
@@ -126,6 +128,10 @@
     SAVELAYERREC_HAS_CLIPMATRIX = 1 << 5,
 };
 
+enum SaveBehindFlatFlags {
+    SAVEBEHIND_HAS_SUBSET = 1 << 0,
+};
+
 ///////////////////////////////////////////////////////////////////////////////
 // clipparams are packed in 5 bits
 //  doAA:1 | clipOp:4
diff --git a/src/core/SkPicturePlayback.cpp b/src/core/SkPicturePlayback.cpp
index c204b5d..3dbe321 100644
--- a/src/core/SkPicturePlayback.cpp
+++ b/src/core/SkPicturePlayback.cpp
@@ -78,7 +78,7 @@
                     fCount = 0;
                 } else {
                     fCount = SkPaintPriv::ValidCountText(fText, fByteLength,
-                                                         (SkTextEncoding)paint->getTextEncoding());
+                                                         SkPaintPriv::GetEncoding(*paint));
                     reader->validate(fCount > 0);
                 }
             }
@@ -459,7 +459,10 @@
             BREAK_ON_READ_ERROR(reader);
 
             if (paint && text.text()) {
-                canvas->drawPosText(text.text(), text.length(), pos, *paint);
+                SkFont font = SkFont::LEGACY_ExtractFromPaint(*paint);
+                auto blob = SkTextBlob::MakeFromPosText(text.text(), text.length(), pos, font,
+                                                        SkPaintPriv::GetEncoding(*paint));
+                canvas->drawTextBlob(blob, 0, 0, *paint);
             }
         } break;
         case DRAW_POS_TEXT_TOP_BOTTOM: {
@@ -474,7 +477,10 @@
 
             SkRect clip = canvas->getLocalClipBounds();
             if (top < clip.fBottom && bottom > clip.fTop && paint && text.text()) {
-                canvas->drawPosText(text.text(), text.length(), pos, *paint);
+                SkFont font = SkFont::LEGACY_ExtractFromPaint(*paint);
+                auto blob = SkTextBlob::MakeFromPosText(text.text(), text.length(), pos, font,
+                                                        SkPaintPriv::GetEncoding(*paint));
+                canvas->drawTextBlob(blob, 0, 0, *paint);
             }
         } break;
         case DRAW_POS_TEXT_H: {
@@ -487,7 +493,10 @@
             BREAK_ON_READ_ERROR(reader);
 
             if (paint && text.text()) {
-                canvas->drawPosTextH(text.text(), text.length(), xpos, constY, *paint);
+                SkFont font = SkFont::LEGACY_ExtractFromPaint(*paint);
+                auto blob = SkTextBlob::MakeFromPosTextH(text.text(), text.length(), xpos, constY,
+                                                         font, SkPaintPriv::GetEncoding(*paint));
+                canvas->drawTextBlob(blob, 0, 0, *paint);
             }
         } break;
         case DRAW_POS_TEXT_H_TOP_BOTTOM: {
@@ -504,7 +513,10 @@
             const SkScalar constY = *xpos++;
             SkRect clip = canvas->getLocalClipBounds();
             if (top < clip.fBottom && bottom > clip.fTop && paint && text.text()) {
-                canvas->drawPosTextH(text.text(), text.length(), xpos, constY, *paint);
+                SkFont font = SkFont::LEGACY_ExtractFromPaint(*paint);
+                auto blob = SkTextBlob::MakeFromPosTextH(text.text(), text.length(), xpos, constY,
+                                                         font, SkPaintPriv::GetEncoding(*paint));
+                canvas->drawTextBlob(blob, 0, 0, *paint);
             }
         } break;
         case DRAW_RECT: {
@@ -606,21 +618,24 @@
             BREAK_ON_READ_ERROR(reader);
             // no longer supported, so we draw nothing
         } break;
-        case DRAW_TEXT_RSXFORM: {
+        case DRAW_TEXT_RSXFORM_DEPRECATED_DEC_2018: {
             const SkPaint* paint = fPictureData->getPaint(reader);
             uint32_t count = reader->readUInt();
             uint32_t flags = reader->readUInt();
             TextContainer text(reader, paint);
             const SkRSXform* xform = (const SkRSXform*)reader->skip(count, sizeof(SkRSXform));
-            const SkRect* cull = nullptr;
             if (flags & DRAW_TEXT_RSXFORM_HAS_CULL) {
-                cull = (const SkRect*)reader->skip(sizeof(SkRect));
+                // skip past cull rect
+                (void)reader->skip(sizeof(SkRect));
             }
             reader->validate(count == text.count());
             BREAK_ON_READ_ERROR(reader);
 
             if (text.text()) {
-                canvas->drawTextRSXform(text.text(), text.length(), xform, cull, *paint);
+                SkFont font = SkFont::LEGACY_ExtractFromPaint(*paint);
+                auto blob = SkTextBlob::MakeFromRSXform(text.text(), text.length(), xform, font,
+                                                        SkPaintPriv::GetEncoding(*paint));
+                canvas->drawTextBlob(blob, 0, 0, *paint);
             }
         } break;
         case DRAW_VERTICES_OBJECT: {
@@ -647,6 +662,16 @@
         case SAVE:
             canvas->save();
             break;
+        case SAVE_BEHIND: {
+            uint32_t flags = reader->readInt();
+            const SkRect* subset = nullptr;
+            SkRect storage;
+            if (flags & SAVEBEHIND_HAS_SUBSET) {
+                reader->readRect(&storage);
+                subset = &storage;
+            }
+            SkCanvasPriv::SaveBehind(canvas, subset);
+        } break;
         case SAVE_LAYER_SAVEFLAGS_DEPRECATED: {
             SkRect storage;
             const SkRect* boundsPtr = get_rect_ptr(reader, &storage);
diff --git a/src/core/SkPictureRecord.cpp b/src/core/SkPictureRecord.cpp
index 5876969..d179b06 100644
--- a/src/core/SkPictureRecord.cpp
+++ b/src/core/SkPictureRecord.cpp
@@ -75,6 +75,26 @@
     return kNoLayer_SaveLayerStrategy;
 }
 
+bool SkPictureRecord::onDoSaveBehind(const SkRect* subset) {
+    fRestoreOffsetStack.push_back(-(int32_t)fWriter.bytesWritten());
+
+    size_t size = sizeof(kUInt32Size) + sizeof(uint32_t); // op + flags
+    uint32_t flags = 0;
+    if (subset) {
+        flags |= SAVEBEHIND_HAS_SUBSET;
+        size += sizeof(*subset);
+    }
+
+    size_t initialOffset = this->addDraw(SAVE_BEHIND, &size);
+    this->addInt(flags);
+    if (subset) {
+        this->addRect(*subset);
+    }
+
+    this->validate(initialOffset, size);
+    return false;
+}
+
 void SkPictureRecord::recordSaveLayer(const SaveLayerRec& rec) {
     // op + flatflags
     size_t size = 2 * kUInt32Size;
@@ -561,79 +581,6 @@
     this->validate(initialOffset, size);
 }
 
-void SkPictureRecord::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                                 const SkPaint& paint) {
-    // op + paint index + length + 'length' worth of chars + x + y
-    size_t size = 3 * kUInt32Size + SkAlign4(byteLength) + 2 * sizeof(SkScalar);
-
-    DrawType op = DRAW_TEXT;
-    size_t initialOffset = this->addDraw(op, &size);
-    this->addPaint(paint);
-    this->addText(text, byteLength);
-    this->addScalar(x);
-    this->addScalar(y);
-    this->validate(initialOffset, size);
-}
-
-void SkPictureRecord::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                                    const SkPaint& paint) {
-    int points = paint.countText(text, byteLength);
-
-    // op + paint index + length + 'length' worth of data + num points + x&y point data
-    size_t size = 3 * kUInt32Size + SkAlign4(byteLength) + kUInt32Size + points * sizeof(SkPoint);
-
-    DrawType op = DRAW_POS_TEXT;
-
-    size_t initialOffset = this->addDraw(op, &size);
-    this->addPaint(paint);
-    this->addText(text, byteLength);
-    this->addInt(points);
-    fWriter.writeMul4(pos, points * sizeof(SkPoint));
-    this->validate(initialOffset, size);
-}
-
-void SkPictureRecord::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                                     SkScalar constY, const SkPaint& paint) {
-    int points = paint.countText(text, byteLength);
-
-    // op + paint index + length + 'length' worth of data + num points
-    size_t size = 3 * kUInt32Size + SkAlign4(byteLength) + 1 * kUInt32Size;
-    // + y + the actual points
-    size += 1 * kUInt32Size + points * sizeof(SkScalar);
-
-    size_t initialOffset = this->addDraw(DRAW_POS_TEXT_H, &size);
-    this->addPaint(paint);
-    this->addText(text, byteLength);
-    this->addInt(points);
-    this->addScalar(constY);
-    fWriter.writeMul4(xpos, points * sizeof(SkScalar));
-    this->validate(initialOffset, size);
-}
-
-void SkPictureRecord::onDrawTextRSXform(const void* text, size_t byteLength,
-                                        const SkRSXform xform[], const SkRect* cull,
-                                        const SkPaint& paint) {
-    const int count = paint.countText(text, byteLength);
-    // [op + paint-index + count + flags + length] + [text] + [xform] + cull
-    size_t size = 5 * kUInt32Size + SkAlign4(byteLength) + count * sizeof(SkRSXform);
-    uint32_t flags = 0;
-    if (cull) {
-        flags |= DRAW_TEXT_RSXFORM_HAS_CULL;
-        size += sizeof(SkRect);
-    }
-
-    size_t initialOffset = this->addDraw(DRAW_TEXT_RSXFORM, &size);
-    this->addPaint(paint);
-    this->addInt(count);
-    this->addInt(flags);
-    this->addText(text, byteLength);
-    fWriter.write(xform, count * sizeof(SkRSXform));
-    if (cull) {
-        fWriter.write(cull, sizeof(SkRect));
-    }
-    this->validate(initialOffset, size);
-}
-
 void SkPictureRecord::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                      const SkPaint& paint) {
 
diff --git a/src/core/SkPictureRecord.h b/src/core/SkPictureRecord.h
index 85eeb5b..8ed9edf 100644
--- a/src/core/SkPictureRecord.h
+++ b/src/core/SkPictureRecord.h
@@ -157,6 +157,7 @@
 
     void willSave() override;
     SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override;
+    bool onDoSaveBehind(const SkRect*) override;
     void willRestore() override;
 
     void didConcat(const SkMatrix&) override;
@@ -164,12 +165,6 @@
 
     void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
 
-    void onDrawText(const void* text, size_t, SkScalar x, SkScalar y, const SkPaint&) override;
-    void onDrawPosText(const void* text, size_t, const SkPoint pos[], const SkPaint&) override;
-    void onDrawPosTextH(const void* text, size_t, const SkScalar xpos[], SkScalar constY,
-                        const SkPaint&) override;
-    void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                           const SkRect* cull, const SkPaint&) override;
     void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                 const SkPaint& paint) override;
 
diff --git a/src/core/SkRWBuffer.cpp b/src/core/SkRWBuffer.cpp
index 69e4a2e..6e180a6 100644
--- a/src/core/SkRWBuffer.cpp
+++ b/src/core/SkRWBuffer.cpp
@@ -7,7 +7,6 @@
 
 #include "SkRWBuffer.h"
 
-#include "SkAtomics.h"
 #include "SkMakeUnique.h"
 #include "SkMalloc.h"
 #include "SkStream.h"
diff --git a/src/core/SkRasterPipeline.cpp b/src/core/SkRasterPipeline.cpp
index ab821d5..50df7ac 100644
--- a/src/core/SkRasterPipeline.cpp
+++ b/src/core/SkRasterPipeline.cpp
@@ -77,20 +77,6 @@
     SkDebugf("\n");
 }
 
-//#define TRACK_COLOR_HISTOGRAM
-#ifdef TRACK_COLOR_HISTOGRAM
-    static int gBlack;
-    static int gWhite;
-    static int gColor;
-    #define INC_BLACK   gBlack++
-    #define INC_WHITE   gWhite++
-    #define INC_COLOR   gColor++
-#else
-    #define INC_BLACK
-    #define INC_WHITE
-    #define INC_COLOR
-#endif
-
 void SkRasterPipeline::append_set_rgb(SkArenaAlloc* alloc, const float rgb[3]) {
     auto arg = alloc->makeArrayDefault<float>(3);
     arg[0] = rgb[0];
@@ -114,10 +100,8 @@
 
     if (rgba[0] == 0 && rgba[1] == 0 && rgba[2] == 0 && rgba[3] == 1) {
         this->append(black_color);
-        INC_BLACK;
     } else if (rgba[0] == 1 && rgba[1] == 1 && rgba[2] == 1 && rgba[3] == 1) {
         this->append(white_color);
-        INC_WHITE;
     } else {
         auto ctx = alloc->make<SkRasterPipeline_UniformColorCtx>();
         Sk4f color = Sk4f::Load(rgba);
@@ -138,32 +122,11 @@
         } else {
             this->unchecked_append(unbounded_uniform_color, ctx);
         }
-
-        INC_COLOR;
     }
-
-#ifdef TRACK_COLOR_HISTOGRAM
-    SkDebugf("B=%d W=%d C=%d\n", gBlack, gWhite, gColor);
-#endif
 }
 
-#undef INC_BLACK
-#undef INC_WHITE
-#undef INC_COLOR
-
-//static int gCounts[5] = { 0, 0, 0, 0, 0 };
-
 void SkRasterPipeline::append_matrix(SkArenaAlloc* alloc, const SkMatrix& matrix) {
     SkMatrix::TypeMask mt = matrix.getType();
-#if 0
-    if (mt > 4) mt = 4;
-    gCounts[mt] += 1;
-    SkDebugf("matrices: %d %d %d %d %d\n",
-             gCounts[0], gCounts[1], gCounts[2], gCounts[3], gCounts[4]);
-#endif
-
-    // Based on a histogram of skps, we determined the following special cases were common, more
-    // or fewer can be used if client behaviors change.
 
     if (mt == SkMatrix::kIdentity_Mask) {
         return;
diff --git a/src/core/SkRasterPipeline.h b/src/core/SkRasterPipeline.h
index aebced0..c93fa1e 100644
--- a/src/core/SkRasterPipeline.h
+++ b/src/core/SkRasterPipeline.h
@@ -12,10 +12,10 @@
 #include "SkColor.h"
 #include "SkImageInfo.h"
 #include "SkNx.h"
-#include "SkTArray.h"
+#include "SkTArray.h" // TODO: unused
 #include "SkTypes.h"
 #include <functional>
-#include <vector>
+#include <vector>  // TODO: unused
 
 /**
  * SkRasterPipeline provides a cheap way to chain together a pixel processing pipeline.
@@ -254,6 +254,7 @@
 
     void unchecked_append(StockStage, void*);
 
+    // Used by old single-program void** style execution.
     SkArenaAlloc* fAlloc;
     StageList*    fStages;
     int           fNumStages;
diff --git a/src/core/SkReadBuffer.h b/src/core/SkReadBuffer.h
index 21e61c2..254ff6f 100644
--- a/src/core/SkReadBuffer.h
+++ b/src/core/SkReadBuffer.h
@@ -43,6 +43,9 @@
         kStoreImageBounds_Version          = 63,
         kRemoveOccluderFromBlurMaskFilter  = 64,
         kFloat4PaintColor_Version          = 65,
+        kSaveBehind_Version                = 66,
+        kSerializeFonts_Version            = 67,
+        kPaintDoesntSerializeFonts_Version = 68,
     };
 
     /**
@@ -245,6 +248,9 @@
         kStoreImageBounds_Version          = 63,
         kRemoveOccluderFromBlurMaskFilter  = 64,
         kFloat4PaintColor_Version          = 65,
+        kSaveBehind_Version                = 66,
+        kSerializeFonts_Version            = 67,
+        kPaintDoesntSerializeFonts_Version = 68,
     };
 
     bool isVersionLT(Version) const { return false; }
diff --git a/src/core/SkRecordDraw.cpp b/src/core/SkRecordDraw.cpp
index be6db22..cb97336 100644
--- a/src/core/SkRecordDraw.cpp
+++ b/src/core/SkRecordDraw.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "SkRecordDraw.h"
+#include "SkCanvasPriv.h"
 #include "SkImage.h"
 #include "SkPatchUtils.h"
 
@@ -82,6 +83,11 @@
                                                  r.clipMask.get(),
                                                  r.clipMatrix,
                                                  r.saveLayerFlags)));
+
+template <> void Draw::draw(const SaveBehind& r) {
+    SkCanvasPriv::SaveBehind(fCanvas, r.subset);
+}
+
 DRAW(SetMatrix, setMatrix(SkMatrix::Concat(fInitialCTM, r.matrix)));
 DRAW(Concat, concat(r.matrix));
 DRAW(Translate, translate(r.dx, r.dy));
@@ -116,14 +122,10 @@
 DRAW(DrawPatch, drawPatch(r.cubics, r.colors, r.texCoords, r.bmode, r.paint));
 DRAW(DrawPicture, drawPicture(r.picture.get(), &r.matrix, r.paint));
 DRAW(DrawPoints, drawPoints(r.mode, r.count, r.pts, r.paint));
-DRAW(DrawPosText, drawPosText(r.text, r.byteLength, r.pos, r.paint));
-DRAW(DrawPosTextH, drawPosTextH(r.text, r.byteLength, r.xpos, r.y, r.paint));
 DRAW(DrawRRect, drawRRect(r.rrect, r.paint));
 DRAW(DrawRect, drawRect(r.rect, r.paint));
 DRAW(DrawRegion, drawRegion(r.region, r.paint));
-DRAW(DrawText, drawText(r.text, r.byteLength, r.x, r.y, r.paint));
 DRAW(DrawTextBlob, drawTextBlob(r.blob.get(), r.x, r.y, r.paint));
-DRAW(DrawTextRSXform, drawTextRSXform(r.text, r.byteLength, r.xforms, r.cull, r.paint));
 DRAW(DrawAtlas, drawAtlas(r.atlas.get(),
                           r.xforms, r.texs, r.colors, r.count, r.mode, r.cull, r.paint));
 DRAW(DrawVertices, drawVertices(r.vertices, r.bones, r.boneCount, r.bmode, r.paint));
@@ -247,6 +249,7 @@
     // from the bounds of the ops in the same Save block.
     void trackBounds(const Save&)          { this->pushSaveBlock(nullptr); }
     void trackBounds(const SaveLayer& op)  { this->pushSaveBlock(op.paint); }
+    void trackBounds(const SaveBehind&)    { this->pushSaveBlock(nullptr); }
     void trackBounds(const Restore&) { fBounds[fCurrentOp] = this->popSaveBlock(); }
 
     void trackBounds(const SetMatrix&)         { this->pushControl(); }
@@ -349,9 +352,6 @@
 
     Bounds bounds(const Flush&) const { return fCullRect; }
 
-    // FIXME: this method could use better bounds
-    Bounds bounds(const DrawText&) const { return fCullRect; }
-
     Bounds bounds(const DrawPaint&) const { return fCullRect; }
     Bounds bounds(const NoOp&)  const { return Bounds::MakeEmpty(); }    // NoOps don't draw.
 
@@ -436,41 +436,6 @@
         return this->adjustAndMap(dst, op.paint);
     }
 
-    Bounds bounds(const DrawPosText& op) const {
-        const int N = op.paint.countText(op.text, op.byteLength);
-        if (N == 0) {
-            return Bounds::MakeEmpty();
-        }
-
-        SkRect dst;
-        dst.set(op.pos, N);
-        AdjustTextForFontMetrics(&dst, op.paint);
-        return this->adjustAndMap(dst, &op.paint);
-    }
-    Bounds bounds(const DrawPosTextH& op) const {
-        const int N = op.paint.countText(op.text, op.byteLength);
-        if (N == 0) {
-            return Bounds::MakeEmpty();
-        }
-
-        SkScalar left = op.xpos[0], right = op.xpos[0];
-        for (int i = 1; i < N; i++) {
-            left  = SkMinScalar(left,  op.xpos[i]);
-            right = SkMaxScalar(right, op.xpos[i]);
-        }
-        SkRect dst = { left, op.y, right, op.y };
-        AdjustTextForFontMetrics(&dst, op.paint);
-        return this->adjustAndMap(dst, &op.paint);
-    }
-
-    Bounds bounds(const DrawTextRSXform& op) const {
-        if (op.cull) {
-            return this->adjustAndMap(*op.cull, nullptr);
-        } else {
-            return fCullRect;
-        }
-    }
-
     Bounds bounds(const DrawTextBlob& op) const {
         SkRect dst = op.blob->bounds();
         dst.offset(op.x, op.y);
@@ -485,30 +450,6 @@
         return this->adjustAndMap(op.rect, nullptr);
     }
 
-    static void AdjustTextForFontMetrics(SkRect* rect, const SkPaint& paint) {
-#ifdef SK_DEBUG
-        SkRect correct = *rect;
-#endif
-        // crbug.com/373785 ~~> xPad = 4x yPad
-        // crbug.com/424824 ~~> bump yPad from 2x text size to 2.5x
-        const SkScalar yPad = 2.5f * paint.getTextSize(),
-                       xPad = 4.0f * yPad;
-        rect->outset(xPad, yPad);
-#ifdef SK_DEBUG
-        SkFontMetrics metrics;
-        paint.getFontMetrics(&metrics);
-        correct.fLeft   += metrics.fXMin;
-        correct.fTop    += metrics.fTop;
-        correct.fRight  += metrics.fXMax;
-        correct.fBottom += metrics.fBottom;
-        // See skia:2862 for why we ignore small text sizes.
-        SkASSERTF(paint.getTextSize() < 0.001f || rect->contains(correct),
-                  "%f %f %f %f vs. %f %f %f %f\n",
-                  -xPad, -yPad, +xPad, +yPad,
-                  metrics.fXMin, metrics.fTop, metrics.fXMax, metrics.fBottom);
-#endif
-    }
-
     // Returns true if rect was meaningfully adjusted for the effects of paint,
     // false if the paint could affect the rect in unknown ways.
     static bool AdjustForPaint(const SkPaint* paint, SkRect* rect) {
diff --git a/src/core/SkRecorder.cpp b/src/core/SkRecorder.cpp
index a6f7bdc..5340bee 100644
--- a/src/core/SkRecorder.cpp
+++ b/src/core/SkRecorder.cpp
@@ -262,43 +262,6 @@
     this->append<SkRecords::DrawImageSet>(std::move(setCopy), count, filterQuality, mode);
 }
 
-void SkRecorder::onDrawText(const void* text, size_t byteLength,
-                            SkScalar x, SkScalar y, const SkPaint& paint) {
-    this->append<SkRecords::DrawText>(
-           paint, this->copy((const char*)text, byteLength), byteLength, x, y);
-}
-
-void SkRecorder::onDrawPosText(const void* text, size_t byteLength,
-                               const SkPoint pos[], const SkPaint& paint) {
-    const int points = paint.countText(text, byteLength);
-    this->append<SkRecords::DrawPosText>(
-           paint,
-           this->copy((const char*)text, byteLength),
-           byteLength,
-           this->copy(pos, points));
-}
-
-void SkRecorder::onDrawPosTextH(const void* text, size_t byteLength,
-                                const SkScalar xpos[], SkScalar constY, const SkPaint& paint) {
-    const int points = paint.countText(text, byteLength);
-    this->append<SkRecords::DrawPosTextH>(
-           paint,
-           this->copy((const char*)text, byteLength),
-           SkToUInt(byteLength),
-           constY,
-           this->copy(xpos, points));
-}
-
-void SkRecorder::onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                                   const SkRect* cull, const SkPaint& paint) {
-    this->append<SkRecords::DrawTextRSXform>(
-           paint,
-           this->copy((const char*)text, byteLength),
-           byteLength,
-           this->copy(xform, paint.countText(text, byteLength)),
-           this->copy(cull));
-}
-
 void SkRecorder::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                 const SkPaint& paint) {
     TRY_MINIRECORDER(drawTextBlob, blob, x, y, paint);
@@ -374,6 +337,11 @@
     return SkCanvas::kNoLayer_SaveLayerStrategy;
 }
 
+bool SkRecorder::onDoSaveBehind(const SkRect* subset) {
+    this->append<SkRecords::SaveBehind>(this->copy(subset));
+    return false;
+}
+
 void SkRecorder::didRestore() {
     this->append<SkRecords::Restore>(this->getTotalMatrix());
 }
diff --git a/src/core/SkRecorder.h b/src/core/SkRecorder.h
index 45ff41a..1a68b1f 100644
--- a/src/core/SkRecorder.h
+++ b/src/core/SkRecorder.h
@@ -58,6 +58,7 @@
 
     void willSave() override;
     SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override;
+    bool onDoSaveBehind(const SkRect*) override;
     void willRestore() override {}
     void didRestore() override;
 
@@ -67,25 +68,6 @@
 
     void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
     void onDrawDrawable(SkDrawable*, const SkMatrix*) override;
-    void onDrawText(const void* text,
-                    size_t byteLength,
-                    SkScalar x,
-                    SkScalar y,
-                    const SkPaint& paint) override;
-    void onDrawPosText(const void* text,
-                       size_t byteLength,
-                       const SkPoint pos[],
-                       const SkPaint& paint) override;
-    void onDrawPosTextH(const void* text,
-                        size_t byteLength,
-                        const SkScalar xpos[],
-                        SkScalar constY,
-                        const SkPaint& paint) override;
-    void onDrawTextRSXform(const void* text,
-                           size_t byteLength,
-                           const SkRSXform[],
-                           const SkRect* cull,
-                           const SkPaint& paint) override;
     void onDrawTextBlob(const SkTextBlob* blob,
                         SkScalar x,
                         SkScalar y,
diff --git a/src/core/SkRecords.h b/src/core/SkRecords.h
index 20e6644..7500adc 100644
--- a/src/core/SkRecords.h
+++ b/src/core/SkRecords.h
@@ -25,14 +25,6 @@
 #include "SkTextBlob.h"
 #include "SkVertices.h"
 
-// Windows.h, will pull in all of the GDI defines.  GDI #defines
-// DrawText to DrawTextA or DrawTextW, but SkRecord has a struct
-// called DrawText. Since this file does not use GDI, undefing
-// DrawText makes things less confusing.
-#ifdef DrawText
-#undef DrawText
-#endif
-
 namespace SkRecords {
 
 // A list of all the types of canvas calls we can record.
@@ -51,6 +43,7 @@
     M(Restore)                                                      \
     M(Save)                                                         \
     M(SaveLayer)                                                    \
+    M(SaveBehind)                                                   \
     M(SetMatrix)                                                    \
     M(Translate)                                                    \
     M(Concat)                                                       \
@@ -72,10 +65,6 @@
     M(DrawPatch)                                                    \
     M(DrawPicture)                                                  \
     M(DrawPoints)                                                   \
-    M(DrawPosText)                                                  \
-    M(DrawPosTextH)                                                 \
-    M(DrawText)                                                     \
-    M(DrawTextRSXform)                                              \
     M(DrawRRect)                                                    \
     M(DrawRect)                                                     \
     M(DrawRegion)                                                   \
@@ -188,6 +177,9 @@
        Optional<SkMatrix> clipMatrix;
        SkCanvas::SaveLayerFlags saveLayerFlags);
 
+RECORD(SaveBehind, 0,
+       Optional<SkRect> subset);
+
 RECORD(SetMatrix, 0,
         TypedMatrix matrix);
 RECORD(Concat, 0,
@@ -288,17 +280,6 @@
         SkCanvas::PointMode mode;
         unsigned count;
         SkPoint* pts);
-RECORD(DrawPosText, kDraw_Tag|kHasText_Tag|kHasPaint_Tag,
-        SkPaint paint;
-        PODArray<char> text;
-        size_t byteLength;
-        PODArray<SkPoint> pos);
-RECORD(DrawPosTextH, kDraw_Tag|kHasText_Tag|kHasPaint_Tag,
-        SkPaint paint;
-        PODArray<char> text;
-        unsigned byteLength;
-        SkScalar y;
-        PODArray<SkScalar> xpos);
 RECORD(DrawRRect, kDraw_Tag|kHasPaint_Tag,
         SkPaint paint;
         SkRRect rrect);
@@ -308,23 +289,11 @@
 RECORD(DrawRegion, kDraw_Tag|kHasPaint_Tag,
         SkPaint paint;
         SkRegion region);
-RECORD(DrawText, kDraw_Tag|kHasText_Tag|kHasPaint_Tag,
-        SkPaint paint;
-        PODArray<char> text;
-        size_t byteLength;
-        SkScalar x;
-        SkScalar y);
 RECORD(DrawTextBlob, kDraw_Tag|kHasText_Tag|kHasPaint_Tag,
         SkPaint paint;
         sk_sp<const SkTextBlob> blob;
         SkScalar x;
         SkScalar y);
-RECORD(DrawTextRSXform, kDraw_Tag|kHasText_Tag|kHasPaint_Tag,
-        SkPaint paint;
-        PODArray<char> text;
-        size_t byteLength;
-        PODArray<SkRSXform> xforms;
-        Optional<SkRect> cull);
 RECORD(DrawPatch, kDraw_Tag|kHasPaint_Tag,
         SkPaint paint;
         PODArray<SkPoint> cubics;
diff --git a/src/core/SkRegion.cpp b/src/core/SkRegion.cpp
index f057553..9176c7a 100644
--- a/src/core/SkRegion.cpp
+++ b/src/core/SkRegion.cpp
@@ -7,7 +7,6 @@
 
 #include "SkRegion.h"
 
-#include "SkAtomics.h"
 #include "SkMacros.h"
 #include "SkRegionPriv.h"
 #include "SkSafeMath.h"
@@ -27,8 +26,6 @@
  *  Y-Sentinel
  */
 
-SkDEBUGCODE(int32_t gRgnAllocCounter;)
-
 /////////////////////////////////////////////////////////////////////////////////////////////////
 
 #define SkRegion_gEmptyRunHeadPtr   ((SkRegionPriv::RunHead*)-1)
@@ -139,9 +136,6 @@
     if (this->isComplex()) {
         SkASSERT(fRunHead->fRefCnt >= 1);
         if (--fRunHead->fRefCnt == 0) {
-            //SkASSERT(gRgnAllocCounter > 0);
-            //SkDEBUGCODE(sk_atomic_dec(&gRgnAllocCounter));
-            //SkDEBUGF("************** gRgnAllocCounter::free %d\n", gRgnAllocCounter);
             sk_free(fRunHead);
         }
     }
diff --git a/src/core/SkRegionPriv.h b/src/core/SkRegionPriv.h
index b34aada..ee33ad1 100644
--- a/src/core/SkRegionPriv.h
+++ b/src/core/SkRegionPriv.h
@@ -8,13 +8,10 @@
 #ifndef SkRegionPriv_DEFINED
 #define SkRegionPriv_DEFINED
 
-#include "SkRegion.h"
-
-#include "SkAtomics.h"
 #include "SkMalloc.h"
+#include "SkRegion.h"
 #include "SkTo.h"
-
-
+#include <atomic>
 #include <functional>
 
 class SkRegionPriv {
@@ -42,8 +39,6 @@
 #define assert_sentinel(value, isSentinel) \
     SkASSERT(SkRegionValueIsSentinel(value) == isSentinel)
 
-//SkDEBUGCODE(extern int32_t gRgnAllocCounter;)
-
 #ifdef SK_DEBUG
 // Given the first interval (just past the interval-count), compute the
 // interval count, by search for the x-sentinel
@@ -86,9 +81,6 @@
     }
 
     static RunHead* Alloc(int count) {
-        //SkDEBUGCODE(sk_atomic_inc(&gRgnAllocCounter);)
-        //SkDEBUGF("************** gRgnAllocCounter::alloc %d\n", gRgnAllocCounter);
-
         if (count < SkRegion::kRectRegionRuns) {
             return nullptr;
         }
@@ -131,9 +123,8 @@
     RunHead* ensureWritable() {
         RunHead* writable = this;
         if (fRefCnt > 1) {
-            // We need to alloc & copy the current region before we call
-            // sk_atomic_dec because it could be freed in the meantime,
-            // otherwise.
+            // We need to alloc & copy the current region before decrease
+            // the refcount because it could be freed in the meantime.
             writable = Alloc(fRunCount, fYSpanCount, fIntervalCount);
             memcpy(writable->writable_runs(), this->readonly_runs(),
                    fRunCount * sizeof(RunType));
diff --git a/src/core/SkRemoteGlyphCache.cpp b/src/core/SkRemoteGlyphCache.cpp
index 2a399dd..95b57e2 100644
--- a/src/core/SkRemoteGlyphCache.cpp
+++ b/src/core/SkRemoteGlyphCache.cpp
@@ -57,10 +57,9 @@
 
 enum DescriptorType : bool { kKey = false, kDevice = true };
 static const SkDescriptor* create_descriptor(
-        DescriptorType type, const SkPaint& paint, const SkMatrix& m,
+        DescriptorType type, const SkPaint& paint, const SkFont& font, const SkMatrix& m,
         const SkSurfaceProps& props, SkScalerContextFlags flags,
         SkAutoDescriptor* ad, SkScalerContextEffects* effects) {
-    SkFont font = SkFont::LEGACY_ExtractFromPaint(paint);
     SkScalerContextRec deviceRec;
     bool enableTypefaceFiltering = (type == kDevice);
     SkScalerContext::MakeRecAndEffects(
@@ -213,7 +212,7 @@
 void SkTextBlobCacheDiffCanvas::TrackLayerDevice::drawGlyphRunList(
         const SkGlyphRunList& glyphRunList) {
     for (auto& glyphRun : glyphRunList) {
-        this->processGlyphRun(glyphRunList.origin(), glyphRun);
+        this->processGlyphRun(glyphRunList.origin(), glyphRun, glyphRunList.paint());
     }
 }
 
@@ -241,6 +240,10 @@
     return kFullLayer_SaveLayerStrategy;
 }
 
+bool SkTextBlobCacheDiffCanvas::onDoSaveBehind(const SkRect*) {
+    return false;
+}
+
 void SkTextBlobCacheDiffCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                                const SkPaint& paint) {
     SkCanvas::onDrawTextBlob(blob, x, y, paint);
@@ -300,12 +303,14 @@
 
 SkStrikeServer::SkGlyphCacheState* SkStrikeServer::getOrCreateCache(
         const SkPaint& paint,
+        const SkFont& font,
         const SkSurfaceProps& props,
         const SkMatrix& matrix,
         SkScalerContextFlags flags,
         SkScalerContextEffects* effects) {
     SkAutoDescriptor keyAutoDesc;
-    auto keyDesc = create_descriptor(kKey, paint, matrix, props, flags, &keyAutoDesc, effects);
+    auto keyDesc = create_descriptor(
+            kKey, paint, font, matrix, props, flags, &keyAutoDesc, effects);
 
     // In cases where tracing is turned off, make sure not to get an unused function warning.
     // Lambdaize the function.
@@ -325,7 +330,7 @@
         auto it = fRemoteGlyphStateMap.find(keyDesc);
         SkASSERT(it != fRemoteGlyphStateMap.end());
         SkGlyphCacheState* cache = it->second.get();
-        cache->setPaint(paint);
+        cache->setFontAndEffects(font, SkScalerContextEffects{paint});
         return cache;
     }
 
@@ -337,13 +342,13 @@
         SkScalerContextEffects deviceEffects;
         SkAutoDescriptor deviceAutoDesc;
         auto deviceDesc = create_descriptor(
-                kDevice, paint, matrix, props, flags, &deviceAutoDesc, &deviceEffects);
+                kDevice, paint, font, matrix, props, flags, &deviceAutoDesc, &deviceEffects);
         SkASSERT(cache->getDeviceDescriptor() == *deviceDesc);
 #endif
         bool locked = fDiscardableHandleManager->lockHandle(it->second->discardableHandleId());
         if (locked) {
             fLockedDescs.insert(it->first);
-            cache->setPaint(paint);
+            cache->setFontAndEffects(font, SkScalerContextEffects{paint});
             return cache;
         }
 
@@ -352,7 +357,7 @@
         fRemoteGlyphStateMap.erase(it);
     }
 
-    auto* tf = paint.getTypeface();
+    auto* tf = font.getTypeface();
     const SkFontID typefaceId = tf->uniqueID();
     if (!fCachedTypefaces.contains(typefaceId)) {
         fCachedTypefaces.add(typefaceId);
@@ -363,7 +368,7 @@
     SkScalerContextEffects deviceEffects;
     SkAutoDescriptor deviceAutoDesc;
     auto deviceDesc = create_descriptor(
-            kDevice, paint, matrix, props, flags, &deviceAutoDesc, &deviceEffects);
+            kDevice, paint, font, matrix, props, flags, &deviceAutoDesc, &deviceEffects);
 
     auto context = tf->createScalerContext(deviceEffects, deviceDesc);
 
@@ -378,7 +383,8 @@
     fRemoteGlyphStateMap[&cacheStatePtr->getKeyDescriptor()] = std::move(cacheState);
 
     checkForDeletedEntries();
-    cacheStatePtr->setPaint(paint);
+
+    cacheStatePtr->setFontAndEffects(font, SkScalerContextEffects{paint});
     return cacheStatePtr;
 }
 
@@ -448,8 +454,7 @@
     // TODO(khushalsagar): Write a strike only if it has any pending glyphs.
     serializer->emplace<bool>(this->hasPendingGlyphs());
     if (!this->hasPendingGlyphs()) {
-        fContext.reset();
-        fPaint = nullptr;
+        this->resetScalerContext();
         return;
     }
 
@@ -491,8 +496,7 @@
         writeGlyphPath(glyphID, serializer);
     }
     fPendingGlyphPaths.clear();
-    fContext.reset();
-    fPaint = nullptr;
+    this->resetScalerContext();
 }
 
 const SkGlyph& SkStrikeServer::SkGlyphCacheState::findGlyph(SkPackedGlyphID glyphID) {
@@ -509,14 +513,20 @@
 
 void SkStrikeServer::SkGlyphCacheState::ensureScalerContext() {
     if (fContext == nullptr) {
-        SkScalerContextEffects effects{*fPaint};
-        auto tf = fPaint->getTypeface();
-        fContext = tf->createScalerContext(effects, fDeviceDescriptor.getDesc());
+        auto tf = fFont->getTypeface();
+        fContext = tf->createScalerContext(fEffects, fDeviceDescriptor.getDesc());
     }
 }
 
-void SkStrikeServer::SkGlyphCacheState::setPaint(const SkPaint& paint) {
-    fPaint = &paint;
+void SkStrikeServer::SkGlyphCacheState::resetScalerContext() {
+    fContext.reset();
+    fFont = nullptr;
+}
+
+void SkStrikeServer::SkGlyphCacheState::setFontAndEffects(
+        const SkFont& font, SkScalerContextEffects effects) {
+    fFont = &font;
+    fEffects = effects;
 }
 
 SkVector SkStrikeServer::SkGlyphCacheState::rounding() const {
diff --git a/src/core/SkRemoteGlyphCache.h b/src/core/SkRemoteGlyphCache.h
index e269391..e4ea6e7 100644
--- a/src/core/SkRemoteGlyphCache.h
+++ b/src/core/SkRemoteGlyphCache.h
@@ -72,13 +72,14 @@
 
 protected:
     SkCanvas::SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override;
+    bool onDoSaveBehind(const SkRect*) override;
     void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                         const SkPaint& paint) override;
 
 private:
     class TrackLayerDevice;
 
-
+    static SkScalar SetupForPath(SkPaint* paint, SkFont* font);
 };
 
 using SkDiscardableHandleId = uint32_t;
@@ -124,7 +125,10 @@
     // Methods used internally in skia ------------------------------------------
     class SkGlyphCacheState;
 
-    SkGlyphCacheState* getOrCreateCache(const SkPaint&, const SkSurfaceProps&, const SkMatrix&,
+    SkGlyphCacheState* getOrCreateCache(const SkPaint&,
+                                        const SkFont& font,
+                                        const SkSurfaceProps&,
+                                        const SkMatrix&,
                                         SkScalerContextFlags flags,
                                         SkScalerContextEffects* effects);
 
diff --git a/src/core/SkRemoteGlyphCacheImpl.h b/src/core/SkRemoteGlyphCacheImpl.h
index 3b50e83..82a6eb9 100644
--- a/src/core/SkRemoteGlyphCacheImpl.h
+++ b/src/core/SkRemoteGlyphCacheImpl.h
@@ -38,7 +38,7 @@
 
     const SkGlyph& findGlyph(SkPackedGlyphID);
 
-    void setPaint(const SkPaint& paint);
+    void setFontAndEffects(const SkFont& font, SkScalerContextEffects effects);
 
     SkVector rounding() const override;
 
@@ -55,6 +55,7 @@
     void writeGlyphPath(const SkPackedGlyphID& glyphID, Serializer* serializer) const;
 
     void ensureScalerContext();
+    void resetScalerContext();
 
     // The set of glyphs cached on the remote client.
     SkTHashSet<SkPackedGlyphID> fCachedGlyphImages;
@@ -80,9 +81,10 @@
     // The context built using fDeviceDescriptor
     std::unique_ptr<SkScalerContext> fContext;
 
-    // This field is set everytime getOrCreateCache. This allows the code to maintain the fContext
-    // as lazy as possible.
-    const SkPaint* fPaint{nullptr};
+    // These fields are set everytime getOrCreateCache. This allows the code to maintain the
+    // fContext as lazy as possible.
+    const SkFont* fFont{nullptr};
+    SkScalerContextEffects fEffects;
 
     // FallbackTextHelper cases require glyph metrics when analyzing a glyph run, in which case
     // we cache them here.
@@ -102,17 +104,21 @@
     void drawGlyphRunList(const SkGlyphRunList& glyphRunList) override;
 
 private:
-    void processGlyphRun(const SkPoint& origin, const SkGlyphRun& glyphRun);
+    void processGlyphRun(
+            const SkPoint& origin, const SkGlyphRun& glyphRun, const SkPaint& runPaint);
 
     void processGlyphRunForMask(
-            const SkGlyphRun& glyphRun, const SkMatrix& runMatrix, SkPoint origin);
+            const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
+            SkPoint origin, const SkPaint& paint);
 
     void processGlyphRunForPaths(
-            const SkGlyphRun& glyphRun, const SkMatrix& runMatrix, SkPoint origin);
+            const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
+            SkPoint origin, const SkPaint& paint);
 
 #if SK_SUPPORT_GPU
     bool maybeProcessGlyphRunForDFT(
-            const SkGlyphRun& glyphRun, const SkMatrix& runMatrix, SkPoint origin);
+            const SkGlyphRun& glyphRun, const SkMatrix& runMatrix,
+            SkPoint origin, const SkPaint& paint);
 #endif
 
     SkStrikeServer* const fStrikeServer;
diff --git a/src/core/SkScalerContext.cpp b/src/core/SkScalerContext.cpp
index 551a769..ffa6c38 100644
--- a/src/core/SkScalerContext.cpp
+++ b/src/core/SkScalerContext.cpp
@@ -555,10 +555,6 @@
     this->generateFontMetrics(fm);
 }
 
-SkUnichar SkScalerContext::generateGlyphToChar(uint16_t glyph) {
-    return 0;
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 
 bool SkScalerContext::internalGetPath(SkPackedGlyphID glyphID, SkPath* devPath) {
@@ -1113,17 +1109,6 @@
     return AutoDescriptorGivenRecAndEffects(rec, *effects, ad);
 }
 
-SkDescriptor* SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
-    const SkPaint& paint, const SkSurfaceProps& surfaceProps,
-    SkScalerContextFlags scalerContextFlags,
-    const SkMatrix& deviceMatrix, SkAutoDescriptor* ad,
-    SkScalerContextEffects* effects)
-{
-    return CreateDescriptorAndEffectsUsingPaint(SkFont::LEGACY_ExtractFromPaint(paint), paint,
-                                                surfaceProps, scalerContextFlags,
-                                                deviceMatrix, ad, effects);
-}
-
 static size_t calculate_size_and_flatten(const SkScalerContextRec& rec,
                                          const SkScalerContextEffects& effects,
                                          SkBinaryWriteBuffer* effectBuffer) {
diff --git a/src/core/SkScalerContext.h b/src/core/SkScalerContext.h
index 315b51e..06ccce2 100644
--- a/src/core/SkScalerContext.h
+++ b/src/core/SkScalerContext.h
@@ -285,13 +285,6 @@
         return generateCharToGlyph(uni);
     }
 
-    /** Map the glyphID to its glyph index, and then to its char code. Unmapped
-        glyphs return zero.
-    */
-    SkUnichar glyphIDToChar(uint16_t glyphID) {
-        return (glyphID < getGlyphCount()) ? generateGlyphToChar(glyphID) : 0;
-    }
-
     unsigned    getGlyphCount() { return this->generateGlyphCount(); }
     void        getAdvance(SkGlyph*);
     void        getMetrics(SkGlyph*);
@@ -368,12 +361,6 @@
         const SkMatrix& deviceMatrix, SkAutoDescriptor* ad,
         SkScalerContextEffects* effects);
 
-    static SkDescriptor* CreateDescriptorAndEffectsUsingPaint(
-        const SkPaint& paint, const SkSurfaceProps& surfaceProps,
-        SkScalerContextFlags scalerContextFlags,
-        const SkMatrix& deviceMatrix, SkAutoDescriptor* ad,
-        SkScalerContextEffects* effects);
-
 protected:
     SkScalerContextRec fRec;
 
@@ -417,12 +404,6 @@
      */
     virtual uint16_t generateCharToGlyph(SkUnichar unichar) = 0;
 
-    /** Returns the unichar for the given glyph id.
-     *  If there is no 1:1 mapping from the glyph id to a unichar, returns 0.
-     *  The default implementation always returns 0, indicating failure.
-     */
-    virtual SkUnichar generateGlyphToChar(uint16_t glyphId);
-
     void forceGenerateImageFromPath() { fGenerateImageFromPath = true; }
     void forceOffGenerateImageFromPath() { fGenerateImageFromPath = false; }
 
diff --git a/src/core/SkScan_Path.cpp b/src/core/SkScan_Path.cpp
index f14559a..2120343 100644
--- a/src/core/SkScan_Path.cpp
+++ b/src/core/SkScan_Path.cpp
@@ -109,7 +109,6 @@
     for (;;) {
         int     w = 0;
         int     left SK_INIT_TO_AVOID_WARNING;
-        bool    in_interval = false;
         SkEdge* currE = prevHead->fNext;
         SkFixed prevX = prevHead->fX;
 
@@ -123,32 +122,34 @@
             SkASSERT(currE->fLastY >= curr_y);
 
             int x = SkFixedRoundToInt(currE->fX);
+
+            if ((w & windingMask) == 0) { // we're starting interval
+                left = x;
+            }
+
             w += currE->fWinding;
+
             if ((w & windingMask) == 0) { // we finished an interval
-                SkASSERT(in_interval);
                 int width = x - left;
                 SkASSERT(width >= 0);
-                if (width)
+                if (width > 0) {
                     blitter->blitH(left, curr_y, width);
-                in_interval = false;
-            } else if (!in_interval) {
-                left = x;
-                in_interval = true;
+                }
             }
 
             SkEdge* next = currE->fNext;
             SkFixed newX;
 
             if (currE->fLastY == curr_y) {    // are we done with this edge?
-                if (currE->fCurveCount < 0) {
-                    if (((SkCubicEdge*)currE)->updateCubic()) {
-                        SkASSERT(currE->fFirstY == curr_y + 1);
-
+                if (currE->fCurveCount > 0) {
+                    if (((SkQuadraticEdge*)currE)->updateQuadratic()) {
                         newX = currE->fX;
                         goto NEXT_X;
                     }
-                } else if (currE->fCurveCount > 0) {
-                    if (((SkQuadraticEdge*)currE)->updateQuadratic()) {
+                } else if (currE->fCurveCount < 0) {
+                    if (((SkCubicEdge*)currE)->updateCubic()) {
+                        SkASSERT(currE->fFirstY == curr_y + 1);
+
                         newX = currE->fX;
                         goto NEXT_X;
                     }
@@ -169,8 +170,7 @@
             SkASSERT(currE);
         }
 
-        // was our right-edge culled away?
-        if (in_interval) {
+        if ((w & windingMask) != 0) { // was our right-edge culled away?
             int width = rightClip - left;
             if (width > 0) {
                 blitter->blitH(left, curr_y, width);
@@ -210,48 +210,49 @@
     return true;
 }
 
-static void walk_convex_edges(SkEdge* prevHead, SkPath::FillType,
-                              SkBlitter* blitter, int start_y, int stop_y,
-                              PrePostProc proc) {
+// Unexpected conditions for which we need to return
+#define ASSERT_RETURN(cond)     \
+    do {                        \
+        if (!(cond)) {          \
+            SkASSERT(false);    \
+            return;             \
+        }                       \
+    } while (0)
+
+// Needs Y to only change once (looser than convex in X)
+static void walk_simple_edges(SkEdge* prevHead, SkBlitter* blitter, int start_y, int stop_y) {
     validate_sort(prevHead->fNext);
 
     SkEdge* leftE = prevHead->fNext;
     SkEdge* riteE = leftE->fNext;
     SkEdge* currE = riteE->fNext;
 
-#if 0
-    int local_top = leftE->fFirstY;
-    SkASSERT(local_top == riteE->fFirstY);
-#else
     // our edge choppers for curves can result in the initial edges
     // not lining up, so we take the max.
     int local_top = SkMax32(leftE->fFirstY, riteE->fFirstY);
-#endif
-    SkASSERT(local_top >= start_y);
+    ASSERT_RETURN(local_top >= start_y);
 
-    for (;;) {
+    while (local_top < stop_y) {
         SkASSERT(leftE->fFirstY <= stop_y);
         SkASSERT(riteE->fFirstY <= stop_y);
 
-        if (leftE->fX > riteE->fX || (leftE->fX == riteE->fX &&
-                                      leftE->fDX > riteE->fDX)) {
-            using std::swap;
-            swap(leftE, riteE);
-        }
-
         int local_bot = SkMin32(leftE->fLastY, riteE->fLastY);
         local_bot = SkMin32(local_bot, stop_y - 1);
-        SkASSERT(local_top <= local_bot);
+        ASSERT_RETURN(local_top <= local_bot);
 
         SkFixed left = leftE->fX;
         SkFixed dLeft = leftE->fDX;
         SkFixed rite = riteE->fX;
         SkFixed dRite = riteE->fDX;
         int count = local_bot - local_top;
-        SkASSERT(count >= 0);
+        ASSERT_RETURN(count >= 0);
+
         if (0 == (dLeft | dRite)) {
             int L = SkFixedRoundToInt(left);
             int R = SkFixedRoundToInt(rite);
+            if (L > R) {
+                std::swap(L, R);
+            }
             if (L < R) {
                 count += 1;
                 blitter->blitRect(L, local_top, R - L, count);
@@ -261,6 +262,9 @@
             do {
                 int L = SkFixedRoundToInt(left);
                 int R = SkFixedRoundToInt(rite);
+                if (L > R) {
+                    std::swap(L, R);
+                }
                 if (L < R) {
                     blitter->blitH(L, local_top, R - L);
                 }
@@ -279,26 +283,19 @@
 
         if (!update_edge(leftE, local_bot)) {
             if (currE->fFirstY >= stop_y) {
-                break;
+                return; // we're done
             }
             leftE = currE;
             currE = currE->fNext;
+            ASSERT_RETURN(leftE->fFirstY == local_top);
         }
         if (!update_edge(riteE, local_bot)) {
             if (currE->fFirstY >= stop_y) {
-                break;
+                return; // we're done
             }
             riteE = currE;
             currE = currE->fNext;
-        }
-
-        SkASSERT(leftE);
-        SkASSERT(riteE);
-
-        // check our bottom clip
-        SkASSERT(local_top == local_bot + 1);
-        if (local_top >= stop_y) {
-            break;
+            ASSERT_RETURN(riteE->fFirstY == local_top);
         }
     }
 }
@@ -472,7 +469,7 @@
 
     // count >= 2 is required as the convex walker does not handle missing right edges
     if (path.isConvex() && (nullptr == proc) && count >= 2) {
-        walk_convex_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, nullptr);
+        walk_simple_edges(&headEdge, blitter, start_y, stop_y);
     } else {
         walk_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, proc,
                 shiftedClip.right());
@@ -738,8 +735,7 @@
     if (clipRect && start_y < clipRect->fTop) {
         start_y = clipRect->fTop;
     }
-    walk_convex_edges(&headEdge, SkPath::kEvenOdd_FillType, blitter, start_y, stop_y, nullptr);
-//    walk_edges(&headEdge, SkPath::kEvenOdd_FillType, blitter, start_y, stop_y, nullptr);
+    walk_simple_edges(&headEdge, blitter, start_y, stop_y);
 }
 
 void SkScan::FillTriangle(const SkPoint pts[], const SkRasterClip& clip,
diff --git a/src/core/SkScopeExit.h b/src/core/SkScopeExit.h
index 4c9adcd..aa4ec63 100644
--- a/src/core/SkScopeExit.h
+++ b/src/core/SkScopeExit.h
@@ -25,6 +25,8 @@
         }
     }
 
+    void clear() { fFn = {}; }
+
     SkScopeExit& operator=(SkScopeExit&& that) {
         fFn = std::move(that.fFn);
         return *this;
diff --git a/src/core/SkSharedMutex.cpp b/src/core/SkSharedMutex.cpp
index 74bea77..fa26053 100644
--- a/src/core/SkSharedMutex.cpp
+++ b/src/core/SkSharedMutex.cpp
@@ -7,7 +7,6 @@
 
 #include "SkSharedMutex.h"
 
-#include "SkAtomics.h"
 #include "SkTypes.h"
 #include "SkSemaphore.h"
 
diff --git a/src/core/SkSpecialImage.cpp b/src/core/SkSpecialImage.cpp
index a1db765..03ed6d7 100644
--- a/src/core/SkSpecialImage.cpp
+++ b/src/core/SkSpecialImage.cpp
@@ -338,12 +338,31 @@
     return sk_make_sp<SkSpecialImage_Raster>(subset, *srcBM, props);
 }
 
+sk_sp<SkSpecialImage> SkSpecialImage::CopyFromRaster(const SkIRect& subset,
+                                                     const SkBitmap& bm,
+                                                     const SkSurfaceProps* props) {
+    SkASSERT(rect_fits(subset, bm.width(), bm.height()));
+
+    if (!bm.pixelRef()) {
+        return nullptr;
+    }
+
+    SkBitmap tmp;
+    if (!tmp.tryAllocPixels(bm.info().makeWH(subset.width(), subset.height()))) {
+        return nullptr;
+    }
+    if (!bm.readPixels(tmp.info(), tmp.getPixels(), tmp.rowBytes(), subset.x(), subset.y())) {
+        return nullptr;
+    }
+    return sk_make_sp<SkSpecialImage_Raster>(subset, tmp, props);
+}
+
 #if SK_SUPPORT_GPU
 ///////////////////////////////////////////////////////////////////////////////
 static sk_sp<SkImage> wrap_proxy_in_image(GrContext* context, sk_sp<GrTextureProxy> proxy,
                                           SkAlphaType alphaType, sk_sp<SkColorSpace> colorSpace) {
     return sk_make_sp<SkImage_Gpu>(sk_ref_sp(context), kNeedNewImageUniqueID, alphaType,
-                                   std::move(proxy), std::move(colorSpace), SkBudgeted::kYes);
+                                   std::move(proxy), std::move(colorSpace));
 }
 
 class SkSpecialImage_Gpu : public SkSpecialImage_Base {
@@ -379,9 +398,9 @@
         // instantiates itself it is going to have to either be okay with having a larger
         // than expected backing texture (unlikely) or the 'fit' of the SurfaceProxy needs
         // to be tightened (if it is deferred).
-        sk_sp<SkImage> img = sk_sp<SkImage>(
-                new SkImage_Gpu(sk_ref_sp(canvas->getGrContext()), this->uniqueID(), fAlphaType,
-                                fTextureProxy, fColorSpace, SkBudgeted::kNo));
+        sk_sp<SkImage> img =
+                sk_sp<SkImage>(new SkImage_Gpu(sk_ref_sp(canvas->getGrContext()), this->uniqueID(),
+                                               fAlphaType, fTextureProxy, fColorSpace));
 
         canvas->drawImageRect(img, this->subset(),
                               dst, paint, SkCanvas::kStrict_SrcRectConstraint);
@@ -469,9 +488,9 @@
                 return wrap_proxy_in_image(fContext, fTextureProxy, fAlphaType, fColorSpace);
             }
 
-            sk_sp<GrTextureProxy> subsetProxy(GrSurfaceProxy::Copy(fContext, fTextureProxy.get(),
-                                                                   GrMipMapped::kNo, *subset,
-                                                                   SkBudgeted::kYes));
+            sk_sp<GrTextureProxy> subsetProxy(
+                    GrSurfaceProxy::Copy(fContext, fTextureProxy.get(), GrMipMapped::kNo, *subset,
+                                         SkBackingFit::kExact, SkBudgeted::kYes));
             if (!subsetProxy) {
                 return nullptr;
             }
diff --git a/src/core/SkSpecialImage.h b/src/core/SkSpecialImage.h
index 9379708..30f74b4 100644
--- a/src/core/SkSpecialImage.h
+++ b/src/core/SkSpecialImage.h
@@ -75,6 +75,9 @@
     static sk_sp<SkSpecialImage> MakeFromRaster(const SkIRect& subset,
                                                 const SkBitmap&,
                                                 const SkSurfaceProps* = nullptr);
+    static sk_sp<SkSpecialImage> CopyFromRaster(const SkIRect& subset,
+                                                const SkBitmap&,
+                                                const SkSurfaceProps* = nullptr);
 #if SK_SUPPORT_GPU
     static sk_sp<SkSpecialImage> MakeDeferredFromGpu(GrContext*,
                                                      const SkIRect& subset,
diff --git a/src/core/SkStrikeCache.cpp b/src/core/SkStrikeCache.cpp
index 8206d94..4bbd16b 100644
--- a/src/core/SkStrikeCache.cpp
+++ b/src/core/SkStrikeCache.cpp
@@ -392,10 +392,10 @@
         if (loose_compare(node->fCache.getDescriptor(), desc)) {
             if (node->fCache.isGlyphCached(glyphID, 0, 0)) {
                 SkGlyph* from = node->fCache.getRawGlyphByID(SkPackedGlyphID(glyphID));
-                if (from->fPathData != nullptr && from->fPathData->fPath != nullptr) {
+                if (from->fPathData != nullptr) {
                     // We can just copy the path out by value here, so no need to worry
                     // about the lifetime of this desperate-match node.
-                    *path = *from->fPathData->fPath;
+                    *path = from->fPathData->fPath;
                     return true;
                 }
             }
diff --git a/src/core/SkString.cpp b/src/core/SkString.cpp
index 6f7d80df..b6ac84e 100644
--- a/src/core/SkString.cpp
+++ b/src/core/SkString.cpp
@@ -7,7 +7,6 @@
 
 #include "SkString.h"
 
-#include "SkAtomics.h"
 #include "SkSafeMath.h"
 #include "SkTo.h"
 #include "SkUtils.h"
diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp
index d31fba9..176871c 100644
--- a/src/core/SkStroke.cpp
+++ b/src/core/SkStroke.cpp
@@ -591,7 +591,8 @@
     SkASSERT(outer2 >= 1 && outer2 <= 2);
     SkASSERT(outer1 < outer2);
     int mid = outer1 ^ outer2 ^ 3;
-    SkScalar lineSlop =  ptMax * ptMax * 0.00001f;  // this multiplier is pulled out of the air
+    const float kCurvatureSlop = 0.000005f;  // this multiplier is pulled out of the air
+    SkScalar lineSlop =  ptMax * ptMax * kCurvatureSlop;
     return pt_to_line(quad[mid], quad[outer1], quad[outer2]) <= lineSlop;
 }
 
@@ -651,20 +652,12 @@
     if (!conic_in_line(conic)) {
         return kQuad_ReductionType;
     }
-#if 0   // once findMaxCurvature is implemented, this will be a better solution
-    SkScalar t;
-    if (!conic.findMaxCurvature(&t) || 0 == t) {
-        return kLine_ReductionType;
-    }
-#else  // but for now, use extrema instead
-    SkScalar xT = 0, yT = 0;
-    (void) conic.findXExtrema(&xT);
-    (void) conic.findYExtrema(&yT);
-    SkScalar t = SkTMax(xT, yT);
+    // SkFindConicMaxCurvature would be a better solution, once we know how to
+    // implement it. Quad curvature is a reasonable substitute
+    SkScalar t = SkFindQuadMaxCurvature(conic.fPts);
     if (0 == t) {
         return kLine_ReductionType;
     }
-#endif
     conic.evalAt(t, reduction, nullptr);
     return kDegenerate_ReductionType;
 }
diff --git a/src/core/SkSurfaceCharacterization.cpp b/src/core/SkSurfaceCharacterization.cpp
index d71559f..63014db 100644
--- a/src/core/SkSurfaceCharacterization.cpp
+++ b/src/core/SkSurfaceCharacterization.cpp
@@ -29,6 +29,7 @@
            fIsTextureable == other.fIsTextureable &&
            fIsMipMapped == other.fIsMipMapped &&
            fUsesGLFBO0 == other.fUsesGLFBO0 &&
+           fVulkanSecondaryCBCompatible == other.fVulkanSecondaryCBCompatible &&
            fSurfaceProps == other.fSurfaceProps;
 }
 
@@ -46,7 +47,7 @@
     return SkSurfaceCharacterization(fContextInfo, fCacheMaxResourceBytes,
                                      fImageInfo.makeWH(width, height), fOrigin, fConfig, fFSAAType,
                                      fStencilCnt, fIsTextureable, fIsMipMapped, fUsesGLFBO0,
-                                     fSurfaceProps);
+                                     fVulkanSecondaryCBCompatible, fSurfaceProps);
 }
 
 #endif
diff --git a/src/core/SkTLS.cpp b/src/core/SkTLS.cpp
index a47dc14..9cbc612 100644
--- a/src/core/SkTLS.cpp
+++ b/src/core/SkTLS.cpp
@@ -7,46 +7,21 @@
 
 #include "SkTLS.h"
 
-// enable to help debug TLS storage
-//#define SK_TRACE_TLS_LIFETIME
-
-
-#ifdef SK_TRACE_TLS_LIFETIME
-    #include "SkAtomics.h"
-    static int32_t gTLSRecCount;
-#endif
-
 struct SkTLSRec {
     SkTLSRec*           fNext;
     void*               fData;
     SkTLS::CreateProc   fCreateProc;
     SkTLS::DeleteProc   fDeleteProc;
 
-#ifdef SK_TRACE_TLS_LIFETIME
-    SkTLSRec() {
-        int n = sk_atomic_inc(&gTLSRecCount);
-        SkDebugf(" SkTLSRec[%d]\n", n);
-    }
-#endif
-
     ~SkTLSRec() {
         if (fDeleteProc) {
             fDeleteProc(fData);
         }
         // else we leak fData, or it will be managed by the caller
-
-#ifdef SK_TRACE_TLS_LIFETIME
-        int n = sk_atomic_dec(&gTLSRecCount);
-        SkDebugf("~SkTLSRec[%d]\n", n - 1);
-#endif
     }
 };
 
 void SkTLS::Destructor(void* ptr) {
-#ifdef SK_TRACE_TLS_LIFETIME
-    SkDebugf("SkTLS::Destructor(%p)\n", ptr);
-#endif
-
     SkTLSRec* rec = (SkTLSRec*)ptr;
     do {
         SkTLSRec* next = rec->fNext;
diff --git a/src/core/SkTextBlob.cpp b/src/core/SkTextBlob.cpp
index 4fa9f69..42168e8 100644
--- a/src/core/SkTextBlob.cpp
+++ b/src/core/SkTextBlob.cpp
@@ -6,15 +6,17 @@
  */
 
 #include "SkTextBlob.h"
-
+#include "SkFontPriv.h"
 #include "SkGlyphRun.h"
 #include "SkPaintPriv.h"
 #include "SkReadBuffer.h"
+#include "SkRSXform.h"
 #include "SkSafeMath.h"
 #include "SkTextBlobPriv.h"
 #include "SkTypeface.h"
 #include "SkWriteBuffer.h"
 
+#include <atomic>
 #include <limits>
 #include <new>
 
@@ -22,34 +24,6 @@
 #include "text/GrTextBlobCache.h"
 #endif
 
-SkRunFont::SkRunFont(const SkPaint& paint)
-        : fSize(paint.getTextSize())
-        , fScaleX(paint.getTextScaleX())
-        , fTypeface(SkPaintPriv::RefTypefaceOrDefault(paint))
-        , fSkewX(paint.getTextSkewX())
-        , fHinting(static_cast<unsigned>(paint.getHinting()))
-        , fFlags(paint.getFlags() & kFlagsMask) { }
-
-void SkRunFont::applyToPaint(SkPaint* paint) const {
-    paint->setTextEncoding(kGlyphID_SkTextEncoding);
-    paint->setTypeface(fTypeface);
-    paint->setTextSize(fSize);
-    paint->setTextScaleX(fScaleX);
-    paint->setTextSkewX(fSkewX);
-    paint->setHinting(static_cast<SkFontHinting>(fHinting));
-
-    paint->setFlags((paint->getFlags() & ~kFlagsMask) | fFlags);
-}
-
-bool SkRunFont::operator==(const SkRunFont& other) const {
-    return fTypeface == other.fTypeface
-           && fSize == other.fSize
-           && fScaleX == other.fScaleX
-           && fSkewX == other.fSkewX
-           && fHinting == other.fHinting
-           && fFlags == other.fFlags;
-}
-
 namespace {
 struct RunFontStorageEquivalent {
     SkScalar fSize, fScaleX;
@@ -57,7 +31,7 @@
     SkScalar fSkewX;
     uint32_t fFlags;
 };
-static_assert(sizeof(SkRunFont) == sizeof(RunFontStorageEquivalent), "runfont_should_stay_packed");
+static_assert(sizeof(SkFont) == sizeof(RunFontStorageEquivalent), "runfont_should_stay_packed");
 }
 
 size_t SkTextBlob::RunRecord::StorageSize(uint32_t glyphCount, uint32_t textSize,
@@ -94,7 +68,7 @@
 
 namespace {
 struct RunRecordStorageEquivalent {
-    SkRunFont  fFont;
+    SkFont   fFont;
     SkPoint  fOffset;
     uint32_t fCount;
     uint32_t fFlags;
@@ -156,11 +130,11 @@
     memmove(posBuffer(), initialPosBuffer, copySize);
 }
 
-static int32_t gNextID = 1;
 static int32_t next_id() {
+    static std::atomic<int32_t> nextID{1};
     int32_t id;
     do {
-        id = sk_atomic_inc(&gNextID);
+        id = nextID++;
     } while (id == SK_InvalidGenID);
     return id;
 }
@@ -202,15 +176,21 @@
 } // namespace
 
 enum SkTextBlob::GlyphPositioning : uint8_t {
-        kDefault_Positioning      = 0, // Default glyph advances -- zero scalars per glyph.
-        kHorizontal_Positioning   = 1, // Horizontal positioning -- one scalar per glyph.
-        kFull_Positioning         = 2  // Point positioning -- two scalars per glyph.
+    kDefault_Positioning      = 0, // Default glyph advances -- zero scalars per glyph.
+    kHorizontal_Positioning   = 1, // Horizontal positioning -- one scalar per glyph.
+    kFull_Positioning         = 2, // Point positioning -- two scalars per glyph.
+    kRSXform_Positioning      = 3, // RSXform positioning -- four scalars per glyph.
 };
 
 unsigned SkTextBlob::ScalarsPerGlyph(GlyphPositioning pos) {
-    // GlyphPositioning values are directly mapped to scalars-per-glyph.
-    SkASSERT(pos <= 2);
-    return pos;
+    const uint8_t gScalarsPerPositioning[] = {
+        0,  // kDefault_Positioning
+        1,  // kHorizontal_Positioning
+        2,  // kFull_Positioning
+        4,  // kRSXform_Positioning
+    };
+    SkASSERT((unsigned)pos <= 3);
+    return gScalarsPerPositioning[pos];
 }
 
 void SkTextBlob::operator delete(void* p) {
@@ -248,18 +228,14 @@
                   kHorizontal_Positioning, "");
     static_assert(static_cast<GlyphPositioning>(SkTextBlob::kFull_Positioning) ==
                   kFull_Positioning, "");
+    static_assert(static_cast<GlyphPositioning>(SkTextBlob::kRSXform_Positioning) ==
+                  kRSXform_Positioning, "");
 
     return SkTo<GlyphPositioning>(fCurrentRun->positioning());
 }
 
-void SkTextBlobRunIterator::applyFontToPaint(SkPaint* paint) const {
-    SkASSERT(!this->done());
-
-    fCurrentRun->font().applyToPaint(paint);
-}
-
 bool SkTextBlobRunIterator::isLCD() const {
-    return SkToBool(fCurrentRun->font().flags() & SkPaint::kLCDRenderText_Flag);
+    return fCurrentRun->font().getEdging() == SkFont::Edging::kSubpixelAntiAlias;
 }
 
 SkTextBlobBuilder::SkTextBlobBuilder()
@@ -280,20 +256,17 @@
 }
 
 SkRect SkTextBlobBuilder::TightRunBounds(const SkTextBlob::RunRecord& run) {
+    const SkFont& font = run.font();
     SkRect bounds;
-    SkPaint paint;
-    run.font().applyToPaint(&paint);
 
     if (SkTextBlob::kDefault_Positioning == run.positioning()) {
-        paint.measureText(run.glyphBuffer(), run.glyphCount() * sizeof(uint16_t), &bounds);
+        font.measureText(run.glyphBuffer(), run.glyphCount() * sizeof(uint16_t),
+                         kGlyphID_SkTextEncoding, &bounds);
         return bounds.makeOffset(run.offset().x(), run.offset().y());
     }
 
     SkAutoSTArray<16, SkRect> glyphBounds(run.glyphCount());
-    paint.getTextWidths(run.glyphBuffer(),
-                        run.glyphCount() * sizeof(uint16_t),
-                        nullptr,
-                        glyphBounds.get());
+    font.getBounds(run.glyphBuffer(), run.glyphCount(), glyphBounds.get(), nullptr);
 
     SkASSERT(SkTextBlob::kFull_Positioning == run.positioning() ||
              SkTextBlob::kHorizontal_Positioning == run.positioning());
@@ -320,14 +293,17 @@
     return bounds.makeOffset(run.offset().x(), run.offset().y());
 }
 
+static SkRect map_quad_to_rect(const SkRSXform& xform, const SkRect& rect) {
+    return SkMatrix().setRSXform(xform).mapRect(rect);
+}
+
 SkRect SkTextBlobBuilder::ConservativeRunBounds(const SkTextBlob::RunRecord& run) {
     SkASSERT(run.glyphCount() > 0);
     SkASSERT(SkTextBlob::kFull_Positioning == run.positioning() ||
-             SkTextBlob::kHorizontal_Positioning == run.positioning());
+             SkTextBlob::kHorizontal_Positioning == run.positioning() ||
+             SkTextBlob::kRSXform_Positioning == run.positioning());
 
-    SkPaint paint;
-    run.font().applyToPaint(&paint);
-    const SkRect fontBounds = paint.getFontBounds();
+    const SkRect fontBounds = SkFontPriv::GetFontBounds(run.font());
     if (fontBounds.isEmpty()) {
         // Empty font bounds are likely a font bug.  TightBounds has a better chance of
         // producing useful results in this case.
@@ -352,20 +328,30 @@
         bounds.setLTRB(minX, 0, maxX, 0);
     } break;
     case SkTextBlob::kFull_Positioning: {
-        const SkPoint* glyphPosPts = reinterpret_cast<const SkPoint*>(run.posBuffer());
+        const SkPoint* glyphPosPts = run.pointBuffer();
         SkASSERT((void*)(glyphPosPts + run.glyphCount()) <= SkTextBlob::RunRecord::Next(&run));
 
         bounds.setBounds(glyphPosPts, run.glyphCount());
     } break;
+    case SkTextBlob::kRSXform_Positioning: {
+        const SkRSXform* xform = run.xformBuffer();
+        SkASSERT((void*)(xform + run.glyphCount()) <= SkTextBlob::RunRecord::Next(&run));
+        bounds = map_quad_to_rect(xform[0], fontBounds);
+        for (unsigned i = 1; i < run.glyphCount(); ++i) {
+            bounds.join(map_quad_to_rect(xform[i], fontBounds));
+        }
+    } break;
     default:
         SK_ABORT("unsupported positioning mode");
     }
 
-    // Expand by typeface glyph bounds.
-    bounds.fLeft   += fontBounds.left();
-    bounds.fTop    += fontBounds.top();
-    bounds.fRight  += fontBounds.right();
-    bounds.fBottom += fontBounds.bottom();
+    if (run.positioning() != SkTextBlob::kRSXform_Positioning) {
+        // Expand by typeface glyph bounds.
+        bounds.fLeft   += fontBounds.left();
+        bounds.fTop    += fontBounds.top();
+        bounds.fRight  += fontBounds.right();
+        bounds.fBottom += fontBounds.bottom();
+    }
 
     // Offset by run position.
     return bounds.makeOffset(run.offset().x(), run.offset().y());
@@ -414,7 +400,7 @@
     fStorage.realloc(safe ? fStorageSize : std::numeric_limits<size_t>::max());
 }
 
-bool SkTextBlobBuilder::mergeRun(const SkPaint &font, SkTextBlob::GlyphPositioning positioning,
+bool SkTextBlobBuilder::mergeRun(const SkFont& font, SkTextBlob::GlyphPositioning positioning,
                                  uint32_t count, SkPoint offset) {
     if (0 == fLastRun) {
         SkASSERT(0 == fRunCount);
@@ -474,13 +460,11 @@
     return true;
 }
 
-void SkTextBlobBuilder::allocInternal(const SkPaint &font,
+void SkTextBlobBuilder::allocInternal(const SkFont& font,
                                       SkTextBlob::GlyphPositioning positioning,
                                       int count, int textSize, SkPoint offset,
                                       const SkRect* bounds) {
-    if (count <= 0 || textSize < 0 ||
-        (SkTextEncoding)font.getTextEncoding() != kGlyphID_SkTextEncoding)
-    {
+    if (count <= 0 || textSize < 0) {
         fCurrentRunBuffer = { nullptr, nullptr, nullptr, nullptr };
         return;
     }
@@ -530,35 +514,30 @@
 const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRun(const SkFont& font, int count,
                                                                 SkScalar x, SkScalar y,
                                                                 const SkRect* bounds) {
-    SkPaint legacyPaint;
-    font.LEGACY_applyToPaint(&legacyPaint);
-    legacyPaint.setTextEncoding(kGlyphID_SkTextEncoding);
-
-    return this->allocRunText(legacyPaint, count, x, y, 0, SkString(), bounds);
+    this->allocInternal(font, SkTextBlob::kDefault_Positioning, count, 0, {x, y}, bounds);
+    return fCurrentRunBuffer;
 }
 
 const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunPosH(const SkFont& font, int count,
                                                                     SkScalar y,
                                                                     const SkRect* bounds) {
-    SkPaint legacyPaint;
-    font.LEGACY_applyToPaint(&legacyPaint);
-    legacyPaint.setTextEncoding(kGlyphID_SkTextEncoding);
-
-    return this->allocRunTextPosH(legacyPaint, count, y, 0, SkString(), bounds);
+    this->allocInternal(font, SkTextBlob::kHorizontal_Positioning, count, 0, {0, y}, bounds);
+    return fCurrentRunBuffer;
 }
 
 const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunPos(const SkFont& font, int count,
                                                                    const SkRect* bounds) {
-    SkPaint legacyPaint;
-    font.LEGACY_applyToPaint(&legacyPaint);
-    legacyPaint.setTextEncoding(kGlyphID_SkTextEncoding);
-
-    return this->allocRunTextPos(legacyPaint, count, 0, SkString(), bounds);
+    this->allocInternal(font, SkTextBlob::kFull_Positioning, count, 0, {0, 0}, bounds);
+    return fCurrentRunBuffer;
 }
 
-// SkPaint versions
+const SkTextBlobBuilder::RunBuffer&
+SkTextBlobBuilder::allocRunRSXform(const SkFont& font, int count) {
+    this->allocInternal(font, SkTextBlob::kRSXform_Positioning, count, 0, {0, 0}, nullptr);
+    return fCurrentRunBuffer;
+}
 
-const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunText(const SkPaint& font, int count,
+const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunText(const SkFont& font, int count,
                                                                     SkScalar x, SkScalar y,
                                                                     int textByteCount,
                                                                     SkString lang,
@@ -567,23 +546,30 @@
     return fCurrentRunBuffer;
 }
 
-const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunTextPosH(const SkPaint& font, int count,
+const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunTextPosH(const SkFont& font, int count,
                                                                         SkScalar y,
                                                                         int textByteCount,
                                                                         SkString lang,
                                                                         const SkRect* bounds) {
     this->allocInternal(font, SkTextBlob::kHorizontal_Positioning, count, textByteCount, SkPoint::Make(0, y),
                         bounds);
-
     return fCurrentRunBuffer;
 }
 
-const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunTextPos(const SkPaint& font, int count,
+const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunTextPos(const SkFont& font, int count,
                                                                        int textByteCount,
                                                                        SkString lang,
                                                                        const SkRect *bounds) {
     this->allocInternal(font, SkTextBlob::kFull_Positioning, count, textByteCount, SkPoint::Make(0, 0), bounds);
+    return fCurrentRunBuffer;
+}
 
+const SkTextBlobBuilder::RunBuffer& SkTextBlobBuilder::allocRunRSXform(const SkFont& font, int count,
+                                                                       int textByteCount,
+                                                                       SkString lang,
+                                                                       const SkRect* bounds) {
+    this->allocInternal(font, SkTextBlob::kRSXform_Positioning, count, textByteCount,
+                        {0, 0}, bounds);
     return fCurrentRunBuffer;
 }
 
@@ -632,11 +618,81 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
+#include "SkTextToPathIter.h"
+
+template <SkTextInterceptsIter::TextType TextType, typename Func>
+int GetTextIntercepts(const SkFont& font, const SkPaint* paint, const SkGlyphID glyphs[],
+                      int glyphCount, const SkScalar bounds[2], SkScalar* array, Func posMaker) {
+    SkASSERT(glyphCount == 0 || glyphs != nullptr);
+
+    const SkPoint pos0 = posMaker(0);
+    SkTextInterceptsIter iter(glyphs, glyphCount, font, paint, bounds, pos0.x(), pos0.y(),
+                              TextType);
+
+    int i = 0;
+    int count = 0;
+    while (iter.next(array, &count)) {
+        if (TextType == SkTextInterceptsIter::TextType::kPosText) {
+            const SkPoint pos = posMaker(++i);
+            iter.setPosition(pos.x(), pos.y());
+        }
+    }
+
+    return count;
+}
+
+int SkTextBlob::getIntercepts(const SkScalar bounds[2], SkScalar intervals[],
+                              const SkPaint* paint) const {
+    int count = 0;
+    SkTextBlobRunIterator it(this);
+
+    while (!it.done()) {
+        SkScalar* runIntervals = intervals ? intervals + count : nullptr;
+        const SkFont& font = it.font();
+        const SkGlyphID* glyphs = it.glyphs();
+        const int glyphCount = it.glyphCount();
+
+        switch (it.positioning()) {
+            case SkTextBlobRunIterator::kDefault_Positioning: {
+                SkPoint loc = it.offset();
+                count += GetTextIntercepts<SkTextInterceptsIter::TextType::kText>(
+                          font, paint, glyphs, glyphCount, bounds, runIntervals, [loc] (int) {
+                    return loc;
+                });
+            } break;
+            case SkTextBlobRunIterator::kHorizontal_Positioning: {
+                const SkScalar* xpos = it.pos();
+                const SkScalar constY = it.offset().fY;
+                count += GetTextIntercepts<SkTextInterceptsIter::TextType::kPosText>(
+                 font, paint, glyphs, glyphCount, bounds, runIntervals, [xpos, constY] (int i) {
+                    return SkPoint::Make(xpos[i], constY);
+                });
+            } break;
+            case SkTextBlobRunIterator::kFull_Positioning: {
+                const SkPoint* pos = reinterpret_cast<const SkPoint*>(it.pos());
+                count += GetTextIntercepts<SkTextInterceptsIter::TextType::kPosText>(
+                             font, paint, glyphs, glyphCount, bounds, runIntervals, [pos] (int i) {
+                    return pos[i];
+                });
+            } break;
+            case SkTextBlobRunIterator::kRSXform_Positioning:
+                // Unimplemented for now -- can/should we try to make this work?
+                break;
+        }
+
+        it.next();
+    }
+
+    return count;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
 
 void SkTextBlobPriv::Flatten(const SkTextBlob& blob, SkWriteBuffer& buffer) {
-    buffer.writeRect(blob.fBounds);
+    // seems like we could skip this, and just recompute bounds in unflatten, but
+    // some cc_unittests fail if we remove this...
+    buffer.writeRect(blob.bounds());
 
-    SkPaint runPaint;
     SkTextBlobRunIterator it(&blob);
     while (!it.done()) {
         SkASSERT(it.glyphCount() > 0);
@@ -654,9 +710,8 @@
             buffer.write32(textSize);
         }
         buffer.writePoint(it.offset());
-        // This should go away when switching to SkFont
-        it.applyFontToPaint(&runPaint);
-        buffer.writePaint(runPaint);
+
+        SkFontPriv::Flatten(it.font(), buffer);
 
         buffer.writeByteArray(it.glyphs(), it.glyphCount() * sizeof(uint16_t));
         buffer.writeByteArray(it.pos(),
@@ -691,7 +746,7 @@
         PositioningAndExtended pe;
         pe.intValue = reader.read32();
         const auto pos = SkTo<SkTextBlob::GlyphPositioning>(pe.positioning);
-        if (glyphCount <= 0 || pos > SkTextBlob::kFull_Positioning) {
+        if (glyphCount <= 0 || pos > SkTextBlob::kRSXform_Positioning) {
             return nullptr;
         }
         int textSize = pe.extended ? reader.read32() : 0;
@@ -701,8 +756,14 @@
 
         SkPoint offset;
         reader.readPoint(&offset);
-        SkPaint font;
-        reader.readPaint(&font);
+        SkFont font;
+        if (reader.isVersionLT(SkReadBuffer::kSerializeFonts_Version)) {
+            SkPaint paint;
+            reader.readPaint(&paint);
+            font = SkFont::LEGACY_ExtractFromPaint(paint);
+        } else {
+            SkFontPriv::Unflatten(&font, reader);
+        }
 
         // Compute the expected size of the buffer and ensure we have enough to deserialize
         // a run before allocating it.
@@ -731,8 +792,9 @@
             case SkTextBlob::kFull_Positioning:
                 buf = &blobBuilder.allocRunTextPos(font, glyphCount, textSize, SkString(), &bounds);
                 break;
-            default:
-                return nullptr;
+            case SkTextBlob::kRSXform_Positioning:
+                buf = &blobBuilder.allocRunRSXform(font, glyphCount, textSize, SkString(), &bounds);
+                break;
         }
 
         if (!buf->glyphs ||
@@ -759,24 +821,47 @@
 
 sk_sp<SkTextBlob> SkTextBlob::MakeFromText(const void* text, size_t byteLength, const SkFont& font,
                                            SkTextEncoding encoding) {
-    SkPaint legacyPaint;
-    font.LEGACY_applyToPaint(&legacyPaint);
-    legacyPaint.setTextEncoding(encoding);
+    // Note: we deliberately promote this to fully positioned blobs, since we'd have to pay the
+    // same cost down stream (i.e. computing bounds), so its cheaper to pay the cost once now.
+    const int count = font.countText(text, byteLength, encoding);
+    SkTextBlobBuilder builder;
+    auto buffer = builder.allocRunPos(font, count);
+    font.textToGlyphs(text, byteLength, encoding, buffer.glyphs, count);
+    font.getPos(buffer.glyphs, count, buffer.points(), {0, 0});
+    return builder.make();
+}
 
-    SkGlyphRunBuilder runBuilder;
+sk_sp<SkTextBlob> SkTextBlob::MakeFromPosText(const void* text, size_t byteLength,
+                                              const SkPoint pos[], const SkFont& font,
+                                              SkTextEncoding encoding) {
+    const int count = font.countText(text, byteLength, encoding);
+    SkTextBlobBuilder builder;
+    auto buffer = builder.allocRunPos(font, count);
+    font.textToGlyphs(text, byteLength, encoding, buffer.glyphs, count);
+    memcpy(buffer.points(), pos, count * sizeof(SkPoint));
+    return builder.make();
+}
 
-    runBuilder.drawText(legacyPaint, text, byteLength, SkPoint::Make(0, 0));
+sk_sp<SkTextBlob> SkTextBlob::MakeFromPosTextH(const void* text, size_t byteLength,
+                                               const SkScalar xpos[], SkScalar constY,
+                                               const SkFont& font, SkTextEncoding encoding) {
+    const int count = font.countText(text, byteLength, encoding);
+    SkTextBlobBuilder builder;
+    auto buffer = builder.allocRunPosH(font, count, constY);
+    font.textToGlyphs(text, byteLength, encoding, buffer.glyphs, count);
+    memcpy(buffer.pos, xpos, count * sizeof(SkScalar));
+    return builder.make();
+}
 
-    auto glyphRunList = runBuilder.useGlyphRunList();
-    SkTextBlobBuilder blobBuilder;
-    if (!glyphRunList.empty()) {
-        auto run = glyphRunList[0];
-
-        auto runData = blobBuilder.allocRunPos(font, run.runSize());
-        run.filloutGlyphsAndPositions(runData.glyphs, (SkPoint *)runData.pos);
-    }
-
-    return blobBuilder.make();
+sk_sp<SkTextBlob> SkTextBlob::MakeFromRSXform(const void* text, size_t byteLength,
+                                              const SkRSXform xform[], const SkFont& font,
+                                              SkTextEncoding encoding) {
+    const int count = font.countText(text, byteLength, encoding);
+    SkTextBlobBuilder builder;
+    auto buffer = builder.allocRunRSXform(font, count);
+    font.textToGlyphs(text, byteLength, encoding, buffer.glyphs, count);
+    memcpy(buffer.xforms(), xform, count * sizeof(SkRSXform));
+    return builder.make();
 }
 
 sk_sp<SkData> SkTextBlob::serialize(const SkSerialProcs& procs) const {
diff --git a/src/core/SkTextBlobPriv.h b/src/core/SkTextBlobPriv.h
index 582c3011..d3a7b94 100644
--- a/src/core/SkTextBlobPriv.h
+++ b/src/core/SkTextBlobPriv.h
@@ -10,6 +10,7 @@
 
 #include "SkColorFilter.h"
 #include "SkDrawLooper.h"
+#include "SkFont.h"
 #include "SkImageFilter.h"
 #include "SkMaskFilter.h"
 #include "SkPaintPriv.h"
@@ -42,64 +43,22 @@
 class SkTextBlobBuilderPriv {
 public:
     static const SkTextBlobBuilder::RunBuffer& AllocRunText(SkTextBlobBuilder* builder,
-            const SkPaint& font, int count, SkScalar x, SkScalar y, int textByteCount,
+            const SkFont& font, int count, SkScalar x, SkScalar y, int textByteCount,
             SkString lang, const SkRect* bounds = nullptr) {
         return builder->allocRunText(font, count, x, y, textByteCount, lang, bounds);
     }
     static const SkTextBlobBuilder::RunBuffer& AllocRunTextPosH(SkTextBlobBuilder* builder,
-            const SkPaint& font, int count, SkScalar y, int textByteCount, SkString lang,
+            const SkFont& font, int count, SkScalar y, int textByteCount, SkString lang,
             const SkRect* bounds = nullptr) {
         return builder->allocRunTextPosH(font, count, y, textByteCount, lang, bounds);
     }
     static const SkTextBlobBuilder::RunBuffer& AllocRunTextPos(SkTextBlobBuilder* builder,
-            const SkPaint& font, int count, int textByteCount, SkString lang,
+            const SkFont& font, int count, int textByteCount, SkString lang,
             const SkRect* bounds = nullptr) {
         return builder->allocRunTextPos(font, count, textByteCount, lang, bounds);
     }
 };
 
-// TODO(fmalita): replace with SkFont.
-class SkRunFont : SkNoncopyable {
-public:
-    SkRunFont(const SkPaint& paint);
-
-    void applyToPaint(SkPaint* paint) const;
-
-    bool operator==(const SkRunFont& other) const;
-
-    bool operator!=(const SkRunFont& other) const {
-        return !(*this == other);
-    }
-
-    uint32_t flags() const { return fFlags; }
-
-private:
-    friend SkPaint;
-    const static uint32_t kFlagsMask =
-            SkPaint::kAntiAlias_Flag          |
-            SkPaint::kFakeBoldText_Flag       |
-            SkPaint::kLinearText_Flag         |
-            SkPaint::kSubpixelText_Flag       |
-            SkPaint::kLCDRenderText_Flag      |
-            SkPaint::kEmbeddedBitmapText_Flag |
-            SkPaint::kAutoHinting_Flag        ;
-
-    SkScalar                 fSize;
-    SkScalar                 fScaleX;
-
-    // Keep this sk_sp off the first position, to avoid interfering with SkNoncopyable
-    // empty baseclass optimization (http://code.google.com/p/skia/issues/detail?id=3694).
-    sk_sp<SkTypeface>        fTypeface;
-    SkScalar                 fSkewX;
-
-    static_assert(static_cast<unsigned>(kFull_SkFontHinting) < 4, "insufficient_hinting_bits");
-    uint32_t                 fHinting : 2;
-    static_assert((kFlagsMask & 0xffff) == kFlagsMask, "insufficient_flags_bits");
-    uint32_t                 fFlags : 16;
-
-    typedef SkNoncopyable INHERITED;
-};
-
 //
 // Textblob data is laid out into externally-managed storage as follows:
 //
@@ -124,7 +83,7 @@
 
 class SkTextBlob::RunRecord {
 public:
-    RunRecord(uint32_t count, uint32_t textSize,  const SkPoint& offset, const SkPaint& font, GlyphPositioning pos)
+    RunRecord(uint32_t count, uint32_t textSize,  const SkPoint& offset, const SkFont& font, GlyphPositioning pos)
             : fFont(font)
             , fCount(count)
             , fOffset(offset)
@@ -146,7 +105,7 @@
         return fOffset;
     }
 
-    const SkRunFont& font() const {
+    const SkFont& font() const {
         return fFont;
     }
 
@@ -160,12 +119,25 @@
         return reinterpret_cast<uint16_t*>(const_cast<RunRecord*>(this) + 1);
     }
 
+    // can be aliased with pointBuffer() or xformBuffer()
     SkScalar* posBuffer() const {
         // Position scalars follow the (aligned) glyph buffer.
         return reinterpret_cast<SkScalar*>(reinterpret_cast<uint8_t*>(this->glyphBuffer()) +
                                            SkAlign4(fCount * sizeof(uint16_t)));
     }
 
+    // alias for posBuffer()
+    SkPoint* pointBuffer() const {
+        SkASSERT(this->positioning() == (GlyphPositioning)2);
+        return reinterpret_cast<SkPoint*>(this->posBuffer());
+    }
+
+    // alias for posBuffer()
+    SkRSXform* xformBuffer() const {
+        SkASSERT(this->positioning() == (GlyphPositioning)3);
+        return reinterpret_cast<SkRSXform*>(this->posBuffer());
+    }
+
     uint32_t textSize() const { return isExtended() ? *this->textSizePtr() : 0; }
 
     uint32_t* clusterBuffer() const {
@@ -212,7 +184,7 @@
         return fFlags & kExtended_Flag;
     }
 
-    SkRunFont        fFont;
+    SkFont           fFont;
     uint32_t         fCount;
     SkPoint          fOffset;
     uint32_t         fFlags;
@@ -220,27 +192,6 @@
     SkDEBUGCODE(unsigned fMagic;)
 };
 
-// (paint->getFlags() & ~kFlagsMask) | fFlags
-inline SkPaint::SkPaint(const SkPaint& basePaint, const SkRunFont& runFont)
-        : fTypeface{runFont.fTypeface}
-        , fPathEffect{basePaint.fPathEffect}
-        , fShader{basePaint.fShader}
-        , fMaskFilter{basePaint.fMaskFilter}
-        , fColorFilter{basePaint.fColorFilter}
-        , fDrawLooper{basePaint.fDrawLooper}
-        , fImageFilter{basePaint.fImageFilter}
-        , fTextSize{runFont.fSize}
-        , fTextScaleX{runFont.fScaleX}
-        , fTextSkewX{runFont.fSkewX}
-        , fColor4f{basePaint.fColor4f}
-        , fWidth{basePaint.fWidth}
-        , fMiterLimit{basePaint.fMiterLimit}
-        , fBlendMode{basePaint.fBlendMode}
-        , fBitfieldsUInt{(basePaint.fBitfieldsUInt & ~SkRunFont::kFlagsMask) | runFont.fFlags} {
-    fBitfields.fTextEncoding = (unsigned)kGlyphID_SkTextEncoding;
-    fBitfields.fHinting = runFont.fHinting;
-}
-
 /**
  *  Iterate through all of the text runs of the text blob.  For example:
  *    for (SkTextBlobRunIterator it(blob); !it.done(); it.next()) {
@@ -254,7 +205,8 @@
     enum GlyphPositioning : uint8_t {
         kDefault_Positioning      = 0, // Default glyph advances -- zero scalars per glyph.
         kHorizontal_Positioning   = 1, // Horizontal positioning -- one scalar per glyph.
-        kFull_Positioning         = 2  // Point positioning -- two scalars per glyph.
+        kFull_Positioning         = 2, // Point positioning -- two scalars per glyph.
+        kRSXform_Positioning      = 3, // RSXform positioning -- four scalars per glyph.
     };
 
     bool done() const {
@@ -274,15 +226,22 @@
         SkASSERT(!this->done());
         return fCurrentRun->posBuffer();
     }
+    // alias for pos()
+    const SkPoint* points() const {
+        return fCurrentRun->pointBuffer();
+    }
+    // alias for pos()
+    const SkRSXform* xforms() const {
+        return fCurrentRun->xformBuffer();
+    }
     const SkPoint& offset() const {
         SkASSERT(!this->done());
         return fCurrentRun->offset();
     }
-    const SkRunFont& runFont() const {
+    const SkFont& font() const {
         SkASSERT(!this->done());
         return fCurrentRun->font();
     }
-    void applyFontToPaint(SkPaint*) const;
     GlyphPositioning positioning() const;
     uint32_t* clusters() const {
         SkASSERT(!this->done());
diff --git a/src/core/SkTextToPathIter.h b/src/core/SkTextToPathIter.h
index 92ddff2..0b15cce 100644
--- a/src/core/SkTextToPathIter.h
+++ b/src/core/SkTextToPathIter.h
@@ -13,35 +13,23 @@
 #include "SkStrikeCache.h"
 
 class SkTextBaseIter {
-protected:
-    SkTextBaseIter(const char text[], size_t length, const SkPaint& paint,
-                   bool applyStrokeAndPathEffects);
-
-    SkExclusiveStrikePtr fCache;
-    SkPaint              fPaint;
-    SkScalar             fScale;
-    SkScalar             fPrevAdvance;
-    const char*          fText;
-    const char*          fStop;
-    SkFontPriv::GlyphCacheProc fGlyphCacheProc;
-
-    SkScalar        fXPos;      // accumulated xpos, returned in next
-};
-
-class SkTextToPathIter : SkTextBaseIter {
 public:
-    SkTextToPathIter(const char text[], size_t length, const SkPaint& paint,
-                     bool applyStrokeAndPathEffects)
-                     : SkTextBaseIter(text, length, paint, applyStrokeAndPathEffects) {
-    }
-
+    const SkFont&   getFont() const { return fFont; }
     const SkPaint&  getPaint() const { return fPaint; }
     SkScalar        getPathScale() const { return fScale; }
 
-    /**
-     *  Returns false when all of the text has been consumed
-     */
-    bool next(const SkPath** path, SkScalar* xpos);
+protected:
+    SkTextBaseIter(const SkGlyphID glyphs[], int count, const SkFont&, const SkPaint*);
+
+    SkExclusiveStrikePtr fCache;
+    SkFont               fFont;
+    SkPaint              fPaint;
+    SkScalar             fScale;
+    SkScalar             fPrevAdvance;
+    const SkGlyphID*     fGlyphs;
+    const SkGlyphID*     fStop;
+
+    SkScalar        fXPos;      // accumulated xpos, returned in next
 };
 
 class SkTextInterceptsIter : SkTextBaseIter {
@@ -51,9 +39,10 @@
         kPosText
     };
 
-    SkTextInterceptsIter(const char text[], size_t length, const SkPaint& paint,
-                         const SkScalar bounds[2], SkScalar x, SkScalar y, TextType textType)
-         : SkTextBaseIter(text, length, paint, false)
+    SkTextInterceptsIter(const SkGlyphID glyphs[], int count, const SkFont& font,
+                         const SkPaint* paint, const SkScalar bounds[2], SkScalar x, SkScalar y,
+                         TextType textType)
+         : SkTextBaseIter(glyphs, count, font, paint)
     {
         fBoundsBase[0] = bounds[0];
         fBoundsBase[1] = bounds[1];
diff --git a/src/core/SkTypefaceCache.cpp b/src/core/SkTypefaceCache.cpp
index 3728b13..0cf0d84 100644
--- a/src/core/SkTypefaceCache.cpp
+++ b/src/core/SkTypefaceCache.cpp
@@ -5,11 +5,9 @@
  * found in the LICENSE file.
  */
 
-
-
 #include "SkTypefaceCache.h"
-#include "SkAtomics.h"
 #include "SkMutex.h"
+#include <atomic>
 
 #define TYPEFACE_CACHE_LIMIT    1024
 
@@ -60,8 +58,8 @@
 }
 
 SkFontID SkTypefaceCache::NewFontID() {
-    static int32_t gFontID;
-    return sk_atomic_inc(&gFontID) + 1;
+    static std::atomic<int32_t> nextID{1};
+    return nextID++;
 }
 
 SK_DECLARE_STATIC_MUTEX(gMutex);
diff --git a/src/core/SkVertices.cpp b/src/core/SkVertices.cpp
index a3a3989..ac7efef 100644
--- a/src/core/SkVertices.cpp
+++ b/src/core/SkVertices.cpp
@@ -7,20 +7,21 @@
 
 #include "SkVertices.h"
 
-#include "SkAtomics.h"
 #include "SkData.h"
 #include "SkReader32.h"
 #include "SkSafeMath.h"
 #include "SkSafeRange.h"
 #include "SkTo.h"
 #include "SkWriter32.h"
+#include <atomic>
 #include <new>
 
-static int32_t gNextID = 1;
 static int32_t next_id() {
+    static std::atomic<int32_t> nextID{1};
+
     int32_t id;
     do {
-        id = sk_atomic_inc(&gNextID);
+        id = nextID++;
     } while (id == SK_InvalidGenID);
     return id;
 }
diff --git a/src/effects/SkLayerDrawLooper.cpp b/src/effects/SkLayerDrawLooper.cpp
index c9c60cc..02ad625 100644
--- a/src/effects/SkLayerDrawLooper.cpp
+++ b/src/effects/SkLayerDrawLooper.cpp
@@ -79,7 +79,6 @@
                     sk_srgb_singleton());
 
     BitFlags bits = info.fPaintBits;
-    SkTextEncoding encoding = (SkTextEncoding)dst->getTextEncoding();
 
     if (0 == bits) {
         return;
@@ -91,7 +90,6 @@
         *dst = src;
         dst->setFlags(f);
         dst->setColor4f(c, sk_srgb_singleton());
-        dst->setTextEncoding(encoding);
         return;
     }
 
@@ -103,10 +101,6 @@
         dst->setStrokeJoin(src.getStrokeJoin());
     }
 
-    if (bits & kTextSkewX_Bit) {
-        dst->setTextSkewX(src.getTextSkewX());
-    }
-
     if (bits & kPathEffect_Bit) {
         dst->setPathEffect(src.refPathEffect());
     }
diff --git a/src/effects/imagefilters/SkAlphaThresholdFilter.cpp b/src/effects/imagefilters/SkAlphaThresholdFilter.cpp
index 02d0c0a..7056e4d 100644
--- a/src/effects/imagefilters/SkAlphaThresholdFilter.cpp
+++ b/src/effects/imagefilters/SkAlphaThresholdFilter.cpp
@@ -115,16 +115,19 @@
         return nullptr;
     }
 
-    GrPaint paint;
-    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
     SkRegion::Iterator iter(fRegion);
     rtContext->clear(nullptr, SK_PMColor4fTRANSPARENT,
                      GrRenderTargetContext::CanClearFullscreen::kYes);
 
     GrFixedClip clip(SkIRect::MakeWH(bounds.width(), bounds.height()));
     while (!iter.done()) {
+        GrPaint paint;
+        paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
+
         SkRect rect = SkRect::Make(iter.rect());
+
         rtContext->drawRect(clip, std::move(paint), GrAA::kNo, inMatrix, rect);
+
         iter.next();
     }
 
diff --git a/src/effects/imagefilters/SkArithmeticImageFilter.cpp b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
index 5c69068..8e8dabf 100644
--- a/src/effects/imagefilters/SkArithmeticImageFilter.cpp
+++ b/src/effects/imagefilters/SkArithmeticImageFilter.cpp
@@ -314,7 +314,7 @@
                 SkIntToScalar(bgSubset.top()  - backgroundOffset.fY));
         bgFP = GrTextureDomainEffect::Make(
                 std::move(backgroundProxy), backgroundMatrix,
-                GrTextureDomain::MakeTexelDomain(bgSubset),
+                GrTextureDomain::MakeTexelDomain(bgSubset, GrTextureDomain::kDecal_Mode),
                 GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
         bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
                                              background->alphaType(),
@@ -331,7 +331,7 @@
                 SkIntToScalar(fgSubset.top()  - foregroundOffset.fY));
         auto foregroundFP = GrTextureDomainEffect::Make(
                 std::move(foregroundProxy), foregroundMatrix,
-                GrTextureDomain::MakeTexelDomain(fgSubset),
+                GrTextureDomain::MakeTexelDomain(fgSubset, GrTextureDomain::kDecal_Mode),
                 GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
         foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
                                                      foreground->getColorSpace(),
diff --git a/src/effects/imagefilters/SkDisplacementMapEffect.cpp b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
index cf95950..a29c38c 100644
--- a/src/effects/imagefilters/SkDisplacementMapEffect.cpp
+++ b/src/effects/imagefilters/SkDisplacementMapEffect.cpp
@@ -178,8 +178,6 @@
     std::unique_ptr<GrFragmentProcessor> clone() const override;
 
 private:
-    static OptimizationFlags OptimizationFlags(GrPixelConfig colorConfig);
-
     GrDisplacementMapEffect(const GrDisplacementMapEffect&);
 
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
@@ -429,13 +427,6 @@
     GrGLDisplacementMapEffect::GenKey(*this, caps, b);
 }
 
-GrFragmentProcessor::OptimizationFlags GrDisplacementMapEffect::OptimizationFlags(
-        GrPixelConfig colorConfig) {
-    return GrPixelConfigIsOpaque(colorConfig)
-                   ? GrFragmentProcessor::kPreservesOpaqueInput_OptimizationFlag
-                   : GrFragmentProcessor::kNone_OptimizationFlags;
-}
-
 GrDisplacementMapEffect::GrDisplacementMapEffect(
         SkDisplacementMapEffect::ChannelSelectorType xChannelSelector,
         SkDisplacementMapEffect::ChannelSelectorType yChannelSelector,
@@ -444,12 +435,15 @@
         const SkMatrix& offsetMatrix,
         sk_sp<GrTextureProxy> color,
         const SkISize& colorDimensions)
-        : INHERITED(kGrDisplacementMapEffect_ClassID, OptimizationFlags(color->config()))
+        : INHERITED(kGrDisplacementMapEffect_ClassID,
+                    GrFragmentProcessor::kNone_OptimizationFlags)
         , fDisplacementTransform(offsetMatrix, displacement.get())
         , fDisplacementSampler(displacement)
         , fColorTransform(color.get())
-        , fDomain(color.get(), GrTextureDomain::MakeTexelDomain(SkIRect::MakeSize(colorDimensions)),
-                  GrTextureDomain::kDecal_Mode)
+        , fDomain(color.get(),
+                  GrTextureDomain::MakeTexelDomain(SkIRect::MakeSize(colorDimensions),
+                                                   GrTextureDomain::kDecal_Mode),
+                  GrTextureDomain::kDecal_Mode, GrTextureDomain::kDecal_Mode)
         , fColorSampler(color)
         , fXChannelSelector(xChannelSelector)
         , fYChannelSelector(yChannelSelector)
@@ -460,8 +454,7 @@
 }
 
 GrDisplacementMapEffect::GrDisplacementMapEffect(const GrDisplacementMapEffect& that)
-        : INHERITED(kGrDisplacementMapEffect_ClassID,
-                    OptimizationFlags(that.fColorSampler.proxy()->config()))
+        : INHERITED(kGrDisplacementMapEffect_ClassID, that.optimizationFlags())
         , fDisplacementTransform(that.fDisplacementTransform)
         , fDisplacementSampler(that.fDisplacementSampler)
         , fColorTransform(that.fColorTransform)
@@ -597,7 +590,7 @@
 void GrGLDisplacementMapEffect::onSetData(const GrGLSLProgramDataManager& pdman,
                                           const GrFragmentProcessor& proc) {
     const GrDisplacementMapEffect& displacementMap = proc.cast<GrDisplacementMapEffect>();
-    GrSurfaceProxy* proxy = displacementMap.textureSampler(1).proxy();
+    GrTextureProxy* proxy = displacementMap.textureSampler(1).proxy();
     GrTexture* colorTex = proxy->peekTexture();
 
     SkScalar scaleX = displacementMap.scale().fX / colorTex->width();
@@ -605,7 +598,8 @@
     pdman.set2f(fScaleUni, SkScalarToFloat(scaleX),
                 proxy->origin() == kTopLeft_GrSurfaceOrigin ?
                 SkScalarToFloat(scaleY) : SkScalarToFloat(-scaleY));
-    fGLDomain.setData(pdman, displacementMap.domain(), proxy);
+    fGLDomain.setData(pdman, displacementMap.domain(), proxy,
+                      displacementMap.textureSampler(1).samplerState());
 }
 
 void GrGLDisplacementMapEffect::GenKey(const GrProcessor& proc,
diff --git a/src/effects/imagefilters/SkLightingImageFilter.cpp b/src/effects/imagefilters/SkLightingImageFilter.cpp
index 92225a3..8e2357e 100644
--- a/src/effects/imagefilters/SkLightingImageFilter.cpp
+++ b/src/effects/imagefilters/SkLightingImageFilter.cpp
@@ -1676,8 +1676,8 @@
 static GrTextureDomain create_domain(GrTextureProxy* proxy, const SkIRect* srcBounds,
                                      GrTextureDomain::Mode mode) {
     if (srcBounds) {
-        SkRect texelDomain = GrTextureDomain::MakeTexelDomainForMode(*srcBounds, mode);
-        return GrTextureDomain(proxy, texelDomain, mode);
+        SkRect texelDomain = GrTextureDomain::MakeTexelDomain(*srcBounds, mode);
+        return GrTextureDomain(proxy, texelDomain, mode, mode);
     } else {
         return GrTextureDomain::IgnoredDomain();
     }
@@ -1926,7 +1926,7 @@
     pdman.set1f(fSurfaceScaleUni, lighting.surfaceScale());
     sk_sp<SkImageFilterLight> transformedLight(
             lighting.light()->transform(lighting.filterMatrix()));
-    fDomain.setData(pdman, lighting.domain(), proxy);
+    fDomain.setData(pdman, lighting.domain(), proxy, lighting.textureSampler(0).samplerState());
     fLight->setData(pdman, transformedLight.get());
 }
 
diff --git a/src/effects/imagefilters/SkMorphologyImageFilter.cpp b/src/effects/imagefilters/SkMorphologyImageFilter.cpp
index 662343b..6ddc80d 100644
--- a/src/effects/imagefilters/SkMorphologyImageFilter.cpp
+++ b/src/effects/imagefilters/SkMorphologyImageFilter.cpp
@@ -309,13 +309,17 @@
                                        int radius,
                                        Type type,
                                        const float range[2])
-        : INHERITED(kGrMorphologyEffect_ClassID, ModulateByConfigOptimizationFlags(proxy->config()))
+        : INHERITED(kGrMorphologyEffect_ClassID,
+                    ModulateForClampedSamplerOptFlags(proxy->config()))
         , fCoordTransform(proxy.get())
         , fTextureSampler(std::move(proxy))
         , fDirection(direction)
         , fRadius(radius)
         , fType(type)
         , fUseRange(SkToBool(range)) {
+    // Make sure the sampler's ctor uses the clamp wrap mode
+    SkASSERT(fTextureSampler.samplerState().wrapModeX() == GrSamplerState::WrapMode::kClamp &&
+             fTextureSampler.samplerState().wrapModeY() == GrSamplerState::WrapMode::kClamp);
     this->addCoordTransform(&fCoordTransform);
     this->setTextureSamplerCnt(1);
     if (fUseRange) {
diff --git a/src/effects/imagefilters/SkXfermodeImageFilter.cpp b/src/effects/imagefilters/SkXfermodeImageFilter.cpp
index 08b24a6..62147e3 100644
--- a/src/effects/imagefilters/SkXfermodeImageFilter.cpp
+++ b/src/effects/imagefilters/SkXfermodeImageFilter.cpp
@@ -280,10 +280,10 @@
         SkMatrix bgMatrix = SkMatrix::MakeTrans(
                 SkIntToScalar(bgSubset.left() - backgroundOffset.fX),
                 SkIntToScalar(bgSubset.top()  - backgroundOffset.fY));
-        bgFP = GrTextureDomainEffect::Make(std::move(backgroundProxy), bgMatrix,
-                                           GrTextureDomain::MakeTexelDomain(bgSubset),
-                                           GrTextureDomain::kDecal_Mode,
-                                           GrSamplerState::Filter::kNearest);
+        bgFP = GrTextureDomainEffect::Make(
+                    std::move(backgroundProxy), bgMatrix,
+                    GrTextureDomain::MakeTexelDomain(bgSubset, GrTextureDomain::kDecal_Mode),
+                    GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
         bgFP = GrColorSpaceXformEffect::Make(std::move(bgFP), background->getColorSpace(),
                                              background->alphaType(),
                                              outputProperties.colorSpace());
@@ -299,7 +299,7 @@
                 SkIntToScalar(fgSubset.top()  - foregroundOffset.fY));
         auto foregroundFP = GrTextureDomainEffect::Make(
                 std::move(foregroundProxy), fgMatrix,
-                GrTextureDomain::MakeTexelDomain(fgSubset),
+                GrTextureDomain::MakeTexelDomain(fgSubset, GrTextureDomain::kDecal_Mode),
                 GrTextureDomain::kDecal_Mode, GrSamplerState::Filter::kNearest);
         foregroundFP = GrColorSpaceXformEffect::Make(std::move(foregroundFP),
                                                      foreground->getColorSpace(),
diff --git a/src/gpu/GrAHardwareBufferImageGenerator.cpp b/src/gpu/GrAHardwareBufferImageGenerator.cpp
index b7ca5bd..6ed48fb 100644
--- a/src/gpu/GrAHardwareBufferImageGenerator.cpp
+++ b/src/gpu/GrAHardwareBufferImageGenerator.cpp
@@ -11,7 +11,6 @@
 #define GL_GLEXT_PROTOTYPES
 #define EGL_EGLEXT_PROTOTYPES
 
-#include "vk/GrVkVulkan.h"
 
 #include "GrAHardwareBufferImageGenerator.h"
 
@@ -189,19 +188,44 @@
         return GrBackendTexture();
     }
 
-    SkASSERT(format == hwbFormatProps.format);
-    SkASSERT(SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & hwbFormatProps.formatFeatures) &&
-             SkToBool(VK_FORMAT_FEATURE_TRANSFER_SRC_BIT & hwbFormatProps.formatFeatures) &&
-             SkToBool(VK_FORMAT_FEATURE_TRANSFER_DST_BIT & hwbFormatProps.formatFeatures));
+    VkExternalFormatANDROID externalFormat;
+    externalFormat.sType = VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID;
+    externalFormat.pNext = nullptr;
+    externalFormat.externalFormat = 0;  // If this is zero it is as if we aren't using this struct.
 
-    const VkExternalMemoryImageCreateInfo externalMemoryImageInfo {
-        VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, // sType
-        nullptr, // pNext
-        VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID, // handleTypes
+    const GrVkYcbcrConversionInfo* ycbcrConversion = backendFormat.getVkYcbcrConversionInfo();
+    if (!ycbcrConversion) {
+        return GrBackendTexture();
+    }
+
+    if (hwbFormatProps.format != VK_FORMAT_UNDEFINED) {
+        // TODO: We should not assume the transfer features here and instead should have a way for
+        // Ganesh's tracking of intenral images to report whether or not they support transfers.
+        SkASSERT(SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & hwbFormatProps.formatFeatures) &&
+                 SkToBool(VK_FORMAT_FEATURE_TRANSFER_SRC_BIT & hwbFormatProps.formatFeatures) &&
+                 SkToBool(VK_FORMAT_FEATURE_TRANSFER_DST_BIT & hwbFormatProps.formatFeatures));
+        SkASSERT(!ycbcrConversion->isValid());
+    } else {
+        SkASSERT(ycbcrConversion->isValid());
+        // We have an external only format
+        SkASSERT(SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & hwbFormatProps.formatFeatures));
+        SkASSERT(format == VK_FORMAT_UNDEFINED);
+        SkASSERT(hwbFormatProps.externalFormat == ycbcrConversion->fExternalFormat);
+        externalFormat.externalFormat = hwbFormatProps.externalFormat;
+    }
+    SkASSERT(format == hwbFormatProps.format);
+
+    const VkExternalMemoryImageCreateInfo externalMemoryImageInfo{
+            VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO,                 // sType
+            &externalFormat,                                                     // pNext
+            VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,  // handleTypes
     };
-    VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT |
-                                   VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
-                                   VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+    VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT;
+    if (format != VK_FORMAT_UNDEFINED) {
+        usageFlags = usageFlags |
+                VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+                VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+    }
 
     // TODO: Check the supported tilings vkGetPhysicalDeviceImageFormatProperties2 to see if we have
     // to use linear. Add better linear support throughout Ganesh.
@@ -231,22 +255,6 @@
         return GrBackendTexture();
     }
 
-    VkImageMemoryRequirementsInfo2 memReqsInfo;
-    memReqsInfo.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2;
-    memReqsInfo.pNext = nullptr;
-    memReqsInfo.image = image;
-
-    VkMemoryDedicatedRequirements dedicatedMemReqs;
-    dedicatedMemReqs.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS;
-    dedicatedMemReqs.pNext = nullptr;
-
-    VkMemoryRequirements2 memReqs;
-    memReqs.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2;
-    memReqs.pNext = &dedicatedMemReqs;
-
-    VK_CALL(GetImageMemoryRequirements2(device, &memReqsInfo, &memReqs));
-    SkASSERT(VK_TRUE == dedicatedMemReqs.requiresDedicatedAllocation);
-
     VkPhysicalDeviceMemoryProperties2 phyDevMemProps;
     phyDevMemProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2;
     phyDevMemProps.pNext = nullptr;
@@ -325,6 +333,7 @@
     // support that extension. Or if we know the source of the AHardwareBuffer is not from a
     // "foreign" device we can leave them as external.
     imageInfo.fCurrentQueueFamily = VK_QUEUE_FAMILY_EXTERNAL;
+    imageInfo.fYcbcrConversionInfo = *ycbcrConversion;
 
     *deleteProc = GrAHardwareBufferImageGenerator::DeleteVkImage;
     *deleteCtx = new VulkanCleanupHelper(gpu, image, memory);
@@ -441,7 +450,8 @@
     }
 }
 
-GrBackendFormat get_backend_format(GrBackendApi backend, uint32_t bufferFormat) {
+GrBackendFormat get_backend_format(GrContext* context, AHardwareBuffer* hardwareBuffer,
+                                   GrBackendApi backend, uint32_t bufferFormat) {
     if (backend == GrBackendApi::kOpenGL) {
         switch (bufferFormat) {
             //TODO: find out if we can detect, which graphic buffers support GR_GL_TEXTURE_2D
@@ -460,8 +470,8 @@
                 return GrBackendFormat::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL);
         }
     } else if (backend == GrBackendApi::kVulkan) {
+#ifdef SK_VULKAN
         switch (bufferFormat) {
-            //TODO: find out if we can detect, which graphic buffers support GR_GL_TEXTURE_2D
             case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
                 return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_UNORM);
             case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
@@ -474,9 +484,54 @@
                 return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8A8_UNORM);
             case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
                 return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8_UNORM);
-            default:
-                return GrBackendFormat::MakeVk(VK_FORMAT_R8G8B8_UNORM);
+            default: {
+                GrVkGpu* gpu = static_cast<GrVkGpu*>(context->contextPriv().getGpu());
+                SkASSERT(gpu);
+                VkDevice device = gpu->device();
+
+                if (!gpu->vkCaps().supportsAndroidHWBExternalMemory()) {
+                    return GrBackendFormat();
+                }
+                VkAndroidHardwareBufferFormatPropertiesANDROID hwbFormatProps;
+                hwbFormatProps.sType =
+                        VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID;
+                hwbFormatProps.pNext = nullptr;
+
+                VkAndroidHardwareBufferPropertiesANDROID hwbProps;
+                hwbProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;
+                hwbProps.pNext = &hwbFormatProps;
+
+                VkResult err = VK_CALL(GetAndroidHardwareBufferProperties(device, hardwareBuffer,
+                                                                          &hwbProps));
+                if (VK_SUCCESS != err) {
+                    return GrBackendFormat();
+                }
+
+                if (hwbFormatProps.format != VK_FORMAT_UNDEFINED) {
+                    return GrBackendFormat();
+                }
+
+                GrVkYcbcrConversionInfo ycbcrConversion;
+                ycbcrConversion.fYcbcrModel = hwbFormatProps.suggestedYcbcrModel;
+                ycbcrConversion.fYcbcrRange = hwbFormatProps.suggestedYcbcrRange;
+                ycbcrConversion.fXChromaOffset = hwbFormatProps.suggestedXChromaOffset;
+                ycbcrConversion.fYChromaOffset = hwbFormatProps.suggestedYChromaOffset;
+                ycbcrConversion.fForceExplicitReconstruction = VK_FALSE;
+                ycbcrConversion.fExternalFormat = hwbFormatProps.externalFormat;
+                ycbcrConversion.fExternalFormatFeatures = hwbFormatProps.formatFeatures;
+                if (VK_FORMAT_FEATURE_SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER_BIT &
+                    hwbFormatProps.formatFeatures) {
+                    ycbcrConversion.fChromaFilter = VK_FILTER_LINEAR;
+                } else {
+                    ycbcrConversion.fChromaFilter = VK_FILTER_NEAREST;
+                }
+
+                return GrBackendFormat::MakeVk(ycbcrConversion);
+            }
         }
+#else
+        return GrBackendFormat();
+#endif
     }
     return GrBackendFormat();
 }
@@ -486,11 +541,13 @@
         return nullptr;
     }
 
-    GrPixelConfig pixelConfig;
-    GrBackendFormat backendFormat = get_backend_format(context->contextPriv().getBackend(),
+    GrBackendFormat backendFormat = get_backend_format(context, fHardwareBuffer,
+                                                       context->contextPriv().getBackend(),
                                                        fBufferFormat);
-    if (!context->contextPriv().caps()->getConfigFromBackendFormat(
-            backendFormat, this->getInfo().colorType(), &pixelConfig)) {
+    GrPixelConfig pixelConfig = context->contextPriv().caps()->getConfigFromBackendFormat(
+            backendFormat, this->getInfo().colorType());
+
+    if (pixelConfig == kUnknown_GrPixelConfig) {
         return nullptr;
     }
 
@@ -505,6 +562,12 @@
     GrTextureType textureType = GrTextureType::k2D;
     if (context->contextPriv().getBackend() == GrBackendApi::kOpenGL) {
         textureType = GrTextureType::kExternal;
+    } else if (context->contextPriv().getBackend() == GrBackendApi::kVulkan) {
+        const VkFormat* format = backendFormat.getVkFormat();
+        SkASSERT(format);
+        if (*format == VK_FORMAT_UNDEFINED) {
+            textureType = GrTextureType::kExternal;
+        }
     }
 
     auto proxyProvider = context->contextPriv().proxyProvider();
@@ -515,8 +578,8 @@
     const bool isProtectedContent = fIsProtectedContent;
 
     sk_sp<GrTextureProxy> texProxy = proxyProvider->createLazyProxy(
-            [context, hardwareBuffer, width, height, pixelConfig, isProtectedContent, backendFormat]
-            (GrResourceProvider* resourceProvider) {
+            [context, hardwareBuffer, width, height, pixelConfig, isProtectedContent,
+             backendFormat](GrResourceProvider* resourceProvider) {
                 if (!resourceProvider) {
                     AHardwareBuffer_release(hardwareBuffer);
                     return sk_sp<GrTexture>();
@@ -537,7 +600,8 @@
                 SkASSERT(deleteImageProc && deleteImageCtx);
 
                 backendTex.fConfig = pixelConfig;
-                sk_sp<GrTexture> tex = resourceProvider->wrapBackendTexture(backendTex);
+                sk_sp<GrTexture> tex = resourceProvider->wrapBackendTexture(
+                        backendTex, kBorrow_GrWrapOwnership, kRead_GrIOType);
                 if (!tex) {
                     deleteImageProc(deleteImageCtx);
                     return sk_sp<GrTexture>();
@@ -551,8 +615,8 @@
 
                 return tex;
             },
-            backendFormat, desc, fSurfaceOrigin, GrMipMapped::kNo, SkBackingFit::kExact,
-            SkBudgeted::kNo);
+            backendFormat, desc, fSurfaceOrigin, GrMipMapped::kNo,
+            GrInternalSurfaceFlags::kReadOnly, SkBackingFit::kExact, SkBudgeted::kNo);
 
     if (!texProxy) {
         AHardwareBuffer_release(hardwareBuffer);
@@ -578,7 +642,8 @@
 
     GrMipMapped mipMapped = willNeedMipMaps ? GrMipMapped::kYes : GrMipMapped::kNo;
 
-    return GrSurfaceProxy::Copy(context, texProxy.get(), mipMapped, subset, SkBudgeted::kYes);
+    return GrSurfaceProxy::Copy(context, texProxy.get(), mipMapped, subset, SkBackingFit::kExact,
+                                SkBudgeted::kYes);
 }
 
 bool GrAHardwareBufferImageGenerator::onIsValid(GrContext* context) const {
diff --git a/src/gpu/GrBackendSurface.cpp b/src/gpu/GrBackendSurface.cpp
index c512c0a..ecb7844 100644
--- a/src/gpu/GrBackendSurface.cpp
+++ b/src/gpu/GrBackendSurface.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "vk/GrVkVulkan.h"
 
 #include "GrBackendSurface.h"
 
@@ -18,6 +17,7 @@
 #endif
 #ifdef SK_METAL
 #include "mtl/GrMtlTypes.h"
+#include "mtl/GrMtlCppUtil.h"
 #endif
 
 GrBackendFormat::GrBackendFormat(GrGLenum format, GrGLenum target)
@@ -342,6 +342,37 @@
     return false;
 }
 
+GrBackendFormat GrBackendTexture::getBackendFormat() const {
+    if (!this->isValid()) {
+        return GrBackendFormat();
+    }
+    switch (fBackend) {
+        case GrBackendApi::kOpenGL:
+            return GrBackendFormat::MakeGL(fGLInfo.fFormat, fGLInfo.fTarget);
+#ifdef SK_VULKAN
+        case GrBackendApi::kVulkan: {
+            auto info = fVkInfo.snapImageInfo();
+            if (info.fYcbcrConversionInfo.isValid()) {
+                SkASSERT(info.fFormat == VK_FORMAT_UNDEFINED);
+                return GrBackendFormat::MakeVk(info.fYcbcrConversionInfo);
+            }
+            return GrBackendFormat::MakeVk(info.fFormat);
+        }
+#endif
+#ifdef SK_METAL
+        case GrBackendApi::kMetal: {
+            GrMtlTextureInfo mtlInfo;
+            SkAssertResult(this->getMtlTextureInfo(&mtlInfo));
+            return GrBackendFormat::MakeMtl(GrGetMTLPixelFormatFromMtlTextureInfo(mtlInfo));
+        }
+#endif
+        case GrBackendApi::kMock:
+            return GrBackendFormat::MakeMock(fMockInfo.fConfig);
+        default:
+            return GrBackendFormat();
+    }
+}
+
 #if GR_TEST_UTILS
 bool GrBackendTexture::TestingOnly_Equals(const GrBackendTexture& t0, const GrBackendTexture& t1) {
     if (!t0.isValid() || !t1.isValid()) {
diff --git a/src/gpu/GrBackendTextureImageGenerator.cpp b/src/gpu/GrBackendTextureImageGenerator.cpp
index 4f887f2..f191174 100644
--- a/src/gpu/GrBackendTextureImageGenerator.cpp
+++ b/src/gpu/GrBackendTextureImageGenerator.cpp
@@ -43,8 +43,13 @@
     context->contextPriv().getResourceCache()->insertCrossContextGpuResource(texture.get());
 
     GrBackendTexture backendTexture = texture->getBackendTexture();
-    if (!context->contextPriv().caps()->validateBackendTexture(backendTexture, colorType,
-                                                               &backendTexture.fConfig)) {
+    GrBackendFormat backendFormat = backendTexture.getBackendFormat();
+    if (!backendFormat.isValid()) {
+        return nullptr;
+    }
+    backendTexture.fConfig =
+            context->contextPriv().caps()->getConfigFromBackendFormat(backendFormat, colorType);
+    if (backendTexture.fConfig == kUnknown_GrPixelConfig) {
         return nullptr;
     }
 
@@ -133,13 +138,12 @@
     GrBackendTexture backendTexture = fBackendTexture;
     RefHelper* refHelper = fRefHelper;
 
-    GrBackendFormat format =
-            context->contextPriv().caps()->createFormatFromBackendTexture(backendTexture);
+    GrBackendFormat format = backendTexture.getBackendFormat();
     SkASSERT(format.isValid());
 
     sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy(
-            [refHelper, releaseProcHelper, semaphore, backendTexture]
-            (GrResourceProvider* resourceProvider) {
+            [refHelper, releaseProcHelper, semaphore,
+             backendTexture](GrResourceProvider* resourceProvider) {
                 if (!resourceProvider) {
                     return sk_sp<GrTexture>();
                 }
@@ -163,9 +167,8 @@
                     // informs us that the context is done with it. This is unfortunate - we'll have
                     // two texture objects referencing the same GPU object. However, no client can
                     // ever see the original texture, so this should be safe.
-                    tex = resourceProvider->wrapBackendTexture(backendTexture,
-                                                               kBorrow_GrWrapOwnership,
-                                                               true);
+                    tex = resourceProvider->wrapBackendTexture(
+                            backendTexture, kBorrow_GrWrapOwnership, kRead_GrIOType, true);
                     if (!tex) {
                         return sk_sp<GrTexture>();
                     }
@@ -176,7 +179,8 @@
 
                 return tex;
             },
-            format, desc, fSurfaceOrigin, mipMapped, SkBackingFit::kExact, SkBudgeted::kNo);
+            format, desc, fSurfaceOrigin, mipMapped, GrInternalSurfaceFlags::kReadOnly,
+            SkBackingFit::kExact, SkBudgeted::kNo);
 
     if (!proxy) {
         return nullptr;
diff --git a/src/gpu/GrBitmapTextureMaker.cpp b/src/gpu/GrBitmapTextureMaker.cpp
index 224af01..291d6b1 100644
--- a/src/gpu/GrBitmapTextureMaker.cpp
+++ b/src/gpu/GrBitmapTextureMaker.cpp
@@ -83,7 +83,8 @@
                 // mipmapped version. The texture backing the unmipped version will remain in the
                 // resource cache until the last texture proxy referencing it is deleted at which
                 // time it too will be deleted or recycled.
-                proxyProvider->removeUniqueKeyFromProxy(fOriginalKey, proxy.get());
+                SkASSERT(proxy->getUniqueKey() == fOriginalKey);
+                proxyProvider->removeUniqueKeyFromProxy(proxy.get());
                 proxyProvider->assignUniqueKeyToProxy(fOriginalKey, mippedProxy.get());
                 GrInstallBitmapUniqueKeyInvalidator(fOriginalKey, proxyProvider->contextUniqueID(),
                                                     fBitmap.pixelRef());
diff --git a/src/gpu/GrBlurUtils.cpp b/src/gpu/GrBlurUtils.cpp
index 4e29461..331e415 100644
--- a/src/gpu/GrBlurUtils.cpp
+++ b/src/gpu/GrBlurUtils.cpp
@@ -285,6 +285,7 @@
         // left to do.
         return;
     }
+    assert_alive(paint);
 
     // If the path is hairline, ignore inverse fill.
     bool inverseFilled = shape->inverseFilled() &&
@@ -370,7 +371,7 @@
         SkAssertResult(as_MFB(maskFilter)->asABlur(&rec));
 
         builder[5] = rec.fStyle;  // TODO: we could put this with the other style bits
-        builder[6] = rec.fSigma;
+        builder[6] = SkFloat2Bits(rec.fSigma);
         shape->writeUnstyledKey(&builder[7]);
     }
 
@@ -421,6 +422,7 @@
                 // This path is completely drawn
                 return;
             }
+            assert_alive(paint);
         }
     }
 
diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp
index 64625d9..986ae20 100644
--- a/src/gpu/GrCaps.cpp
+++ b/src/gpu/GrCaps.cpp
@@ -6,9 +6,10 @@
  */
 
 #include "GrCaps.h"
-
 #include "GrBackendSurface.h"
 #include "GrContextOptions.h"
+#include "GrSurface.h"
+#include "GrSurfaceProxy.h"
 #include "GrTypesPriv.h"
 #include "GrWindowRectangles.h"
 #include "SkJSONWriter.h"
@@ -33,7 +34,6 @@
     fPreferFullscreenClears = false;
     fMustClearUploadedBufferData = false;
     fSupportsAHardwareBufferImages = false;
-    fSampleShadingSupport = false;
     fFenceSyncSupport = false;
     fCrossContextTextureSupport = false;
     fHalfFloatVertexAttributeSupport = false;
@@ -72,6 +72,9 @@
 
     fPreferVRAMUseOverFlushes = true;
 
+    // Default to true, allow older versions of OpenGL to disable explicitly
+    fClampToBorderSupport = true;
+
     fDriverBugWorkarounds = options.fDriverBugWorkarounds;
 }
 
@@ -121,6 +124,7 @@
         case kRGBA_4444_GrPixelConfig: return "RGBA444";
         case kRGBA_8888_GrPixelConfig: return "RGBA8888";
         case kRGB_888_GrPixelConfig: return "RGB888";
+        case kRG_88_GrPixelConfig: return "RG88";
         case kBGRA_8888_GrPixelConfig: return "BGRA8888";
         case kSRGBA_8888_GrPixelConfig: return "SRGBA8888";
         case kSBGRA_8888_GrPixelConfig: return "SBGRA8888";
@@ -177,7 +181,6 @@
     writer->appendBool("Prefer fullscreen clears", fPreferFullscreenClears);
     writer->appendBool("Must clear buffer memory", fMustClearUploadedBufferData);
     writer->appendBool("Supports importing AHardwareBuffers", fSupportsAHardwareBufferImages);
-    writer->appendBool("Sample shading support", fSampleShadingSupport);
     writer->appendBool("Fence sync support", fFenceSyncSupport);
     writer->appendBool("Cross context texture support", fCrossContextTextureSupport);
     writer->appendBool("Half float vertex attribute support", fHalfFloatVertexAttributeSupport);
@@ -242,6 +245,15 @@
 void GrCaps::dumpJSON(SkJSONWriter* writer) const { }
 #endif
 
+bool GrCaps::surfaceSupportsWritePixels(const GrSurface* surface) const {
+    return surface->readOnly() ? false : this->onSurfaceSupportsWritePixels(surface);
+}
+
+bool GrCaps::canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                            const SkIRect& srcRect, const SkIPoint& dstPoint) const {
+    return dst->readOnly() ? false : this->onCanCopySurface(dst, src, srcRect, dstPoint);
+}
+
 bool GrCaps::validateSurfaceDesc(const GrSurfaceDesc& desc, GrMipMapped mipped) const {
     if (!this->isConfigTexturable(desc.fConfig)) {
         return false;
@@ -280,11 +292,3 @@
 GrBackendFormat GrCaps::getBackendFormatFromColorType(SkColorType ct) const {
     return this->getBackendFormatFromGrColorType(SkColorTypeToGrColorType(ct), GrSRGBEncoded::kNo);
 }
-
-GrBackendFormat GrCaps::createFormatFromBackendTexture(const GrBackendTexture& backendTex) const {
-    if (!backendTex.isValid()) {
-        return GrBackendFormat();
-    }
-    return this->onCreateFormatFromBackendTexture(backendTex);
-}
-
diff --git a/src/gpu/GrCaps.h b/src/gpu/GrCaps.h
index c695ebe..0fee8cf 100644
--- a/src/gpu/GrCaps.h
+++ b/src/gpu/GrCaps.h
@@ -192,7 +192,7 @@
      * If this returns false then the caller should implement a fallback where a temporary texture
      * is created, pixels are written to it, and then that is copied or drawn into the the surface.
      */
-    virtual bool surfaceSupportsWritePixels(const GrSurface*) const = 0;
+    bool surfaceSupportsWritePixels(const GrSurface*) const;
 
     /**
      * Backends may have restrictions on what types of surfaces support GrGpu::readPixels().
@@ -239,15 +239,13 @@
 
     bool wireframeMode() const { return fWireframeMode; }
 
-    bool sampleShadingSupport() const { return fSampleShadingSupport; }
-
     bool fenceSyncSupport() const { return fFenceSyncSupport; }
     bool crossContextTextureSupport() const { return fCrossContextTextureSupport; }
     /**
      * Returns whether or not we will be able to do a copy given the passed in params
      */
-    virtual bool canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
-                                const SkIRect& srcRect, const SkIPoint& dstPoint) const = 0;
+    bool canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                        const SkIRect& srcRect, const SkIPoint& dstPoint) const;
 
     bool dynamicStateArrayGeometryProcessorTextureSupport() const {
         return fDynamicStateArrayGeometryProcessorTextureSupport;
@@ -272,46 +270,35 @@
     bool validateSurfaceDesc(const GrSurfaceDesc&, GrMipMapped) const;
 
     /**
-     * Returns true if the GrBackendTexture can be used with the supplied SkColorType. If it is
-     * compatible, the passed in GrPixelConfig will be set to a config that matches the backend
-     * format and requested SkColorType.
+     * If the GrBackendRenderTarget can be used with the supplied SkColorType the return will be
+     * the config that matches the backend format and requested SkColorType. Otherwise, kUnknown is
+     * returned.
      */
-    virtual bool validateBackendTexture(const GrBackendTexture& tex, SkColorType ct,
-                                        GrPixelConfig*) const = 0;
-    virtual bool validateBackendRenderTarget(const GrBackendRenderTarget&, SkColorType,
-                                             GrPixelConfig*) const = 0;
+    virtual GrPixelConfig validateBackendRenderTarget(const GrBackendRenderTarget&,
+                                                      SkColorType) const = 0;
 
-    // TODO: replace validateBackendTexture and validateBackendRenderTarget with calls to
-    // getConfigFromBackendFormat?
+    // TODO: replace validateBackendRenderTarget with calls to getConfigFromBackendFormat?
     // TODO: it seems like we could pass the full SkImageInfo and validate its colorSpace too
-    virtual bool getConfigFromBackendFormat(const GrBackendFormat& format, SkColorType ct,
-                                            GrPixelConfig*) const = 0;
+    // Returns kUnknown if a valid config could not be determined.
+    virtual GrPixelConfig getConfigFromBackendFormat(const GrBackendFormat& format,
+                                                     SkColorType ct) const = 0;
 
     /**
-     * Special method only for YUVA images. Returns true if the format can be used for a
-     * YUVA plane, and the passed in GrPixelConfig will be set to a config that matches
-     * the backend texture.
+     * Special method only for YUVA images. Returns a config that matches the backend format or
+     * kUnknown if a config could not be determined.
      */
-    virtual bool getYUVAConfigFromBackendTexture(const GrBackendTexture& tex,
-                                                 GrPixelConfig*) const = 0;
+    virtual GrPixelConfig getYUVAConfigFromBackendFormat(const GrBackendFormat& format) const = 0;
 
-    /**
-     * Special method only for YUVA images. Returns true if the format can be used for a
-     * YUVA plane, and the passed in GrPixelConfig will be set to a config that matches
-     * the backend format.
-     */
-    virtual bool getYUVAConfigFromBackendFormat(const GrBackendFormat& format,
-                                                GrPixelConfig*) const = 0;
-
+    /** These are used when creating a new texture internally. */
     virtual GrBackendFormat getBackendFormatFromGrColorType(GrColorType ct,
                                                             GrSRGBEncoded srgbEncoded) const = 0;
     GrBackendFormat getBackendFormatFromColorType(SkColorType ct) const;
 
     /**
-     * Creates a GrBackendFormat which matches the backend texture. If the backend texture is
-     * invalid, the function will return the default GrBackendFormat.
+     * The CLAMP_TO_BORDER wrap mode for texture coordinates was added to desktop GL in 1.3, and
+     * GLES 3.2, but is also available in extensions. Vulkan and Metal always have support.
      */
-    GrBackendFormat createFormatFromBackendTexture(const GrBackendTexture&) const;
+    bool clampToBorderSupport() const { return fClampToBorderSupport; }
 
     const GrDriverBugWorkarounds& workarounds() const { return fDriverBugWorkarounds; }
 
@@ -321,12 +308,6 @@
         expand them. */
     void applyOptionsOverrides(const GrContextOptions& options);
 
-    /**
-     * Subclasses implement this to actually create a GrBackendFormat to match backend texture. At
-     * this point, the backend texture has already been validated.
-     */
-    virtual GrBackendFormat onCreateFormatFromBackendTexture(const GrBackendTexture&) const = 0;
-
     sk_sp<GrShaderCaps> fShaderCaps;
 
     bool fNPOTTextureTileSupport                     : 1;
@@ -349,6 +330,7 @@
     bool fMustClearUploadedBufferData                : 1;
     bool fSupportsAHardwareBufferImages              : 1;
     bool fHalfFloatVertexAttributeSupport            : 1;
+    bool fClampToBorderSupport                       : 1;
 
     // Driver workaround
     bool fBlacklistCoverageCounting                  : 1;
@@ -358,7 +340,6 @@
     // ANGLE performance workaround
     bool fPreferVRAMUseOverFlushes                   : 1;
 
-    bool fSampleShadingSupport                       : 1;
     // TODO: this may need to be an enum to support different fence types
     bool fFenceSyncSupport                           : 1;
 
@@ -389,6 +370,9 @@
 private:
     virtual void onApplyOptionsOverrides(const GrContextOptions&) {}
     virtual void onDumpJSON(SkJSONWriter*) const {}
+    virtual bool onSurfaceSupportsWritePixels(const GrSurface*) const = 0;
+    virtual bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                                  const SkIRect& srcRect, const SkIPoint& dstPoint) const = 0;
 
     // Backends should implement this if they have any extra requirements for use of window
     // rectangles for a specific GrBackendRenderTarget outside of basic support.
diff --git a/src/gpu/GrClipStackClip.cpp b/src/gpu/GrClipStackClip.cpp
index ca5043d..7aadaa4 100644
--- a/src/gpu/GrClipStackClip.cpp
+++ b/src/gpu/GrClipStackClip.cpp
@@ -127,6 +127,8 @@
                                              renderTargetContext->fsaaType(),
                                              GrAllowMixedSamples::kYes,
                                              *context->contextPriv().caps());
+        SkASSERT(!renderTargetContext->wrapsVkSecondaryCB());
+        canDrawArgs.fTargetIsWrappedVkSecondaryCB = false;
         canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings;
 
         // the 'false' parameter disallows use of the SW path renderer
@@ -156,8 +158,10 @@
     // a clip gets complex enough it can just be done in SW regardless
     // of whether it would invoke the GrSoftwarePathRenderer.
 
-    // If we're avoiding stencils, always use SW:
-    if (context->contextPriv().caps()->avoidStencilBuffers()) {
+    // If we're avoiding stencils, always use SW. This includes drawing into a wrapped vulkan
+    // secondary command buffer which can't handle stencils.
+    if (context->contextPriv().caps()->avoidStencilBuffers() ||
+        renderTargetContext->wrapsVkSecondaryCB()) {
         return true;
     }
 
@@ -258,7 +262,8 @@
 
     // If the stencil buffer is multisampled we can use it to do everything.
     if ((GrFSAAType::kNone == renderTargetContext->fsaaType() && reducedClip.maskRequiresAA()) ||
-        context->contextPriv().caps()->avoidStencilBuffers()) {
+        context->contextPriv().caps()->avoidStencilBuffers() ||
+        renderTargetContext->wrapsVkSecondaryCB()) {
         sk_sp<GrTextureProxy> result;
         if (UseSWOnlyPath(context, hasUserStencilSettings, renderTargetContext, reducedClip)) {
             // The clip geometry is complex enough that it will be more efficient to create it
@@ -277,7 +282,8 @@
 
         // If alpha or software clip mask creation fails, fall through to the stencil code paths,
         // unless stencils are disallowed.
-        if (context->contextPriv().caps()->avoidStencilBuffers()) {
+        if (context->contextPriv().caps()->avoidStencilBuffers() ||
+            renderTargetContext->wrapsVkSecondaryCB()) {
             SkDebugf("WARNING: Clip mask requires stencil, but stencil unavailable. "
                      "Clip will be ignored.\n");
             return false;
@@ -316,15 +322,15 @@
     builder[3] = numAnalyticFPs;
 }
 
-static void add_invalidate_on_pop_message(const SkClipStack& stack, uint32_t clipGenID,
-                                          const GrUniqueKey& clipMaskKey,
-                                          uint32_t contextUniqueID) {
+static void add_invalidate_on_pop_message(GrContext* context,
+                                          const SkClipStack& stack, uint32_t clipGenID,
+                                          const GrUniqueKey& clipMaskKey) {
+    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
+
     SkClipStack::Iter iter(stack, SkClipStack::Iter::kTop_IterStart);
     while (const Element* element = iter.prev()) {
         if (element->getGenID() == clipGenID) {
-            std::unique_ptr<GrUniqueKeyInvalidatedMessage> msg(
-                    new GrUniqueKeyInvalidatedMessage(clipMaskKey, contextUniqueID));
-            element->addResourceInvalidationMessage(std::move(msg));
+            element->addResourceInvalidationMessage(proxyProvider, clipMaskKey);
             return;
         }
     }
@@ -371,7 +377,7 @@
 
     SkASSERT(result->origin() == kTopLeft_GrSurfaceOrigin);
     proxyProvider->assignUniqueKeyToProxy(key, result.get());
-    add_invalidate_on_pop_message(*fStack, reducedClip.maskGenID(), key, context->uniqueID());
+    add_invalidate_on_pop_message(context, *fStack, reducedClip.maskGenID(), key);
 
     return result;
 }
@@ -517,6 +523,6 @@
 
     SkASSERT(proxy->origin() == kTopLeft_GrSurfaceOrigin);
     proxyProvider->assignUniqueKeyToProxy(key, proxy.get());
-    add_invalidate_on_pop_message(*fStack, reducedClip.maskGenID(), key, context->uniqueID());
+    add_invalidate_on_pop_message(context, *fStack, reducedClip.maskGenID(), key);
     return proxy;
 }
diff --git a/src/gpu/GrColorSpaceXform.cpp b/src/gpu/GrColorSpaceXform.cpp
index e8cb662..2cd5e5a 100644
--- a/src/gpu/GrColorSpaceXform.cpp
+++ b/src/gpu/GrColorSpaceXform.cpp
@@ -8,7 +8,6 @@
 #include "GrColorSpaceXform.h"
 #include "SkColorSpace.h"
 #include "SkColorSpacePriv.h"
-#include "SkMatrix44.h"
 #include "glsl/GrGLSLColorSpaceXformHelper.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index d962bbd..88d1ec2 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -6,7 +6,6 @@
  */
 
 #include "GrContext.h"
-#include <unordered_map>
 #include "GrBackendSemaphore.h"
 #include "GrClip.h"
 #include "GrContextOptions.h"
@@ -39,6 +38,8 @@
 #include "effects/GrSkSLFP.h"
 #include "ccpr/GrCoverageCountingPathRenderer.h"
 #include "text/GrTextBlobCache.h"
+#include <atomic>
+#include <unordered_map>
 
 #define ASSERT_OWNED_PROXY(P) \
     SkASSERT(!(P) || !((P)->peekTexture()) || (P)->peekTexture()->getContext() == this)
@@ -58,11 +59,11 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-static int32_t gNextID = 1;
 static int32_t next_id() {
+    static std::atomic<int32_t> nextID{1};
     int32_t id;
     do {
-        id = sk_atomic_inc(&gNextID);
+        id = nextID++;
     } while (id == SK_InvalidGenID);
     return id;
 }
@@ -203,8 +204,8 @@
         isMipMapped = false;
     }
 
-    GrPixelConfig config = kUnknown_GrPixelConfig;
-    if (!fCaps->getConfigFromBackendFormat(backendFormat, ii.colorType(), &config)) {
+    GrPixelConfig config = fCaps->getConfigFromBackendFormat(backendFormat, ii.colorType());
+    if (config == kUnknown_GrPixelConfig) {
         return SkSurfaceCharacterization(); // return an invalid characterization
     }
 
@@ -234,6 +235,7 @@
                                      SkSurfaceCharacterization::Textureable(true),
                                      SkSurfaceCharacterization::MipMapped(isMipMapped),
                                      SkSurfaceCharacterization::UsesGLFBO0(willUseGLFBO0),
+                                     SkSurfaceCharacterization::VulkanSecondaryCBCompatible(false),
                                      surfaceProps);
 }
 
@@ -265,6 +267,9 @@
 void GrContext::releaseResourcesAndAbandonContext() {
     ASSERT_SINGLE_OWNER
 
+    if (this->abandoned()) {
+        return;
+    }
     fProxyProvider->abandon();
     fResourceProvider->abandon();
 
@@ -312,7 +317,7 @@
     fResourceCache->purgeResourcesNotUsedSince(purgeTime);
 
     if (auto ccpr = fDrawingManager->getCoverageCountingPathRenderer()) {
-        ccpr->purgeCacheEntriesOlderThan(purgeTime);
+        ccpr->purgeCacheEntriesOlderThan(fProxyProvider, purgeTime);
     }
 
     fTextBlobCache->purgeStaleBlobs();
@@ -392,6 +397,16 @@
     fContext->fDrawingManager->flush(proxy);
 }
 
+////////////////////////////////////////////////////////////////////////////////
+
+void GrContext::storeVkPipelineCacheData() {
+    if (fGpu) {
+        fGpu->storeVkPipelineCacheData();
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 // TODO: This will be removed when GrSurfaceContexts are aware of their color types.
 // (skbug.com/6718)
 static bool valid_premul_config(GrPixelConfig config) {
@@ -403,6 +418,7 @@
         case kRGBA_4444_GrPixelConfig:          return true;
         case kRGBA_8888_GrPixelConfig:          return true;
         case kRGB_888_GrPixelConfig:            return false;
+        case kRG_88_GrPixelConfig:              return false;
         case kBGRA_8888_GrPixelConfig:          return true;
         case kSRGBA_8888_GrPixelConfig:         return true;
         case kSBGRA_8888_GrPixelConfig:         return true;
@@ -429,6 +445,7 @@
         case GrColorType::kABGR_4444:    return true;
         case GrColorType::kRGBA_8888:    return true;
         case GrColorType::kRGB_888x:     return false;
+        case GrColorType::kRG_88:        return false;
         case GrColorType::kBGRA_8888:    return true;
         case GrColorType::kRGBA_1010102: return true;
         case GrColorType::kGray_8:       return false;
@@ -927,7 +944,8 @@
                                                                  sk_sp<SkColorSpace> colorSpace) {
     ASSERT_SINGLE_OWNER_PRIV
 
-    sk_sp<GrSurfaceProxy> proxy = this->proxyProvider()->wrapBackendTexture(tex, origin);
+    sk_sp<GrSurfaceProxy> proxy = this->proxyProvider()->wrapBackendTexture(
+            tex, origin, kBorrow_GrWrapOwnership, kRW_GrIOType);
     if (!proxy) {
         return nullptr;
     }
@@ -990,6 +1008,20 @@
                                                            props);
 }
 
+sk_sp<GrRenderTargetContext> GrContextPriv::makeVulkanSecondaryCBRenderTargetContext(
+        const SkImageInfo& imageInfo, const GrVkDrawableInfo& vkInfo, const SkSurfaceProps* props) {
+    ASSERT_SINGLE_OWNER_PRIV
+    sk_sp<GrSurfaceProxy> proxy(
+            this->proxyProvider()->wrapVulkanSecondaryCBAsRenderTarget(imageInfo, vkInfo));
+    if (!proxy) {
+        return nullptr;
+    }
+
+    return this->drawingManager()->makeRenderTargetContext(std::move(proxy),
+                                                           imageInfo.refColorSpace(),
+                                                           props);
+}
+
 void GrContextPriv::addOnFlushCallbackObject(GrOnFlushCallbackObject* onFlushCBObject) {
     fContext->fDrawingManager->addOnFlushCallbackObject(onFlushCBObject);
 }
diff --git a/src/gpu/GrContextPriv.h b/src/gpu/GrContextPriv.h
index f62979a..8480deb 100644
--- a/src/gpu/GrContextPriv.h
+++ b/src/gpu/GrContextPriv.h
@@ -77,6 +77,9 @@
                                                                  sk_sp<SkColorSpace> colorSpace,
                                                                  const SkSurfaceProps* = nullptr);
 
+    sk_sp<GrRenderTargetContext> makeVulkanSecondaryCBRenderTargetContext(
+            const SkImageInfo&, const GrVkDrawableInfo&, const SkSurfaceProps* = nullptr);
+
     bool disableGpuYUVConversion() const { return fContext->fDisableGpuYUVConversion; }
     bool sharpenMipmappedTextures() const { return fContext->fSharpenMipmappedTextures; }
 
diff --git a/src/gpu/GrCoordTransform.h b/src/gpu/GrCoordTransform.h
index 43405f2..aee2253 100644
--- a/src/gpu/GrCoordTransform.h
+++ b/src/gpu/GrCoordTransform.h
@@ -36,7 +36,7 @@
     GrCoordTransform(GrTextureProxy* proxy) {
         SkASSERT(proxy);
         SkDEBUGCODE(fInProcessor = false);
-        this->reset(SkMatrix::I(), proxy, true);
+        this->reset(SkMatrix::I(), proxy);
     }
 
     /**
@@ -46,7 +46,7 @@
     GrCoordTransform(const SkMatrix& m, GrTextureProxy* proxy) {
         SkASSERT(proxy);
         SkDEBUGCODE(fInProcessor = false);
-        this->reset(m, proxy, true);
+        this->reset(m, proxy);
     }
 
     /**
@@ -57,24 +57,6 @@
         this->reset(m);
     }
 
-    void reset(const SkMatrix& m, GrTextureProxy* proxy, bool normalize) {
-        SkASSERT(proxy);
-        SkASSERT(!fInProcessor);
-
-        fMatrix = m;
-        fProxy = proxy;
-        fNormalize = normalize;
-        fReverseY = kBottomLeft_GrSurfaceOrigin == proxy->origin();
-    }
-
-    void reset(const SkMatrix& m) {
-        SkASSERT(!fInProcessor);
-        fMatrix = m;
-        fProxy = nullptr;
-        fNormalize = false;
-        fReverseY = false;
-    }
-
     GrCoordTransform& operator= (const GrCoordTransform& that) {
         SkASSERT(!fInProcessor);
         fMatrix = that.fMatrix;
@@ -119,6 +101,15 @@
     GrTexture* peekTexture() const { return fProxy->peekTexture(); }
 
 private:
+    void reset(const SkMatrix& m, GrTextureProxy* proxy = nullptr) {
+        SkASSERT(!fInProcessor);
+
+        fMatrix = m;
+        fProxy = proxy;
+        fNormalize = proxy && proxy->textureType() != GrTextureType::kRectangle;
+        fReverseY = proxy && kBottomLeft_GrSurfaceOrigin == proxy->origin();
+    }
+
     // The textures' effect is to optionally normalize the final matrix, so a blind
     // equality check could be misleading
     bool operator==(const GrCoordTransform& that) const;
diff --git a/src/gpu/GrDefaultGeoProcFactory.cpp b/src/gpu/GrDefaultGeoProcFactory.cpp
index 623bf81..3a81a25 100644
--- a/src/gpu/GrDefaultGeoProcFactory.cpp
+++ b/src/gpu/GrDefaultGeoProcFactory.cpp
@@ -26,9 +26,11 @@
 enum GPFlag {
     kColorAttribute_GPFlag          = 0x1,
     kColorAttributeIsSkColor_GPFlag = 0x2,
-    kLocalCoordAttribute_GPFlag     = 0x4,
-    kCoverageAttribute_GPFlag       = 0x8,
-    kBonesAttribute_GPFlag          = 0x10,
+    kColorAttributeIsWide_GPFlag    = 0x4,
+    kLocalCoordAttribute_GPFlag     = 0x8,
+    kCoverageAttribute_GPFlag       = 0x10,
+    kCoverageAttributeTweak_GPFlag  = 0x20,
+    kBonesAttribute_GPFlag          = 0x40,
 };
 
 static constexpr int kNumVec2sPerBone = 3; // Our bone matrices are 3x2 matrices passed in as
@@ -82,13 +84,26 @@
             // emit attributes
             varyingHandler->emitAttributes(gp);
 
+            bool tweakAlpha = SkToBool(gp.fFlags & kCoverageAttributeTweak_GPFlag);
+            SkASSERT(!tweakAlpha || gp.hasVertexCoverage());
+
             // Setup pass through color
-            if (gp.hasVertexColor()) {
+            if (gp.hasVertexColor() || tweakAlpha) {
                 GrGLSLVarying varying(kHalf4_GrSLType);
                 varyingHandler->addVarying("color", &varying);
 
-                // There are several optional steps to process the color. Start with the attribute:
-                vertBuilder->codeAppendf("half4 color = %s;", gp.fInColor.name());
+                // There are several optional steps to process the color. Start with the attribute,
+                // or with uniform color (in the case of folding coverage into a uniform color):
+                if (gp.hasVertexColor()) {
+                    vertBuilder->codeAppendf("half4 color = %s;", gp.fInColor.name());
+                } else {
+                    const char* colorUniformName;
+                    fColorUniform = uniformHandler->addUniform(kVertex_GrShaderFlag,
+                                                               kHalf4_GrSLType,
+                                                               "Color",
+                                                               &colorUniformName);
+                    vertBuilder->codeAppendf("half4 color = %s;", colorUniformName);
+                }
 
                 // For SkColor, do a red/blue swap, possible color space conversion, and premul
                 if (gp.fFlags & kColorAttributeIsSkColor_GPFlag) {
@@ -106,6 +121,10 @@
                     vertBuilder->codeAppend("color = half4(color.rgb * color.a, color.a);");
                 }
 
+                // Optionally fold coverage into alpha (color).
+                if (tweakAlpha) {
+                    vertBuilder->codeAppendf("color = color * %s;", gp.fInCoverage.name());
+                }
                 vertBuilder->codeAppendf("%s = color;\n", varying.vsOut());
                 fragBuilder->codeAppendf("%s = %s;", args.fOutputColor, varying.fsIn());
             } else {
@@ -191,7 +210,7 @@
             }
 
             // Setup coverage as pass through
-            if (gp.hasVertexCoverage()) {
+            if (gp.hasVertexCoverage() && !tweakAlpha) {
                 fragBuilder->codeAppendf("half alpha = 1.0;");
                 varyingHandler->addPassThroughAttribute(gp.fInCoverage, "alpha");
                 fragBuilder->codeAppendf("%s = half4(alpha);", args.fOutputCoverage);
@@ -212,8 +231,8 @@
                                   GrProcessorKeyBuilder* b) {
             const DefaultGeoProc& def = gp.cast<DefaultGeoProc>();
             uint32_t key = def.fFlags;
-            key |= (def.coverage() == 0xff) ? 0x20 : 0;
-            key |= (def.localCoordsWillBeRead() && def.localMatrix().hasPerspective()) ? 0x40 : 0x0;
+            key |= (def.coverage() == 0xff) ? 0x80 : 0;
+            key |= (def.localCoordsWillBeRead() && def.localMatrix().hasPerspective()) ? 0x100 : 0;
             key |= ComputePosKey(def.viewMatrix()) << 20;
             b->add32(key);
             b->add32(GrColorSpaceXform::XformKey(def.fColorSpaceXform.get()));
@@ -323,7 +342,8 @@
             , fBoneCount(boneCount) {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
         if (fFlags & kColorAttribute_GPFlag) {
-            fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
+            fInColor = MakeColorAttribute("inColor",
+                                          SkToBool(fFlags & kColorAttributeIsWide_GPFlag));
         }
         if (fFlags & kLocalCoordAttribute_GPFlag) {
             fInLocalCoords = {"inLocalCoord", kFloat2_GrVertexAttribType,
@@ -390,7 +410,13 @@
         flags |= kColorAttributeIsSkColor_GPFlag;
     }
     if (d->fRandom->nextBool()) {
+        flags |= kColorAttributeIsWide_GPFlag;
+    }
+    if (d->fRandom->nextBool()) {
         flags |= kCoverageAttribute_GPFlag;
+        if (d->fRandom->nextBool()) {
+            flags |= kCoverageAttributeTweak_GPFlag;
+        }
     }
     if (d->fRandom->nextBool()) {
         flags |= kLocalCoordAttribute_GPFlag;
@@ -422,8 +448,14 @@
         flags |= kColorAttribute_GPFlag;
     } else if (Color::kUnpremulSkColorAttribute_Type == color.fType) {
         flags |= kColorAttribute_GPFlag | kColorAttributeIsSkColor_GPFlag;
+    } else if (Color::kPremulWideColorAttribute_Type == color.fType) {
+        flags |= kColorAttribute_GPFlag | kColorAttributeIsWide_GPFlag;
     }
-    flags |= coverage.fType == Coverage::kAttribute_Type ? kCoverageAttribute_GPFlag : 0;
+    if (Coverage::kAttribute_Type == coverage.fType) {
+        flags |= kCoverageAttribute_GPFlag;
+    } else if (Coverage::kAttributeTweakAlpha_Type == coverage.fType) {
+        flags |= kCoverageAttribute_GPFlag | kCoverageAttributeTweak_GPFlag;
+    }
     flags |= localCoords.fType == LocalCoords::kHasExplicit_Type ? kLocalCoordAttribute_GPFlag : 0;
 
     uint8_t inCoverage = coverage.fCoverage;
@@ -474,8 +506,14 @@
         flags |= kColorAttribute_GPFlag;
     } else if (Color::kUnpremulSkColorAttribute_Type == color.fType) {
         flags |= kColorAttribute_GPFlag | kColorAttributeIsSkColor_GPFlag;
+    } else if (Color::kPremulWideColorAttribute_Type == color.fType) {
+        flags |= kColorAttribute_GPFlag | kColorAttributeIsWide_GPFlag;
     }
-    flags |= coverage.fType == Coverage::kAttribute_Type ? kCoverageAttribute_GPFlag : 0;
+    if (Coverage::kAttribute_Type == coverage.fType) {
+        flags |= kCoverageAttribute_GPFlag;
+    } else if (Coverage::kAttributeTweakAlpha_Type == coverage.fType) {
+        flags |= kCoverageAttribute_GPFlag | kCoverageAttributeTweak_GPFlag;
+    }
     flags |= localCoords.fType == LocalCoords::kHasExplicit_Type ? kLocalCoordAttribute_GPFlag : 0;
     flags |= kBonesAttribute_GPFlag;
 
diff --git a/src/gpu/GrDefaultGeoProcFactory.h b/src/gpu/GrDefaultGeoProcFactory.h
index 3b014f7..96363f0 100644
--- a/src/gpu/GrDefaultGeoProcFactory.h
+++ b/src/gpu/GrDefaultGeoProcFactory.h
@@ -23,6 +23,7 @@
         enum Type {
             kPremulGrColorUniform_Type,
             kPremulGrColorAttribute_Type,
+            kPremulWideColorAttribute_Type,
             kUnpremulSkColorAttribute_Type,
         };
         explicit Color(const SkPMColor4f& color)
@@ -49,6 +50,7 @@
             kSolid_Type,
             kUniform_Type,
             kAttribute_Type,
+            kAttributeTweakAlpha_Type,
         };
         explicit Coverage(uint8_t coverage) : fType(kUniform_Type), fCoverage(coverage) {}
         Coverage(Type type) : fType(type), fCoverage(0xff) {
diff --git a/src/gpu/GrUninstantiateProxyTracker.cpp b/src/gpu/GrDeinstantiateProxyTracker.cpp
similarity index 65%
rename from src/gpu/GrUninstantiateProxyTracker.cpp
rename to src/gpu/GrDeinstantiateProxyTracker.cpp
index 2bd5e3d..9870617 100644
--- a/src/gpu/GrUninstantiateProxyTracker.cpp
+++ b/src/gpu/GrDeinstantiateProxyTracker.cpp
@@ -5,15 +5,15 @@
  * found in the LICENSE file.
  */
 
-#include "GrUninstantiateProxyTracker.h"
+#include "GrDeinstantiateProxyTracker.h"
 
 #include "GrSurfaceProxy.h"
 #include "GrSurfaceProxyPriv.h"
 
-void GrUninstantiateProxyTracker::addProxy(GrSurfaceProxy* proxy) {
+void GrDeinstantiateProxyTracker::addProxy(GrSurfaceProxy* proxy) {
 #ifdef SK_DEBUG
     using LazyType = GrSurfaceProxy::LazyInstantiationType;
-    SkASSERT(LazyType::kUninstantiate == proxy->priv().lazyInstantiationType());
+    SkASSERT(LazyType::kDeinstantiate == proxy->priv().lazyInstantiationType());
     for (int i = 0; i < fProxies.count(); ++i) {
         SkASSERT(proxy != fProxies[i].get());
     }
@@ -21,11 +21,11 @@
     fProxies.push_back(sk_ref_sp(proxy));
 }
 
-void GrUninstantiateProxyTracker::uninstantiateAllProxies() {
+void GrDeinstantiateProxyTracker::deinstantiateAllProxies() {
     for (int i = 0; i < fProxies.count(); ++i) {
         GrSurfaceProxy* proxy = fProxies[i].get();
-        SkASSERT(proxy->priv().isSafeToUninstantiate());
-        proxy->deInstantiate();
+        SkASSERT(proxy->priv().isSafeToDeinstantiate());
+        proxy->deinstantiate();
     }
 
     fProxies.reset();
diff --git a/src/gpu/GrDeinstantiateProxyTracker.h b/src/gpu/GrDeinstantiateProxyTracker.h
new file mode 100644
index 0000000..2555ab1
--- /dev/null
+++ b/src/gpu/GrDeinstantiateProxyTracker.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrDeinstantiateProxyTracker_DEFINED
+#define GrDeinstantiateProxyTracker_DEFINED
+
+#include "GrSurfaceProxy.h"
+#include "SkTArray.h"
+
+class GrDeinstantiateProxyTracker {
+public:
+    GrDeinstantiateProxyTracker() {}
+
+    // Adds a proxy which will be deinstantiated at the end of flush. The same proxy may not be
+    // added multiple times.
+    void addProxy(GrSurfaceProxy* proxy);
+
+    // Loops through all tracked proxies and deinstantiates them.
+    void deinstantiateAllProxies();
+
+private:
+    SkTArray<sk_sp<GrSurfaceProxy>> fProxies;
+};
+
+#endif
diff --git a/src/gpu/GrDirectContext.cpp b/src/gpu/GrDirectContext.cpp
index a726e76..d25852f 100644
--- a/src/gpu/GrDirectContext.cpp
+++ b/src/gpu/GrDirectContext.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "vk/GrVkVulkan.h"
 
 #include "GrContext.h"
 
diff --git a/src/gpu/GrDrawOpAtlas.cpp b/src/gpu/GrDrawOpAtlas.cpp
index 6918123..94afadc 100644
--- a/src/gpu/GrDrawOpAtlas.cpp
+++ b/src/gpu/GrDrawOpAtlas.cpp
@@ -36,11 +36,11 @@
 std::unique_ptr<GrDrawOpAtlas> GrDrawOpAtlas::Make(GrProxyProvider* proxyProvider,
                                                    const GrBackendFormat& format,
                                                    GrPixelConfig config, int width,
-                                                   int height, int numPlotsX, int numPlotsY,
+                                                   int height, int plotWidth, int plotHeight,
                                                    AllowMultitexturing allowMultitexturing,
                                                    GrDrawOpAtlas::EvictionFunc func, void* data) {
     std::unique_ptr<GrDrawOpAtlas> atlas(new GrDrawOpAtlas(proxyProvider, format, config, width,
-                                                           height, numPlotsX, numPlotsY,
+                                                           height, plotWidth, plotHeight,
                                                            allowMultitexturing));
     if (!atlas->getProxies()[0]) {
         return nullptr;
@@ -181,17 +181,19 @@
 
 GrDrawOpAtlas::GrDrawOpAtlas(GrProxyProvider* proxyProvider, const GrBackendFormat& format,
                              GrPixelConfig config, int width, int height,
-                             int numPlotsX, int numPlotsY, AllowMultitexturing allowMultitexturing)
+                             int plotWidth, int plotHeight, AllowMultitexturing allowMultitexturing)
         : fFormat(format)
         , fPixelConfig(config)
         , fTextureWidth(width)
         , fTextureHeight(height)
+        , fPlotWidth(plotWidth)
+        , fPlotHeight(plotHeight)
         , fAtlasGeneration(kInvalidAtlasGeneration + 1)
         , fPrevFlushToken(GrDeferredUploadToken::AlreadyFlushedToken())
         , fMaxPages(AllowMultitexturing::kYes == allowMultitexturing ? kMaxMultitexturePages : 1)
         , fNumActivePages(0) {
-    fPlotWidth = fTextureWidth / numPlotsX;
-    fPlotHeight = fTextureHeight / numPlotsY;
+    int numPlotsX = width/plotWidth;
+    int numPlotsY = height/plotHeight;
     SkASSERT(numPlotsX * numPlotsY <= GrDrawOpAtlas::kMaxPlots);
     SkASSERT(fPlotWidth * numPlotsX == fTextureWidth);
     SkASSERT(fPlotHeight * numPlotsY == fTextureHeight);
@@ -285,8 +287,7 @@
         for (unsigned int pageIdx = 0; pageIdx < fNumActivePages; ++pageIdx) {
             Plot* plot = fPages[pageIdx].fPlotList.tail();
             SkASSERT(plot);
-            if (plot->lastUseToken() < target->tokenTracker()->nextTokenToFlush() ||
-                plot->flushesSinceLastUsed() >= kRecentlyUsedCount) {
+            if (plot->lastUseToken() < target->tokenTracker()->nextTokenToFlush()) {
                 this->processEvictionAndResetRects(plot);
                 SkASSERT(GrBytesPerPixel(fProxies[pageIdx]->config()) == plot->bpp());
                 SkDEBUGCODE(bool verify = )plot->addSubImage(width, height, image, loc);
@@ -594,58 +595,61 @@
     }
 
     // remove ref to the backing texture
-    fProxies[lastPageIndex]->deInstantiate();
+    fProxies[lastPageIndex]->deinstantiate();
     --fNumActivePages;
 }
 
-GrDrawOpAtlasConfig::GrDrawOpAtlasConfig(int maxDimension, size_t maxBytes)
-        : fPlotsPerLongDimension{PlotsPerLongDimensionForARGB(maxDimension)} {
-    SkASSERT(kPlotSize >= SkGlyphCacheCommon::kSkSideTooBigForAtlas);
-}
+GrDrawOpAtlasConfig::GrDrawOpAtlasConfig(int maxTextureSize, size_t maxBytes) {
+    static const SkISize kARGBDimensions[] = {
+        {256, 256},   // maxBytes < 2^19
+        {512, 256},   // 2^19 <= maxBytes < 2^20
+        {512, 512},   // 2^20 <= maxBytes < 2^21
+        {1024, 512},  // 2^21 <= maxBytes < 2^22
+        {1024, 1024}, // 2^22 <= maxBytes < 2^23
+        {2048, 1024}, // 2^23 <= maxBytes
+    };
 
-GrDrawOpAtlasConfig::GrDrawOpAtlasConfig() : fPlotsPerLongDimension{1} {
-    SkASSERT(kPlotSize >= SkGlyphCacheCommon::kSkSideTooBigForAtlas);
-}
+    // Index 0 corresponds to maxBytes of 2^18, so start by dividing it by that
+    maxBytes >>= 18;
+    // Take the floor of the log to get the index
+    int index = maxBytes > 0
+        ? SkTPin<int>(SkPrevLog2(maxBytes), 0, SK_ARRAY_COUNT(kARGBDimensions) - 1)
+        : 0;
 
-SkISize GrDrawOpAtlasConfig::numPlots(GrMaskFormat type) const {
-    switch(type) {
-        case kA8_GrMaskFormat:
-            if (fPlotsPerLongDimension * fPlotsPerLongDimension > GrDrawOpAtlas::kMaxPlots) {
-                return {fPlotsPerLongDimension / 2, fPlotsPerLongDimension / 2};
-            }
-            return {fPlotsPerLongDimension, fPlotsPerLongDimension};
-            // Note: because of the 2048 limit in the longest dimension, the largest atlas can be
-            // 1024 x 2048. The maximum number of plots will be 32 for 256 x 256 so that plot
-            // size is always safe.
-        case kA565_GrMaskFormat:
-        case kARGB_GrMaskFormat: {
-            static_assert((kMaxDistanceFieldDim / kPlotSize)
-                          * (kMaxDistanceFieldDim / kPlotSize) / 2 <= GrDrawOpAtlas::kMaxPlots,
-                          "");
-            int plotsPerWidth = std::max(1, fPlotsPerLongDimension / 2);
-            return {plotsPerWidth, fPlotsPerLongDimension};
-        }
-    }
-
-    // This make some compilers happy.
-    return {1,1};
+    SkASSERT(kARGBDimensions[index].width() <= kMaxAtlasDim);
+    SkASSERT(kARGBDimensions[index].height() <= kMaxAtlasDim);
+    fARGBDimensions.set(SkTMin<int>(kARGBDimensions[index].width(), maxTextureSize),
+                        SkTMin<int>(kARGBDimensions[index].height(), maxTextureSize));
+    fMaxTextureSize = SkTMin<int>(maxTextureSize, kMaxAtlasDim);
 }
 
 SkISize GrDrawOpAtlasConfig::atlasDimensions(GrMaskFormat type) const {
-    SkISize plots = this->numPlots(type);
-    return {plots.width() * kPlotSize, plots.height() * kPlotSize};
+    if (kA8_GrMaskFormat == type) {
+        // A8 is always 2x the ARGB dimensions, clamped to the max allowed texture size
+        return { SkTMin<int>(2 * fARGBDimensions.width(), fMaxTextureSize),
+                 SkTMin<int>(2 * fARGBDimensions.height(), fMaxTextureSize) };
+    } else {
+        return fARGBDimensions;
+    }
 }
 
-int GrDrawOpAtlasConfig::PlotsPerLongDimensionForARGB(int maxDimension) {
+SkISize GrDrawOpAtlasConfig::plotDimensions(GrMaskFormat type) const {
+    if (kA8_GrMaskFormat == type) {
+        SkISize atlasDimensions = this->atlasDimensions(type);
+        // For A8 we want to grow the plots at larger texture sizes to accept more of the
+        // larger SDF glyphs. Since the largest SDF glyph can be 170x170 with padding, this
+        // allows us to pack 3 in a 512x256 plot, or 9 in a 512x512 plot.
 
-    SkASSERT(maxDimension > 0);
+        // This will give us 512x256 plots for 2048x1024, 512x512 plots for 2048x2048,
+        // and 256x256 plots otherwise.
+        int plotWidth = atlasDimensions.width() >= 2048 ? 512 : 256;
+        int plotHeight = atlasDimensions.height() >= 2048 ? 512 : 256;
 
-    // Must be as large as a plot and small enough for distance fields.
-    int dimension = SkTClamp(maxDimension, kPlotSize, kMaxDistanceFieldDim);
-
-    // Return the number of plots along the longest dimension aligned down to a power of 2.
-    return SkPrevPow2(dimension / kPlotSize);
+        return { plotWidth, plotHeight };
+    } else {
+        // ARGB and LCD always use 256x256 plots -- this has been shown to be faster
+        return { 256, 256 };
+    }
 }
 
-constexpr int GrDrawOpAtlasConfig::kMaxDistanceFieldDim;
-constexpr int GrDrawOpAtlasConfig::kPlotSize;
+constexpr int GrDrawOpAtlasConfig::kMaxAtlasDim;
diff --git a/src/gpu/GrDrawOpAtlas.h b/src/gpu/GrDrawOpAtlas.h
index c9d5546..099524f 100644
--- a/src/gpu/GrDrawOpAtlas.h
+++ b/src/gpu/GrDrawOpAtlas.h
@@ -56,7 +56,8 @@
     /** Is the atlas allowed to use more than one texture? */
     enum class AllowMultitexturing : bool { kNo, kYes };
 
-    static constexpr int kMaxPlots = 32;
+    static constexpr int kMaxPlots = 32; // restricted by the fPlotAlreadyUpdated bitfield
+                                         // in BulkUseTokenUpdater
 
     /**
      * An AtlasID is an opaque handle which callers can use to determine if the atlas contains
@@ -94,7 +95,7 @@
                                                const GrBackendFormat& format,
                                                GrPixelConfig,
                                                int width, int height,
-                                               int numPlotsX, int numPlotsY,
+                                               int plotWidth, int plotHeight,
                                                AllowMultitexturing allowMultitexturing,
                                                GrDrawOpAtlas::EvictionFunc func, void* data);
 
@@ -174,12 +175,14 @@
             memcpy(fPlotAlreadyUpdated, that.fPlotAlreadyUpdated, sizeof(fPlotAlreadyUpdated));
         }
 
-        void add(AtlasID id) {
+        bool add(AtlasID id) {
             int index = GrDrawOpAtlas::GetPlotIndexFromID(id);
             int pageIdx = GrDrawOpAtlas::GetPageIndexFromID(id);
-            if (!this->find(pageIdx, index)) {
-                this->set(pageIdx, index);
+            if (this->find(pageIdx, index)) {
+                return false;
             }
+            this->set(pageIdx, index);
+            return true;
         }
 
         void reset() {
@@ -207,7 +210,8 @@
 
         static constexpr int kMinItems = 4;
         SkSTArray<kMinItems, PlotData, true> fPlotsToUpdate;
-        uint32_t fPlotAlreadyUpdated[kMaxMultitexturePages];
+        uint32_t fPlotAlreadyUpdated[kMaxMultitexturePages]; // TODO: increase this to uint64_t
+                                                             //       to allow more plots per page
 
         friend class GrDrawOpAtlas;
     };
@@ -243,7 +247,7 @@
 
 private:
     GrDrawOpAtlas(GrProxyProvider*, const GrBackendFormat& format, GrPixelConfig, int width,
-                  int height, int numPlotsX, int numPlotsY,
+                  int height, int plotWidth, int plotHeight,
                   AllowMultitexturing allowMultitexturing);
 
     /**
@@ -414,31 +418,31 @@
 };
 
 // There are three atlases (A8, 565, ARGB) that are kept in relation with one another. In
-// general, the A8 dimensions are NxN and 565 and ARGB are N/2xN with the constraint that an atlas
+// general, the A8 dimensions are 2x the 565 and ARGB dimensions with the constraint that an atlas
 // size will always contain at least one plot. Since the ARGB atlas takes the most space, its
 // dimensions are used to size the other two atlases.
 class GrDrawOpAtlasConfig {
 public:
-    GrDrawOpAtlasConfig(int maxDimension, size_t maxBytes);
+    // The capabilities of the GPU define maxTextureSize. The client provides maxBytes, and this
+    // represents the largest they want a single atlas texture to be. Due to multitexturing, we
+    // may expand temporarily to use more space as needed.
+    GrDrawOpAtlasConfig(int maxTextureSize, size_t maxBytes);
 
-    // For testing only - make minimum sized atlases -- 1x1 plots wide.
-    GrDrawOpAtlasConfig();
-
-    SkISize numPlots(GrMaskFormat type) const;
+    // For testing only - make minimum sized atlases -- a single plot for ARGB, four for A8
+    GrDrawOpAtlasConfig() : GrDrawOpAtlasConfig(kMaxAtlasDim, 0) {}
 
     SkISize atlasDimensions(GrMaskFormat type) const;
-
-    static int PlotsPerLongDimensionForARGB(int maxDimension);
+    SkISize plotDimensions(GrMaskFormat type) const;
 
 private:
-    // The distance field text implementation limits the largest atlas dimension to 2048.
-    static constexpr int kMaxDistanceFieldDim = 2048;
+    // On some systems texture coordinates are represented using half-precision floating point,
+    // which limits the largest atlas dimensions to 2048x2048.
+    // For simplicity we'll use this constraint for all of our atlas textures.
+    // This can be revisited later if we need larger atlases.
+    static constexpr int kMaxAtlasDim = 2048;
 
-    // The width and height of a plot.
-    static constexpr int kPlotSize = 256;
-
-    // This is the height (longest dimension) of the ARGB atlas divided by the plot size.
-    const int fPlotsPerLongDimension;
+    SkISize fARGBDimensions;
+    int     fMaxTextureSize;
 };
 
 #endif
diff --git a/src/gpu/GrDrawingManager.cpp b/src/gpu/GrDrawingManager.cpp
index 79e1684..942daf2 100644
--- a/src/gpu/GrDrawingManager.cpp
+++ b/src/gpu/GrDrawingManager.cpp
@@ -284,7 +284,7 @@
 
     {
         GrResourceAllocator alloc(fContext->contextPriv().resourceProvider(),
-                                  flushState.uninstantiateProxyTracker());
+                                  flushState.deinstantiateProxyTracker());
         for (int i = 0; i < fDAG.numOpLists(); ++i) {
             if (fDAG.opList(i)) {
                 fDAG.opList(i)->gatherProxyIntervals(&alloc);
@@ -293,6 +293,7 @@
         }
 
         GrResourceAllocator::AssignError error = GrResourceAllocator::AssignError::kNoError;
+        int numOpListsExecuted = 0;
         while (alloc.assign(&startIndex, &stopIndex, &error)) {
             if (GrResourceAllocator::AssignError::kFailedProxyInstantiation == error) {
                 for (int i = startIndex; i < stopIndex; ++i) {
@@ -306,7 +307,7 @@
                 }
             }
 
-            if (this->executeOpLists(startIndex, stopIndex, &flushState)) {
+            if (this->executeOpLists(startIndex, stopIndex, &flushState, &numOpListsExecuted)) {
                 flushed = true;
             }
         }
@@ -333,7 +334,7 @@
 
     GrSemaphoresSubmitted result = gpu->finishFlush(numSemaphores, backendSemaphores);
 
-    flushState.uninstantiateProxyTracker()->uninstantiateAllProxies();
+    flushState.deinstantiateProxyTracker()->deinstantiateAllProxies();
 
     // Give the cache a chance to purge resources that become purgeable due to flushing.
     if (flushed) {
@@ -349,7 +350,8 @@
     return result;
 }
 
-bool GrDrawingManager::executeOpLists(int startIndex, int stopIndex, GrOpFlushState* flushState) {
+bool GrDrawingManager::executeOpLists(int startIndex, int stopIndex, GrOpFlushState* flushState,
+                                      int* numOpListsExecuted) {
     SkASSERT(startIndex <= stopIndex && stopIndex <= fDAG.numOpLists());
 
 #if GR_FLUSH_TIME_OP_SPEW
@@ -394,6 +396,13 @@
     // Upload all data to the GPU
     flushState->preExecuteDraws();
 
+    // For Vulkan, if we have too many oplists to be flushed we end up allocating a lot of resources
+    // for each command buffer associated with the oplists. If this gets too large we can cause the
+    // devices to go OOM. In practice we usually only hit this case in our tests, but to be safe we
+    // put a cap on the number of oplists we will execute before flushing to the GPU to relieve some
+    // memory pressure.
+    static constexpr int kMaxOpListsBeforeFlush = 100;
+
     // Execute the onFlush op lists first, if any.
     for (sk_sp<GrOpList>& onFlushOpList : fOnFlushCBOpLists) {
         if (!onFlushOpList->execute(flushState)) {
@@ -401,6 +410,11 @@
         }
         SkASSERT(onFlushOpList->unique());
         onFlushOpList = nullptr;
+        (*numOpListsExecuted)++;
+        if (*numOpListsExecuted >= kMaxOpListsBeforeFlush) {
+            flushState->gpu()->finishFlush(0, nullptr);
+            *numOpListsExecuted = 0;
+        }
     }
     fOnFlushCBOpLists.reset();
 
@@ -413,6 +427,11 @@
         if (fDAG.opList(i)->execute(flushState)) {
             anyOpListsExecuted = true;
         }
+        (*numOpListsExecuted)++;
+        if (*numOpListsExecuted >= kMaxOpListsBeforeFlush) {
+            flushState->gpu()->finishFlush(0, nullptr);
+            *numOpListsExecuted = 0;
+        }
     }
 
     SkASSERT(!flushState->commandBuffer());
diff --git a/src/gpu/GrDrawingManager.h b/src/gpu/GrDrawingManager.h
index 6f2b2da..66b2259 100644
--- a/src/gpu/GrDrawingManager.h
+++ b/src/gpu/GrDrawingManager.h
@@ -140,7 +140,7 @@
     void cleanup();
 
     // return true if any opLists were actually executed; false otherwise
-    bool executeOpLists(int startIndex, int stopIndex, GrOpFlushState*);
+    bool executeOpLists(int startIndex, int stopIndex, GrOpFlushState*, int* numOpListsExecuted);
 
     GrSemaphoresSubmitted flush(GrSurfaceProxy* proxy,
                                 int numSemaphores = 0,
diff --git a/src/gpu/GrFragmentProcessor.h b/src/gpu/GrFragmentProcessor.h
index 8069541..82a3d18 100644
--- a/src/gpu/GrFragmentProcessor.h
+++ b/src/gpu/GrFragmentProcessor.h
@@ -260,8 +260,21 @@
      * This assumes that the subclass output color will be a modulation of the input color with a
      * value read from a texture of the passed config and that the texture contains premultiplied
      * color or alpha values that are in range.
+     *
+     * Since there are multiple ways in which a sampler may have its coordinates clamped or wrapped,
+     * callers must determine on their own if the sampling uses a decal strategy in any way, in
+     * which case the texture may become transparent regardless of the pixel config.
      */
-    static OptimizationFlags ModulateByConfigOptimizationFlags(GrPixelConfig config) {
+    static OptimizationFlags ModulateForSamplerOptFlags(GrPixelConfig config, bool samplingDecal) {
+        if (samplingDecal) {
+            return kCompatibleWithCoverageAsAlpha_OptimizationFlag;
+        } else {
+            return ModulateForClampedSamplerOptFlags(config);
+        }
+    }
+
+    // As above, but callers should somehow ensure or assert their sampler still uses clamping
+    static OptimizationFlags ModulateForClampedSamplerOptFlags(GrPixelConfig config) {
         if (GrPixelConfigIsOpaque(config)) {
             return kCompatibleWithCoverageAsAlpha_OptimizationFlag |
                    kPreservesOpaqueInput_OptimizationFlag;
diff --git a/src/gpu/GrGeometryProcessor.h b/src/gpu/GrGeometryProcessor.h
index a12ab00..f0f8a74 100644
--- a/src/gpu/GrGeometryProcessor.h
+++ b/src/gpu/GrGeometryProcessor.h
@@ -21,27 +21,23 @@
 public:
     GrGeometryProcessor(ClassID classID)
         : INHERITED(classID)
-        , fWillUseGeoShader(false)
-        , fSampleShading(0.0) {}
+        , fWillUseGeoShader(false) {}
 
     bool willUseGeoShader() const final { return fWillUseGeoShader; }
 
-    /**
-     * Returns the minimum fraction of samples for which the fragment shader will be run. For
-     * instance, if sampleShading is 0.5 in MSAA16 mode, the fragment shader will run a minimum of
-     * 8 times per pixel. The default value is zero.
-     */
-    float getSampleShading() const final { return fSampleShading; }
-
 protected:
     void setWillUseGeoShader() { fWillUseGeoShader = true; }
-    void setSampleShading(float sampleShading) {
-        fSampleShading = sampleShading;
+
+    // GPs that need to use either half-float or ubyte colors can just call this to get a correctly
+    // configured Attribute struct
+    static Attribute MakeColorAttribute(const char* name, bool wideColor) {
+        return { name,
+                 wideColor ? kHalf4_GrVertexAttribType : kUByte4_norm_GrVertexAttribType,
+                 kHalf4_GrSLType };
     }
 
 private:
     bool fWillUseGeoShader;
-    float fSampleShading;
 
     typedef GrPrimitiveProcessor INHERITED;
 };
diff --git a/src/gpu/GrGlyph.h b/src/gpu/GrGlyph.h
index c73c482..45f333d 100644
--- a/src/gpu/GrGlyph.h
+++ b/src/gpu/GrGlyph.h
@@ -16,79 +16,96 @@
 #include "SkFixed.h"
 #include "SkPath.h"
 
-/*  Need this to be quad-state:
-    - complete w/ image
-    - just metrics
-    - failed to get image, but has metrics
-    - failed to get metrics
- */
 struct GrGlyph {
     enum MaskStyle {
         kCoverage_MaskStyle,
         kDistance_MaskStyle
     };
 
-    typedef uint32_t PackedID;
-
-    GrDrawOpAtlas::AtlasID fID;
-    PackedID              fPackedID;
-    GrMaskFormat          fMaskFormat;
-    GrIRect16             fBounds;
-    SkIPoint16            fAtlasLocation;
-
-    void init(GrGlyph::PackedID packed, const SkIRect& bounds, GrMaskFormat format) {
-        fID = GrDrawOpAtlas::kInvalidAtlasID;
-        fPackedID = packed;
-        fBounds.set(bounds);
-        fMaskFormat = format;
-        fAtlasLocation.set(0, 0);
+    static GrMaskFormat FormatFromSkGlyph(const SkGlyph& glyph) {
+        SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
+        switch (format) {
+            case SkMask::kBW_Format:
+            case SkMask::kSDF_Format:
+                // fall through to kA8 -- we store BW and SDF glyphs in our 8-bit cache
+            case SkMask::kA8_Format:
+                return kA8_GrMaskFormat;
+            case SkMask::k3D_Format:
+                return kA8_GrMaskFormat; // ignore the mul and add planes, just use the mask
+            case SkMask::kLCD16_Format:
+                return kA565_GrMaskFormat;
+            case SkMask::kARGB32_Format:
+                return kARGB_GrMaskFormat;
+            default:
+                SkDEBUGFAIL("unsupported SkMask::Format");
+                return kA8_GrMaskFormat;
+        }
     }
 
-    void reset() { }
+    static GrIRect16 BoundsFromSkGlyph(const SkGlyph& glyph) {
+        return GrIRect16::MakeXYWH(glyph.fLeft,
+                                   glyph.fTop,
+                                   glyph.fWidth,
+                                   glyph.fHeight);
+    }
+
+    static MaskStyle MaskStyleFromSkGlyph(const SkGlyph& skGlyph) {
+        return (SkMask::Format)skGlyph.fMaskFormat == SkMask::kSDF_Format
+           ? GrGlyph::MaskStyle::kDistance_MaskStyle
+           : GrGlyph::MaskStyle::kCoverage_MaskStyle;
+    }
+
+    GrGlyph(const SkGlyph& skGlyph)
+        : fPackedID{skGlyph.getPackedID()}
+        , fMaskFormat{FormatFromSkGlyph(skGlyph)}
+        , fMaskStyle{MaskStyleFromSkGlyph(skGlyph)}
+        , fBounds{BoundsFromSkGlyph(skGlyph)} {}
+
+
+    SkRect destRect(SkPoint origin) {
+        return SkRect::MakeXYWH(
+                SkIntToScalar(fBounds.fLeft) + origin.x(),
+                SkIntToScalar(fBounds.fTop)  + origin.y(),
+                SkIntToScalar(fBounds.width()),
+                SkIntToScalar(fBounds.height()));
+    }
+
+    SkRect destRect(SkPoint origin, SkScalar textScale) {
+        if (fMaskStyle == kCoverage_MaskStyle) {
+            return SkRect::MakeXYWH(
+                    SkIntToScalar(fBounds.fLeft)    * textScale + origin.x(),
+                    SkIntToScalar(fBounds.fTop)     * textScale + origin.y(),
+                    SkIntToScalar(fBounds.width())  * textScale,
+                    SkIntToScalar(fBounds.height()) * textScale);
+        } else {
+            return SkRect::MakeXYWH(
+                    (SkIntToScalar(fBounds.fLeft) + SK_DistanceFieldInset) * textScale + origin.x(),
+                    (SkIntToScalar(fBounds.fTop)  + SK_DistanceFieldInset) * textScale + origin.y(),
+                    (SkIntToScalar(fBounds.width())  - 2 * SK_DistanceFieldInset) * textScale,
+                    (SkIntToScalar(fBounds.height()) - 2 * SK_DistanceFieldInset) * textScale);
+        }
+    }
 
     int width() const { return fBounds.width(); }
     int height() const { return fBounds.height(); }
-    bool isEmpty() const { return fBounds.isEmpty(); }
-    uint16_t glyphID() const { return UnpackID(fPackedID); }
     uint32_t pageIndex() const { return GrDrawOpAtlas::GetPageIndexFromID(fID); }
+    MaskStyle maskStyle() const { return fMaskStyle; }
 
-    ///////////////////////////////////////////////////////////////////////////
-
-    static inline unsigned ExtractSubPixelBitsFromFixed(SkFixed pos) {
-        // two most significant fraction bits from fixed-point
-        return (pos >> 14) & 3;
-    }
-
-    static inline PackedID Pack(uint16_t glyphID, SkFixed x, SkFixed y, MaskStyle ms) {
-        x = ExtractSubPixelBitsFromFixed(x);
-        y = ExtractSubPixelBitsFromFixed(y);
-        int dfFlag = (ms == kDistance_MaskStyle) ? 0x1 : 0x0;
-        return (dfFlag << 20) | (x << 18) | (y << 16) | glyphID;
-    }
-
-    static inline SkFixed UnpackFixedX(PackedID packed) {
-        return ((packed >> 18) & 3) << 14;
-    }
-
-    static inline SkFixed UnpackFixedY(PackedID packed) {
-        return ((packed >> 16) & 3) << 14;
-    }
-
-    static inline MaskStyle UnpackMaskStyle(PackedID packed) {
-        return ((packed >> 20) & 1) ? kDistance_MaskStyle : kCoverage_MaskStyle;
-    }
-
-    static inline uint16_t UnpackID(PackedID packed) {
-        return (uint16_t)packed;
-    }
-
-    static inline const GrGlyph::PackedID& GetKey(const GrGlyph& glyph) {
+    // GetKey and Hash for the the hash table.
+    static const SkPackedGlyphID& GetKey(const GrGlyph& glyph) {
         return glyph.fPackedID;
     }
 
-    static inline uint32_t Hash(GrGlyph::PackedID key) {
-        return SkChecksum::Mix(key);
+    static uint32_t Hash(SkPackedGlyphID key) {
+        return SkChecksum::Mix(key.hash());
     }
+
+    const SkPackedGlyphID  fPackedID;
+    const GrMaskFormat     fMaskFormat;
+    const MaskStyle        fMaskStyle;
+    const GrIRect16        fBounds;
+    SkIPoint16             fAtlasLocation{0, 0};
+    GrDrawOpAtlas::AtlasID fID{GrDrawOpAtlas::kInvalidAtlasID};
 };
 
 #endif
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index 8e18fb8..cd93f8d 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -145,7 +145,9 @@
 
 sk_sp<GrTexture> GrGpu::wrapBackendTexture(const GrBackendTexture& backendTex,
                                            GrWrapOwnership ownership,
+                                           GrIOType ioType,
                                            bool purgeImmediately) {
+    SkASSERT(ioType != kWrite_GrIOType);
     this->handleDirtyContext();
     if (!this->caps()->isConfigTexturable(backendTex.config())) {
         return nullptr;
@@ -154,7 +156,7 @@
         backendTex.height() > this->caps()->maxTextureSize()) {
         return nullptr;
     }
-    return this->onWrapBackendTexture(backendTex, ownership, purgeImmediately);
+    return this->onWrapBackendTexture(backendTex, ownership, ioType, purgeImmediately);
 }
 
 sk_sp<GrTexture> GrGpu::wrapRenderableBackendTexture(const GrBackendTexture& backendTex,
@@ -198,6 +200,17 @@
     return this->onWrapBackendTextureAsRenderTarget(tex, sampleCnt);
 }
 
+sk_sp<GrRenderTarget> GrGpu::wrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo& imageInfo,
+                                                                 const GrVkDrawableInfo& vkInfo) {
+    return this->onWrapVulkanSecondaryCBAsRenderTarget(imageInfo, vkInfo);
+}
+
+sk_sp<GrRenderTarget> GrGpu::onWrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo& imageInfo,
+                                                                   const GrVkDrawableInfo& vkInfo) {
+    // This is only supported on Vulkan so we default to returning nullptr here
+    return nullptr;
+}
+
 GrBuffer* GrGpu::createBuffer(size_t size, GrBufferType intendedType,
                               GrAccessPattern accessPattern, const void* data) {
     this->handleDirtyContext();
@@ -214,7 +227,13 @@
                         bool canDiscardOutsideDstRect) {
     GR_CREATE_TRACE_MARKER_CONTEXT("GrGpu", "copySurface", fContext);
     SkASSERT(dst && src);
+
+    if (dst->readOnly()) {
+        return false;
+    }
+
     this->handleDirtyContext();
+
     return this->onCopySurface(dst, dstOrigin, src, srcOrigin, srcRect, dstPoint,
                                canDiscardOutsideDstRect);
 }
@@ -239,6 +258,11 @@
 bool GrGpu::writePixels(GrSurface* surface, int left, int top, int width, int height,
                         GrColorType srcColorType, const GrMipLevel texels[], int mipLevelCount) {
     SkASSERT(surface);
+
+    if (surface->readOnly()) {
+        return false;
+    }
+
     if (1 == mipLevelCount) {
         // We require that if we are not mipped, then the write region is contained in the surface
         SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height);
@@ -271,8 +295,13 @@
 bool GrGpu::transferPixels(GrTexture* texture, int left, int top, int width, int height,
                            GrColorType bufferColorType, GrBuffer* transferBuffer, size_t offset,
                            size_t rowBytes) {
+    SkASSERT(texture);
     SkASSERT(transferBuffer);
 
+    if (texture->readOnly()) {
+        return false;
+    }
+
     // We require that the write region is contained in the texture
     SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height);
     SkIRect bounds = SkIRect::MakeWH(texture->width(), texture->height());
@@ -298,6 +327,9 @@
     SkASSERT(texture->texturePriv().mipMapped() == GrMipMapped::kYes);
     SkASSERT(texture->texturePriv().mipMapsAreDirty());
     SkASSERT(!texture->asRenderTarget() || !texture->asRenderTarget()->needsResolve());
+    if (texture->readOnly()) {
+        return false;
+    }
     if (this->onRegenerateMipMapLevels(texture)) {
         texture->texturePriv().markMipMapsClean();
         return true;
@@ -314,6 +346,7 @@
 void GrGpu::didWriteToSurface(GrSurface* surface, GrSurfaceOrigin origin, const SkIRect* bounds,
                               uint32_t mipLevels) const {
     SkASSERT(surface);
+    SkASSERT(!surface->readOnly());
     // Mark any MIP chain and resolve buffer as dirty if and only if there is a non-empty bounds.
     if (nullptr == bounds || !bounds->isEmpty()) {
         if (GrRenderTarget* target = surface->asRenderTarget()) {
@@ -334,6 +367,7 @@
 
 GrSemaphoresSubmitted GrGpu::finishFlush(int numSemaphores,
                                          GrBackendSemaphore backendSemaphores[]) {
+    this->stats()->incNumFinishFlushes();
     GrResourceProvider* resourceProvider = fContext->contextPriv().resourceProvider();
 
     if (this->caps()->fenceSyncSupport()) {
@@ -346,7 +380,7 @@
             } else {
                 semaphore = resourceProvider->makeSemaphore(false);
             }
-            this->insertSemaphore(semaphore, false);
+            this->insertSemaphore(semaphore);
 
             if (!backendSemaphores[i].isInitialized()) {
                 backendSemaphores[i] = semaphore->backendSemaphore();
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index cb8b2e9..c99733d 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -104,7 +104,7 @@
     /**
      * Implements GrResourceProvider::wrapBackendTexture
      */
-    sk_sp<GrTexture> wrapBackendTexture(const GrBackendTexture&, GrWrapOwnership,
+    sk_sp<GrTexture> wrapBackendTexture(const GrBackendTexture&, GrWrapOwnership, GrIOType,
                                         bool purgeImmediately);
 
     /**
@@ -125,6 +125,12 @@
                                                            int sampleCnt);
 
     /**
+     * Implements GrResourceProvider::wrapVulkanSecondaryCBAsRenderTarget
+     */
+    sk_sp<GrRenderTarget> wrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&,
+                                                              const GrVkDrawableInfo&);
+
+    /**
      * Creates a buffer in GPU memory. For a client-side buffer use GrBuffer::CreateCPUBacked.
      *
      * @param size            size of buffer to create.
@@ -267,7 +273,7 @@
     virtual sk_sp<GrSemaphore> wrapBackendSemaphore(const GrBackendSemaphore& semaphore,
                                                     GrResourceProvider::SemaphoreWrapType wrapType,
                                                     GrWrapOwnership ownership) = 0;
-    virtual void insertSemaphore(sk_sp<GrSemaphore> semaphore, bool flush = false) = 0;
+    virtual void insertSemaphore(sk_sp<GrSemaphore> semaphore) = 0;
     virtual void waitSemaphore(sk_sp<GrSemaphore> semaphore) = 0;
 
     /**
@@ -294,6 +300,7 @@
             fStencilAttachmentCreates = 0;
             fNumDraws = 0;
             fNumFailedDraws = 0;
+            fNumFinishFlushes = 0;
         }
 
         int renderTargetBinds() const { return fRenderTargetBinds; }
@@ -309,10 +316,12 @@
         void incStencilAttachmentCreates() { fStencilAttachmentCreates++; }
         void incNumDraws() { fNumDraws++; }
         void incNumFailedDraws() { ++fNumFailedDraws; }
+        void incNumFinishFlushes() { ++fNumFinishFlushes; }
         void dump(SkString*);
         void dumpKeyValuePairs(SkTArray<SkString>* keys, SkTArray<double>* values);
         int numDraws() const { return fNumDraws; }
         int numFailedDraws() const { return fNumFailedDraws; }
+        int numFinishFlushes() const { return fNumFinishFlushes; }
     private:
         int fRenderTargetBinds;
         int fShaderCompilations;
@@ -322,6 +331,7 @@
         int fStencilAttachmentCreates;
         int fNumDraws;
         int fNumFailedDraws;
+        int fNumFinishFlushes;
 #else
         void dump(SkString*) {}
         void dumpKeyValuePairs(SkTArray<SkString>*, SkTArray<double>*) {}
@@ -333,6 +343,7 @@
         void incStencilAttachmentCreates() {}
         void incNumDraws() {}
         void incNumFailedDraws() {}
+        void incNumFinishFlushes() {}
 #endif
     };
 
@@ -418,6 +429,8 @@
         return 0;
     }
 
+    virtual void storeVkPipelineCacheData() {}
+
 protected:
     // Handles cases where a surface will be updated without a call to flushRenderTarget.
     void didWriteToSurface(GrSurface* surface, GrSurfaceOrigin origin, const SkIRect* bounds,
@@ -445,13 +458,16 @@
                                              const GrMipLevel texels[], int mipLevelCount) = 0;
 
     virtual sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership,
-                                                  bool purgeImmediately) = 0;
+                                                  GrIOType, bool purgeImmediately) = 0;
     virtual sk_sp<GrTexture> onWrapRenderableBackendTexture(const GrBackendTexture&,
                                                             int sampleCnt,
                                                             GrWrapOwnership) = 0;
     virtual sk_sp<GrRenderTarget> onWrapBackendRenderTarget(const GrBackendRenderTarget&) = 0;
     virtual sk_sp<GrRenderTarget> onWrapBackendTextureAsRenderTarget(const GrBackendTexture&,
                                                                      int sampleCnt) = 0;
+    virtual sk_sp<GrRenderTarget> onWrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&,
+                                                                        const GrVkDrawableInfo&);
+
     virtual GrBuffer* onCreateBuffer(size_t size, GrBufferType intendedType, GrAccessPattern,
                                      const void* data) = 0;
 
diff --git a/src/gpu/GrGpuResource.cpp b/src/gpu/GrGpuResource.cpp
index 0ff6076..a4e0f54 100644
--- a/src/gpu/GrGpuResource.cpp
+++ b/src/gpu/GrGpuResource.cpp
@@ -12,6 +12,7 @@
 #include "GrGpu.h"
 #include "GrGpuResourcePriv.h"
 #include "SkTraceMemoryDump.h"
+#include <atomic>
 
 static inline GrResourceCache* get_resource_cache(GrGpu* gpu) {
     SkASSERT(gpu);
@@ -206,10 +207,10 @@
 }
 
 uint32_t GrGpuResource::CreateUniqueID() {
-    static int32_t gUniqueID = SK_InvalidUniqueID;
+    static std::atomic<uint32_t> nextID{1};
     uint32_t id;
     do {
-        id = static_cast<uint32_t>(sk_atomic_inc(&gUniqueID) + 1);
+        id = nextID++;
     } while (id == SK_InvalidUniqueID);
     return id;
 }
diff --git a/src/gpu/GrGpuResourceCacheAccess.h b/src/gpu/GrGpuResourceCacheAccess.h
index 8e9a701..ce3b597 100644
--- a/src/gpu/GrGpuResourceCacheAccess.h
+++ b/src/gpu/GrGpuResourceCacheAccess.h
@@ -35,6 +35,12 @@
     bool shouldPurgeImmediately() const { return fResource->fShouldPurgeImmediately; }
 
     /**
+     * Called by GrResourceCache when a resource becomes purgeable regardless of whether the cache
+     * has decided to keep the resource ot purge it immediately.
+     */
+    void becamePurgeable() { fResource->becamePurgeable(); }
+
+    /**
      * Called by the cache to delete the resource under normal circumstances.
      */
     void release() {
diff --git a/src/gpu/GrGpuResourcePriv.h b/src/gpu/GrGpuResourcePriv.h
index affc753..9537ffb 100644
--- a/src/gpu/GrGpuResourcePriv.h
+++ b/src/gpu/GrGpuResourcePriv.h
@@ -69,6 +69,8 @@
      */
     void removeScratchKey() const { fResource->removeScratchKey();  }
 
+    bool isPurgeable() const { return fResource->isPurgeable(); }
+
 protected:
     ResourcePriv(GrGpuResource* resource) : fResource(resource) {   }
     ResourcePriv(const ResourcePriv& that) : fResource(that.fResource) {}
diff --git a/src/gpu/GrMemoryPool.cpp b/src/gpu/GrMemoryPool.cpp
index 89e8f01..1366c4c 100644
--- a/src/gpu/GrMemoryPool.cpp
+++ b/src/gpu/GrMemoryPool.cpp
@@ -7,10 +7,10 @@
 
 #include "GrMemoryPool.h"
 #include "SkMalloc.h"
-#ifdef SK_DEBUG
-#include "SkAtomics.h"
-#endif
 #include "ops/GrOp.h"
+#ifdef SK_DEBUG
+    #include <atomic>
+#endif
 
 #ifdef SK_DEBUG
     #define VALIDATE this->validate()
@@ -89,7 +89,10 @@
     // so that we can decrement the live count on delete in constant time.
     AllocHeader* allocData = reinterpret_cast<AllocHeader*>(ptr);
     SkDEBUGCODE(allocData->fSentinal = kAssignedMarker);
-    SkDEBUGCODE(allocData->fID = []{static int32_t gID; return sk_atomic_inc(&gID) + 1;}());
+    SkDEBUGCODE(allocData->fID = []{
+        static std::atomic<int32_t> nextID{1};
+        return nextID++;
+    }());
     // You can set a breakpoint here when a leaked ID is allocated to see the stack frame.
     SkDEBUGCODE(fAllocatedIDs.add(allocData->fID));
     allocData->fHeader = fTail;
diff --git a/src/gpu/GrOnFlushResourceProvider.cpp b/src/gpu/GrOnFlushResourceProvider.cpp
index bbf1029..aadd353 100644
--- a/src/gpu/GrOnFlushResourceProvider.cpp
+++ b/src/gpu/GrOnFlushResourceProvider.cpp
@@ -44,10 +44,15 @@
     return proxyProvider->assignUniqueKeyToProxy(key, proxy);
 }
 
-void GrOnFlushResourceProvider::removeUniqueKeyFromProxy(const GrUniqueKey& key,
-                                                         GrTextureProxy* proxy) {
+void GrOnFlushResourceProvider::removeUniqueKeyFromProxy(GrTextureProxy* proxy) {
     auto proxyProvider = fDrawingMgr->getContext()->contextPriv().proxyProvider();
-    proxyProvider->removeUniqueKeyFromProxy(key, proxy);
+    proxyProvider->removeUniqueKeyFromProxy(proxy);
+}
+
+void GrOnFlushResourceProvider::processInvalidUniqueKey(const GrUniqueKey& key) {
+    auto proxyProvider = fDrawingMgr->getContext()->contextPriv().proxyProvider();
+    proxyProvider->processInvalidUniqueKey(key, nullptr,
+                                           GrProxyProvider::InvalidateGPUResource::kYes);
 }
 
 sk_sp<GrTextureProxy> GrOnFlushResourceProvider::findOrCreateProxyByUniqueKey(
@@ -60,7 +65,7 @@
     auto resourceProvider = fDrawingMgr->getContext()->contextPriv().resourceProvider();
 
     if (GrSurfaceProxy::LazyState::kNot != proxy->lazyInstantiationState()) {
-        // DDL TODO: Decide if we ever plan to have these proxies use the GrUninstantiateTracker
+        // DDL TODO: Decide if we ever plan to have these proxies use the GrDeinstantiateTracker
         // to support unistantiating them at the end of a flush.
         return proxy->priv().doLazyInstantiation(resourceProvider);
     }
diff --git a/src/gpu/GrOnFlushResourceProvider.h b/src/gpu/GrOnFlushResourceProvider.h
index 9ff2384..9cbcddc 100644
--- a/src/gpu/GrOnFlushResourceProvider.h
+++ b/src/gpu/GrOnFlushResourceProvider.h
@@ -77,9 +77,10 @@
                                                          sk_sp<SkColorSpace>,
                                                          const SkSurfaceProps*);
 
-    // Proxy unique key management. See GrProxyProvider.
+    // Proxy unique key management. See GrProxyProvider.h.
     bool assignUniqueKeyToProxy(const GrUniqueKey&, GrTextureProxy*);
-    void removeUniqueKeyFromProxy(const GrUniqueKey&, GrTextureProxy*);
+    void removeUniqueKeyFromProxy(GrTextureProxy*);
+    void processInvalidUniqueKey(const GrUniqueKey&);
     sk_sp<GrTextureProxy> findOrCreateProxyByUniqueKey(const GrUniqueKey&, GrSurfaceOrigin);
 
     bool instatiateProxy(GrSurfaceProxy*);
diff --git a/src/gpu/GrOpFlushState.h b/src/gpu/GrOpFlushState.h
index 1e44935..f963c69 100644
--- a/src/gpu/GrOpFlushState.h
+++ b/src/gpu/GrOpFlushState.h
@@ -12,7 +12,7 @@
 #include "GrAppliedClip.h"
 #include "GrBufferAllocPool.h"
 #include "GrDeferredUpload.h"
-#include "GrUninstantiateProxyTracker.h"
+#include "GrDeinstantiateProxyTracker.h"
 #include "SkArenaAlloc.h"
 #include "SkArenaAllocList.h"
 #include "ops/GrMeshDrawOp.h"
@@ -105,9 +105,7 @@
     // permissible).
     GrAtlasManager* atlasManager() const final;
 
-    GrUninstantiateProxyTracker* uninstantiateProxyTracker() {
-        return &fUninstantiateProxyTracker;
-    }
+    GrDeinstantiateProxyTracker* deinstantiateProxyTracker() { return &fDeinstantiateProxyTracker; }
 
 private:
     /** GrMeshDrawOp::Target override. */
@@ -164,8 +162,8 @@
     SkArenaAllocList<Draw>::Iter fCurrDraw;
     SkArenaAllocList<InlineUpload>::Iter fCurrUpload;
 
-    // Used to track the proxies that need to be uninstantiated after we finish a flush
-    GrUninstantiateProxyTracker fUninstantiateProxyTracker;
+    // Used to track the proxies that need to be deinstantiated after we finish a flush
+    GrDeinstantiateProxyTracker fDeinstantiateProxyTracker;
 };
 
 #endif
diff --git a/src/gpu/GrOpList.cpp b/src/gpu/GrOpList.cpp
index dd889e8..b1ccc82 100644
--- a/src/gpu/GrOpList.cpp
+++ b/src/gpu/GrOpList.cpp
@@ -13,15 +13,13 @@
 #include "GrRenderTargetPriv.h"
 #include "GrSurfaceProxy.h"
 #include "GrTextureProxyPriv.h"
-
-#include "SkAtomics.h"
+#include <atomic>
 
 uint32_t GrOpList::CreateUniqueID() {
-    static int32_t gUniqueID = SK_InvalidUniqueID;
+    static std::atomic<uint32_t> nextID{1};
     uint32_t id;
-    // Loop in case our global wraps around, as we never want to return a 0.
     do {
-        id = static_cast<uint32_t>(sk_atomic_inc(&gUniqueID) + 1);
+        id = nextID++;
     } while (id == SK_InvalidUniqueID);
     return id;
 }
diff --git a/src/gpu/GrPaint.h b/src/gpu/GrPaint.h
index b900c24..36f38ed 100644
--- a/src/gpu/GrPaint.h
+++ b/src/gpu/GrPaint.h
@@ -115,6 +115,10 @@
      **/
     bool isTrivial() const { return fTrivial; }
 
+    friend void assert_alive(GrPaint& p) {
+        SkASSERT(p.fAlive);
+    }
+
 private:
     // Since paint copying is expensive if there are fragment processors, we require going through
     // the Clone() method.
@@ -128,6 +132,7 @@
     SkSTArray<2, std::unique_ptr<GrFragmentProcessor>> fCoverageFragmentProcessors;
     bool fTrivial = true;
     SkPMColor4f fColor = SK_PMColor4fWHITE;
+    SkDEBUGCODE(bool fAlive = true;)  // Set false after moved from.
 };
 
 #endif
diff --git a/src/gpu/GrPathRenderer.cpp b/src/gpu/GrPathRenderer.cpp
index 71a34ea..631f96d 100644
--- a/src/gpu/GrPathRenderer.cpp
+++ b/src/gpu/GrPathRenderer.cpp
@@ -50,6 +50,7 @@
     canArgs.fViewMatrix = args.fViewMatrix;
     canArgs.fShape = args.fShape;
     canArgs.fAAType = args.fAAType;
+    canArgs.fTargetIsWrappedVkSecondaryCB = args.fRenderTargetContext->wrapsVkSecondaryCB();
     canArgs.validate();
 
     canArgs.fHasUserStencilSettings = !args.fUserStencilSettings->isUnused();
diff --git a/src/gpu/GrPathRenderer.h b/src/gpu/GrPathRenderer.h
index 5b59563..a6c3ed5 100644
--- a/src/gpu/GrPathRenderer.h
+++ b/src/gpu/GrPathRenderer.h
@@ -80,8 +80,9 @@
         const SkMatrix*             fViewMatrix;
         const GrShape*              fShape;
         GrAAType                    fAAType;
+        bool                        fTargetIsWrappedVkSecondaryCB;
 
-        // These next two are only used by GrStencilAndCoverPathRenderer
+        // This is only used by GrStencilAndCoverPathRenderer
         bool                        fHasUserStencilSettings;
 
 #ifdef SK_DEBUG
diff --git a/src/gpu/GrPathRendererChain.cpp b/src/gpu/GrPathRendererChain.cpp
index a6ad513..6e28c5d 100644
--- a/src/gpu/GrPathRendererChain.cpp
+++ b/src/gpu/GrPathRendererChain.cpp
@@ -40,7 +40,8 @@
     if (options.fGpuPathRenderers & GpuPathRenderers::kCoverageCounting) {
         using AllowCaching = GrCoverageCountingPathRenderer::AllowCaching;
         if (auto ccpr = GrCoverageCountingPathRenderer::CreateIfSupported(
-                                caps, AllowCaching(options.fAllowPathMaskCaching))) {
+                                caps, AllowCaching(options.fAllowPathMaskCaching),
+                                context->uniqueID())) {
             fCoverageCountingPathRenderer = ccpr.get();
             context->contextPriv().addOnFlushCallbackObject(fCoverageCountingPathRenderer);
             fChain.push_back(std::move(ccpr));
diff --git a/src/gpu/GrPathUtils.h b/src/gpu/GrPathUtils.h
index a1a1fe7..f358dfc 100644
--- a/src/gpu/GrPathUtils.h
+++ b/src/gpu/GrPathUtils.h
@@ -59,34 +59,29 @@
         void set(const SkPoint controlPts[3]);
 
         /**
-         * Applies the matrix to vertex positions to compute UV coords. This
-         * has been templated so that the compiler can easliy unroll the loop
-         * and reorder to avoid stalling for loads. The assumption is that a
-         * path renderer will have a small fixed number of vertices that it
-         * uploads for each quad.
+         * Applies the matrix to vertex positions to compute UV coords.
          *
-         * N is the number of vertices.
-         * STRIDE is the size of each vertex.
-         * UV_OFFSET is the offset of the UV values within each vertex.
          * vertices is a pointer to the first vertex.
+         * vertexCount is the number of vertices.
+         * stride is the size of each vertex.
+         * uvOffset is the offset of the UV values within each vertex.
          */
-        template <int N, size_t STRIDE, size_t UV_OFFSET>
-        void apply(const void* vertices) const {
+        void apply(void* vertices, int vertexCount, size_t stride, size_t uvOffset) const {
             intptr_t xyPtr = reinterpret_cast<intptr_t>(vertices);
-            intptr_t uvPtr = reinterpret_cast<intptr_t>(vertices) + UV_OFFSET;
+            intptr_t uvPtr = reinterpret_cast<intptr_t>(vertices) + uvOffset;
             float sx = fM[0];
             float kx = fM[1];
             float tx = fM[2];
             float ky = fM[3];
             float sy = fM[4];
             float ty = fM[5];
-            for (int i = 0; i < N; ++i) {
+            for (int i = 0; i < vertexCount; ++i) {
                 const SkPoint* xy = reinterpret_cast<const SkPoint*>(xyPtr);
                 SkPoint* uv = reinterpret_cast<SkPoint*>(uvPtr);
                 uv->fX = sx * xy->fX + kx * xy->fY + tx;
                 uv->fY = ky * xy->fX + sy * xy->fY + ty;
-                xyPtr += STRIDE;
-                uvPtr += STRIDE;
+                xyPtr += stride;
+                uvPtr += stride;
             }
         }
     private:
diff --git a/src/gpu/GrPrimitiveProcessor.h b/src/gpu/GrPrimitiveProcessor.h
index be3d86a..b9a1fe3 100644
--- a/src/gpu/GrPrimitiveProcessor.h
+++ b/src/gpu/GrPrimitiveProcessor.h
@@ -128,6 +128,7 @@
 
         void init(const Attribute* attrs, int count) {
             fAttributes = attrs;
+            fRawCount = count;
             fCount = 0;
             fStride = 0;
             for (int i = 0; i < count; ++i) {
@@ -139,6 +140,7 @@
         }
 
         const Attribute* fAttributes = nullptr;
+        int              fRawCount = 0;
         int              fCount = 0;
         size_t           fStride = 0;
     };
@@ -185,6 +187,22 @@
     virtual void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const = 0;
 
 
+    void getAttributeKey(GrProcessorKeyBuilder* b) const {
+        // Ensure that our CPU and GPU type fields fit together in a 32-bit value, and we never
+        // collide with the "uninitialized" value.
+        static_assert(kGrVertexAttribTypeCount < (1 << 8), "");
+        static_assert(kGrSLTypeCount           < (1 << 8), "");
+
+        auto add_attributes = [=](const Attribute* attrs, int attrCount) {
+            for (int i = 0; i < attrCount; ++i) {
+                b->add32(attrs[i].isInitialized() ? (attrs[i].cpuType() << 16) | attrs[i].gpuType()
+                                                  : ~0);
+            }
+        };
+        add_attributes(fVertexAttributes.fAttributes, fVertexAttributes.fRawCount);
+        add_attributes(fInstanceAttributes.fAttributes, fInstanceAttributes.fRawCount);
+    }
+
     /** Returns a new instance of the appropriate *GL* implementation class
         for the given GrProcessor; caller is responsible for deleting
         the object. */
@@ -192,13 +210,6 @@
 
     virtual bool isPathRendering() const { return false; }
 
-    /**
-     * If non-null, overrides the dest color returned by GrGLSLFragmentShaderBuilder::dstColor().
-     */
-    virtual const char* getDestColorOverride() const { return nullptr; }
-
-    virtual float getSampleShading() const { return 0.0; }
-
 protected:
     void setVertexAttributes(const Attribute* attrs, int attrCount) {
         fVertexAttributes.init(attrs, attrCount);
diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h
index b8338b3..8ae9493 100644
--- a/src/gpu/GrProcessor.h
+++ b/src/gpu/GrProcessor.h
@@ -8,7 +8,6 @@
 #ifndef GrProcessor_DEFINED
 #define GrProcessor_DEFINED
 
-#include "../private/SkAtomics.h"
 #include "GrBuffer.h"
 #include "GrColor.h"
 #include "GrProcessorUnitTest.h"
diff --git a/src/gpu/GrProcessorAnalysis.h b/src/gpu/GrProcessorAnalysis.h
index e8c7458..633c258 100644
--- a/src/gpu/GrProcessorAnalysis.h
+++ b/src/gpu/GrProcessorAnalysis.h
@@ -39,6 +39,8 @@
 
     void setToUnknownOpaque() { fFlags = kIsOpaque_Flag; }
 
+    bool isUnknown() const { return SkToBool(fFlags == 0); }
+
     bool isOpaque() const { return SkToBool(kIsOpaque_Flag & fFlags); }
 
     bool isConstant(SkPMColor4f* color = nullptr) const {
diff --git a/src/gpu/GrProcessorSet.cpp b/src/gpu/GrProcessorSet.cpp
index fc9c801..b605695 100644
--- a/src/gpu/GrProcessorSet.cpp
+++ b/src/gpu/GrProcessorSet.cpp
@@ -39,6 +39,7 @@
         SkDebugf("Insane number of color fragment processors in paint. Dropping all processors.");
         fColorFragmentProcessorCnt = 0;
     }
+    SkDEBUGCODE(paint.fAlive = false;)
 }
 
 GrProcessorSet::GrProcessorSet(SkBlendMode mode)
diff --git a/src/gpu/GrProgramDesc.cpp b/src/gpu/GrProgramDesc.cpp
index aa84c98..d888a63 100644
--- a/src/gpu/GrProgramDesc.cpp
+++ b/src/gpu/GrProgramDesc.cpp
@@ -212,6 +212,7 @@
     GrProcessorKeyBuilder b(&desc->key());
 
     primProc.getGLSLProcessorKey(shaderCaps, &b);
+    primProc.getAttributeKey(&b);
     if (!gen_meta_key(primProc, shaderCaps, 0, &b)) {
         desc->key().reset();
         return false;
diff --git a/src/gpu/GrProgramDesc.h b/src/gpu/GrProgramDesc.h
index 0a634cb..e8f0877 100644
--- a/src/gpu/GrProgramDesc.h
+++ b/src/gpu/GrProgramDesc.h
@@ -50,13 +50,11 @@
         return reinterpret_cast<const uint32_t*>(fKey.begin());
     }
 
-    // Gets the number of bytes in asKey(). It will be a 4-byte aligned value. When comparing two
-    // keys the size of either key can be used with memcmp() since the lengths themselves begin the
-    // keys and thus the memcmp will exit early if the keys are of different lengths.
-    uint32_t keyLength() const { return *this->atOffset<uint32_t, kLengthOffset>(); }
-
-    // Gets the a checksum of the key. Can be used as a hash value for a fast lookup in a cache.
-    uint32_t getChecksum() const { return *this->atOffset<uint32_t, kChecksumOffset>(); }
+    // Gets the number of bytes in asKey(). It will be a 4-byte aligned value.
+    uint32_t keyLength() const {
+        SkASSERT(0 == (fKey.count() % 4));
+        return fKey.count();
+    }
 
     GrProgramDesc& operator= (const GrProgramDesc& other) {
         uint32_t keyLength = other.keyLength();
@@ -66,6 +64,10 @@
     }
 
     bool operator== (const GrProgramDesc& that) const {
+        if (this->keyLength() != that.keyLength()) {
+            return false;
+        }
+
         SkASSERT(SkIsAlign4(this->keyLength()));
         int l = this->keyLength() >> 2;
         const uint32_t* aKey = this->asKey();
@@ -87,19 +89,6 @@
         header->fSurfaceOriginKey = key;
     }
 
-    static bool Less(const GrProgramDesc& a, const GrProgramDesc& b) {
-        SkASSERT(SkIsAlign4(a.keyLength()));
-        int l = a.keyLength() >> 2;
-        const uint32_t* aKey = a.asKey();
-        const uint32_t* bKey = b.asKey();
-        for (int i = 0; i < l; ++i) {
-            if (aKey[i] != bKey[i]) {
-                return aKey[i] < bKey[i] ? true : false;
-            }
-        }
-        return false;
-    }
-
     struct KeyHeader {
         // Set to uniquely idenitify any swizzling of the shader's output color(s).
         uint8_t fOutputSwizzle;
@@ -116,16 +105,6 @@
     // This should really only be used internally, base classes should return their own headers
     const KeyHeader& header() const { return *this->atOffset<KeyHeader, kHeaderOffset>(); }
 
-    void finalize() {
-        int keyLength = fKey.count();
-        SkASSERT(0 == (keyLength % 4));
-        *(this->atOffset<uint32_t, GrProgramDesc::kLengthOffset>()) = SkToU32(keyLength);
-
-        uint32_t* checksum = this->atOffset<uint32_t, GrProgramDesc::kChecksumOffset>();
-        *checksum = 0;  // We'll hash through these bytes, so make sure they're initialized.
-        *checksum = SkOpts::hash(fKey.begin(), keyLength);
-    }
-
 protected:
     template<typename T, size_t OFFSET> T* atOffset() {
         return reinterpret_cast<T*>(reinterpret_cast<intptr_t>(fKey.begin()) + OFFSET);
@@ -135,18 +114,11 @@
         return reinterpret_cast<const T*>(reinterpret_cast<intptr_t>(fKey.begin()) + OFFSET);
     }
 
-    // The key, stored in fKey, is composed of four parts:
-    // 1. uint32_t for total key length.
-    // 2. uint32_t for a checksum.
-    // 3. Header struct defined above.
-    // 4. A Backend specific payload which includes the per-processor keys.
+    // The key, stored in fKey, is composed of two parts:
+    // 1. Header struct defined above.
+    // 2. A Backend specific payload which includes the per-processor keys.
     enum KeyOffsets {
-        // Part 1.
-        kLengthOffset = 0,
-        // Part 2.
-        kChecksumOffset = kLengthOffset + sizeof(uint32_t),
-        // Part 3.
-        kHeaderOffset = kChecksumOffset + sizeof(uint32_t),
+        kHeaderOffset = 0,
         kHeaderSize = SkAlign4(sizeof(KeyHeader)),
         // Part 4.
         // This is the offset into the backenend specific part of the key, which includes
diff --git a/src/gpu/GrProxyProvider.cpp b/src/gpu/GrProxyProvider.cpp
index 3259f28..657d607 100644
--- a/src/gpu/GrProxyProvider.cpp
+++ b/src/gpu/GrProxyProvider.cpp
@@ -104,13 +104,16 @@
     fUniquelyKeyedProxies.add(proxy);
 }
 
-void GrProxyProvider::removeUniqueKeyFromProxy(const GrUniqueKey& key, GrTextureProxy* proxy) {
+void GrProxyProvider::removeUniqueKeyFromProxy(GrTextureProxy* proxy) {
     ASSERT_SINGLE_OWNER
-    if (this->isAbandoned() || !proxy) {
+    SkASSERT(proxy);
+    SkASSERT(proxy->getUniqueKey().isValid());
+
+    if (this->isAbandoned()) {
         return;
     }
 
-    this->processInvalidProxyUniqueKey(key, proxy, true);
+    this->processInvalidUniqueKey(proxy->getUniqueKey(), proxy, InvalidateGPUResource::kYes);
 }
 
 sk_sp<GrTextureProxy> GrProxyProvider::findProxyByUniqueKey(const GrUniqueKey& key,
@@ -221,9 +224,6 @@
         if (fCaps->usesMixedSamples() && sampleCnt > 1) {
             surfaceFlags |= GrInternalSurfaceFlags::kMixedSampled;
         }
-        if (fCaps->maxWindowRectangles() > 0) {
-            surfaceFlags |= GrInternalSurfaceFlags::kWindowRectsSupport;
-        }
     }
 
     GrSurfaceDesc desc;
@@ -415,8 +415,10 @@
 sk_sp<GrTextureProxy> GrProxyProvider::wrapBackendTexture(const GrBackendTexture& backendTex,
                                                           GrSurfaceOrigin origin,
                                                           GrWrapOwnership ownership,
+                                                          GrIOType ioType,
                                                           ReleaseProc releaseProc,
                                                           ReleaseContext releaseCtx) {
+    SkASSERT(ioType != kWrite_GrIOType);
     if (this->isAbandoned()) {
         return nullptr;
     }
@@ -426,7 +428,7 @@
         return nullptr;
     }
 
-    sk_sp<GrTexture> tex = fResourceProvider->wrapBackendTexture(backendTex, ownership);
+    sk_sp<GrTexture> tex = fResourceProvider->wrapBackendTexture(backendTex, ownership, ioType);
     if (!tex) {
         return nullptr;
     }
@@ -516,12 +518,41 @@
     }
     SkASSERT(!rt->asTexture());  // A GrRenderTarget that's not textureable
     SkASSERT(!rt->getUniqueKey().isValid());
-    // Make sure we match how we created the proxy with SkBudgeted::kNo
+    // This proxy should be unbudgeted because we're just wrapping an external resource
     SkASSERT(SkBudgeted::kNo == rt->resourcePriv().isBudgeted());
 
     return sk_sp<GrSurfaceProxy>(new GrRenderTargetProxy(std::move(rt), origin));
 }
 
+sk_sp<GrRenderTargetProxy> GrProxyProvider::wrapVulkanSecondaryCBAsRenderTarget(
+        const SkImageInfo& imageInfo, const GrVkDrawableInfo& vkInfo) {
+    if (this->isAbandoned()) {
+        return nullptr;
+    }
+
+    // This is only supported on a direct GrContext.
+    if (!fResourceProvider) {
+        return nullptr;
+    }
+
+    sk_sp<GrRenderTarget> rt = fResourceProvider->wrapVulkanSecondaryCBAsRenderTarget(imageInfo,
+                                                                                      vkInfo);
+
+    if (!rt) {
+        return nullptr;
+    }
+    SkASSERT(!rt->asTexture());  // A GrRenderTarget that's not textureable
+    SkASSERT(!rt->getUniqueKey().isValid());
+    // This proxy should be unbudgeted because we're just wrapping an external resource
+    SkASSERT(SkBudgeted::kNo == rt->resourcePriv().isBudgeted());
+
+    // All Vulkan surfaces uses top left origins.
+    return sk_sp<GrRenderTargetProxy>(
+            new GrRenderTargetProxy(std::move(rt),
+                                    kTopLeft_GrSurfaceOrigin,
+                                    GrRenderTargetProxy::WrapsVkSecondaryCB::kYes));
+}
+
 sk_sp<GrTextureProxy> GrProxyProvider::createLazyProxy(LazyInstantiateCallback&& callback,
                                                        const GrBackendFormat& format,
                                                        const GrSurfaceDesc& desc,
@@ -570,9 +601,6 @@
         if (SkToBool(surfaceFlags & GrInternalSurfaceFlags::kMixedSampled)) {
             SkASSERT(fCaps->usesMixedSamples() && desc.fSampleCnt > 1);
         }
-        if (SkToBool(surfaceFlags & GrInternalSurfaceFlags::kWindowRectsSupport)) {
-            SkASSERT(fCaps->maxWindowRectangles() > 0);
-        }
     }
 #endif
 
@@ -601,9 +629,6 @@
     if (SkToBool(surfaceFlags & GrInternalSurfaceFlags::kMixedSampled)) {
         SkASSERT(fCaps->usesMixedSamples() && desc.fSampleCnt > 1);
     }
-    if (SkToBool(surfaceFlags & GrInternalSurfaceFlags::kWindowRectsSupport)) {
-        SkASSERT(fCaps->maxWindowRectangles() > 0);
-    }
 #endif
 
     using LazyInstantiationType = GrSurfaceProxy::LazyInstantiationType;
@@ -631,9 +656,6 @@
     GrInternalSurfaceFlags surfaceFlags = GrInternalSurfaceFlags::kNoPendingIO;
     if (Renderable::kYes == renderable) {
         desc.fFlags = kRenderTarget_GrSurfaceFlag;
-        if (caps.maxWindowRectangles() > 0) {
-            surfaceFlags |= GrInternalSurfaceFlags::kWindowRectsSupport;
-        }
     }
     desc.fWidth = -1;
     desc.fHeight = -1;
@@ -663,29 +685,37 @@
                               proxy->worstCaseHeight() == proxy->height());
 }
 
-void GrProxyProvider::processInvalidProxyUniqueKey(const GrUniqueKey& key) {
+void GrProxyProvider::processInvalidUniqueKey(const GrUniqueKey& key, GrTextureProxy* proxy,
+                                              InvalidateGPUResource invalidateGPUResource) {
+    SkASSERT(key.isValid());
+
+    if (!proxy) {
+        proxy = fUniquelyKeyedProxies.find(key);
+    }
+    SkASSERT(!proxy || proxy->getUniqueKey() == key);
+
+    // Locate the corresponding GrGpuResource (if it needs to be invalidated) before clearing the
+    // proxy's unique key. We must do it in this order because 'key' may alias the proxy's key.
+    sk_sp<GrGpuResource> invalidGpuResource;
+    if (InvalidateGPUResource::kYes == invalidateGPUResource) {
+        if (proxy && proxy->isInstantiated()) {
+            invalidGpuResource = sk_ref_sp(proxy->peekSurface());
+        }
+        if (!invalidGpuResource && fResourceProvider) {
+            invalidGpuResource = fResourceProvider->findByUniqueKey<GrGpuResource>(key);
+        }
+        SkASSERT(!invalidGpuResource || invalidGpuResource->getUniqueKey() == key);
+    }
+
     // Note: this method is called for the whole variety of GrGpuResources so often 'key'
     // will not be in 'fUniquelyKeyedProxies'.
-    GrTextureProxy* proxy = fUniquelyKeyedProxies.find(key);
     if (proxy) {
-        this->processInvalidProxyUniqueKey(key, proxy, false);
+        fUniquelyKeyedProxies.remove(key);
+        proxy->cacheAccess().clearUniqueKey();
     }
-}
 
-void GrProxyProvider::processInvalidProxyUniqueKey(const GrUniqueKey& key, GrTextureProxy* proxy,
-                                                   bool invalidateSurface) {
-    SkASSERT(proxy);
-    SkASSERT(proxy->getUniqueKey().isValid());
-    SkASSERT(proxy->getUniqueKey() == key);
-
-    fUniquelyKeyedProxies.remove(key);
-    proxy->cacheAccess().clearUniqueKey();
-
-    if (invalidateSurface && proxy->isInstantiated()) {
-        GrSurface* surface = proxy->peekSurface();
-        if (surface) {
-            surface->resourcePriv().removeUniqueKey();
-        }
+    if (invalidGpuResource) {
+        invalidGpuResource->resourcePriv().removeUniqueKey();
     }
 }
 
@@ -703,7 +733,7 @@
     for (UniquelyKeyedProxyHash::Iter iter(&fUniquelyKeyedProxies); !iter.done(); ++iter) {
         GrTextureProxy& tmp = *iter;
 
-        this->processInvalidProxyUniqueKey(tmp.getUniqueKey(), &tmp, false);
+        this->processInvalidUniqueKey(tmp.getUniqueKey(), &tmp, InvalidateGPUResource::kNo);
     }
     SkASSERT(!fUniquelyKeyedProxies.count());
 }
diff --git a/src/gpu/GrProxyProvider.h b/src/gpu/GrProxyProvider.h
index 1e26f51..8e8c2e0 100644
--- a/src/gpu/GrProxyProvider.h
+++ b/src/gpu/GrProxyProvider.h
@@ -47,7 +47,7 @@
      * Removes a unique key from a proxy. If the proxy has already been instantiated, it will
      * also remove the unique key from the target GrSurface.
      */
-    void removeUniqueKeyFromProxy(const GrUniqueKey&, GrTextureProxy*);
+    void removeUniqueKeyFromProxy(GrTextureProxy*);
 
     /*
      * Finds a proxy by unique key.
@@ -104,11 +104,12 @@
     typedef void (*ReleaseProc)(ReleaseContext);
 
     /*
-     * Create a texture proxy that wraps a (non-renderable) backend texture.
+     * Create a texture proxy that wraps a (non-renderable) backend texture. GrIOType must be
+     * kRead or kRW.
      */
     sk_sp<GrTextureProxy> wrapBackendTexture(const GrBackendTexture&, GrSurfaceOrigin,
-                                             GrWrapOwnership = kBorrow_GrWrapOwnership,
-                                             ReleaseProc = nullptr, ReleaseContext = nullptr);
+                                             GrWrapOwnership, GrIOType, ReleaseProc = nullptr,
+                                             ReleaseContext = nullptr);
 
     /*
      * Create a texture proxy that wraps a backend texture and is both texture-able and renderable
@@ -119,7 +120,7 @@
                                                        GrWrapOwnership = kBorrow_GrWrapOwnership);
 
     /*
-     * Create a render target proxy that wraps a backend rendertarget
+     * Create a render target proxy that wraps a backend render target
      */
     sk_sp<GrSurfaceProxy> wrapBackendRenderTarget(const GrBackendRenderTarget&, GrSurfaceOrigin);
 
@@ -130,6 +131,9 @@
                                                            GrSurfaceOrigin origin,
                                                            int sampleCnt);
 
+    sk_sp<GrRenderTargetProxy> wrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&,
+                                                                   const GrVkDrawableInfo&);
+
     using LazyInstantiateCallback = std::function<sk_sp<GrSurface>(GrResourceProvider*)>;
 
     enum class Renderable : bool {
@@ -188,21 +192,22 @@
     // determine if it is going to need a texture domain or a full clear.
     static bool IsFunctionallyExact(GrSurfaceProxy* proxy);
 
-    /**
-     * Either the proxy attached to the unique key is being deleted (in which case we
-     * don't want it cluttering up the hash table) or the client has indicated that
-     * it will never refer to the unique key again. In either case, remove the key
-     * from the hash table.
-     * Note: this does not, by itself, alter unique key attached to the underlying GrTexture.
-     */
-    void processInvalidProxyUniqueKey(const GrUniqueKey&);
+    enum class InvalidateGPUResource : bool { kNo = false, kYes = true };
 
-    /**
-     * Same as above, but you must pass in a GrTextureProxy to save having to search for it. The
-     * GrUniqueKey of the proxy must be valid and it must match the passed in key. This function
-     * also gives the option to invalidate the GrUniqueKey on the underlying GrTexture.
+    /*
+     * This method ensures that, if a proxy w/ the supplied unique key exists, it is removed from
+     * the proxy provider's map and its unique key is removed. If 'invalidateSurface' is true, it
+     * will independently ensure that the unique key is removed from any GrGpuResources that may
+     * have it.
+     *
+     * If 'proxy' is provided (as an optimization to stop re-looking it up), its unique key must be
+     * valid and match the provided unique key.
+     *
+     * This method is called if either the proxy attached to the unique key is being deleted
+     * (in which case we don't want it cluttering up the hash table) or the client has indicated
+     * that it will never refer to the unique key again.
      */
-    void processInvalidProxyUniqueKey(const GrUniqueKey&, GrTextureProxy*, bool invalidateSurface);
+    void processInvalidUniqueKey(const GrUniqueKey&, GrTextureProxy*, InvalidateGPUResource);
 
     uint32_t contextUniqueID() const { return fContextUniqueID; }
     const GrCaps* caps() const { return fCaps.get(); }
diff --git a/src/gpu/GrQuad.cpp b/src/gpu/GrQuad.cpp
index 567301d..badd98f 100644
--- a/src/gpu/GrQuad.cpp
+++ b/src/gpu/GrQuad.cpp
@@ -30,9 +30,7 @@
         dotValue = dot({sign(e1.fX), sign(e1.fY)}, {sign(e2.fX), sign(e2.fY)});
     }
 
-    // Unfortunately must have a pretty healthy tolerance here or transformed rects that are
-    // effectively rectilinear will have edge dot products of around .005
-    return SkScalarNearlyZero(dotValue, 1e-2f);
+    return SkScalarNearlyZero(dotValue, 5e-4f);
 }
 
 // This is not the most performance critical function; code using GrQuad should rely on the faster
@@ -45,11 +43,16 @@
 }
 
 static bool coords_rectilinear(const float xs[4], const float ys[4]) {
-    SkVector e0{xs[1] - xs[0], ys[1] - ys[0]}; // Connects to e1 and e2(repeat)
+    SkVector e0{xs[1] - xs[0], ys[1] - ys[0]}; // connects to e1 and e2(repeat)
     SkVector e1{xs[3] - xs[1], ys[3] - ys[1]}; // connects to e0(repeat) and e3
     SkVector e2{xs[0] - xs[2], ys[0] - ys[2]}; // connects to e0 and e3(repeat)
     SkVector e3{xs[2] - xs[3], ys[2] - ys[3]}; // connects to e1(repeat) and e2
 
+    e0.normalize();
+    e1.normalize();
+    e2.normalize();
+    e3.normalize();
+
     return dot_nearly_zero(e0, e1) && dot_nearly_zero(e1, e3) &&
            dot_nearly_zero(e2, e0) && dot_nearly_zero(e3, e2);
 }
@@ -199,7 +202,6 @@
         SkNx_shuffle<0, 0, 2, 2>(r).store(fX);
         SkNx_shuffle<1, 3, 1, 3>(r).store(fY);
         fW[0] = fW[1] = fW[2] = fW[3] = 1.f;
-        fIW[0] = fIW[1] = fIW[2] = fIW[3] = 1.f;
     } else {
         Sk4f rx(rect.fLeft, rect.fLeft, rect.fRight, rect.fRight);
         Sk4f ry(rect.fTop, rect.fBottom, rect.fTop, rect.fBottom);
@@ -217,14 +219,19 @@
             Sk4f w2(m.get(SkMatrix::kMPersp2));
             auto w = SkNx_fma(w0, rx, SkNx_fma(w1, ry, w2));
             w.store(fW);
-            w.invert().store(fIW);
         } else {
             fW[0] = fW[1] = fW[2] = fW[3] = 1.f;
-            fIW[0] = fIW[1] = fIW[2] = fIW[3] = 1.f;
         }
     }
 }
 
+// Private constructor used by GrQuadList to quickly fill in a quad's values from the channel arrays
+GrPerspQuad::GrPerspQuad(const float* xs, const float* ys, const float* ws) {
+    memcpy(fX, xs, 4 * sizeof(float));
+    memcpy(fY, ys, 4 * sizeof(float));
+    memcpy(fW, ws, 4 * sizeof(float));
+}
+
 bool GrPerspQuad::aaHasEffectOnRect() const {
     SkASSERT(this->quadType() == GrQuadType::kRect);
     // If rect, ws must all be 1s so no need to divide
diff --git a/src/gpu/GrQuad.h b/src/gpu/GrQuad.h
index 824131b..33fd326 100644
--- a/src/gpu/GrQuad.h
+++ b/src/gpu/GrQuad.h
@@ -12,6 +12,7 @@
 #include "SkNx.h"
 #include "SkPoint.h"
 #include "SkPoint3.h"
+#include "SkTArray.h"
 
 enum class GrAAType : unsigned;
 enum class GrQuadAAFlags;
@@ -90,6 +91,9 @@
 #endif
 
 private:
+    template<typename T>
+    friend class GrQuadListBase;
+
     float fX[4];
     float fY[4];
 };
@@ -104,21 +108,29 @@
 
     SkPoint3 point(int i) const { return {fX[i], fY[i], fW[i]}; }
 
-    SkRect bounds() const {
-        auto x = this->x4f() * this->iw4f();
-        auto y = this->y4f() * this->iw4f();
+    SkRect bounds(GrQuadType type) const {
+        SkASSERT(this->quadType() <= type);
+
+        Sk4f x = this->x4f();
+        Sk4f y = this->y4f();
+        if (type == GrQuadType::kPerspective) {
+            Sk4f iw = this->iw4f();
+            x *= iw;
+            y *= iw;
+        }
+
         return {x.min(), y.min(), x.max(), y.max()};
     }
 
     float x(int i) const { return fX[i]; }
     float y(int i) const { return fY[i]; }
     float w(int i) const { return fW[i]; }
-    float iw(int i) const { return fIW[i]; }
+    float iw(int i) const { return sk_ieee_float_divide(1.f, fW[i]); }
 
     Sk4f x4f() const { return Sk4f::Load(fX); }
     Sk4f y4f() const { return Sk4f::Load(fY); }
     Sk4f w4f() const { return Sk4f::Load(fW); }
-    Sk4f iw4f() const { return Sk4f::Load(fIW); }
+    Sk4f iw4f() const { return this->w4f().invert(); }
 
     bool hasPerspective() const { return (w4f() != Sk4f(1.f)).anyTrue(); }
 
@@ -130,10 +142,197 @@
 #endif
 
 private:
+    template<typename T>
+    friend class GrQuadListBase;
+
+    // Copy 4 values from each of the arrays into the quad's components
+    GrPerspQuad(const float xs[4], const float ys[4], const float ws[4]);
+
     float fX[4];
     float fY[4];
     float fW[4];
-    float fIW[4];  // 1/w
+};
+
+// Underlying data used by GrQuadListBase. It is defined outside of GrQuadListBase due to compiler
+// issues related to specializing member types.
+template<typename T>
+struct QuadData {
+    float fX[4];
+    float fY[4];
+    T fMetadata;
+};
+
+template<>
+struct QuadData<void> {
+    float fX[4];
+    float fY[4];
+};
+
+// A dynamic list of (possibly) perspective quads that tracks the most general quad type of all
+// added quads. It avoids storing the 3rd component if the quad type never becomes perspective.
+// Use GrQuadList subclass when only storing quads. Use GrTQuadList subclass when storing quads
+// and per-quad templated metadata (such as color or domain).
+template<typename T>
+class GrQuadListBase {
+public:
+
+    int count() const { return fXYs.count(); }
+
+    GrQuadType quadType() const { return fType; }
+
+    void reserve(int count, GrQuadType forType) {
+        fXYs.reserve(count);
+        if (forType == GrQuadType::kPerspective || fType == GrQuadType::kPerspective) {
+            fWs.reserve(4 * count);
+        }
+    }
+
+    GrPerspQuad operator[] (int i) const {
+        SkASSERT(i < this->count());
+        SkASSERT(i >= 0);
+
+        const QuadData<T>& item = fXYs[i];
+        if (fType == GrQuadType::kPerspective) {
+            // Read the explicit ws
+            return GrPerspQuad(item.fX, item.fY, fWs.begin() + 4 * i);
+        } else {
+            // Ws are implicitly 1s.
+            static constexpr float kNoPerspectiveWs[4] = {1.f, 1.f, 1.f, 1.f};
+            return GrPerspQuad(item.fX, item.fY, kNoPerspectiveWs);
+        }
+    }
+
+    // Subclasses expose push_back(const GrQuad|GrPerspQuad&, GrQuadType, [const T&]), where
+    // the metadata argument is only present in GrTQuadList's push_back definition.
+
+protected:
+    GrQuadListBase() : fType(GrQuadType::kRect) {}
+
+    void concatImpl(const GrQuadListBase<T>& that) {
+        this->upgradeType(that.fType);
+        fXYs.push_back_n(that.fXYs.count(), that.fXYs.begin());
+        if (fType == GrQuadType::kPerspective) {
+            if (that.fType == GrQuadType::kPerspective) {
+                // Copy the other's ws into the end of this list's data
+                fWs.push_back_n(that.fWs.count(), that.fWs.begin());
+            } else {
+                // This list stores ws but the appended list had implicit 1s, so add explicit 1s to
+                // fill out the total list
+                fWs.push_back_n(4 * that.count(), 1.f);
+            }
+        }
+    }
+
+    // Returns the added item data so that its metadata can be initialized if T is not void
+    QuadData<T>& pushBackImpl(const GrQuad& quad, GrQuadType type) {
+        SkASSERT(quad.quadType() <= type);
+
+        this->upgradeType(type);
+        QuadData<T>& item = fXYs.push_back();
+        memcpy(item.fX, quad.fX, 4 * sizeof(float));
+        memcpy(item.fY, quad.fY, 4 * sizeof(float));
+        if (fType == GrQuadType::kPerspective) {
+            fWs.push_back_n(4, 1.f);
+        }
+        return item;
+    }
+
+    QuadData<T>& pushBackImpl(const GrPerspQuad& quad, GrQuadType type) {
+        SkASSERT(quad.quadType() <= type);
+
+        this->upgradeType(type);
+        QuadData<T>& item = fXYs.push_back();
+        memcpy(item.fX, quad.fX, 4 * sizeof(float));
+        memcpy(item.fY, quad.fY, 4 * sizeof(float));
+        if (fType == GrQuadType::kPerspective) {
+            fWs.push_back_n(4, quad.fW);
+        }
+        return item;
+    }
+
+    const QuadData<T>& item(int i) const {
+        return fXYs[i];
+    }
+
+    QuadData<T>& item(int i) {
+        return fXYs[i];
+    }
+
+private:
+    void upgradeType(GrQuadType type) {
+        // Possibly upgrade the overall type tracked by the list
+        if (type > fType) {
+            fType = type;
+            if (type == GrQuadType::kPerspective) {
+                // All existing quads were 2D, so the ws array just needs to be filled with 1s
+                fWs.push_back_n(4 * this->count(), 1.f);
+            }
+        }
+    }
+
+    // Interleaves xs, ys, and per-quad metadata so that all data for a single quad is together
+    // (barring ws, which can be dropped entirely if the quad type allows it).
+    SkSTArray<1, QuadData<T>, true> fXYs;
+    // The w channel is kept separate so that it can remain empty when only dealing with 2D quads.
+    SkTArray<float, true> fWs;
+
+    GrQuadType fType;
+};
+
+// This list only stores the quad data itself.
+class GrQuadList : public GrQuadListBase<void> {
+public:
+    GrQuadList() : INHERITED() {}
+
+    void concat(const GrQuadList& that) {
+        this->concatImpl(that);
+    }
+
+    void push_back(const GrQuad& quad, GrQuadType type) {
+        this->pushBackImpl(quad, type);
+    }
+
+    void push_back(const GrPerspQuad& quad, GrQuadType type) {
+        this->pushBackImpl(quad, type);
+    }
+
+private:
+    typedef GrQuadListBase<void> INHERITED;
+};
+
+// This variant of the list allows simple metadata to be stored per quad as well, such as color
+// or texture domain.
+template<typename T>
+class GrTQuadList : public GrQuadListBase<T> {
+public:
+    GrTQuadList() : INHERITED() {}
+
+    void concat(const GrTQuadList<T>& that) {
+        this->concatImpl(that);
+    }
+
+    // Adding to the list requires metadata
+    void push_back(const GrQuad& quad, GrQuadType type, T&& metadata) {
+        QuadData<T>& item = this->pushBackImpl(quad, type);
+        item.fMetadata = std::move(metadata);
+    }
+
+    void push_back(const GrPerspQuad& quad, GrQuadType type, T&& metadata) {
+        QuadData<T>& item = this->pushBackImpl(quad, type);
+        item.fMetadata = std::move(metadata);
+    }
+
+    // And provide access to the metadata per quad
+    const T& metadata(int i) const {
+        return this->item(i).fMetadata;
+    }
+
+    T& metadata(int i) {
+        return this->item(i).fMetadata;
+    }
+
+private:
+    typedef GrQuadListBase<T> INHERITED;
 };
 
 #endif
diff --git a/src/gpu/GrReducedClip.cpp b/src/gpu/GrReducedClip.cpp
index 97af1de..6379cbd 100644
--- a/src/gpu/GrReducedClip.cpp
+++ b/src/gpu/GrReducedClip.cpp
@@ -855,6 +855,7 @@
             canDrawArgs.fShape = &shape;
             canDrawArgs.fAAType = aaType;
             canDrawArgs.fHasUserStencilSettings = false;
+            canDrawArgs.fTargetIsWrappedVkSecondaryCB = renderTargetContext->wrapsVkSecondaryCB();
 
             GrDrawingManager* dm = context->contextPriv().drawingManager();
             pr = dm->getPathRenderer(canDrawArgs, false, GrPathRendererChain::DrawType::kStencil,
diff --git a/src/gpu/GrRenderTarget.cpp b/src/gpu/GrRenderTarget.cpp
index 5e827ec..e654648 100644
--- a/src/gpu/GrRenderTarget.cpp
+++ b/src/gpu/GrRenderTarget.cpp
@@ -25,7 +25,6 @@
         , fStencilAttachment(stencil) {
     SkASSERT(desc.fFlags & kRenderTarget_GrSurfaceFlag);
     SkASSERT(!this->hasMixedSamples() || fSampleCnt > 1);
-    SkASSERT(!this->supportsWindowRects() || gpu->caps()->maxWindowRectangles() > 0);
     fResolveRect = SkRectPriv::MakeILargestInverted();
 }
 
diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp
index cb3471a..ec40e48 100644
--- a/src/gpu/GrRenderTargetContext.cpp
+++ b/src/gpu/GrRenderTargetContext.cpp
@@ -49,11 +49,11 @@
 #include "ops/GrLatticeOp.h"
 #include "ops/GrOp.h"
 #include "ops/GrOvalOpFactory.h"
-#include "ops/GrRectOpFactory.h"
 #include "ops/GrRegionOp.h"
 #include "ops/GrSemaphoreOp.h"
 #include "ops/GrShadowRRectOp.h"
 #include "ops/GrStencilPathOp.h"
+#include "ops/GrStrokeRectOp.h"
 #include "ops/GrTextureOp.h"
 #include "text/GrTextContext.h"
 #include "text/GrTextTarget.h"
@@ -241,6 +241,13 @@
     SkDEBUGCODE(this->validate();)
     GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawGlyphRunList", fContext);
 
+    // Drawing text can cause us to do inline uploads. This is not supported for wrapped vulkan
+    // secondary command buffers because it would require stopping and starting a render pass which
+    // we don't have access to.
+    if (this->wrapsVkSecondaryCB()) {
+        return;
+    }
+
     GrTextContext* atlasTextContext = this->drawingManager()->getTextContext();
     atlasTextContext->drawGlyphRunList(fContext, fTextTarget.get(), clip, viewMatrix,
                                        fSurfaceProps, blob);
@@ -302,9 +309,9 @@
         GrPaint paint;
         paint.setColor4f(color);
         SkRect scissor = SkRect::Make(rtRect);
-        std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(fRenderTargetContext->fContext,
-                                                                    std::move(paint), SkMatrix::I(),
-                                                                    scissor, GrAAType::kNone));
+        std::unique_ptr<GrDrawOp> op(GrFillRectOp::Make(fRenderTargetContext->fContext,
+                                                        std::move(paint), GrAAType::kNone,
+                                                        SkMatrix::I(), scissor));
         if (!op) {
             return;
         }
@@ -351,9 +358,9 @@
             GrPaint paint;
             paint.setColor4f(color);
             SkRect scissor = SkRect::Make(clip.scissorRect());
-            std::unique_ptr<GrDrawOp> op(GrRectOpFactory::MakeNonAAFill(fContext, std::move(paint),
-                                                                        SkMatrix::I(), scissor,
-                                                                        GrAAType::kNone));
+            std::unique_ptr<GrDrawOp> op(GrFillRectOp::Make(fContext, std::move(paint),
+                                                            GrAAType::kNone, SkMatrix::I(),
+                                                            scissor));
             if (!op) {
                 return;
             }
@@ -451,8 +458,8 @@
 
         AutoCheckFlush acf(this->drawingManager());
 
-        std::unique_ptr<GrDrawOp> op = GrRectOpFactory::MakeNonAAFillWithLocalMatrix(
-                fContext, std::move(paint), SkMatrix::I(), localMatrix, r, GrAAType::kNone);
+        std::unique_ptr<GrDrawOp> op = GrFillRectOp::MakeWithLocalMatrix(
+                fContext, std::move(paint), GrAAType::kNone, SkMatrix::I(), localMatrix, r);
         this->addDrawOp(clip, std::move(op));
     }
 }
@@ -519,17 +526,8 @@
     }
 
     GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
-    std::unique_ptr<GrDrawOp> op;
-    if (GrAAType::kCoverage == aaType) {
-        op = GrRectOpFactory::MakeAAFill(fContext, std::move(paint), viewMatrix, croppedRect, ss);
-    } else {
-        op = GrRectOpFactory::MakeNonAAFill(fContext, std::move(paint), viewMatrix, croppedRect,
-                                            aaType, ss);
-    }
-    if (!op) {
-        return false;
-    }
-    this->addDrawOp(clip, std::move(op));
+    this->addDrawOp(clip, GrFillRectOp::Make(fContext, std::move(paint), aaType, viewMatrix,
+                                             croppedRect, ss));
     return true;
 }
 
@@ -620,22 +618,15 @@
         std::unique_ptr<GrDrawOp> op;
 
         GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
-        if (GrAAType::kCoverage == aaType) {
-            // The stroke path needs the rect to remain axis aligned (no rotation or skew).
-            if (viewMatrix.rectStaysRect()) {
-                op = GrRectOpFactory::MakeAAStroke(fContext, std::move(paint), viewMatrix, rect,
-                                                   stroke);
-            }
-        } else {
-            op = GrRectOpFactory::MakeNonAAStroke(fContext, std::move(paint), viewMatrix, rect,
-                                                  stroke, aaType);
-        }
-
+        op = GrStrokeRectOp::Make(fContext, std::move(paint), aaType, viewMatrix, rect, stroke);
+        // op may be null if the stroke is not supported or if using coverage aa and the view matrix
+        // does not preserve rectangles.
         if (op) {
             this->addDrawOp(clip, std::move(op));
             return;
         }
     }
+    assert_alive(paint);
     this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewMatrix, GrShape(rect, *style));
 }
 
@@ -730,9 +721,8 @@
 
     GrPaint paint;
     paint.setXPFactory(GrDisableColorXPFactory::Get());
-    std::unique_ptr<GrDrawOp> op = GrRectOpFactory::MakeNonAAFill(fRenderTargetContext->fContext,
-                                                                  std::move(paint), viewMatrix,
-                                                                  rect, aaType, ss);
+    std::unique_ptr<GrDrawOp> op = GrFillRectOp::Make(
+            fRenderTargetContext->fContext, std::move(paint), aaType, viewMatrix,  rect, ss);
     fRenderTargetContext->addDrawOp(clip, std::move(op));
 }
 
@@ -784,29 +774,8 @@
     AutoCheckFlush acf(this->drawingManager());
 
     GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
-    if (GrAAType::kCoverage != aaType) {
-        std::unique_ptr<GrDrawOp> op = GrRectOpFactory::MakeNonAAFillWithLocalRect(
-                fContext, std::move(paint), viewMatrix, croppedRect, croppedLocalRect, aaType);
-        this->addDrawOp(clip, std::move(op));
-        return;
-    }
-
-    std::unique_ptr<GrDrawOp> op = GrRectOpFactory::MakeAAFillWithLocalRect(
-            fContext, std::move(paint), viewMatrix, croppedRect, croppedLocalRect);
-    if (op) {
-        this->addDrawOp(clip, std::move(op));
-        return;
-    }
-
-    SkMatrix viewAndUnLocalMatrix;
-    if (!viewAndUnLocalMatrix.setRectToRect(localRect, rectToDraw, SkMatrix::kFill_ScaleToFit)) {
-        SkDebugf("fillRectToRect called with empty local matrix.\n");
-        return;
-    }
-    viewAndUnLocalMatrix.postConcat(viewMatrix);
-
-    this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewAndUnLocalMatrix,
-                                     GrShape(localRect));
+    this->addDrawOp(clip, GrFillRectOp::MakeWithLocalRect(fContext, std::move(paint), aaType,
+            viewMatrix, croppedRect, croppedLocalRect));
 }
 
 void GrRenderTargetContext::drawTexture(const GrClip& clip, sk_sp<GrTextureProxy> proxy,
@@ -871,33 +840,8 @@
     AutoCheckFlush acf(this->drawingManager());
 
     GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
-    if (GrAAType::kCoverage != aaType) {
-        std::unique_ptr<GrDrawOp> op = GrRectOpFactory::MakeNonAAFillWithLocalMatrix(
-                fContext, std::move(paint), viewMatrix, localMatrix, croppedRect, aaType);
-        this->addDrawOp(clip, std::move(op));
-        return;
-    }
-
-    std::unique_ptr<GrDrawOp> op = GrRectOpFactory::MakeAAFillWithLocalMatrix(
-            fContext, std::move(paint), viewMatrix, localMatrix, croppedRect);
-    if (op) {
-        this->addDrawOp(clip, std::move(op));
-        return;
-    }
-
-    SkMatrix viewAndUnLocalMatrix;
-    if (!localMatrix.invert(&viewAndUnLocalMatrix)) {
-        SkDebugf("fillRectWithLocalMatrix called with degenerate local matrix.\n");
-        return;
-    }
-    viewAndUnLocalMatrix.postConcat(viewMatrix);
-
-    SkPath path;
-    path.setIsVolatile(true);
-    path.addRect(rectToDraw);
-    path.transform(localMatrix);
-    this->drawShapeUsingPathRenderer(clip, std::move(paint), aa, viewAndUnLocalMatrix,
-                                     GrShape(path));
+    this->addDrawOp(clip, GrFillRectOp::MakeWithLocalMatrix(fContext, std::move(paint), aaType,
+                                                            viewMatrix, localMatrix, croppedRect));
 }
 
 void GrRenderTargetContext::drawVertices(const GrClip& clip,
@@ -987,6 +931,7 @@
                                        std::move(paint));
         }
         if (!op) {
+            assert_alive(paint);
             op = GrOvalOpFactory::MakeRRectOp(fContext, std::move(paint), viewMatrix, rrect, stroke,
                                               this->caps()->shaderCaps());
         }
@@ -997,6 +942,7 @@
         }
     }
 
+    assert_alive(paint);
     this->drawShapeUsingPathRenderer(*clip, std::move(paint), aa, viewMatrix,
                                      GrShape(rrect, style));
 }
@@ -1239,6 +1185,7 @@
                 this->addDrawOp(clip, std::move(op));
                 return true;
             }
+            assert_alive(paint);
         }
     }
 
@@ -1310,6 +1257,7 @@
     if (this->drawFilledDRRect(clip, std::move(paint), aa, viewMatrix, outer, inner)) {
         return;
     }
+    assert_alive(paint);
 
     SkPath path;
     path.setIsVolatile(true);
@@ -1397,6 +1345,7 @@
                                        std::move(paint));
         }
         if (!op) {
+            assert_alive(paint);
             op = GrOvalOpFactory::MakeOvalOp(fContext, std::move(paint), viewMatrix, oval, style,
                                              this->caps()->shaderCaps());
         }
@@ -1406,6 +1355,7 @@
         }
     }
 
+    assert_alive(paint);
     this->drawShapeUsingPathRenderer(
             clip, std::move(paint), aa, viewMatrix,
             GrShape(SkRRect::MakeOval(oval), SkPath::kCW_Direction, 2, false, style));
@@ -1443,6 +1393,7 @@
             this->addDrawOp(clip, std::move(op));
             return;
         }
+        assert_alive(paint);
     }
     this->drawShapeUsingPathRenderer(
             clip, std::move(paint), aa, viewMatrix,
@@ -1579,7 +1530,7 @@
             SkRect rects[2];
             if (shape.asNestedRects(rects)) {
                 // Concave AA paths are expensive - try to avoid them for special cases
-                std::unique_ptr<GrDrawOp> op = GrRectOpFactory::MakeAAFillNestedRects(
+                std::unique_ptr<GrDrawOp> op = GrStrokeRectOp::MakeNested(
                                 fContext, std::move(paint), viewMatrix, rects);
                 if (op) {
                     this->addDrawOp(clip, std::move(op));
@@ -1633,6 +1584,8 @@
     canDrawArgs.fShape = &shape;
     canDrawArgs.fClipConservativeBounds = &clipConservativeBounds;
     canDrawArgs.fAAType = aaType;
+    SkASSERT(!fRenderTargetContext->wrapsVkSecondaryCB());
+    canDrawArgs.fTargetIsWrappedVkSecondaryCB = false;
     canDrawArgs.fHasUserStencilSettings = hasUserStencilSettings;
 
     // Don't allow the SW renderer
@@ -1700,6 +1653,7 @@
     canDrawArgs.fViewMatrix = &viewMatrix;
     canDrawArgs.fShape = &originalShape;
     canDrawArgs.fClipConservativeBounds = &clipConservativeBounds;
+    canDrawArgs.fTargetIsWrappedVkSecondaryCB = this->wrapsVkSecondaryCB();
     canDrawArgs.fHasUserStencilSettings = false;
 
     GrPathRenderer* pr;
@@ -1833,6 +1787,13 @@
 bool GrRenderTargetContext::setupDstProxy(GrRenderTargetProxy* rtProxy, const GrClip& clip,
                                           const GrOp& op,
                                           GrXferProcessor::DstProxy* dstProxy) {
+    // If we are wrapping a vulkan secondary command buffer, we can't make a dst copy because we
+    // don't actually have a VkImage to make a copy of. Additionally we don't have the power to
+    // start and stop the render pass in order to make the copy.
+    if (rtProxy->wrapsVkSecondaryCB()) {
+        return false;
+    }
+
     if (this->caps()->textureBarrierSupport()) {
         if (GrTextureProxy* texProxy = rtProxy->asTextureProxy()) {
             // The render target is a texture, so we can read from it directly in the shader. The XP
diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h
index 4c73746..f66acc5 100644
--- a/src/gpu/GrRenderTargetContext.h
+++ b/src/gpu/GrRenderTargetContext.h
@@ -377,6 +377,7 @@
     int numStencilSamples() const { return fRenderTargetProxy->numStencilSamples(); }
     const SkSurfaceProps& surfaceProps() const { return fSurfaceProps; }
     GrSurfaceOrigin origin() const { return fRenderTargetProxy->origin(); }
+    bool wrapsVkSecondaryCB() const { return fRenderTargetProxy->wrapsVkSecondaryCB(); }
     GrMipMapped mipMapped() const;
 
     bool wasAbandoned() const;
diff --git a/src/gpu/GrRenderTargetOpList.cpp b/src/gpu/GrRenderTargetOpList.cpp
index 1eec9d8..b77f906 100644
--- a/src/gpu/GrRenderTargetOpList.cpp
+++ b/src/gpu/GrRenderTargetOpList.cpp
@@ -308,7 +308,7 @@
     std::tie(fList, chain) =
             TryConcat(std::move(fList), this->dstProxy(), fAppliedClip, std::move(chain), *dstProxy,
                       appliedClip, caps, pool, auditTrail);
-    if (!chain.empty()) {
+    if (!chain.empty()) {  // NOLINT(bugprone-use-after-move)
         // append failed, give the op back to the caller.
         this->validate();
         return chain.popHead();
@@ -523,9 +523,15 @@
     if (this->isEmpty() || !fTarget.get()->asRenderTargetProxy()->needsStencil()) {
         this->deleteOps();
         fDeferredProxies.reset();
-        fColorLoadOp = GrLoadOp::kClear;
-        fLoadClearColor = color;
-        return;
+
+        // If the opList is using a render target which wraps a vulkan command buffer, we can't do a
+        // clear load since we cannot change the render pass that we are using. Thus we fall back to
+        // making a clear op in this case.
+        if (!fTarget.get()->asRenderTargetProxy()->wrapsVkSecondaryCB()) {
+            fColorLoadOp = GrLoadOp::kClear;
+            fLoadClearColor = color;
+            return;
+        }
     }
 
     std::unique_ptr<GrClearOp> op(GrClearOp::Make(context, GrFixedClip::Disabled(),
diff --git a/src/gpu/GrRenderTargetProxy.cpp b/src/gpu/GrRenderTargetProxy.cpp
index 39fdee3..ee32309 100644
--- a/src/gpu/GrRenderTargetProxy.cpp
+++ b/src/gpu/GrRenderTargetProxy.cpp
@@ -25,15 +25,13 @@
                                          GrInternalSurfaceFlags surfaceFlags)
         : INHERITED(format, desc, origin, fit, budgeted, surfaceFlags)
         , fSampleCnt(desc.fSampleCnt)
-        , fNeedsStencil(false) {
+        , fNeedsStencil(false)
+        , fWrapsVkSecondaryCB(WrapsVkSecondaryCB::kNo) {
     // Since we know the newly created render target will be internal, we are able to precompute
     // what the flags will ultimately end up being.
     if (caps.usesMixedSamples() && fSampleCnt > 1) {
         this->setHasMixedSamples();
     }
-    if (caps.maxWindowRectangles() > 0) {
-        this->setSupportsWindowRects();
-    }
 }
 
 // Lazy-callback version
@@ -45,19 +43,22 @@
         : INHERITED(std::move(callback), lazyType, format, desc, origin, fit, budgeted,
                     surfaceFlags)
         , fSampleCnt(desc.fSampleCnt)
-        , fNeedsStencil(false) {
+        , fNeedsStencil(false)
+        , fWrapsVkSecondaryCB(WrapsVkSecondaryCB::kNo) {
     SkASSERT(SkToBool(kRenderTarget_GrSurfaceFlag & desc.fFlags));
 }
 
 // Wrapped version
-GrRenderTargetProxy::GrRenderTargetProxy(sk_sp<GrSurface> surf, GrSurfaceOrigin origin)
+GrRenderTargetProxy::GrRenderTargetProxy(sk_sp<GrSurface> surf, GrSurfaceOrigin origin,
+                                         WrapsVkSecondaryCB wrapsVkSecondaryCB)
         : INHERITED(std::move(surf), origin, SkBackingFit::kExact)
         , fSampleCnt(fTarget->asRenderTarget()->numStencilSamples())
-        , fNeedsStencil(false) {
+        , fNeedsStencil(false)
+        , fWrapsVkSecondaryCB(wrapsVkSecondaryCB) {
 }
 
 int GrRenderTargetProxy::maxWindowRectangles(const GrCaps& caps) const {
-    return this->supportsWindowRects() ? caps.maxWindowRectangles() : 0;
+    return this->glRTFBOIDIs0() ? 0 : caps.maxWindowRectangles();
 }
 
 bool GrRenderTargetProxy::instantiate(GrResourceProvider* resourceProvider) {
diff --git a/src/gpu/GrResourceAllocator.cpp b/src/gpu/GrResourceAllocator.cpp
index eab856f..0f70894 100644
--- a/src/gpu/GrResourceAllocator.cpp
+++ b/src/gpu/GrResourceAllocator.cpp
@@ -7,6 +7,7 @@
 
 #include "GrResourceAllocator.h"
 
+#include "GrDeinstantiateProxyTracker.h"
 #include "GrGpuResourcePriv.h"
 #include "GrOpList.h"
 #include "GrRenderTargetProxy.h"
@@ -16,17 +17,18 @@
 #include "GrSurfaceProxy.h"
 #include "GrSurfaceProxyPriv.h"
 #include "GrTextureProxy.h"
-#include "GrUninstantiateProxyTracker.h"
 
 #if GR_TRACK_INTERVAL_CREATION
-uint32_t GrResourceAllocator::Interval::CreateUniqueID() {
-    static int32_t gUniqueID = SK_InvalidUniqueID;
-    uint32_t id;
-    do {
-        id = static_cast<uint32_t>(sk_atomic_inc(&gUniqueID) + 1);
-    } while (id == SK_InvalidUniqueID);
-    return id;
-}
+    #include <atomic>
+
+    uint32_t GrResourceAllocator::Interval::CreateUniqueID() {
+        static std::atomic<uint32_t> nextID{1};
+        uint32_t id;
+        do {
+            id = nextID++;
+        } while (id == SK_InvalidUniqueID);
+        return id;
+    }
 #endif
 
 void GrResourceAllocator::Interval::assign(sk_sp<GrSurface> s) {
@@ -58,45 +60,57 @@
     SkASSERT(start <= end);
     SkASSERT(!fAssigned);      // We shouldn't be adding any intervals after (or during) assignment
 
-    if (Interval* intvl = fIntvlHash.find(proxy->uniqueID().asUInt())) {
-        // Revise the interval for an existing use
-#ifdef SK_DEBUG
-        if (0 == start && 0 == end) {
-            // This interval is for the initial upload to a deferred proxy. Due to the vagaries
-            // of how deferred proxies are collected they can appear as uploads multiple times in a
-            // single opLists' list and as uploads in several opLists.
-            SkASSERT(0 == intvl->start());
-        } else if (isDirectDstRead) {
-            // Direct reads from the render target itself should occur w/in the existing interval
-            SkASSERT(intvl->start() <= start && intvl->end() >= end);
-        } else {
-            SkASSERT(intvl->end() <= start && intvl->end() <= end);
-        }
-#endif
-        intvl->extendEnd(end);
-        return;
-    }
-
-    Interval* newIntvl;
-    if (fFreeIntervalList) {
-        newIntvl = fFreeIntervalList;
-        fFreeIntervalList = newIntvl->next();
-        newIntvl->setNext(nullptr);
-        newIntvl->resetTo(proxy, start, end);
+    // If a proxy is read only it must refer to a texture with specific content that cannot be
+    // recycled. We don't need to assign a texture to it and no other proxy can be instantiated
+    // with the same texture.
+    if (proxy->readOnly()) {
+        // Since we aren't going to add an interval we won't revisit this proxy in assign(). So it
+        // must already be instantiated or it must be a lazy proxy that we will instantiate below.
+        SkASSERT(proxy->isInstantiated() ||
+                 GrSurfaceProxy::LazyState::kNot != proxy->lazyInstantiationState());
     } else {
-        newIntvl = fIntervalAllocator.make<Interval>(proxy, start, end);
+        if (Interval* intvl = fIntvlHash.find(proxy->uniqueID().asUInt())) {
+            // Revise the interval for an existing use
+#ifdef SK_DEBUG
+            if (0 == start && 0 == end) {
+                // This interval is for the initial upload to a deferred proxy. Due to the vagaries
+                // of how deferred proxies are collected they can appear as uploads multiple times
+                // in a single opLists' list and as uploads in several opLists.
+                SkASSERT(0 == intvl->start());
+            } else if (isDirectDstRead) {
+                // Direct reads from the render target itself should occur w/in the existing
+                // interval
+                SkASSERT(intvl->start() <= start && intvl->end() >= end);
+            } else {
+                SkASSERT(intvl->end() <= start && intvl->end() <= end);
+            }
+#endif
+            intvl->extendEnd(end);
+            return;
+        }
+        Interval* newIntvl;
+        if (fFreeIntervalList) {
+            newIntvl = fFreeIntervalList;
+            fFreeIntervalList = newIntvl->next();
+            newIntvl->setNext(nullptr);
+            newIntvl->resetTo(proxy, start, end);
+        } else {
+            newIntvl = fIntervalAllocator.make<Interval>(proxy, start, end);
+        }
+
+        fIntvlList.insertByIncreasingStart(newIntvl);
+        fIntvlHash.add(newIntvl);
     }
 
-    fIntvlList.insertByIncreasingStart(newIntvl);
-    fIntvlHash.add(newIntvl);
-
-    if (!fResourceProvider->explicitlyAllocateGPUResources()) {
+    // Because readOnly proxies do not get a usage interval we must instantiate them here (since it
+    // won't occur in GrResourceAllocator::assign)
+    if (proxy->readOnly() || !fResourceProvider->explicitlyAllocateGPUResources()) {
         // FIXME: remove this once we can do the lazy instantiation from assign instead.
         if (GrSurfaceProxy::LazyState::kNot != proxy->lazyInstantiationState()) {
             if (proxy->priv().doLazyInstantiation(fResourceProvider)) {
                 if (proxy->priv().lazyInstantiationType() ==
-                    GrSurfaceProxy::LazyInstantiationType::kUninstantiate) {
-                    fUninstantiateTracker->addProxy(proxy);
+                    GrSurfaceProxy::LazyInstantiationType::kDeinstantiate) {
+                    fDeinstantiateTracker->addProxy(proxy);
                 }
             }
         }
@@ -362,9 +376,9 @@
             if (!cur->proxy()->priv().doLazyInstantiation(fResourceProvider)) {
                 *outError = AssignError::kFailedProxyInstantiation;
             } else {
-                if (GrSurfaceProxy::LazyInstantiationType::kUninstantiate ==
+                if (GrSurfaceProxy::LazyInstantiationType::kDeinstantiate ==
                     cur->proxy()->priv().lazyInstantiationType()) {
-                    fUninstantiateTracker->addProxy(cur->proxy());
+                    fDeinstantiateTracker->addProxy(cur->proxy());
                 }
             }
         } else if (sk_sp<GrSurface> surface = this->findSurfaceFor(cur->proxy(), needsStencil)) {
diff --git a/src/gpu/GrResourceAllocator.h b/src/gpu/GrResourceAllocator.h
index c4d2343..ea1250f 100644
--- a/src/gpu/GrResourceAllocator.h
+++ b/src/gpu/GrResourceAllocator.h
@@ -16,8 +16,8 @@
 #include "SkTDynamicHash.h"
 #include "SkTMultiMap.h"
 
+class GrDeinstantiateProxyTracker;
 class GrResourceProvider;
-class GrUninstantiateProxyTracker;
 
 // Print out explicit allocation information
 #define GR_ALLOCATION_SPEW 0
@@ -42,8 +42,8 @@
  */
 class GrResourceAllocator {
 public:
-    GrResourceAllocator(GrResourceProvider* resourceProvider, GrUninstantiateProxyTracker* tracker)
-            : fResourceProvider(resourceProvider), fUninstantiateTracker(tracker) {}
+    GrResourceAllocator(GrResourceProvider* resourceProvider, GrDeinstantiateProxyTracker* tracker)
+            : fResourceProvider(resourceProvider), fDeinstantiateTracker(tracker) {}
 
     ~GrResourceAllocator();
 
@@ -212,7 +212,7 @@
     static const int kInitialArenaSize = 128 * sizeof(Interval);
 
     GrResourceProvider*          fResourceProvider;
-    GrUninstantiateProxyTracker* fUninstantiateTracker;
+    GrDeinstantiateProxyTracker* fDeinstantiateTracker;
     FreePoolMultiMap             fFreePool;          // Recently created/used GrSurfaces
     IntvlHash                    fIntvlHash;         // All the intervals, hashed by proxyID
 
diff --git a/src/gpu/GrResourceCache.cpp b/src/gpu/GrResourceCache.cpp
index 817d188..53dfe9b 100644
--- a/src/gpu/GrResourceCache.cpp
+++ b/src/gpu/GrResourceCache.cpp
@@ -6,10 +6,11 @@
  */
 
 #include "GrResourceCache.h"
+#include <atomic>
 #include "GrCaps.h"
-#include "GrSingleOwner.h"
 #include "GrGpuResourceCacheAccess.h"
 #include "GrProxyProvider.h"
+#include "GrSingleOwner.h"
 #include "GrTexture.h"
 #include "GrTextureProxyCacheAccess.h"
 #include "GrTracing.h"
@@ -17,6 +18,7 @@
 #include "SkMessageBus.h"
 #include "SkOpts.h"
 #include "SkRandom.h"
+#include "SkScopeExit.h"
 #include "SkTSort.h"
 #include "SkTo.h"
 
@@ -30,9 +32,9 @@
 //////////////////////////////////////////////////////////////////////////////
 
 GrScratchKey::ResourceType GrScratchKey::GenerateResourceType() {
-    static int32_t gType = INHERITED::kInvalidDomain + 1;
+    static std::atomic<int32_t> nextType{INHERITED::kInvalidDomain + 1};
 
-    int32_t type = sk_atomic_inc(&gType);
+    int32_t type = nextType++;
     if (type > SkTo<int32_t>(UINT16_MAX)) {
         SK_ABORT("Too many Resource Types");
     }
@@ -41,9 +43,9 @@
 }
 
 GrUniqueKey::Domain GrUniqueKey::GenerateDomain() {
-    static int32_t gDomain = INHERITED::kInvalidDomain + 1;
+    static std::atomic<int32_t> nextDomain{INHERITED::kInvalidDomain + 1};
 
-    int32_t domain = sk_atomic_inc(&gDomain);
+    int32_t domain = nextDomain++;
     if (domain > SkTo<int32_t>(UINT16_MAX)) {
         SK_ABORT("Too many GrUniqueKey Domains");
     }
@@ -108,7 +110,7 @@
     SkASSERT(resource);
     SkASSERT(!this->isInCache(resource));
     SkASSERT(!resource->wasDestroyed());
-    SkASSERT(!resource->isPurgeable());
+    SkASSERT(!resource->resourcePriv().isPurgeable());
 
     // We must set the timestamp before adding to the array in case the timestamp wraps and we wind
     // up iterating over all the resources that already have timestamps.
@@ -148,7 +150,7 @@
     SkASSERT(this->isInCache(resource));
 
     size_t size = resource->gpuMemorySize();
-    if (resource->isPurgeable()) {
+    if (resource->resourcePriv().isPurgeable()) {
         fPurgeableQueue.remove(resource);
         fPurgeableBytes -= size;
     } else {
@@ -185,6 +187,9 @@
     while (fNonpurgeableResources.count()) {
         GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
         SkASSERT(!back->wasDestroyed());
+        // If these resources we're relying on a purgeable notification to release something, notify
+        // them now. They aren't in the purgeable queue but they're getting purged anyway.
+        back->cacheAccess().becamePurgeable();
         back->cacheAccess().abandon();
     }
 
@@ -222,9 +227,12 @@
     // they also have a raw pointer back to this class (which is presumably going away)!
     fProxyProvider->removeAllUniqueKeys();
 
-    while(fNonpurgeableResources.count()) {
+    while (fNonpurgeableResources.count()) {
         GrGpuResource* back = *(fNonpurgeableResources.end() - 1);
         SkASSERT(!back->wasDestroyed());
+        // If these resources we're relying on a purgeable notification to release something, notify
+        // them now. They aren't in the purgeable queue but they're getting purged anyway.
+        back->cacheAccess().becamePurgeable();
         back->cacheAccess().release();
     }
 
@@ -328,7 +336,8 @@
     if (newKey.isValid()) {
         if (GrGpuResource* old = fUniqueHash.find(newKey)) {
             // If the old resource using the key is purgeable and is unreachable, then remove it.
-            if (!old->resourcePriv().getScratchKey().isValid() && old->isPurgeable()) {
+            if (!old->resourcePriv().getScratchKey().isValid() &&
+                old->resourcePriv().isPurgeable()) {
                 old->cacheAccess().release();
             } else {
                 this->removeUniqueKey(old);
@@ -363,7 +372,7 @@
     SkASSERT(resource);
     SkASSERT(this->isInCache(resource));
 
-    if (resource->isPurgeable()) {
+    if (resource->resourcePriv().isPurgeable()) {
         // It's about to become unpurgeable.
         fPurgeableBytes -= resource->gpuMemorySize();
         fPurgeableQueue.remove(resource);
@@ -390,7 +399,7 @@
         // When the timestamp overflows validate() is called. validate() checks that resources in
         // the nonpurgeable array are indeed not purgeable. However, the movement from the array to
         // the purgeable queue happens just below in this function. So we mark it as an exception.
-        if (resource->isPurgeable()) {
+        if (resource->resourcePriv().isPurgeable()) {
             fNewlyPurgeableResourceForValidation = resource;
         }
 #endif
@@ -399,11 +408,11 @@
     }
 
     if (!SkToBool(ResourceAccess::kAllCntsReachedZero_RefNotificationFlag & flags)) {
-        SkASSERT(!resource->isPurgeable());
+        SkASSERT(!resource->resourcePriv().isPurgeable());
         return;
     }
 
-    SkASSERT(resource->isPurgeable());
+    SkASSERT(resource->resourcePriv().isPurgeable());
     this->removeFromNonpurgeableArray(resource);
     fPurgeableQueue.insert(resource);
     resource->cacheAccess().setTimeWhenResourceBecomePurgeable();
@@ -411,32 +420,34 @@
 
     bool hasUniqueKey = resource->getUniqueKey().isValid();
 
-    if (SkBudgeted::kNo == resource->resourcePriv().isBudgeted()) {
-        // We keep unbudgeted resources with a unique key in the purgable queue of the cache so they
-        // can be reused again by the image connected to the unique key.
-        if (hasUniqueKey && !resource->cacheAccess().shouldPurgeImmediately()) {
-            return;
-        }
-        // Check whether this resource could still be used as a scratch resource.
-        if (!resource->resourcePriv().refsWrappedObjects() &&
-            resource->resourcePriv().getScratchKey().isValid()) {
-            // We won't purge an existing resource to make room for this one.
-            if (fBudgetedCount < fMaxCount &&
-                fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes) {
-                resource->resourcePriv().makeBudgeted();
+    {
+        SkScopeExit notifyPurgeable([resource] { resource->cacheAccess().becamePurgeable(); });
+
+        if (SkBudgeted::kNo == resource->resourcePriv().isBudgeted()) {
+            // We keep unbudgeted resources with a unique key in the purgable queue of the cache so
+            // they can be reused again by the image connected to the unique key.
+            if (hasUniqueKey && !resource->cacheAccess().shouldPurgeImmediately()) {
+                return;
+            }
+            // Check whether this resource could still be used as a scratch resource.
+            if (!resource->resourcePriv().refsWrappedObjects() &&
+                resource->resourcePriv().getScratchKey().isValid()) {
+                // We won't purge an existing resource to make room for this one.
+                if (fBudgetedCount < fMaxCount &&
+                    fBudgetedBytes + resource->gpuMemorySize() <= fMaxBytes) {
+                    resource->resourcePriv().makeBudgeted();
+                    return;
+                }
+            }
+        } else {
+            // Purge the resource immediately if we're over budget
+            // Also purge if the resource has neither a valid scratch key nor a unique key.
+            bool hasKey = resource->resourcePriv().getScratchKey().isValid() || hasUniqueKey;
+            if (!this->overBudget() && hasKey) {
                 return;
             }
         }
-    } else {
-        // Purge the resource immediately if we're over budget
-        // Also purge if the resource has neither a valid scratch key nor a unique key.
-        bool hasKey = resource->resourcePriv().getScratchKey().isValid() ||
-                      hasUniqueKey;
-        if (!this->overBudget() && hasKey) {
-            return;
-        }
     }
-
     SkDEBUGCODE(int beforeCount = this->getResourceCount();)
     resource->cacheAccess().release();
     // We should at least free this resource, perhaps dependent resources as well.
@@ -473,7 +484,13 @@
     SkTArray<GrUniqueKeyInvalidatedMessage> invalidKeyMsgs;
     fInvalidUniqueKeyInbox.poll(&invalidKeyMsgs);
     if (invalidKeyMsgs.count()) {
-        this->processInvalidUniqueKeys(invalidKeyMsgs);
+        SkASSERT(fProxyProvider);
+
+        for (int i = 0; i < invalidKeyMsgs.count(); ++i) {
+            fProxyProvider->processInvalidUniqueKey(invalidKeyMsgs[i].key(), nullptr,
+                                                    GrProxyProvider::InvalidateGPUResource::kYes);
+            SkASSERT(!this->findAndRefUniqueResource(invalidKeyMsgs[i].key()));
+        }
     }
 
     this->processFreedGpuResources();
@@ -481,7 +498,7 @@
     bool stillOverbudget = this->overBudget();
     while (stillOverbudget && fPurgeableQueue.count()) {
         GrGpuResource* resource = fPurgeableQueue.peek();
-        SkASSERT(resource->isPurgeable());
+        SkASSERT(resource->resourcePriv().isPurgeable());
         resource->cacheAccess().release();
         stillOverbudget = this->overBudget();
     }
@@ -495,7 +512,7 @@
         // complexity. Moreover, this is rarely called.
         while (fPurgeableQueue.count()) {
             GrGpuResource* resource = fPurgeableQueue.peek();
-            SkASSERT(resource->isPurgeable());
+            SkASSERT(resource->resourcePriv().isPurgeable());
             resource->cacheAccess().release();
         }
     } else {
@@ -506,7 +523,7 @@
         SkTDArray<GrGpuResource*> scratchResources;
         for (int i = 0; i < fPurgeableQueue.count(); i++) {
             GrGpuResource* resource = fPurgeableQueue.at(i);
-            SkASSERT(resource->isPurgeable());
+            SkASSERT(resource->resourcePriv().isPurgeable());
             if (!resource->getUniqueKey().isValid()) {
                 *scratchResources.append() = resource;
             }
@@ -535,7 +552,7 @@
             break;
         }
         GrGpuResource* resource = fPurgeableQueue.peek();
-        SkASSERT(resource->isPurgeable());
+        SkASSERT(resource->resourcePriv().isPurgeable());
         resource->cacheAccess().release();
     }
 }
@@ -554,7 +571,7 @@
         size_t scratchByteCount = 0;
         for (int i = 0; i < fPurgeableQueue.count() && stillOverbudget; i++) {
             GrGpuResource* resource = fPurgeableQueue.at(i);
-            SkASSERT(resource->isPurgeable());
+            SkASSERT(resource->resourcePriv().isPurgeable());
             if (!resource->getUniqueKey().isValid()) {
                 *scratchResources.append() = resource;
                 scratchByteCount += resource->gpuMemorySize();
@@ -581,21 +598,6 @@
     }
 }
 
-void GrResourceCache::processInvalidUniqueKeys(
-                                            const SkTArray<GrUniqueKeyInvalidatedMessage>& msgs) {
-    SkASSERT(fProxyProvider); // better have called setProxyProvider
-
-    for (int i = 0; i < msgs.count(); ++i) {
-        fProxyProvider->processInvalidProxyUniqueKey(msgs[i].key());
-
-        GrGpuResource* resource = this->findAndRefUniqueResource(msgs[i].key());
-        if (resource) {
-            resource->resourcePriv().removeUniqueKey();
-            resource->unref(); // If this resource is now purgeable, the cache will be notified.
-        }
-    }
-}
-
 void GrResourceCache::insertCrossContextGpuResource(GrGpuResource* resource) {
     resource->ref();
     SkASSERT(!fResourcesWaitingForFreeMsg.contains(resource));
@@ -737,7 +739,7 @@
         void update(GrGpuResource* resource) {
             fBytes += resource->gpuMemorySize();
 
-            if (!resource->isPurgeable()) {
+            if (!resource->resourcePriv().isPurgeable()) {
                 ++fLocked;
             }
 
@@ -793,14 +795,14 @@
     size_t purgeableBytes = 0;
 
     for (int i = 0; i < fNonpurgeableResources.count(); ++i) {
-        SkASSERT(!fNonpurgeableResources[i]->isPurgeable() ||
+        SkASSERT(!fNonpurgeableResources[i]->resourcePriv().isPurgeable() ||
                  fNewlyPurgeableResourceForValidation == fNonpurgeableResources[i]);
         SkASSERT(*fNonpurgeableResources[i]->cacheAccess().accessCacheIndex() == i);
         SkASSERT(!fNonpurgeableResources[i]->wasDestroyed());
         stats.update(fNonpurgeableResources[i]);
     }
     for (int i = 0; i < fPurgeableQueue.count(); ++i) {
-        SkASSERT(fPurgeableQueue.at(i)->isPurgeable());
+        SkASSERT(fPurgeableQueue.at(i)->resourcePriv().isPurgeable());
         SkASSERT(*fPurgeableQueue.at(i)->cacheAccess().accessCacheIndex() == i);
         SkASSERT(!fPurgeableQueue.at(i)->wasDestroyed());
         stats.update(fPurgeableQueue.at(i));
diff --git a/src/gpu/GrResourceCache.h b/src/gpu/GrResourceCache.h
index 50f26bf..7730ebd 100644
--- a/src/gpu/GrResourceCache.h
+++ b/src/gpu/GrResourceCache.h
@@ -264,7 +264,6 @@
     void refAndMakeResourceMRU(GrGpuResource*);
     /// @}
 
-    void processInvalidUniqueKeys(const SkTArray<GrUniqueKeyInvalidatedMessage>&);
     void processFreedGpuResources();
     void addToNonpurgeableArray(GrGpuResource*);
     void removeFromNonpurgeableArray(GrGpuResource*);
diff --git a/src/gpu/GrResourceProvider.cpp b/src/gpu/GrResourceProvider.cpp
index 78bd97b..6e821db 100644
--- a/src/gpu/GrResourceProvider.cpp
+++ b/src/gpu/GrResourceProvider.cpp
@@ -226,12 +226,13 @@
 
 sk_sp<GrTexture> GrResourceProvider::wrapBackendTexture(const GrBackendTexture& tex,
                                                         GrWrapOwnership ownership,
+                                                        GrIOType ioType,
                                                         bool purgeImmediately) {
     ASSERT_SINGLE_OWNER
     if (this->isAbandoned()) {
         return nullptr;
     }
-    return fGpu->wrapBackendTexture(tex, ownership, purgeImmediately);
+    return fGpu->wrapBackendTexture(tex, ownership, ioType, purgeImmediately);
 }
 
 sk_sp<GrTexture> GrResourceProvider::wrapRenderableBackendTexture(const GrBackendTexture& tex,
@@ -251,6 +252,14 @@
     return this->isAbandoned() ? nullptr : fGpu->wrapBackendRenderTarget(backendRT);
 }
 
+sk_sp<GrRenderTarget> GrResourceProvider::wrapVulkanSecondaryCBAsRenderTarget(
+        const SkImageInfo& imageInfo, const GrVkDrawableInfo& vkInfo) {
+    ASSERT_SINGLE_OWNER
+    return this->isAbandoned() ? nullptr : fGpu->wrapVulkanSecondaryCBAsRenderTarget(imageInfo,
+                                                                                     vkInfo);
+
+}
+
 void GrResourceProvider::assignUniqueKeyToResource(const GrUniqueKey& key,
                                                    GrGpuResource* resource) {
     ASSERT_SINGLE_OWNER
diff --git a/src/gpu/GrResourceProvider.h b/src/gpu/GrResourceProvider.h
index 74e1dce..fe1bdd3 100644
--- a/src/gpu/GrResourceProvider.h
+++ b/src/gpu/GrResourceProvider.h
@@ -25,6 +25,7 @@
 class GrSingleOwner;
 class GrStencilAttachment;
 class GrTexture;
+struct GrVkDrawableInfo;
 
 class GrStyle;
 class SkDescriptor;
@@ -98,13 +99,17 @@
     /**
      * Wraps an existing texture with a GrTexture object.
      *
+     * GrIOType must either be kRead or kRW. kRead blocks any operations that would modify the
+     * pixels (e.g. dst for a copy, regenerating MIP levels, write pixels).
+     *
      * OpenGL: if the object is a texture Gr may change its GL texture params
      *         when it is drawn.
      *
      * @return GrTexture object or NULL on failure.
      */
     sk_sp<GrTexture> wrapBackendTexture(const GrBackendTexture& tex,
-                                        GrWrapOwnership = kBorrow_GrWrapOwnership,
+                                        GrWrapOwnership /* = kBorrow_GrWrapOwnership*/,
+                                        GrIOType,
                                         bool purgeImmediately = false);
 
     /**
@@ -127,6 +132,9 @@
      */
     sk_sp<GrRenderTarget> wrapBackendRenderTarget(const GrBackendRenderTarget&);
 
+    sk_sp<GrRenderTarget> wrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&,
+                                                              const GrVkDrawableInfo&);
+
     static const uint32_t kMinScratchTextureSize;
 
     /**
diff --git a/src/gpu/GrSWMaskHelper.cpp b/src/gpu/GrSWMaskHelper.cpp
index 8551665..41249be 100644
--- a/src/gpu/GrSWMaskHelper.cpp
+++ b/src/gpu/GrSWMaskHelper.cpp
@@ -113,7 +113,16 @@
         // have pending IO.
         surfaceFlags |= GrInternalSurfaceFlags::kNoPendingIO;
     }
-
+    auto clearFlag = kNone_GrSurfaceFlags;
+    // In a WASM build on Firefox, we see warnings like
+    // WebGL warning: texSubImage2D: This operation requires zeroing texture data. This is slow.
+    // WebGL warning: texSubImage2D: Texture has not been initialized prior to a partial upload,
+    //                forcing the browser to clear it. This may be slow.
+    // Setting the initial clear seems to make those warnings go away and offers a substantial
+    // boost in performance in Firefox. Chrome sees a more modest increase.
+#if IS_WEBGL==1
+    clearFlag = kPerformInitialClear_GrSurfaceFlag;
+#endif
     return context->contextPriv().proxyProvider()->createTextureProxy(
-            std::move(img), kNone_GrSurfaceFlags, 1, SkBudgeted::kYes, fit, surfaceFlags);
+            std::move(img), clearFlag, 1, SkBudgeted::kYes, fit, surfaceFlags);
 }
diff --git a/src/gpu/GrSoftwarePathRenderer.cpp b/src/gpu/GrSoftwarePathRenderer.cpp
index 68e51c9..e7b070e 100644
--- a/src/gpu/GrSoftwarePathRenderer.cpp
+++ b/src/gpu/GrSoftwarePathRenderer.cpp
@@ -22,7 +22,7 @@
 #include "SkTaskGroup.h"
 #include "SkTraceEvent.h"
 #include "ops/GrDrawOp.h"
-#include "ops/GrRectOpFactory.h"
+#include "ops/GrFillRectOp.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 GrPathRenderer::CanDrawPath
@@ -100,9 +100,9 @@
                                            const SkMatrix& localMatrix) {
     GrContext* context = renderTargetContext->surfPriv().getContext();
     renderTargetContext->addDrawOp(clip,
-                                   GrRectOpFactory::MakeNonAAFillWithLocalMatrix(
-                                           context, std::move(paint), viewMatrix, localMatrix, rect,
-                                           GrAAType::kNone, &userStencilSettings));
+                                   GrFillRectOp::MakeWithLocalMatrix(
+                                           context, std::move(paint), GrAAType::kNone, viewMatrix,
+                                           localMatrix, rect, &userStencilSettings));
 }
 
 void GrSoftwarePathRenderer::DrawAroundInvPath(GrRenderTargetContext* renderTargetContext,
diff --git a/src/gpu/GrSurfaceProxy.cpp b/src/gpu/GrSurfaceProxy.cpp
index ac650af..61fb25d 100644
--- a/src/gpu/GrSurfaceProxy.cpp
+++ b/src/gpu/GrSurfaceProxy.cpp
@@ -222,8 +222,8 @@
                                      GrMipMapped mipMapped, const GrUniqueKey* uniqueKey) {
     SkASSERT(LazyState::kNot == this->lazyInstantiationState());
     if (fTarget) {
-        if (uniqueKey) {
-            SkASSERT(fTarget->getUniqueKey() == *uniqueKey);
+        if (uniqueKey && uniqueKey->isValid()) {
+            SkASSERT(fTarget->getUniqueKey().isValid() && fTarget->getUniqueKey() == *uniqueKey);
         }
         return GrSurfaceProxyPriv::AttachStencilIfNeeded(resourceProvider, fTarget, needsStencil);
     }
@@ -245,13 +245,12 @@
     return true;
 }
 
-void GrSurfaceProxy::deInstantiate() {
+void GrSurfaceProxy::deinstantiate() {
     SkASSERT(this->isInstantiated());
 
     this->release();
 }
 
-
 void GrSurfaceProxy::computeScratchKey(GrScratchKey* key) const {
     SkASSERT(LazyState::kFully != this->lazyInstantiationState());
     const GrRenderTargetProxy* rtp = this->asRenderTargetProxy();
@@ -330,6 +329,7 @@
                                            GrSurfaceProxy* src,
                                            GrMipMapped mipMapped,
                                            SkIRect srcRect,
+                                           SkBackingFit fit,
                                            SkBudgeted budgeted) {
     SkASSERT(LazyState::kFully != src->lazyInstantiationState());
     if (!srcRect.intersect(SkIRect::MakeWH(src->width(), src->height()))) {
@@ -347,7 +347,7 @@
     }
 
     sk_sp<GrSurfaceContext> dstContext(context->contextPriv().makeDeferredSurfaceContext(
-            format, dstDesc, src->origin(), mipMapped, SkBackingFit::kExact, budgeted));
+            format, dstDesc, src->origin(), mipMapped, fit, budgeted));
     if (!dstContext) {
         return nullptr;
     }
@@ -360,9 +360,11 @@
 }
 
 sk_sp<GrTextureProxy> GrSurfaceProxy::Copy(GrContext* context, GrSurfaceProxy* src,
-                                           GrMipMapped mipMapped, SkBudgeted budgeted) {
+                                           GrMipMapped mipMapped, SkBackingFit fit,
+                                           SkBudgeted budgeted) {
     SkASSERT(LazyState::kFully != src->lazyInstantiationState());
-    return Copy(context, src, mipMapped, SkIRect::MakeWH(src->width(), src->height()), budgeted);
+    return Copy(context, src, mipMapped, SkIRect::MakeWH(src->width(), src->height()), fit,
+                budgeted);
 }
 
 sk_sp<GrSurfaceContext> GrSurfaceProxy::TestCopy(GrContext* context, const GrSurfaceDesc& dstDesc,
diff --git a/src/gpu/GrSurfaceProxyPriv.h b/src/gpu/GrSurfaceProxyPriv.h
index ecddf58..a9076b8 100644
--- a/src/gpu/GrSurfaceProxyPriv.h
+++ b/src/gpu/GrSurfaceProxyPriv.h
@@ -60,10 +60,10 @@
         return fProxy->fLazyInstantiationType;
     }
 
-    bool isSafeToUninstantiate() const {
+    bool isSafeToDeinstantiate() const {
         return SkToBool(fProxy->fTarget) &&
                SkToBool(fProxy->fLazyInstantiateCallback) &&
-               GrSurfaceProxy::LazyInstantiationType::kUninstantiate == lazyInstantiationType();
+               GrSurfaceProxy::LazyInstantiationType::kDeinstantiate == lazyInstantiationType();
     }
 
     static bool SK_WARN_UNUSED_RESULT AttachStencilIfNeeded(GrResourceProvider*, GrSurface*,
diff --git a/src/gpu/GrSwizzle.h b/src/gpu/GrSwizzle.h
index 51e01ef..109c3a6 100644
--- a/src/gpu/GrSwizzle.h
+++ b/src/gpu/GrSwizzle.h
@@ -80,6 +80,7 @@
     static constexpr GrSwizzle RRRR() { return GrSwizzle("rrrr"); }
     static constexpr GrSwizzle RRRA() { return GrSwizzle("rrra"); }
     static constexpr GrSwizzle BGRA() { return GrSwizzle("bgra"); }
+    static constexpr GrSwizzle RGRG() { return GrSwizzle("rgrg"); }
 
 private:
     char fSwiz[5];
diff --git a/src/gpu/GrTessellator.cpp b/src/gpu/GrTessellator.cpp
index 19b859e..b753707 100644
--- a/src/gpu/GrTessellator.cpp
+++ b/src/gpu/GrTessellator.cpp
@@ -175,10 +175,17 @@
 
 /***************************************************************************************/
 
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
 struct AAParams {
     bool fTweakAlpha;
     GrColor fColor;
 };
+#define AA_PARAM const AAParams* aaParams
+#define AA_ARG   aaParams
+#else
+#define AA_PARAM bool emitCoverage
+#define AA_ARG   emitCoverage
+#endif
 
 typedef bool (*CompareFunc)(const SkPoint& a, const SkPoint& b);
 
@@ -199,10 +206,11 @@
     Direction fDirection;
 };
 
-inline void* emit_vertex(Vertex* v, const AAParams* aaParams, void* data) {
+inline void* emit_vertex(Vertex* v, AA_PARAM, void* data) {
     GrVertexWriter verts{data};
     verts.write(v->fPoint);
 
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
     if (aaParams) {
         if (aaParams->fTweakAlpha) {
             verts.write(SkAlphaMulQ(aaParams->fColor, SkAlpha255To256(v->fAlpha)));
@@ -210,24 +218,29 @@
             verts.write(aaParams->fColor, GrNormalizeByteToFloat(v->fAlpha));
         }
     }
+#else
+    if (emitCoverage) {
+        verts.write(GrNormalizeByteToFloat(v->fAlpha));
+    }
+#endif
     return verts.fPtr;
 }
 
-void* emit_triangle(Vertex* v0, Vertex* v1, Vertex* v2, const AAParams* aaParams, void* data) {
+void* emit_triangle(Vertex* v0, Vertex* v1, Vertex* v2, AA_PARAM, void* data) {
     LOG("emit_triangle %g (%g, %g) %d\n", v0->fID, v0->fPoint.fX, v0->fPoint.fY, v0->fAlpha);
     LOG("              %g (%g, %g) %d\n", v1->fID, v1->fPoint.fX, v1->fPoint.fY, v1->fAlpha);
     LOG("              %g (%g, %g) %d\n", v2->fID, v2->fPoint.fX, v2->fPoint.fY, v2->fAlpha);
 #if TESSELLATOR_WIREFRAME
-    data = emit_vertex(v0, aaParams, data);
-    data = emit_vertex(v1, aaParams, data);
-    data = emit_vertex(v1, aaParams, data);
-    data = emit_vertex(v2, aaParams, data);
-    data = emit_vertex(v2, aaParams, data);
-    data = emit_vertex(v0, aaParams, data);
+    data = emit_vertex(v0, AA_ARG, data);
+    data = emit_vertex(v1, AA_ARG, data);
+    data = emit_vertex(v1, AA_ARG, data);
+    data = emit_vertex(v2, AA_ARG, data);
+    data = emit_vertex(v2, AA_ARG, data);
+    data = emit_vertex(v0, AA_ARG, data);
 #else
-    data = emit_vertex(v0, aaParams, data);
-    data = emit_vertex(v1, aaParams, data);
-    data = emit_vertex(v2, aaParams, data);
+    data = emit_vertex(v0, AA_ARG, data);
+    data = emit_vertex(v1, AA_ARG, data);
+    data = emit_vertex(v2, AA_ARG, data);
 #endif
     return data;
 }
@@ -553,7 +566,7 @@
             }
         }
 
-        void* emit(const AAParams* aaParams, void* data) {
+        void* emit(AA_PARAM, void* data) {
             Edge* e = fFirstEdge;
             VertexList vertices;
             vertices.append(e->fTop);
@@ -576,14 +589,14 @@
                 Vertex* curr = v;
                 Vertex* next = v->fNext;
                 if (count == 3) {
-                    return emit_triangle(prev, curr, next, aaParams, data);
+                    return emit_triangle(prev, curr, next, AA_ARG, data);
                 }
                 double ax = static_cast<double>(curr->fPoint.fX) - prev->fPoint.fX;
                 double ay = static_cast<double>(curr->fPoint.fY) - prev->fPoint.fY;
                 double bx = static_cast<double>(next->fPoint.fX) - curr->fPoint.fX;
                 double by = static_cast<double>(next->fPoint.fY) - curr->fPoint.fY;
                 if (ax * by - ay * bx >= 0.0) {
-                    data = emit_triangle(prev, curr, next, aaParams, data);
+                    data = emit_triangle(prev, curr, next, AA_ARG, data);
                     v->fPrev->fNext = v->fNext;
                     v->fNext->fPrev = v->fPrev;
                     count--;
@@ -640,13 +653,13 @@
         }
         return poly;
     }
-    void* emit(const AAParams* aaParams, void *data) {
+    void* emit(AA_PARAM, void *data) {
         if (fCount < 3) {
             return data;
         }
         LOG("emit() %d, size %d\n", fID, fCount);
         for (MonotonePoly* m = fHead; m != nullptr; m = m->fNext) {
-            data = m->emit(aaParams, data);
+            data = m->emit(AA_ARG, data);
         }
         return data;
     }
@@ -2166,11 +2179,10 @@
 }
 
 // Stage 6: Triangulate the monotone polygons into a vertex buffer.
-void* polys_to_triangles(Poly* polys, SkPath::FillType fillType, const AAParams* aaParams,
-                         void* data) {
+void* polys_to_triangles(Poly* polys, SkPath::FillType fillType, AA_PARAM, void* data) {
     for (Poly* poly = polys; poly; poly = poly->fNext) {
         if (apply_fill_type(fillType, poly)) {
-            data = poly->emit(aaParams, data);
+            data = poly->emit(AA_ARG, data);
         }
     }
     return data;
@@ -2219,15 +2231,15 @@
     return count;
 }
 
-void* outer_mesh_to_triangles(const VertexList& outerMesh, const AAParams* aaParams, void* data) {
+void* outer_mesh_to_triangles(const VertexList& outerMesh, AA_PARAM, void* data) {
     for (Vertex* v = outerMesh.fHead; v; v = v->fNext) {
         for (Edge* e = v->fFirstEdgeBelow; e; e = e->fNextEdgeBelow) {
             Vertex* v0 = e->fTop;
             Vertex* v1 = e->fBottom;
             Vertex* v2 = e->fBottom->fPartner;
             Vertex* v3 = e->fTop->fPartner;
-            data = emit_triangle(v0, v1, v2, aaParams, data);
-            data = emit_triangle(v0, v2, v3, aaParams, data);
+            data = emit_triangle(v0, v1, v2, AA_ARG, data);
+            data = emit_triangle(v0, v2, v3, AA_ARG, data);
         }
     }
     return data;
@@ -2240,8 +2252,11 @@
 // Stage 6: Triangulate the monotone polygons into a vertex buffer.
 
 int PathToTriangles(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
-                    VertexAllocator* vertexAllocator, bool antialias, const GrColor& color,
-                    bool canTweakAlphaForCoverage, bool* isLinear) {
+                    VertexAllocator* vertexAllocator, bool antialias,
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
+                    const GrColor& color, bool canTweakAlphaForCoverage,
+#endif
+                    bool* isLinear) {
     int contourCnt = get_contour_count(path, tolerance);
     if (contourCnt <= 0) {
         *isLinear = true;
@@ -2268,12 +2283,17 @@
     }
 
     LOG("emitting %d verts\n", count);
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
     AAParams aaParams;
     aaParams.fTweakAlpha = canTweakAlphaForCoverage;
     aaParams.fColor = color;
-
     void* end = polys_to_triangles(polys, fillType, antialias ? &aaParams : nullptr, verts);
     end = outer_mesh_to_triangles(outerMesh, &aaParams, end);
+#else
+    void* end = polys_to_triangles(polys, fillType, antialias, verts);
+    end = outer_mesh_to_triangles(outerMesh, true, end);
+#endif
+
     int actualCount = static_cast<int>((static_cast<uint8_t*>(end) - static_cast<uint8_t*>(verts))
                                        / vertexAllocator->stride());
     SkASSERT(actualCount <= count);
@@ -2307,7 +2327,11 @@
     for (Poly* poly = polys; poly; poly = poly->fNext) {
         if (apply_fill_type(fillType, poly)) {
             SkPoint* start = pointsEnd;
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
             pointsEnd = static_cast<SkPoint*>(poly->emit(nullptr, pointsEnd));
+#else
+            pointsEnd = static_cast<SkPoint*>(poly->emit(false, pointsEnd));
+#endif
             while (start != pointsEnd) {
                 vertsEnd->fPos = *start;
                 vertsEnd->fWinding = poly->fWinding;
diff --git a/src/gpu/GrTessellator.h b/src/gpu/GrTessellator.h
index 4f82ea8..324a72c 100644
--- a/src/gpu/GrTessellator.h
+++ b/src/gpu/GrTessellator.h
@@ -47,8 +47,11 @@
                    WindingVertex** verts);
 
 int PathToTriangles(const SkPath& path, SkScalar tolerance, const SkRect& clipBounds,
-                    VertexAllocator*, bool antialias, const GrColor& color,
-                    bool canTweakAlphaForCoverage, bool *isLinear);
+                    VertexAllocator*, bool antialias,
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
+                    const GrColor& color, bool canTweakAlphaForCoverage,
+#endif
+                    bool *isLinear);
 }
 
 #endif
diff --git a/src/gpu/GrTextureAdjuster.cpp b/src/gpu/GrTextureAdjuster.cpp
index 8bbcd52..2b8f44c 100644
--- a/src/gpu/GrTextureAdjuster.cpp
+++ b/src/gpu/GrTextureAdjuster.cpp
@@ -62,7 +62,8 @@
                 // If we had a cachedProxy, that means there already is a proxy in the cache which
                 // matches the key, but it does not have mip levels and we require them. Thus we
                 // must remove the unique key from that proxy.
-                proxyProvider->removeUniqueKeyFromProxy(key, cachedCopy.get());
+                SkASSERT(cachedCopy->getUniqueKey() == key);
+                proxyProvider->removeUniqueKeyFromProxy(cachedCopy.get());
             }
             proxyProvider->assignUniqueKeyToProxy(key, copy.get());
             this->didCacheCopy(key, proxyProvider->contextUniqueID());
diff --git a/src/gpu/GrTextureMaker.cpp b/src/gpu/GrTextureMaker.cpp
index 9f4651f..fa4d986 100644
--- a/src/gpu/GrTextureMaker.cpp
+++ b/src/gpu/GrTextureMaker.cpp
@@ -94,7 +94,8 @@
             // If we had a cachedProxy, that means there already is a proxy in the cache which
             // matches the key, but it does not have mip levels and we require them. Thus we must
             // remove the unique key from that proxy.
-            proxyProvider->removeUniqueKeyFromProxy(copyKey, cachedProxy.get());
+            SkASSERT(cachedProxy->getUniqueKey() == copyKey);
+            proxyProvider->removeUniqueKeyFromProxy(cachedProxy.get());
         }
         proxyProvider->assignUniqueKeyToProxy(copyKey, result.get());
         this->didCacheCopy(copyKey, proxyProvider->contextUniqueID());
diff --git a/src/gpu/GrTextureOpList.cpp b/src/gpu/GrTextureOpList.cpp
index 4b93803..ae51807 100644
--- a/src/gpu/GrTextureOpList.cpp
+++ b/src/gpu/GrTextureOpList.cpp
@@ -25,6 +25,7 @@
                                  GrAuditTrail* auditTrail)
         : INHERITED(resourceProvider, std::move(opMemoryPool), proxy, auditTrail) {
     SkASSERT(fOpMemoryPool);
+    SkASSERT(!proxy->readOnly());
 }
 
 void GrTextureOpList::deleteOp(int index) {
diff --git a/src/gpu/GrTextureProxy.cpp b/src/gpu/GrTextureProxy.cpp
index 854ed17..b15b0b2 100644
--- a/src/gpu/GrTextureProxy.cpp
+++ b/src/gpu/GrTextureProxy.cpp
@@ -69,7 +69,8 @@
     // proxy provider has gone away. In that case there is noone to send the invalid key
     // message to (Note: in this case we don't want to remove its cached resource).
     if (fUniqueKey.isValid() && fProxyProvider) {
-        fProxyProvider->processInvalidProxyUniqueKey(fUniqueKey, this, false);
+        fProxyProvider->processInvalidUniqueKey(fUniqueKey, this,
+                                                GrProxyProvider::InvalidateGPUResource::kNo);
     } else {
         SkASSERT(!fProxyProvider);
     }
@@ -173,7 +174,14 @@
     SkASSERT(surface->asTexture());
     SkASSERT(GrMipMapped::kNo == this->proxyMipMapped() ||
              GrMipMapped::kYes == surface->asTexture()->texturePriv().mipMapped());
+
     SkASSERT(surface->asTexture()->texturePriv().textureType() == this->textureType());
+
+    GrInternalSurfaceFlags proxyFlags = fSurfaceFlags;
+    GrInternalSurfaceFlags surfaceFlags = surface->surfacePriv().flags();
+    SkASSERT((proxyFlags & GrInternalSurfaceFlags::kTextureMask) ==
+             (surfaceFlags & GrInternalSurfaceFlags::kTextureMask));
 }
+
 #endif
 
diff --git a/src/gpu/GrTextureRenderTargetProxy.cpp b/src/gpu/GrTextureRenderTargetProxy.cpp
index a665270..3e52b2c 100644
--- a/src/gpu/GrTextureRenderTargetProxy.cpp
+++ b/src/gpu/GrTextureRenderTargetProxy.cpp
@@ -124,11 +124,19 @@
     SkASSERT(surface->asRenderTarget());
     SkASSERT(surface->asRenderTarget()->numStencilSamples() == this->numStencilSamples());
 
+    SkASSERT(surface->asTexture()->texturePriv().textureType() == this->textureType());
+
     GrInternalSurfaceFlags proxyFlags = fSurfaceFlags;
     GrInternalSurfaceFlags surfaceFlags = surface->surfacePriv().flags();
+
+    // Only non-RT textures can be read only.
+    SkASSERT(!(proxyFlags & GrInternalSurfaceFlags::kReadOnly));
+    SkASSERT(!(surfaceFlags & GrInternalSurfaceFlags::kReadOnly));
+
     SkASSERT((proxyFlags & GrInternalSurfaceFlags::kRenderTargetMask) ==
              (surfaceFlags & GrInternalSurfaceFlags::kRenderTargetMask));
-    SkASSERT(surface->asTexture()->texturePriv().textureType() == this->textureType());
+    SkASSERT((proxyFlags & GrInternalSurfaceFlags::kTextureMask) ==
+             (surfaceFlags & GrInternalSurfaceFlags::kTextureMask));
 }
 #endif
 
diff --git a/src/gpu/GrUninstantiateProxyTracker.h b/src/gpu/GrUninstantiateProxyTracker.h
deleted file mode 100644
index 5245466..0000000
--- a/src/gpu/GrUninstantiateProxyTracker.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrUninstantiateProxyTracker_DEFINED
-#define GrUninstantiateProxyTracker_DEFINED
-
-#include "GrSurfaceProxy.h"
-#include "SkTArray.h"
-
-class GrUninstantiateProxyTracker {
-public:
-    GrUninstantiateProxyTracker() {}
-
-    // Adds a proxy which will be uninstantiated at the end of flush. The same proxy may not be
-    // added multiple times.
-    void addProxy(GrSurfaceProxy* proxy);
-
-    // Loops through all tracked proxies and uninstantiates them.
-    void uninstantiateAllProxies();
-
-private:
-    SkTArray<sk_sp<GrSurfaceProxy>> fProxies;
-};
-
-#endif
diff --git a/src/gpu/GrVertexWriter.h b/src/gpu/GrVertexWriter.h
index e215793..de4c78f 100644
--- a/src/gpu/GrVertexWriter.h
+++ b/src/gpu/GrVertexWriter.h
@@ -41,6 +41,9 @@
         return Conditional<T>(condition, value);
     }
 
+    template <typename T>
+    struct Skip {};
+
     template <typename T, typename... Args>
     void write(const T& val, const Args&... remainder) {
         static_assert(std::is_pod<T>::value, "");
@@ -79,6 +82,12 @@
         this->write(remainder...);
     }
 
+    template <typename T, typename... Args>
+    void write(const Skip<T>& val, const Args&... remainder) {
+        fPtr = SkTAddOffset<void>(fPtr, sizeof(T));
+        this->write(remainder...);
+    }
+
     template <typename... Args>
     void write(const Sk4f& vector, const Args&... remainder) {
         float buffer[4];
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 1ca23df..1524b7b 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -1230,6 +1230,14 @@
 }
 
 sk_sp<SkSpecialImage> SkGpuDevice::snapSpecial() {
+    // If we are wrapping a vulkan secondary command buffer, then we can't snap off a special image
+    // since it would require us to make a copy of the underlying VkImage which we don't have access
+    // to. Additionaly we can't stop and start the render pass that is used with the secondary
+    // command buffer.
+    if (this->accessRenderTargetContext()->wrapsVkSecondaryCB()) {
+        return nullptr;
+    }
+
     sk_sp<GrTextureProxy> proxy(this->accessRenderTargetContext()->asTextureProxyRef());
     if (!proxy) {
         // When the device doesn't have a texture, we create a temporary texture.
@@ -1238,6 +1246,7 @@
         proxy = GrSurfaceProxy::Copy(fContext.get(),
                                      this->accessRenderTargetContext()->asSurfaceProxy(),
                                      GrMipMapped::kNo,
+                                     SkBackingFit::kApprox,
                                      SkBudgeted::kYes);
         if (!proxy) {
             return nullptr;
@@ -1255,6 +1264,37 @@
                                                &this->surfaceProps());
 }
 
+sk_sp<SkSpecialImage> SkGpuDevice::snapBackImage(const SkIRect& subset) {
+    GrRenderTargetContext* rtc = this->accessRenderTargetContext();
+
+    // If we are wrapping a vulkan secondary command buffer, then we can't snap off a special image
+    // since it would require us to make a copy of the underlying VkImage which we don't have access
+    // to. Additionaly we can't stop and start the render pass that is used with the secondary
+    // command buffer.
+    if (rtc->wrapsVkSecondaryCB()) {
+        return nullptr;
+    }
+
+
+    GrContext* ctx = this->context();
+    SkASSERT(rtc->asSurfaceProxy());
+
+    auto srcProxy =
+            GrSurfaceProxy::Copy(ctx, rtc->asSurfaceProxy(), rtc->mipMapped(), subset,
+                                 SkBackingFit::kApprox, rtc->asSurfaceProxy()->isBudgeted());
+    if (!srcProxy) {
+        return nullptr;
+    }
+
+    // Note, can't move srcProxy since we also refer to this in the 2nd parameter
+    return SkSpecialImage::MakeDeferredFromGpu(fContext.get(),
+                                               SkIRect::MakeSize(srcProxy->isize()),
+                                               kNeedNewImageUniqueID_SpecialImage,
+                                               srcProxy,
+                                               this->imageInfo().refColorSpace(),
+                                               &this->surfaceProps());
+}
+
 void SkGpuDevice::drawDevice(SkBaseDevice* device,
                              int left, int top, const SkPaint& paint) {
     SkASSERT(!paint.getImageFilter());
@@ -1692,9 +1732,7 @@
 
     // Check for valid input
     const SkMatrix& ctm = this->ctm();
-    const SkPaint& paint = glyphRunList.paint();
-    if (!ctm.isFinite() || !SkScalarIsFinite(paint.getTextSize()) ||
-        !SkScalarIsFinite(paint.getTextScaleX()) || !SkScalarIsFinite(paint.getTextSkewX())) {
+    if (!ctm.isFinite() || !glyphRunList.allFontsFinite()) {
         return;
     }
 
@@ -1709,7 +1747,7 @@
         const SkMatrix& ctm = canvas->getTotalMatrix();
         const SkMatrix& combinedMatrix = matrix ? SkMatrix::Concat(ctm, *matrix) : ctm;
         std::unique_ptr<SkDrawable::GpuDrawHandler> gpuDraw =
-                drawable->snapGpuDrawHandler(api, combinedMatrix);
+                drawable->snapGpuDrawHandler(api, combinedMatrix, canvas->getDeviceClipBounds());
         if (gpuDraw) {
             fRenderTargetContext->drawDrawable(std::move(gpuDraw), drawable->getBounds());
             return;
diff --git a/src/gpu/SkGpuDevice.h b/src/gpu/SkGpuDevice.h
index dfc5fc7..d14572a 100644
--- a/src/gpu/SkGpuDevice.h
+++ b/src/gpu/SkGpuDevice.h
@@ -115,6 +115,7 @@
     sk_sp<SkSpecialImage> makeSpecial(const SkBitmap&) override;
     sk_sp<SkSpecialImage> makeSpecial(const SkImage*) override;
     sk_sp<SkSpecialImage> snapSpecial() override;
+    sk_sp<SkSpecialImage> snapBackImage(const SkIRect&) override;
 
     void flush() override;
     GrSemaphoresSubmitted flushAndSignalSemaphores(int numSemaphores,
diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp
index e51925e..261c1f9 100644
--- a/src/gpu/SkGr.cpp
+++ b/src/gpu/SkGr.cpp
@@ -342,6 +342,7 @@
         case kGray_8_as_Red_GrPixelConfig:
         case kRGBA_8888_GrPixelConfig:
         case kRGB_888_GrPixelConfig:
+        case kRG_88_GrPixelConfig:
         case kBGRA_8888_GrPixelConfig:
             return 0;
         case kRGB_565_GrPixelConfig:
diff --git a/src/gpu/ccpr/GrCCAtlas.cpp b/src/gpu/ccpr/GrCCAtlas.cpp
index 6dc523d..4d147fd 100644
--- a/src/gpu/ccpr/GrCCAtlas.cpp
+++ b/src/gpu/ccpr/GrCCAtlas.cpp
@@ -16,6 +16,8 @@
 #include "GrTextureProxy.h"
 #include "SkMakeUnique.h"
 #include "SkMathPriv.h"
+#include "ccpr/GrCCPathCache.h"
+#include <atomic>
 
 class GrCCAtlas::Node {
 public:
@@ -46,8 +48,9 @@
     GrRectanizerSkyline fRectanizer;
 };
 
-GrCCAtlas::GrCCAtlas(GrPixelConfig pixelConfig, const Specs& specs, const GrCaps& caps)
-        : fMaxTextureSize(SkTMax(SkTMax(specs.fMinHeight, specs.fMinWidth),
+GrCCAtlas::GrCCAtlas(CoverageType coverageType, const Specs& specs, const GrCaps& caps)
+        : fCoverageType(coverageType)
+        , fMaxTextureSize(SkTMax(SkTMax(specs.fMinHeight, specs.fMinWidth),
                                  specs.fMaxPreferredTextureSize)) {
     // Caller should have cropped any paths to the destination render target instead of asking for
     // an atlas larger than maxRenderTargetSize.
@@ -72,12 +75,12 @@
 
     fTopNode = skstd::make_unique<Node>(nullptr, 0, 0, fWidth, fHeight);
 
-    // TODO: don't have this rely on the GrPixelConfig
-    GrSRGBEncoded srgbEncoded = GrSRGBEncoded::kNo;
-    GrColorType colorType = GrPixelConfigToColorTypeAndEncoding(pixelConfig, &srgbEncoded);
-
+    GrColorType colorType = (CoverageType::kFP16_CoverageCount == fCoverageType)
+            ? GrColorType::kAlpha_F16 : GrColorType::kAlpha_8;
     const GrBackendFormat format =
-            caps.getBackendFormatFromGrColorType(colorType, srgbEncoded);
+            caps.getBackendFormatFromGrColorType(colorType, GrSRGBEncoded::kNo);
+    GrPixelConfig pixelConfig = (CoverageType::kFP16_CoverageCount == fCoverageType)
+            ? kAlpha_half_GrPixelConfig : kAlpha_8_GrPixelConfig;
 
     fTextureProxy = GrProxyProvider::MakeFullyLazyProxy(
             [this, pixelConfig](GrResourceProvider* resourceProvider) {
@@ -154,31 +157,27 @@
 }
 
 static uint32_t next_atlas_unique_id() {
-    static int32_t nextID;
-    return sk_atomic_inc(&nextID);
+    static std::atomic<uint32_t> nextID;
+    return nextID++;
 }
 
-const GrUniqueKey& GrCCAtlas::getOrAssignUniqueKey(GrOnFlushResourceProvider* onFlushRP) {
-    static const GrUniqueKey::Domain kAtlasDomain = GrUniqueKey::GenerateDomain();
+sk_sp<GrCCCachedAtlas> GrCCAtlas::refOrMakeCachedAtlas(GrOnFlushResourceProvider* onFlushRP) {
+    if (!fCachedAtlas) {
+        static const GrUniqueKey::Domain kAtlasDomain = GrUniqueKey::GenerateDomain();
 
-    if (!fUniqueKey.isValid()) {
-        GrUniqueKey::Builder builder(&fUniqueKey, kAtlasDomain, 1, "CCPR Atlas");
+        GrUniqueKey atlasUniqueKey;
+        GrUniqueKey::Builder builder(&atlasUniqueKey, kAtlasDomain, 1, "CCPR Atlas");
         builder[0] = next_atlas_unique_id();
         builder.finish();
 
-        if (fTextureProxy->isInstantiated()) {
-            onFlushRP->assignUniqueKeyToProxy(fUniqueKey, fTextureProxy.get());
-        }
-    }
-    return fUniqueKey;
-}
+        onFlushRP->assignUniqueKeyToProxy(atlasUniqueKey, fTextureProxy.get());
 
-sk_sp<GrCCAtlas::CachedAtlasInfo> GrCCAtlas::refOrMakeCachedAtlasInfo(uint32_t contextUniqueID) {
-    if (!fCachedAtlasInfo) {
-        fCachedAtlasInfo = sk_make_sp<CachedAtlasInfo>(contextUniqueID);
+        fCachedAtlas = sk_make_sp<GrCCCachedAtlas>(fCoverageType, atlasUniqueKey, fTextureProxy);
     }
-    SkASSERT(fCachedAtlasInfo->fContextUniqueID == contextUniqueID);
-    return fCachedAtlasInfo;
+
+    SkASSERT(fCachedAtlas->coverageType() == fCoverageType);
+    SkASSERT(fCachedAtlas->getOnFlushProxy() == fTextureProxy.get());
+    return fCachedAtlas;
 }
 
 sk_sp<GrRenderTargetContext> GrCCAtlas::makeRenderTargetContext(
@@ -204,10 +203,6 @@
         return nullptr;
     }
 
-    if (fUniqueKey.isValid()) {
-        onFlushRP->assignUniqueKeyToProxy(fUniqueKey, fTextureProxy.get());
-    }
-
     SkIRect clearRect = SkIRect::MakeSize(fDrawBounds);
     rtc->clear(&clearRect, SK_PMColor4fTRANSPARENT,
                GrRenderTargetContext::CanClearFullscreen::kYes);
@@ -219,7 +214,7 @@
     if (fAtlases.empty() || !fAtlases.back().addRect(devIBounds, devToAtlasOffset)) {
         // The retired atlas is out of room and can't grow any bigger.
         retiredAtlas = !fAtlases.empty() ? &fAtlases.back() : nullptr;
-        fAtlases.emplace_back(fPixelConfig, fSpecs, *fCaps);
+        fAtlases.emplace_back(fCoverageType, fSpecs, *fCaps);
         SkASSERT(devIBounds.width() <= fSpecs.fMinWidth);
         SkASSERT(devIBounds.height() <= fSpecs.fMinHeight);
         SkAssertResult(fAtlases.back().addRect(devIBounds, devToAtlasOffset));
diff --git a/src/gpu/ccpr/GrCCAtlas.h b/src/gpu/ccpr/GrCCAtlas.h
index 4a762bc..03eed8c 100644
--- a/src/gpu/ccpr/GrCCAtlas.h
+++ b/src/gpu/ccpr/GrCCAtlas.h
@@ -15,6 +15,7 @@
 #include "SkRefCnt.h"
 #include "SkSize.h"
 
+class GrCCCachedAtlas;
 class GrOnFlushResourceProvider;
 class GrRenderTargetContext;
 class GrTextureProxy;
@@ -45,7 +46,12 @@
         void accountForSpace(int width, int height);
     };
 
-    GrCCAtlas(GrPixelConfig, const Specs&, const GrCaps&);
+    enum class CoverageType : bool {
+        kFP16_CoverageCount,
+        kA8_LiteralCoverage
+    };
+
+    GrCCAtlas(CoverageType, const Specs&, const GrCaps&);
     ~GrCCAtlas();
 
     GrTextureProxy* textureProxy() const { return fTextureProxy.get(); }
@@ -64,23 +70,7 @@
     void setStrokeBatchID(int id);
     int getStrokeBatchID() const { return fStrokeBatchID; }
 
-    // Manages a unique resource cache key that gets assigned to the atlas texture. The unique key
-    // does not get assigned to the texture proxy until it is instantiated.
-    const GrUniqueKey& getOrAssignUniqueKey(GrOnFlushResourceProvider*);
-    const GrUniqueKey& uniqueKey() const { return fUniqueKey; }
-
-    // An object for simple bookkeeping on the atlas texture once it has a unique key. In practice,
-    // we use it to track the percentage of the original atlas pixels that could still ever
-    // potentially be reused (i.e., those which still represent an extant path). When the percentage
-    // of useful pixels drops below 50%, the entire texture is purged from the resource cache.
-    struct CachedAtlasInfo : public GrNonAtomicRef<CachedAtlasInfo> {
-        CachedAtlasInfo(uint32_t contextUniqueID) : fContextUniqueID(contextUniqueID) {}
-        const uint32_t fContextUniqueID;
-        int fNumPathPixels = 0;
-        int fNumInvalidatedPathPixels = 0;
-        bool fIsPurgedFromResourceCache = false;
-    };
-    sk_sp<CachedAtlasInfo> refOrMakeCachedAtlasInfo(uint32_t contextUniqueID);
+    sk_sp<GrCCCachedAtlas> refOrMakeCachedAtlas(GrOnFlushResourceProvider*);
 
     // Instantiates our texture proxy for the atlas and returns a pre-cleared GrRenderTargetContext
     // that the caller may use to render the content. After this call, it is no longer valid to call
@@ -97,6 +87,7 @@
 
     bool internalPlaceRect(int w, int h, SkIPoint16* loc);
 
+    const CoverageType fCoverageType;
     const int fMaxTextureSize;
     int fWidth, fHeight;
     std::unique_ptr<Node> fTopNode;
@@ -105,11 +96,7 @@
     int fFillBatchID;
     int fStrokeBatchID;
 
-    // Not every atlas will have a unique key -- a mainline CCPR one won't if we don't stash any
-    // paths, and only the first atlas in the stack is eligible to be stashed.
-    GrUniqueKey fUniqueKey;
-
-    sk_sp<CachedAtlasInfo> fCachedAtlasInfo;
+    sk_sp<GrCCCachedAtlas> fCachedAtlas;
     sk_sp<GrTextureProxy> fTextureProxy;
     sk_sp<GrTexture> fBackingTexture;
 };
@@ -120,8 +107,10 @@
  */
 class GrCCAtlasStack {
 public:
-    GrCCAtlasStack(GrPixelConfig pixelConfig, const GrCCAtlas::Specs& specs, const GrCaps* caps)
-            : fPixelConfig(pixelConfig), fSpecs(specs), fCaps(caps) {}
+    using CoverageType = GrCCAtlas::CoverageType;
+
+    GrCCAtlasStack(CoverageType coverageType, const GrCCAtlas::Specs& specs, const GrCaps* caps)
+            : fCoverageType(coverageType), fSpecs(specs), fCaps(caps) {}
 
     bool empty() const { return fAtlases.empty(); }
     const GrCCAtlas& front() const { SkASSERT(!this->empty()); return fAtlases.front(); }
@@ -147,7 +136,7 @@
     GrCCAtlas* addRect(const SkIRect& devIBounds, SkIVector* devToAtlasOffset);
 
 private:
-    const GrPixelConfig fPixelConfig;
+    const CoverageType fCoverageType;
     const GrCCAtlas::Specs fSpecs;
     const GrCaps* const fCaps;
     GrSTAllocator<4, GrCCAtlas> fAtlases;
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.cpp b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
index c1384fe..fe1365b 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.cpp
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.cpp
@@ -6,6 +6,7 @@
  */
 
 #include "GrCCDrawPathsOp.h"
+
 #include "GrContext.h"
 #include "GrContextPriv.h"
 #include "GrMemoryPool.h"
@@ -24,10 +25,6 @@
     return false;
 }
 
-static int64_t area(const SkIRect& r) {
-    return sk_64_mul(r.height(), r.width());
-}
-
 std::unique_ptr<GrCCDrawPathsOp> GrCCDrawPathsOp::Make(
         GrContext* context, const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape,
         GrPaint&& paint) {
@@ -90,36 +87,22 @@
     conservativeDevBounds.roundOut(&shapeConservativeIBounds);
 
     SkIRect maskDevIBounds;
-    Visibility maskVisibility;
-    if (clipIBounds.contains(shapeConservativeIBounds)) {
-        maskDevIBounds = shapeConservativeIBounds;
-        maskVisibility = Visibility::kComplete;
-    } else {
-        if (!maskDevIBounds.intersect(clipIBounds, shapeConservativeIBounds)) {
-            return nullptr;
-        }
-        int64_t unclippedArea = area(shapeConservativeIBounds);
-        int64_t clippedArea = area(maskDevIBounds);
-        maskVisibility = (clippedArea >= unclippedArea/2 || unclippedArea < 100*100)
-                ? Visibility::kMostlyComplete  // i.e., visible enough to justify rendering the
-                                               // whole thing if we think we can cache it.
-                : Visibility::kPartial;
+    if (!maskDevIBounds.intersect(clipIBounds, shapeConservativeIBounds)) {
+        return nullptr;
     }
 
     GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
-
     return pool->allocate<GrCCDrawPathsOp>(m, shape, strokeDevWidth, shapeConservativeIBounds,
-                                           maskDevIBounds, maskVisibility, conservativeDevBounds,
-                                           std::move(paint));
+                                           maskDevIBounds, conservativeDevBounds, std::move(paint));
 }
 
 GrCCDrawPathsOp::GrCCDrawPathsOp(const SkMatrix& m, const GrShape& shape, float strokeDevWidth,
                                  const SkIRect& shapeConservativeIBounds,
-                                 const SkIRect& maskDevIBounds, Visibility maskVisibility,
-                                 const SkRect& conservativeDevBounds, GrPaint&& paint)
+                                 const SkIRect& maskDevIBounds, const SkRect& conservativeDevBounds,
+                                 GrPaint&& paint)
         : GrDrawOp(ClassID())
         , fViewMatrixIfUsingLocalCoords(has_coord_transforms(paint) ? m : SkMatrix::I())
-        , fDraws(m, shape, strokeDevWidth, shapeConservativeIBounds, maskDevIBounds, maskVisibility,
+        , fDraws(m, shape, strokeDevWidth, shapeConservativeIBounds, maskDevIBounds,
                  paint.getColor4f())
         , fProcessors(std::move(paint)) {  // Paint must be moved after fetching its color above.
     SkDEBUGCODE(fBaseInstance = -1);
@@ -138,14 +121,12 @@
 GrCCDrawPathsOp::SingleDraw::SingleDraw(const SkMatrix& m, const GrShape& shape,
                                         float strokeDevWidth,
                                         const SkIRect& shapeConservativeIBounds,
-                                        const SkIRect& maskDevIBounds, Visibility maskVisibility,
-                                        const SkPMColor4f& color)
+                                        const SkIRect& maskDevIBounds, const SkPMColor4f& color)
         : fMatrix(m)
         , fShape(shape)
         , fStrokeDevWidth(strokeDevWidth)
         , fShapeConservativeIBounds(shapeConservativeIBounds)
         , fMaskDevIBounds(maskDevIBounds)
-        , fMaskVisibility(maskVisibility)
         , fColor(color) {
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
     if (fShape.hasUnstyledKey()) {
@@ -157,49 +138,45 @@
 #endif
 }
 
-GrCCDrawPathsOp::SingleDraw::~SingleDraw() {
-    if (fCacheEntry) {
-        // All currFlushAtlas references must be reset back to null before the flush is finished.
-        fCacheEntry->setCurrFlushAtlas(nullptr);
-    }
-}
-
 GrDrawOp::RequiresDstTexture GrCCDrawPathsOp::finalize(const GrCaps& caps,
                                                        const GrAppliedClip* clip) {
     SkASSERT(1 == fNumDraws);  // There should only be one single path draw in this Op right now.
-    SingleDraw* draw = &fDraws.head();
+    return fDraws.head().finalize(caps, clip, &fProcessors);
+}
 
-    const GrProcessorSet::Analysis& analysis = fProcessors.finalize(
-            draw->fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, false, caps,
-            &draw->fColor);
+GrDrawOp::RequiresDstTexture GrCCDrawPathsOp::SingleDraw::finalize(
+        const GrCaps& caps, const GrAppliedClip* clip, GrProcessorSet* processors) {
+    const GrProcessorSet::Analysis& analysis = processors->finalize(
+            fColor, GrProcessorAnalysisCoverage::kSingleChannel, clip, false, caps,
+            &fColor);
 
     // Lines start looking jagged when they get thinner than 1px. For thin strokes it looks better
     // if we can convert them to hairline (i.e., inflate the stroke width to 1px), and instead
     // reduce the opacity to create the illusion of thin-ness. This strategy also helps reduce
     // artifacts from coverage dilation when there are self intersections.
     if (analysis.isCompatibleWithCoverageAsAlpha() &&
-            !draw->fShape.style().strokeRec().isFillStyle() && draw->fStrokeDevWidth < 1) {
+            !fShape.style().strokeRec().isFillStyle() && fStrokeDevWidth < 1) {
         // Modifying the shape affects its cache key. The draw can't have a cache entry yet or else
         // our next step would invalidate it.
-        SkASSERT(!draw->fCacheEntry);
-        SkASSERT(SkStrokeRec::kStroke_Style == draw->fShape.style().strokeRec().getStyle());
+        SkASSERT(!fCacheEntry);
+        SkASSERT(SkStrokeRec::kStroke_Style == fShape.style().strokeRec().getStyle());
 
         SkPath path;
-        draw->fShape.asPath(&path);
+        fShape.asPath(&path);
 
         // Create a hairline version of our stroke.
-        SkStrokeRec hairlineStroke = draw->fShape.style().strokeRec();
+        SkStrokeRec hairlineStroke = fShape.style().strokeRec();
         hairlineStroke.setStrokeStyle(0);
 
         // How transparent does a 1px stroke have to be in order to appear as thin as the real one?
-        float coverage = draw->fStrokeDevWidth;
+        float coverage = fStrokeDevWidth;
 
-        draw->fShape = GrShape(path, GrStyle(hairlineStroke, nullptr));
-        draw->fStrokeDevWidth = 1;
+        fShape = GrShape(path, GrStyle(hairlineStroke, nullptr));
+        fStrokeDevWidth = 1;
 
         // TODO4F: Preserve float colors
         // fShapeConservativeIBounds already accounted for this possibility of inflating the stroke.
-        draw->fColor = draw->fColor * coverage;
+        fColor = fColor * coverage;
     }
 
     return RequiresDstTexture(analysis.requiresDstTexture());
@@ -233,177 +210,106 @@
 
 void GrCCDrawPathsOp::accountForOwnPaths(GrCCPathCache* pathCache,
                                          GrOnFlushResourceProvider* onFlushRP,
-                                         const GrUniqueKey& stashedAtlasKey,
                                          GrCCPerFlushResourceSpecs* specs) {
-    using CreateIfAbsent = GrCCPathCache::CreateIfAbsent;
-    using MaskTransform = GrCCPathCache::MaskTransform;
-
     for (SingleDraw& draw : fDraws) {
-        SkPath path;
-        draw.fShape.asPath(&path);
-
-        SkASSERT(!draw.fCacheEntry);
-
-        if (pathCache) {
-            MaskTransform m(draw.fMatrix, &draw.fCachedMaskShift);
-            bool canStashPathMask = draw.fMaskVisibility >= Visibility::kMostlyComplete;
-            draw.fCacheEntry = pathCache->find(draw.fShape, m, CreateIfAbsent(canStashPathMask));
-        }
-
-        if (auto cacheEntry = draw.fCacheEntry.get()) {
-            SkASSERT(!cacheEntry->currFlushAtlas());  // Shouldn't be set until setupResources().
-
-            if (cacheEntry->atlasKey().isValid()) {
-                // Does the path already exist in a cached atlas?
-                if (cacheEntry->hasCachedAtlas() &&
-                    (draw.fCachedAtlasProxy = onFlushRP->findOrCreateProxyByUniqueKey(
-                                                     cacheEntry->atlasKey(),
-                                                     GrCCAtlas::kTextureOrigin))) {
-                    ++specs->fNumCachedPaths;
-                    continue;
-                }
-
-                // Does the path exist in the atlas that we stashed away from last flush? If so we
-                // can copy it into a new 8-bit atlas and keep it in the resource cache.
-                if (stashedAtlasKey.isValid() && stashedAtlasKey == cacheEntry->atlasKey()) {
-                    SkASSERT(!cacheEntry->hasCachedAtlas());
-                    int idx = (draw.fShape.style().strokeRec().isFillStyle())
-                            ? GrCCPerFlushResourceSpecs::kFillIdx
-                            : GrCCPerFlushResourceSpecs::kStrokeIdx;
-                    ++specs->fNumCopiedPaths[idx];
-                    specs->fCopyPathStats[idx].statPath(path);
-                    specs->fCopyAtlasSpecs.accountForSpace(cacheEntry->width(),
-                                                           cacheEntry->height());
-                    continue;
-                }
-
-                // Whatever atlas the path used to reside in, it no longer exists.
-                cacheEntry->resetAtlasKeyAndInfo();
-            }
-
-            if (Visibility::kMostlyComplete == draw.fMaskVisibility && cacheEntry->hitCount() > 1) {
-                int shapeSize = SkTMax(draw.fShapeConservativeIBounds.height(),
-                                       draw.fShapeConservativeIBounds.width());
-                if (shapeSize <= onFlushRP->caps()->maxRenderTargetSize()) {
-                    // We've seen this path before with a compatible matrix, and it's mostly
-                    // visible. Just render the whole mask so we can try to cache it.
-                    draw.fMaskDevIBounds = draw.fShapeConservativeIBounds;
-                    draw.fMaskVisibility = Visibility::kComplete;
-                }
-            }
-        }
-
-        int idx = (draw.fShape.style().strokeRec().isFillStyle())
-                ? GrCCPerFlushResourceSpecs::kFillIdx
-                : GrCCPerFlushResourceSpecs::kStrokeIdx;
-        ++specs->fNumRenderedPaths[idx];
-        specs->fRenderedPathStats[idx].statPath(path);
-        specs->fRenderedAtlasSpecs.accountForSpace(draw.fMaskDevIBounds.width(),
-                                                   draw.fMaskDevIBounds.height());
+        draw.accountForOwnPath(pathCache, onFlushRP, specs);
     }
 }
 
-void GrCCDrawPathsOp::setupResources(GrOnFlushResourceProvider* onFlushRP,
-                                     GrCCPerFlushResources* resources, DoCopiesToCache doCopies) {
-    using DoEvenOddFill = GrCCPathProcessor::DoEvenOddFill;
+void GrCCDrawPathsOp::SingleDraw::accountForOwnPath(
+        GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP,
+        GrCCPerFlushResourceSpecs* specs) {
+    using CoverageType = GrCCAtlas::CoverageType;
+
+    SkPath path;
+    fShape.asPath(&path);
+
+    SkASSERT(!fCacheEntry);
+
+    if (pathCache) {
+        fCacheEntry =
+                pathCache->find(onFlushRP, fShape, fMaskDevIBounds, fMatrix, &fCachedMaskShift);
+    }
+
+    if (fCacheEntry) {
+        if (const GrCCCachedAtlas* cachedAtlas = fCacheEntry->cachedAtlas()) {
+            SkASSERT(cachedAtlas->getOnFlushProxy());
+            if (CoverageType::kA8_LiteralCoverage == cachedAtlas->coverageType()) {
+                ++specs->fNumCachedPaths;
+            } else {
+                // Suggest that this path be copied to a literal coverage atlas, to save memory.
+                // (The client may decline this copy via DoCopiesToA8Coverage::kNo.)
+                int idx = (fShape.style().strokeRec().isFillStyle())
+                        ? GrCCPerFlushResourceSpecs::kFillIdx
+                        : GrCCPerFlushResourceSpecs::kStrokeIdx;
+                ++specs->fNumCopiedPaths[idx];
+                specs->fCopyPathStats[idx].statPath(path);
+                specs->fCopyAtlasSpecs.accountForSpace(fCacheEntry->width(), fCacheEntry->height());
+                fDoCopyToA8Coverage = true;
+            }
+            return;
+        }
+
+        if (this->shouldCachePathMask(onFlushRP->caps()->maxRenderTargetSize())) {
+            fDoCachePathMask = true;
+            // We don't cache partial masks; ensure the bounds include the entire path.
+            fMaskDevIBounds = fShapeConservativeIBounds;
+        }
+    }
+
+    // Plan on rendering this path in a new atlas.
+    int idx = (fShape.style().strokeRec().isFillStyle())
+            ? GrCCPerFlushResourceSpecs::kFillIdx
+            : GrCCPerFlushResourceSpecs::kStrokeIdx;
+    ++specs->fNumRenderedPaths[idx];
+    specs->fRenderedPathStats[idx].statPath(path);
+    specs->fRenderedAtlasSpecs.accountForSpace(fMaskDevIBounds.width(), fMaskDevIBounds.height());
+}
+
+bool GrCCDrawPathsOp::SingleDraw::shouldCachePathMask(int maxRenderTargetSize) const {
+    SkASSERT(fCacheEntry);
+    SkASSERT(!fCacheEntry->cachedAtlas());
+    if (fCacheEntry->hitCount() <= 1) {
+        return false;  // Don't cache a path mask until at least its second hit.
+    }
+
+    int shapeMaxDimension = SkTMax(fShapeConservativeIBounds.height(),
+                                   fShapeConservativeIBounds.width());
+    if (shapeMaxDimension > maxRenderTargetSize) {
+        return false;  // This path isn't cachable.
+    }
+
+    int64_t shapeArea = sk_64_mul(fShapeConservativeIBounds.height(),
+                                  fShapeConservativeIBounds.width());
+    if (shapeArea < 100*100) {
+        // If a path is small enough, we might as well try to render and cache the entire thing, no
+        // matter how much of it is actually visible.
+        return true;
+    }
+
+    // The hitRect should already be contained within the shape's bounds, but we still intersect it
+    // because it's possible for edges very near pixel boundaries (e.g., 0.999999), to round out
+    // inconsistently, depending on the integer translation values and fp32 precision.
+    SkIRect hitRect = fCacheEntry->hitRect().makeOffset(fCachedMaskShift.x(), fCachedMaskShift.y());
+    hitRect.intersect(fShapeConservativeIBounds);
+
+    // Render and cache the entire path mask if we see enough of it to justify rendering all the
+    // pixels. Our criteria for "enough" is that we must have seen at least 50% of the path in the
+    // past, and in this particular draw we must see at least 10% of it.
+    int64_t hitArea = sk_64_mul(hitRect.height(), hitRect.width());
+    int64_t drawArea = sk_64_mul(fMaskDevIBounds.height(), fMaskDevIBounds.width());
+    return hitArea*2 >= shapeArea && drawArea*10 >= shapeArea;
+}
+
+void GrCCDrawPathsOp::setupResources(
+        GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP,
+        GrCCPerFlushResources* resources, DoCopiesToA8Coverage doCopies) {
     SkASSERT(fNumDraws > 0);
     SkASSERT(-1 == fBaseInstance);
     fBaseInstance = resources->nextPathInstanceIdx();
 
     for (SingleDraw& draw : fDraws) {
-        SkPath path;
-        draw.fShape.asPath(&path);
-
-        auto doEvenOddFill = DoEvenOddFill(draw.fShape.style().strokeRec().isFillStyle() &&
-                                           SkPath::kEvenOdd_FillType == path.getFillType());
-        SkASSERT(SkPath::kEvenOdd_FillType == path.getFillType() ||
-                 SkPath::kWinding_FillType == path.getFillType());
-
-        if (auto cacheEntry = draw.fCacheEntry.get()) {
-            // Does the path already exist in a cached atlas texture?
-            if (auto proxy = draw.fCachedAtlasProxy.get()) {
-                SkASSERT(!cacheEntry->currFlushAtlas());
-                this->recordInstance(proxy, resources->nextPathInstanceIdx());
-                // TODO4F: Preserve float colors
-                resources->appendDrawPathInstance().set(*cacheEntry, draw.fCachedMaskShift,
-                                                        draw.fColor.toBytes_RGBA());
-                continue;
-            }
-
-            // Have we already encountered this path during the flush? (i.e. was the same SkPath
-            // drawn more than once during the same flush, with a compatible matrix?)
-            if (auto atlas = cacheEntry->currFlushAtlas()) {
-                this->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
-                // TODO4F: Preserve float colors
-                resources->appendDrawPathInstance().set(
-                        *cacheEntry, draw.fCachedMaskShift, draw.fColor.toBytes_RGBA(),
-                        cacheEntry->hasCachedAtlas() ? DoEvenOddFill::kNo : doEvenOddFill);
-                continue;
-            }
-
-            // If the cache entry still has a valid atlas key at this point, it means the path
-            // exists in the atlas that we stashed away from last flush. Copy it into a permanent
-            // 8-bit atlas in the resource cache.
-            if (DoCopiesToCache::kYes == doCopies && cacheEntry->atlasKey().isValid()) {
-                SkIVector newOffset;
-                GrCCAtlas* atlas =
-                        resources->copyPathToCachedAtlas(*cacheEntry, doEvenOddFill, &newOffset);
-                cacheEntry->updateToCachedAtlas(
-                        atlas->getOrAssignUniqueKey(onFlushRP), newOffset,
-                        atlas->refOrMakeCachedAtlasInfo(onFlushRP->contextUniqueID()));
-                this->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
-                // TODO4F: Preserve float colors
-                resources->appendDrawPathInstance().set(*cacheEntry, draw.fCachedMaskShift,
-                                                        draw.fColor.toBytes_RGBA());
-                // Remember this atlas in case we encounter the path again during the same flush.
-                cacheEntry->setCurrFlushAtlas(atlas);
-                continue;
-            }
-        }
-
-        // Render the raw path into a coverage count atlas. renderPathInAtlas() gives us two tight
-        // bounding boxes: One in device space, as well as a second one rotated an additional 45
-        // degrees. The path vertex shader uses these two bounding boxes to generate an octagon that
-        // circumscribes the path.
-        SkASSERT(!draw.fCachedAtlasProxy);
-        SkRect devBounds, devBounds45;
-        SkIRect devIBounds;
-        SkIVector devToAtlasOffset;
-        if (auto atlas = resources->renderShapeInAtlas(
-                    draw.fMaskDevIBounds, draw.fMatrix, draw.fShape, draw.fStrokeDevWidth,
-                    &devBounds, &devBounds45, &devIBounds, &devToAtlasOffset)) {
-            this->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
-            // TODO4F: Preserve float colors
-            resources->appendDrawPathInstance().set(devBounds, devBounds45, devToAtlasOffset,
-                                                    draw.fColor.toBytes_RGBA(), doEvenOddFill);
-
-            // If we have a spot in the path cache, try to make a note of where this mask is so we
-            // can reuse it in the future.
-            if (auto cacheEntry = draw.fCacheEntry.get()) {
-                SkASSERT(!cacheEntry->hasCachedAtlas());
-
-                if (Visibility::kComplete != draw.fMaskVisibility || cacheEntry->hitCount() <= 1) {
-                    // Don't cache a path mask unless it's completely visible with a hit count > 1.
-                    //
-                    // NOTE: mostly-visible paths with a hit count > 1 should have been promoted to
-                    // fully visible during accountForOwnPaths().
-                    continue;
-                }
-
-                if (resources->nextAtlasToStash() != atlas) {
-                    // This mask does not belong to the atlas that will be stashed for next flush.
-                    continue;
-                }
-
-                const GrUniqueKey& atlasKey =
-                        resources->nextAtlasToStash()->getOrAssignUniqueKey(onFlushRP);
-                cacheEntry->initAsStashedAtlas(atlasKey, devToAtlasOffset, devBounds, devBounds45,
-                                               devIBounds, draw.fCachedMaskShift);
-                // Remember this atlas in case we encounter the path again during the same flush.
-                cacheEntry->setCurrFlushAtlas(atlas);
-            }
-            continue;
-        }
+        draw.setupResources(pathCache, onFlushRP, resources, doCopies, this);
     }
 
     if (!fInstanceRanges.empty()) {
@@ -411,6 +317,71 @@
     }
 }
 
+void GrCCDrawPathsOp::SingleDraw::setupResources(
+        GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP,
+        GrCCPerFlushResources* resources, DoCopiesToA8Coverage doCopies, GrCCDrawPathsOp* op) {
+    using DoEvenOddFill = GrCCPathProcessor::DoEvenOddFill;
+
+    SkPath path;
+    fShape.asPath(&path);
+
+    auto doEvenOddFill = DoEvenOddFill(fShape.style().strokeRec().isFillStyle() &&
+                                       SkPath::kEvenOdd_FillType == path.getFillType());
+    SkASSERT(SkPath::kEvenOdd_FillType == path.getFillType() ||
+             SkPath::kWinding_FillType == path.getFillType());
+
+    if (fCacheEntry) {
+        // Does the path already exist in a cached atlas texture?
+        if (fCacheEntry->cachedAtlas()) {
+            SkASSERT(fCacheEntry->cachedAtlas()->getOnFlushProxy());
+            if (DoCopiesToA8Coverage::kYes == doCopies && fDoCopyToA8Coverage) {
+                resources->upgradeEntryToLiteralCoverageAtlas(pathCache, onFlushRP,
+                                                              fCacheEntry.get(), doEvenOddFill);
+                SkASSERT(fCacheEntry->cachedAtlas());
+                SkASSERT(GrCCAtlas::CoverageType::kA8_LiteralCoverage
+                                 == fCacheEntry->cachedAtlas()->coverageType());
+                SkASSERT(fCacheEntry->cachedAtlas()->getOnFlushProxy());
+            }
+#if 0
+            // Simple color manipulation to visualize cached paths.
+            fColor = (GrCCAtlas::CoverageType::kA8_LiteralCoverage
+                              == fCacheEntry->cachedAtlas()->coverageType())
+                    ? SkPMColor4f{0,0,.25,.25} : SkPMColor4f{0,.25,0,.25};
+#endif
+            op->recordInstance(fCacheEntry->cachedAtlas()->getOnFlushProxy(),
+                               resources->nextPathInstanceIdx());
+            // TODO4F: Preserve float colors
+            resources->appendDrawPathInstance().set(*fCacheEntry, fCachedMaskShift,
+                                                    fColor.toBytes_RGBA());
+            return;
+        }
+    }
+
+    // Render the raw path into a coverage count atlas. renderShapeInAtlas() gives us two tight
+    // bounding boxes: One in device space, as well as a second one rotated an additional 45
+    // degrees. The path vertex shader uses these two bounding boxes to generate an octagon that
+    // circumscribes the path.
+    SkRect devBounds, devBounds45;
+    SkIRect devIBounds;
+    SkIVector devToAtlasOffset;
+    if (auto atlas = resources->renderShapeInAtlas(
+                fMaskDevIBounds, fMatrix, fShape, fStrokeDevWidth, &devBounds, &devBounds45,
+                &devIBounds, &devToAtlasOffset)) {
+        op->recordInstance(atlas->textureProxy(), resources->nextPathInstanceIdx());
+        // TODO4F: Preserve float colors
+        resources->appendDrawPathInstance().set(devBounds, devBounds45, devToAtlasOffset,
+                                                fColor.toBytes_RGBA(), doEvenOddFill);
+
+        if (fDoCachePathMask) {
+            SkASSERT(fCacheEntry);
+            SkASSERT(!fCacheEntry->cachedAtlas());
+            SkASSERT(fShapeConservativeIBounds == fMaskDevIBounds);
+            fCacheEntry->setCoverageCountAtlas(onFlushRP, atlas, devToAtlasOffset, devBounds,
+                                               devBounds45, devIBounds, fCachedMaskShift);
+        }
+    }
+}
+
 inline void GrCCDrawPathsOp::recordInstance(GrTextureProxy* atlasProxy, int instanceIdx) {
     if (fInstanceRanges.empty()) {
         fInstanceRanges.push_back({atlasProxy, instanceIdx});
diff --git a/src/gpu/ccpr/GrCCDrawPathsOp.h b/src/gpu/ccpr/GrCCDrawPathsOp.h
index 9eacbb0..82e05ae 100644
--- a/src/gpu/ccpr/GrCCDrawPathsOp.h
+++ b/src/gpu/ccpr/GrCCDrawPathsOp.h
@@ -11,14 +11,13 @@
 #include "GrShape.h"
 #include "SkTInternalLList.h"
 #include "ccpr/GrCCSTLList.h"
+#include "ccpr/GrCCPathCache.h"
 #include "ops/GrDrawOp.h"
 
 struct GrCCPerFlushResourceSpecs;
 struct GrCCPerOpListPaths;
 class GrCCAtlas;
 class GrOnFlushResourceProvider;
-class GrCCPathCache;
-class GrCCPathCacheEntry;
 class GrCCPerFlushResources;
 
 /**
@@ -45,26 +44,24 @@
     void addToOwningPerOpListPaths(sk_sp<GrCCPerOpListPaths> owningPerOpListPaths);
 
     // Makes decisions about how to draw each path (cached, copied, rendered, etc.), and
-    // increments/fills out the corresponding GrCCPerFlushResourceSpecs. 'stashedAtlasKey', if
-    // valid, references the mainline coverage count atlas from the previous flush. Paths found in
-    // this atlas will be copied to more permanent atlases in the resource cache.
-    void accountForOwnPaths(GrCCPathCache*, GrOnFlushResourceProvider*,
-                            const GrUniqueKey& stashedAtlasKey, GrCCPerFlushResourceSpecs*);
+    // increments/fills out the corresponding GrCCPerFlushResourceSpecs.
+    void accountForOwnPaths(GrCCPathCache*, GrOnFlushResourceProvider*, GrCCPerFlushResourceSpecs*);
 
-    // Allows the caller to decide whether to copy paths out of the stashed atlas and into the
-    // resource cache, or to just re-render the paths from scratch. If there aren't many copies or
-    // the copies would only fill a small atlas, it's probably best to just re-render.
-    enum class DoCopiesToCache : bool {
+    // Allows the caller to decide whether to actually do the suggested copies from cached 16-bit
+    // coverage count atlases, and into 8-bit literal coverage atlases. Purely to save space.
+    enum class DoCopiesToA8Coverage : bool {
         kNo = false,
         kYes = true
     };
 
     // Allocates the GPU resources indicated by accountForOwnPaths(), in preparation for drawing. If
-    // DoCopiesToCache is kNo, the paths slated for copy will instead be re-rendered from scratch.
+    // DoCopiesToA8Coverage is kNo, the paths slated for copy will instead be left in their 16-bit
+    // coverage count atlases.
     //
-    // NOTE: If using DoCopiesToCache::kNo, it is the caller's responsibility to call
-    //       convertCopiesToRenders() on the GrCCPerFlushResourceSpecs.
-    void setupResources(GrOnFlushResourceProvider*, GrCCPerFlushResources*, DoCopiesToCache);
+    // NOTE: If using DoCopiesToA8Coverage::kNo, it is the caller's responsibility to have called
+    // cancelCopies() on the GrCCPerFlushResourceSpecs, prior to making this call.
+    void setupResources(GrCCPathCache*, GrOnFlushResourceProvider*, GrCCPerFlushResources*,
+                        DoCopiesToA8Coverage);
 
     void onExecute(GrOpFlushState*, const SkRect& chainBounds) override;
 
@@ -76,39 +73,46 @@
                                                          float strokeDevWidth,
                                                          const SkRect& conservativeDevBounds,
                                                          GrPaint&&);
-    enum class Visibility {
-        kPartial,
-        kMostlyComplete,  // (i.e., can we cache the whole path mask if we think it will be reused?)
-        kComplete
-    };
 
     GrCCDrawPathsOp(const SkMatrix&, const GrShape&, float strokeDevWidth,
                     const SkIRect& shapeConservativeIBounds, const SkIRect& maskDevIBounds,
-                    Visibility maskVisibility, const SkRect& conservativeDevBounds, GrPaint&&);
+                    const SkRect& conservativeDevBounds, GrPaint&&);
 
     void recordInstance(GrTextureProxy* atlasProxy, int instanceIdx);
 
     const SkMatrix fViewMatrixIfUsingLocalCoords;
 
-    struct SingleDraw {
+    class SingleDraw {
+    public:
         SingleDraw(const SkMatrix&, const GrShape&, float strokeDevWidth,
                    const SkIRect& shapeConservativeIBounds, const SkIRect& maskDevIBounds,
-                   Visibility maskVisibility, const SkPMColor4f&);
-        ~SingleDraw();
+                   const SkPMColor4f&);
+
+        // See the corresponding methods in GrCCDrawPathsOp.
+        RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*, GrProcessorSet*);
+        void accountForOwnPath(GrCCPathCache*, GrOnFlushResourceProvider*,
+                               GrCCPerFlushResourceSpecs*);
+        void setupResources(GrCCPathCache*, GrOnFlushResourceProvider*, GrCCPerFlushResources*,
+                            DoCopiesToA8Coverage, GrCCDrawPathsOp*);
+
+    private:
+        bool shouldCachePathMask(int maxRenderTargetSize) const;
 
         SkMatrix fMatrix;
         GrShape fShape;
         float fStrokeDevWidth;
         const SkIRect fShapeConservativeIBounds;
         SkIRect fMaskDevIBounds;
-        Visibility fMaskVisibility;
         SkPMColor4f fColor;
 
-        sk_sp<GrCCPathCacheEntry> fCacheEntry;
-        sk_sp<GrTextureProxy> fCachedAtlasProxy;
+        GrCCPathCache::OnFlushEntryRef fCacheEntry;
         SkIVector fCachedMaskShift;
+        bool fDoCopyToA8Coverage = false;
+        bool fDoCachePathMask = false;
 
         SingleDraw* fNext = nullptr;
+
+        friend class GrCCSTLList<SingleDraw>;  // To access fNext.
     };
 
     // Declare fOwningPerOpListPaths first, before fDraws. The draws use memory allocated by
diff --git a/src/gpu/ccpr/GrCCPathCache.cpp b/src/gpu/ccpr/GrCCPathCache.cpp
index a5b9a10..1342431 100644
--- a/src/gpu/ccpr/GrCCPathCache.cpp
+++ b/src/gpu/ccpr/GrCCPathCache.cpp
@@ -7,7 +7,8 @@
 
 #include "GrCCPathCache.h"
 
-#include "GrShape.h"
+#include "GrOnFlushResourceProvider.h"
+#include "GrProxyProvider.h"
 #include "SkNx.h"
 
 static constexpr int kMaxKeyDataCountU32 = 256;  // 1kB of uint32_t's.
@@ -84,66 +85,33 @@
     return reinterpret_cast<uint32_t*>(reinterpret_cast<char*>(this) + sizeof(Key));
 }
 
-inline bool GrCCPathCache::Key::operator==(const GrCCPathCache::Key& that) const {
-    return fDataSizeInBytes == that.fDataSizeInBytes &&
-           !memcmp(this->data(), that.data(), fDataSizeInBytes);
-}
-
 void GrCCPathCache::Key::onChange() {
     // Our key's corresponding path was invalidated. Post a thread-safe eviction message.
     SkMessageBus<sk_sp<Key>>::Post(sk_ref_sp(this));
 }
 
-inline const GrCCPathCache::Key& GrCCPathCache::HashNode::GetKey(
-        const GrCCPathCache::HashNode& node) {
-    return *node.entry()->fCacheKey;
-}
-
-inline uint32_t GrCCPathCache::HashNode::Hash(const Key& key) {
-    return GrResourceKeyHash(key.data(), key.dataSizeInBytes());
-}
-
-inline GrCCPathCache::HashNode::HashNode(GrCCPathCache* pathCache, sk_sp<Key> key,
-                                         const MaskTransform& m, const GrShape& shape)
-        : fPathCache(pathCache)
-        , fEntry(new GrCCPathCacheEntry(key, m)) {
-    SkASSERT(shape.hasUnstyledKey());
-    shape.addGenIDChangeListener(std::move(key));
-}
-
-inline GrCCPathCache::HashNode::~HashNode() {
-    this->willExitHashTable();
-}
-
-inline GrCCPathCache::HashNode& GrCCPathCache::HashNode::operator=(HashNode&& node) {
-    this->willExitHashTable();
-    fPathCache = node.fPathCache;
-    fEntry = std::move(node.fEntry);
-    SkASSERT(!node.fEntry);
-    return *this;
-}
-
-inline void GrCCPathCache::HashNode::willExitHashTable() {
-    if (!fEntry) {
-        return;  // We were moved.
-    }
-
-    SkASSERT(fPathCache);
-    SkASSERT(fPathCache->fLRU.isInList(fEntry.get()));
-
-    fEntry->fCacheKey->markShouldUnregisterFromPath();  // Unregister the path listener.
-    fPathCache->fLRU.remove(fEntry.get());
-}
-
-
-GrCCPathCache::GrCCPathCache()
-        : fInvalidatedKeysInbox(next_path_cache_id())
+GrCCPathCache::GrCCPathCache(uint32_t contextUniqueID)
+        : fContextUniqueID(contextUniqueID)
+        , fInvalidatedKeysInbox(next_path_cache_id())
         , fScratchKey(Key::Make(fInvalidatedKeysInbox.uniqueID(), kMaxKeyDataCountU32)) {
 }
 
 GrCCPathCache::~GrCCPathCache() {
-    fHashTable.reset();  // Must be cleared first; ~HashNode calls fLRU.remove() on us.
-    SkASSERT(fLRU.isEmpty());  // Ensure the hash table and LRU list were coherent.
+    while (!fLRU.isEmpty()) {
+        this->evict(*fLRU.tail()->fCacheKey, fLRU.tail());
+    }
+    SkASSERT(0 == fHashTable.count());  // Ensure the hash table and LRU list were coherent.
+
+    // Now take all the atlas textures we just invalidated and purge them from the GrResourceCache.
+    // We just purge via message bus since we don't have any access to the resource cache right now.
+    for (sk_sp<GrTextureProxy>& proxy : fInvalidatedProxies) {
+        SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
+                GrUniqueKeyInvalidatedMessage(proxy->getUniqueKey(), fContextUniqueID));
+    }
+    for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) {
+        SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
+                GrUniqueKeyInvalidatedMessage(key, fContextUniqueID));
+    }
 }
 
 namespace {
@@ -190,33 +158,37 @@
 
 }
 
-sk_sp<GrCCPathCacheEntry> GrCCPathCache::find(const GrShape& shape, const MaskTransform& m,
-                                              CreateIfAbsent createIfAbsent) {
+GrCCPathCache::OnFlushEntryRef GrCCPathCache::find(
+        GrOnFlushResourceProvider* onFlushRP, const GrShape& shape,
+        const SkIRect& clippedDrawBounds, const SkMatrix& viewMatrix, SkIVector* maskShift) {
     if (!shape.hasUnstyledKey()) {
-        return nullptr;
+        return OnFlushEntryRef();
     }
 
     WriteKeyHelper writeKeyHelper(shape);
     if (writeKeyHelper.allocCountU32() > kMaxKeyDataCountU32) {
-        return nullptr;
+        return OnFlushEntryRef();
     }
 
     SkASSERT(fScratchKey->unique());
     fScratchKey->resetDataCountU32(writeKeyHelper.allocCountU32());
     writeKeyHelper.write(shape, fScratchKey->data());
 
+    MaskTransform m(viewMatrix, maskShift);
     GrCCPathCacheEntry* entry = nullptr;
     if (HashNode* node = fHashTable.find(*fScratchKey)) {
         entry = node->entry();
         SkASSERT(fLRU.isInList(entry));
+
         if (!fuzzy_equals(m, entry->fMaskTransform)) {
             // The path was reused with an incompatible matrix.
-            if (CreateIfAbsent::kYes == createIfAbsent && entry->unique()) {
+            if (entry->unique()) {
                 // This entry is unique: recycle it instead of deleting and malloc-ing a new one.
+                SkASSERT(0 == entry->fOnFlushRefCnt);  // Because we are unique.
                 entry->fMaskTransform = m;
                 entry->fHitCount = 0;
-                entry->invalidateAtlas();
-                SkASSERT(!entry->fCurrFlushAtlas);  // Should be null because 'entry' is unique.
+                entry->fHitRect = SkIRect::MakeEmpty();
+                entry->releaseCachedAtlas(this);
             } else {
                 this->evict(*fScratchKey);
                 entry = nullptr;
@@ -225,9 +197,6 @@
     }
 
     if (!entry) {
-        if (CreateIfAbsent::kNo == createIfAbsent) {
-            return nullptr;
-        }
         if (fHashTable.count() >= kMaxCacheCount) {
             SkDEBUGCODE(HashNode* node = fHashTable.find(*fLRU.tail()->fCacheKey));
             SkASSERT(node && node->entry() == fLRU.tail());
@@ -250,20 +219,56 @@
     SkASSERT(node && node->entry() == entry);
     fLRU.addToHead(entry);
 
-    entry->fTimestamp = this->quickPerFlushTimestamp();
-    ++entry->fHitCount;
-    return sk_ref_sp(entry);
+    if (0 == entry->fOnFlushRefCnt) {
+        // Only update the time stamp and hit count if we haven't seen this entry yet during the
+        // current flush.
+        entry->fTimestamp = this->quickPerFlushTimestamp();
+        ++entry->fHitCount;
+
+        if (entry->fCachedAtlas) {
+            SkASSERT(SkToBool(entry->fCachedAtlas->peekOnFlushRefCnt())
+                             == SkToBool(entry->fCachedAtlas->getOnFlushProxy()));
+            if (!entry->fCachedAtlas->getOnFlushProxy()) {
+                entry->fCachedAtlas->setOnFlushProxy(
+                    onFlushRP->findOrCreateProxyByUniqueKey(entry->fCachedAtlas->textureKey(),
+                                                            GrCCAtlas::kTextureOrigin));
+            }
+            if (!entry->fCachedAtlas->getOnFlushProxy()) {
+                // Our atlas's backing texture got purged from the GrResourceCache. Release the
+                // cached atlas.
+                entry->releaseCachedAtlas(this);
+            }
+        }
+    }
+    entry->fHitRect.join(clippedDrawBounds.makeOffset(-maskShift->x(), -maskShift->y()));
+    SkASSERT(!entry->fCachedAtlas || entry->fCachedAtlas->getOnFlushProxy());
+    return OnFlushEntryRef::OnFlushRef(entry);
 }
 
-void GrCCPathCache::doPostFlushProcessing() {
-    this->purgeInvalidatedKeys();
+void GrCCPathCache::evict(const GrCCPathCache::Key& key, GrCCPathCacheEntry* entry) {
+    if (!entry) {
+        HashNode* node = fHashTable.find(key);
+        SkASSERT(node);
+        entry = node->entry();
+    }
+    SkASSERT(*entry->fCacheKey == key);
+    SkASSERT(!entry->hasBeenEvicted());
+    entry->fCacheKey->markShouldUnregisterFromPath();  // Unregister the path listener.
+    entry->releaseCachedAtlas(this);
+    fLRU.remove(entry);
+    fHashTable.remove(key);
+}
+
+void GrCCPathCache::doPreFlushProcessing() {
+    this->evictInvalidatedCacheKeys();
 
     // Mark the per-flush timestamp as needing to be updated with a newer clock reading.
     fPerFlushTimestamp = GrStdSteadyClock::time_point::min();
 }
 
-void GrCCPathCache::purgeEntriesOlderThan(const GrStdSteadyClock::time_point& purgeTime) {
-    this->purgeInvalidatedKeys();
+void GrCCPathCache::purgeEntriesOlderThan(GrProxyProvider* proxyProvider,
+                                          const GrStdSteadyClock::time_point& purgeTime) {
+    this->evictInvalidatedCacheKeys();
 
 #ifdef SK_DEBUG
     auto lastTimestamp = (fLRU.isEmpty())
@@ -271,7 +276,7 @@
             : fLRU.tail()->fTimestamp;
 #endif
 
-    // Drop every cache entry whose timestamp is older than purgeTime.
+    // Evict every entry from our local path cache whose timestamp is older than purgeTime.
     while (!fLRU.isEmpty() && fLRU.tail()->fTimestamp < purgeTime) {
 #ifdef SK_DEBUG
         // Verify that fLRU is sorted by timestamp.
@@ -281,9 +286,37 @@
 #endif
         this->evict(*fLRU.tail()->fCacheKey);
     }
+
+    // Now take all the atlas textures we just invalidated and purge them from the GrResourceCache.
+    this->purgeInvalidatedAtlasTextures(proxyProvider);
 }
 
-void GrCCPathCache::purgeInvalidatedKeys() {
+void GrCCPathCache::purgeInvalidatedAtlasTextures(GrOnFlushResourceProvider* onFlushRP) {
+    for (sk_sp<GrTextureProxy>& proxy : fInvalidatedProxies) {
+        onFlushRP->removeUniqueKeyFromProxy(proxy.get());
+    }
+    fInvalidatedProxies.reset();
+
+    for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) {
+        onFlushRP->processInvalidUniqueKey(key);
+    }
+    fInvalidatedProxyUniqueKeys.reset();
+}
+
+void GrCCPathCache::purgeInvalidatedAtlasTextures(GrProxyProvider* proxyProvider) {
+    for (sk_sp<GrTextureProxy>& proxy : fInvalidatedProxies) {
+        proxyProvider->removeUniqueKeyFromProxy(proxy.get());
+    }
+    fInvalidatedProxies.reset();
+
+    for (const GrUniqueKey& key : fInvalidatedProxyUniqueKeys) {
+        proxyProvider->processInvalidUniqueKey(key, nullptr,
+                                               GrProxyProvider::InvalidateGPUResource::kYes);
+    }
+    fInvalidatedProxyUniqueKeys.reset();
+}
+
+void GrCCPathCache::evictInvalidatedCacheKeys() {
     SkTArray<sk_sp<Key>> invalidatedKeys;
     fInvalidatedKeysInbox.poll(&invalidatedKeys);
     for (const sk_sp<Key>& key : invalidatedKeys) {
@@ -294,17 +327,47 @@
     }
 }
 
+GrCCPathCache::OnFlushEntryRef
+GrCCPathCache::OnFlushEntryRef::OnFlushRef(GrCCPathCacheEntry* entry) {
+    entry->ref();
+    ++entry->fOnFlushRefCnt;
+    if (entry->fCachedAtlas) {
+        entry->fCachedAtlas->incrOnFlushRefCnt();
+    }
+    return OnFlushEntryRef(entry);
+}
 
-void GrCCPathCacheEntry::initAsStashedAtlas(const GrUniqueKey& atlasKey,
-                                            const SkIVector& atlasOffset, const SkRect& devBounds,
-                                            const SkRect& devBounds45, const SkIRect& devIBounds,
-                                            const SkIVector& maskShift) {
-    SkASSERT(atlasKey.isValid());
-    SkASSERT(!fCurrFlushAtlas);  // Otherwise we should reuse the atlas from last time.
+GrCCPathCache::OnFlushEntryRef::~OnFlushEntryRef() {
+    if (!fEntry) {
+        return;
+    }
+    --fEntry->fOnFlushRefCnt;
+    SkASSERT(fEntry->fOnFlushRefCnt >= 0);
+    if (fEntry->fCachedAtlas) {
+        fEntry->fCachedAtlas->decrOnFlushRefCnt();
+    }
+    fEntry->unref();
+}
 
-    fAtlasKey = atlasKey;
+
+void GrCCPathCacheEntry::setCoverageCountAtlas(
+        GrOnFlushResourceProvider* onFlushRP, GrCCAtlas* atlas, const SkIVector& atlasOffset,
+        const SkRect& devBounds, const SkRect& devBounds45, const SkIRect& devIBounds,
+        const SkIVector& maskShift) {
+    SkASSERT(fOnFlushRefCnt > 0);
+    SkASSERT(!fCachedAtlas);  // Otherwise we would need to call releaseCachedAtlas().
+
+    if (this->hasBeenEvicted()) {
+        // This entry will never be found in the path cache again. Don't bother trying to save an
+        // atlas texture for it in the GrResourceCache.
+        return;
+    }
+
+    fCachedAtlas = atlas->refOrMakeCachedAtlas(onFlushRP);
+    fCachedAtlas->incrOnFlushRefCnt(fOnFlushRefCnt);
+    fCachedAtlas->addPathPixels(devIBounds.height() * devIBounds.width());
+
     fAtlasOffset = atlasOffset + maskShift;
-    SkASSERT(!fCachedAtlasInfo);  // Otherwise they should have reused the cached atlas instead.
 
     float dx = (float)maskShift.fX, dy = (float)maskShift.fY;
     fDevBounds = devBounds.makeOffset(-dx, -dy);
@@ -312,34 +375,66 @@
     fDevIBounds = devIBounds.makeOffset(-maskShift.fX, -maskShift.fY);
 }
 
-void GrCCPathCacheEntry::updateToCachedAtlas(const GrUniqueKey& atlasKey,
-                                             const SkIVector& newAtlasOffset,
-                                             sk_sp<GrCCAtlas::CachedAtlasInfo> info) {
-    SkASSERT(atlasKey.isValid());
-    SkASSERT(!fCurrFlushAtlas);  // Otherwise we should reuse the atlas from last time.
+GrCCPathCacheEntry::ReleaseAtlasResult GrCCPathCacheEntry::upgradeToLiteralCoverageAtlas(
+        GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP, GrCCAtlas* atlas,
+        const SkIVector& newAtlasOffset) {
+    SkASSERT(!this->hasBeenEvicted());
+    SkASSERT(fOnFlushRefCnt > 0);
+    SkASSERT(fCachedAtlas);
+    SkASSERT(GrCCAtlas::CoverageType::kFP16_CoverageCount == fCachedAtlas->coverageType());
 
-    fAtlasKey = atlasKey;
+    ReleaseAtlasResult releaseAtlasResult = this->releaseCachedAtlas(pathCache);
+
+    fCachedAtlas = atlas->refOrMakeCachedAtlas(onFlushRP);
+    fCachedAtlas->incrOnFlushRefCnt(fOnFlushRefCnt);
+    fCachedAtlas->addPathPixels(this->height() * this->width());
+
     fAtlasOffset = newAtlasOffset;
-
-    SkASSERT(!fCachedAtlasInfo);  // Otherwise we need to invalidate our pixels in the old info.
-    fCachedAtlasInfo = std::move(info);
-    fCachedAtlasInfo->fNumPathPixels += this->height() * this->width();
+    return releaseAtlasResult;
 }
 
-void GrCCPathCacheEntry::invalidateAtlas() {
-    if (fCachedAtlasInfo) {
-        // Mark our own pixels invalid in the cached atlas texture.
-        fCachedAtlasInfo->fNumInvalidatedPathPixels += this->height() * this->width();
-        if (!fCachedAtlasInfo->fIsPurgedFromResourceCache &&
-            fCachedAtlasInfo->fNumInvalidatedPathPixels >= fCachedAtlasInfo->fNumPathPixels / 2) {
-            // Too many invalidated pixels: purge the atlas texture from the resource cache.
-            // The GrContext and CCPR path cache both share the same unique ID.
-            SkMessageBus<GrUniqueKeyInvalidatedMessage>::Post(
-                    GrUniqueKeyInvalidatedMessage(fAtlasKey, fCachedAtlasInfo->fContextUniqueID));
-            fCachedAtlasInfo->fIsPurgedFromResourceCache = true;
+GrCCPathCacheEntry::ReleaseAtlasResult GrCCPathCacheEntry::releaseCachedAtlas(
+        GrCCPathCache* pathCache) {
+    ReleaseAtlasResult result = ReleaseAtlasResult::kNone;
+    if (fCachedAtlas) {
+        result = fCachedAtlas->invalidatePathPixels(pathCache, this->height() * this->width());
+        if (fOnFlushRefCnt) {
+            SkASSERT(fOnFlushRefCnt > 0);
+            fCachedAtlas->decrOnFlushRefCnt(fOnFlushRefCnt);
         }
+        fCachedAtlas = nullptr;
     }
+    return result;
+}
 
-    fAtlasKey.reset();
-    fCachedAtlasInfo = nullptr;
+GrCCPathCacheEntry::ReleaseAtlasResult GrCCCachedAtlas::invalidatePathPixels(
+        GrCCPathCache* pathCache, int numPixels) {
+    // Mark the pixels invalid in the cached atlas texture.
+    fNumInvalidatedPathPixels += numPixels;
+    SkASSERT(fNumInvalidatedPathPixels <= fNumPathPixels);
+    if (!fIsInvalidatedFromResourceCache && fNumInvalidatedPathPixels >= fNumPathPixels / 2) {
+        // Too many invalidated pixels: purge the atlas texture from the resource cache.
+        if (fOnFlushProxy) {
+            // Don't clear (or std::move) fOnFlushProxy. Other path cache entries might still have a
+            // reference on this atlas and expect to use our proxy during the current flush.
+            // fOnFlushProxy will be cleared once fOnFlushRefCnt decrements to zero.
+            pathCache->fInvalidatedProxies.push_back(fOnFlushProxy);
+        } else {
+            pathCache->fInvalidatedProxyUniqueKeys.push_back(fTextureKey);
+        }
+        fIsInvalidatedFromResourceCache = true;
+        return ReleaseAtlasResult::kDidInvalidateFromCache;
+    }
+    return ReleaseAtlasResult::kNone;
+}
+
+void GrCCCachedAtlas::decrOnFlushRefCnt(int count) const {
+    SkASSERT(count > 0);
+    fOnFlushRefCnt -= count;
+    SkASSERT(fOnFlushRefCnt >= 0);
+    if (0 == fOnFlushRefCnt) {
+        // Don't hold the actual proxy past the end of the current flush.
+        SkASSERT(fOnFlushProxy);
+        fOnFlushProxy = nullptr;
+    }
 }
diff --git a/src/gpu/ccpr/GrCCPathCache.h b/src/gpu/ccpr/GrCCPathCache.h
index 3b34fe2..5fa5d4d 100644
--- a/src/gpu/ccpr/GrCCPathCache.h
+++ b/src/gpu/ccpr/GrCCPathCache.h
@@ -8,6 +8,7 @@
 #ifndef GrCCPathCache_DEFINED
 #define GrCCPathCache_DEFINED
 
+#include "GrShape.h"
 #include "SkExchange.h"
 #include "SkTHash.h"
 #include "SkTInternalLList.h"
@@ -24,7 +25,7 @@
  */
 class GrCCPathCache {
 public:
-    GrCCPathCache();
+    GrCCPathCache(uint32_t contextUniqueID);
     ~GrCCPathCache();
 
     class Key : public SkPathRef::GenIDChangeListener {
@@ -43,7 +44,10 @@
         }
         uint32_t* data();
 
-        bool operator==(const Key&) const;
+        bool operator==(const Key& that) const {
+            return fDataSizeInBytes == that.fDataSizeInBytes &&
+                   !memcmp(this->data(), that.data(), fDataSizeInBytes);
+        }
 
         // Called when our corresponding path is modified or deleted. Not threadsafe.
         void onChange() override;
@@ -76,18 +80,45 @@
 #endif
     };
 
-    enum class CreateIfAbsent : bool {
-        kNo = false,
-        kYes = true
+    // Represents a ref on a GrCCPathCacheEntry that should only be used during the current flush.
+    class OnFlushEntryRef : SkNoncopyable {
+    public:
+        static OnFlushEntryRef OnFlushRef(GrCCPathCacheEntry*);
+        OnFlushEntryRef() = default;
+        OnFlushEntryRef(OnFlushEntryRef&& ref) : fEntry(skstd::exchange(ref.fEntry, nullptr)) {}
+        ~OnFlushEntryRef();
+
+        GrCCPathCacheEntry* get() const { return fEntry; }
+        GrCCPathCacheEntry* operator->() const { return fEntry; }
+        GrCCPathCacheEntry& operator*() const { return *fEntry; }
+        explicit operator bool() const { return fEntry; }
+        void operator=(OnFlushEntryRef&& ref) { fEntry = skstd::exchange(ref.fEntry, nullptr); }
+
+    private:
+        OnFlushEntryRef(GrCCPathCacheEntry* entry) : fEntry(entry) {}
+        GrCCPathCacheEntry* fEntry = nullptr;
     };
 
-    // Finds an entry in the cache. Shapes are only given one entry, so any time they are accessed
-    // with a different MaskTransform, the old entry gets evicted.
-    sk_sp<GrCCPathCacheEntry> find(const GrShape&, const MaskTransform&,
-                                   CreateIfAbsent = CreateIfAbsent::kNo);
+    // Finds an entry in the cache that matches the given shape and transformation matrix.
+    // 'maskShift' is filled with an integer post-translate that the caller must apply when drawing
+    // the entry's mask to the device.
+    //
+    // NOTE: Shapes are only given one entry, so any time they are accessed with a new
+    // transformation, the old entry gets evicted.
+    OnFlushEntryRef find(GrOnFlushResourceProvider*, const GrShape&,
+                         const SkIRect& clippedDrawBounds, const SkMatrix& viewMatrix,
+                         SkIVector* maskShift);
 
-    void doPostFlushProcessing();
-    void purgeEntriesOlderThan(const GrStdSteadyClock::time_point& purgeTime);
+    void doPreFlushProcessing();
+
+    void purgeEntriesOlderThan(GrProxyProvider*, const GrStdSteadyClock::time_point& purgeTime);
+
+    // As we evict entries from our local path cache, we accumulate a list of invalidated atlas
+    // textures. This call purges the invalidated atlas textures from the mainline GrResourceCache.
+    // This call is available with two different "provider" objects, to accomodate whatever might
+    // be available at the callsite.
+    void purgeInvalidatedAtlasTextures(GrOnFlushResourceProvider*);
+    void purgeInvalidatedAtlasTextures(GrProxyProvider*);
 
 private:
     // This is a special ref ptr for GrCCPathCacheEntry, used by the hash table. It provides static
@@ -97,7 +128,9 @@
     class HashNode : SkNoncopyable {
     public:
         static const Key& GetKey(const HashNode&);
-        static uint32_t Hash(const Key&);
+        inline static uint32_t Hash(const Key& key) {
+            return GrResourceKeyHash(key.data(), key.dataSizeInBytes());
+        }
 
         HashNode() = default;
         HashNode(GrCCPathCache*, sk_sp<Key>, const MaskTransform&, const GrShape&);
@@ -108,13 +141,11 @@
 
         ~HashNode();
 
-        HashNode& operator=(HashNode&& node);
+        void operator=(HashNode&& node);
 
         GrCCPathCacheEntry* entry() const { return fEntry.get(); }
 
     private:
-        void willExitHashTable();
-
         GrCCPathCache* fPathCache = nullptr;
         sk_sp<GrCCPathCacheEntry> fEntry;
     };
@@ -127,13 +158,15 @@
         return fPerFlushTimestamp;
     }
 
-    void evict(const GrCCPathCache::Key& key) {
-        fHashTable.remove(key);  // HashNode::willExitHashTable() takes care of the rest.
-    }
+    void evict(const GrCCPathCache::Key&, GrCCPathCacheEntry* = nullptr);
 
-    void purgeInvalidatedKeys();
+    // Evicts all the cache entries whose keys have been queued up in fInvalidatedKeysInbox via
+    // SkPath listeners.
+    void evictInvalidatedCacheKeys();
 
-    SkTHashTable<HashNode, const GrCCPathCache::Key&> fHashTable;
+    const uint32_t fContextUniqueID;
+
+    SkTHashTable<HashNode, const Key&> fHashTable;
     SkTInternalLList<GrCCPathCacheEntry> fLRU;
     SkMessageBus<sk_sp<Key>>::Inbox fInvalidatedKeysInbox;
     sk_sp<Key> fScratchKey;  // Reused for creating a temporary key in the find() method.
@@ -141,6 +174,18 @@
     // We only read the clock once per flush, and cache it in this variable. This prevents us from
     // excessive clock reads for cache timestamps that might degrade performance.
     GrStdSteadyClock::time_point fPerFlushTimestamp = GrStdSteadyClock::time_point::min();
+
+    // As we evict entries from our local path cache, we accumulate lists of invalidated atlas
+    // textures in these two members. We hold these until we purge them from the GrResourceCache
+    // (e.g. via purgeInvalidatedAtlasTextures().)
+    SkSTArray<4, sk_sp<GrTextureProxy>> fInvalidatedProxies;
+    SkSTArray<4, GrUniqueKey> fInvalidatedProxyUniqueKeys;
+
+    friend class GrCCCachedAtlas;  // To append to fInvalidatedProxies, fInvalidatedProxyUniqueKeys.
+
+public:
+    const SkTHashTable<HashNode, const Key&>& testingOnly_getHashTable() const;
+    const SkTInternalLList<GrCCPathCacheEntry>& testingOnly_getLRU() const;
 };
 
 /**
@@ -152,56 +197,46 @@
     SK_DECLARE_INTERNAL_LLIST_INTERFACE(GrCCPathCacheEntry);
 
     ~GrCCPathCacheEntry() {
-        SkASSERT(!fCurrFlushAtlas);  // Client is required to reset fCurrFlushAtlas back to null.
-        this->invalidateAtlas();
+        SkASSERT(this->hasBeenEvicted());  // Should have called GrCCPathCache::evict().
+        SkASSERT(!fCachedAtlas);
+        SkASSERT(0 == fOnFlushRefCnt);
     }
 
-    // The number of times this specific entry (path + matrix combination) has been pulled from
-    // the path cache. As long as the caller does exactly one lookup per draw, this translates to
-    // the number of times the path has been drawn with a compatible matrix.
+    const GrCCPathCache::Key& cacheKey() const { SkASSERT(fCacheKey); return *fCacheKey; }
+
+    // The number of flushes during which this specific entry (path + matrix combination) has been
+    // pulled from the path cache. If a path is pulled from the cache more than once in a single
+    // flush, the hit count is only incremented once.
     //
-    // If the entry did not previously exist and was created during
-    // GrCCPathCache::find(.., CreateIfAbsent::kYes), its hit count will be 1.
+    // If the entry did not previously exist, its hit count will be 1.
     int hitCount() const { return fHitCount; }
 
-    // Does this entry reference a permanent, 8-bit atlas that resides in the resource cache?
-    // (i.e. not a temporarily-stashed, fp16 coverage count atlas.)
-    bool hasCachedAtlas() const { return SkToBool(fCachedAtlasInfo); }
+    // The accumulative region of the path that has been drawn during the lifetime of this cache
+    // entry (as defined by the 'clippedDrawBounds' parameter for GrCCPathCache::find).
+    const SkIRect& hitRect() const { return fHitRect; }
+
+    const GrCCCachedAtlas* cachedAtlas() const { return fCachedAtlas.get(); }
 
     const SkIRect& devIBounds() const { return fDevIBounds; }
     int width() const { return fDevIBounds.width(); }
     int height() const { return fDevIBounds.height(); }
 
+    enum class ReleaseAtlasResult : bool {
+        kNone,
+        kDidInvalidateFromCache
+    };
+
     // Called once our path has been rendered into the mainline CCPR (fp16, coverage count) atlas.
     // The caller will stash this atlas texture away after drawing, and during the next flush,
     // recover it and attempt to copy any paths that got reused into permanent 8-bit atlases.
-    void initAsStashedAtlas(const GrUniqueKey& atlasKey, const SkIVector& atlasOffset,
-                            const SkRect& devBounds, const SkRect& devBounds45,
-                            const SkIRect& devIBounds, const SkIVector& maskShift);
+    void setCoverageCountAtlas(GrOnFlushResourceProvider*, GrCCAtlas*, const SkIVector& atlasOffset,
+                               const SkRect& devBounds, const SkRect& devBounds45,
+                               const SkIRect& devIBounds, const SkIVector& maskShift);
 
     // Called once our path mask has been copied into a permanent, 8-bit atlas. This method points
-    // the entry at the new atlas and updates the CachedAtlasInfo data.
-    void updateToCachedAtlas(const GrUniqueKey& atlasKey, const SkIVector& newAtlasOffset,
-                             sk_sp<GrCCAtlas::CachedAtlasInfo>);
-
-    const GrUniqueKey& atlasKey() const { return fAtlasKey; }
-
-    void resetAtlasKeyAndInfo() {
-        fAtlasKey.reset();
-        fCachedAtlasInfo.reset();
-    }
-
-    // This is a utility for the caller to detect when a path gets drawn more than once during the
-    // same flush, with compatible matrices. Before adding a path to an atlas, the caller may check
-    // here to see if they have already placed the path previously during the same flush. The caller
-    // is required to reset all currFlushAtlas references back to null before any subsequent flush.
-    void setCurrFlushAtlas(const GrCCAtlas* currFlushAtlas) {
-        // This should not get called more than once in a single flush. Once fCurrFlushAtlas is
-        // non-null, it can only be set back to null (once the flush is over).
-        SkASSERT(!fCurrFlushAtlas || !currFlushAtlas);
-        fCurrFlushAtlas = currFlushAtlas;
-    }
-    const GrCCAtlas* currFlushAtlas() const { return fCurrFlushAtlas; }
+    // the entry at the new atlas and updates the GrCCCCachedAtlas data.
+    ReleaseAtlasResult upgradeToLiteralCoverageAtlas(GrCCPathCache*, GrOnFlushResourceProvider*,
+                                                     GrCCAtlas*, const SkIVector& newAtlasOffset);
 
 private:
     using MaskTransform = GrCCPathCache::MaskTransform;
@@ -210,34 +245,120 @@
             : fCacheKey(std::move(cacheKey)), fMaskTransform(maskTransform) {
     }
 
+    bool hasBeenEvicted() const { return fCacheKey->shouldUnregisterFromPath(); }
+
     // Resets this entry back to not having an atlas, and purges its previous atlas texture from the
     // resource cache if needed.
-    void invalidateAtlas();
+    ReleaseAtlasResult releaseCachedAtlas(GrCCPathCache*);
 
     sk_sp<GrCCPathCache::Key> fCacheKey;
-
     GrStdSteadyClock::time_point fTimestamp;
     int fHitCount = 0;
-    MaskTransform fMaskTransform;
+    SkIRect fHitRect = SkIRect::MakeEmpty();
 
-    GrUniqueKey fAtlasKey;
+    sk_sp<GrCCCachedAtlas> fCachedAtlas;
     SkIVector fAtlasOffset;
 
+    MaskTransform fMaskTransform;
     SkRect fDevBounds;
     SkRect fDevBounds45;
     SkIRect fDevIBounds;
 
-    // If null, then we are referencing a "stashed" atlas (see initAsStashedAtlas()).
-    sk_sp<GrCCAtlas::CachedAtlasInfo> fCachedAtlasInfo;
-
-    // This field is for when a path gets drawn more than once during the same flush.
-    const GrCCAtlas* fCurrFlushAtlas = nullptr;
+    int fOnFlushRefCnt = 0;
 
     friend class GrCCPathCache;
     friend void GrCCPathProcessor::Instance::set(const GrCCPathCacheEntry&, const SkIVector&,
                                                  GrColor, DoEvenOddFill);  // To access data.
+
+public:
+    int testingOnly_peekOnFlushRefCnt() const;
 };
 
+/**
+ * Encapsulates the data for an atlas whose texture is stored in the mainline GrResourceCache. Many
+ * instances of GrCCPathCacheEntry will reference the same GrCCCachedAtlas.
+ *
+ * We use this object to track the percentage of the original atlas pixels that could still ever
+ * potentially be reused (i.e., those which still represent an extant path). When the percentage
+ * of useful pixels drops below 50%, we purge the entire texture from the resource cache.
+ *
+ * This object also holds a ref on the atlas's actual texture proxy during flush. When
+ * fOnFlushRefCnt decrements back down to zero, we release fOnFlushProxy and reset it back to null.
+ */
+class GrCCCachedAtlas : public GrNonAtomicRef<GrCCCachedAtlas> {
+public:
+    using ReleaseAtlasResult = GrCCPathCacheEntry::ReleaseAtlasResult;
+
+    GrCCCachedAtlas(GrCCAtlas::CoverageType type, const GrUniqueKey& textureKey,
+                    sk_sp<GrTextureProxy> onFlushProxy)
+            : fCoverageType(type)
+            , fTextureKey(textureKey)
+            , fOnFlushProxy(std::move(onFlushProxy)) {}
+
+    ~GrCCCachedAtlas() {
+        SkASSERT(!fOnFlushProxy);
+        SkASSERT(!fOnFlushRefCnt);
+    }
+
+    GrCCAtlas::CoverageType coverageType() const  { return fCoverageType; }
+    const GrUniqueKey& textureKey() const { return fTextureKey; }
+
+    GrTextureProxy* getOnFlushProxy() const { return fOnFlushProxy.get(); }
+
+    void setOnFlushProxy(sk_sp<GrTextureProxy> proxy) {
+        SkASSERT(!fOnFlushProxy);
+        fOnFlushProxy = std::move(proxy);
+    }
+
+    void addPathPixels(int numPixels) { fNumPathPixels += numPixels; }
+    ReleaseAtlasResult invalidatePathPixels(GrCCPathCache*, int numPixels);
+
+    int peekOnFlushRefCnt() const { return fOnFlushRefCnt; }
+    void incrOnFlushRefCnt(int count = 1) const {
+        SkASSERT(count > 0);
+        SkASSERT(fOnFlushProxy);
+        fOnFlushRefCnt += count;
+    }
+    void decrOnFlushRefCnt(int count = 1) const;
+
+private:
+    const GrCCAtlas::CoverageType fCoverageType;
+    const GrUniqueKey fTextureKey;
+
+    int fNumPathPixels = 0;
+    int fNumInvalidatedPathPixels = 0;
+    bool fIsInvalidatedFromResourceCache = false;
+
+    mutable sk_sp<GrTextureProxy> fOnFlushProxy;
+    mutable int fOnFlushRefCnt = 0;
+
+public:
+    int testingOnly_peekOnFlushRefCnt() const;
+};
+
+
+inline GrCCPathCache::HashNode::HashNode(GrCCPathCache* pathCache, sk_sp<Key> key,
+                                         const MaskTransform& m, const GrShape& shape)
+        : fPathCache(pathCache)
+        , fEntry(new GrCCPathCacheEntry(key, m)) {
+    SkASSERT(shape.hasUnstyledKey());
+    shape.addGenIDChangeListener(std::move(key));
+}
+
+inline const GrCCPathCache::Key& GrCCPathCache::HashNode::GetKey(
+        const GrCCPathCache::HashNode& node) {
+    return *node.entry()->fCacheKey;
+}
+
+inline GrCCPathCache::HashNode::~HashNode() {
+    SkASSERT(!fEntry || fEntry->hasBeenEvicted());  // Should have called GrCCPathCache::evict().
+}
+
+inline void GrCCPathCache::HashNode::operator=(HashNode&& node) {
+    SkASSERT(!fEntry || fEntry->hasBeenEvicted());  // Should have called GrCCPathCache::evict().
+    fEntry = skstd::exchange(node.fEntry, nullptr);
+}
+
 inline void GrCCPathProcessor::Instance::set(const GrCCPathCacheEntry& entry,
                                              const SkIVector& shift, GrColor color,
                                              DoEvenOddFill doEvenOddFill) {
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.cpp b/src/gpu/ccpr/GrCCPerFlushResources.cpp
index 41cd2e2..e6cf8bb 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.cpp
+++ b/src/gpu/ccpr/GrCCPerFlushResources.cpp
@@ -33,8 +33,9 @@
         return RequiresDstTexture::kNo;
     }
     CombineResult onCombineIfPossible(GrOp* other, const GrCaps&) override {
-        SK_ABORT("Only expected one Op per CCPR atlas.");
-        return CombineResult::kMerged;
+        // We will only make multiple copy ops if they have different source proxies.
+        // TODO: make use of texture chaining.
+        return CombineResult::kCannotCombine;
     }
     void onPrepare(GrOpFlushState*) override {}
 
@@ -50,7 +51,7 @@
     const sk_sp<const GrCCPerFlushResources> fResources;
 };
 
-// Copies paths from a stashed coverage count atlas into an 8-bit literal-coverage atlas.
+// Copies paths from a cached coverage count atlas into an 8-bit literal-coverage atlas.
 class CopyAtlasOp : public AtlasOp {
 public:
     DEFINE_OP_CLASS_ID
@@ -66,18 +67,16 @@
     }
 
     const char* name() const override { return "CopyAtlasOp (CCPR)"; }
-    void visitProxies(const VisitProxyFunc& fn, VisitorType) const override {
-        fn(fStashedAtlasProxy.get());
-    }
+    void visitProxies(const VisitProxyFunc& fn, VisitorType) const override { fn(fSrcProxy.get()); }
 
     void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
-        SkASSERT(fStashedAtlasProxy);
+        SkASSERT(fSrcProxy);
         GrPipeline::FixedDynamicState dynamicState;
-        auto atlasProxy = fStashedAtlasProxy.get();
-        dynamicState.fPrimitiveProcessorTextures = &atlasProxy;
+        auto srcProxy = fSrcProxy.get();
+        dynamicState.fPrimitiveProcessorTextures = &srcProxy;
 
         GrPipeline pipeline(flushState->proxy(), GrScissorTest::kDisabled, SkBlendMode::kSrc);
-        GrCCPathProcessor pathProc(atlasProxy);
+        GrCCPathProcessor pathProc(srcProxy);
         pathProc.drawPaths(flushState, pipeline, &dynamicState, *fResources, fBaseInstance,
                            fEndInstance, this->bounds());
     }
@@ -85,15 +84,14 @@
 private:
     friend class ::GrOpMemoryPool; // for ctor
 
-    CopyAtlasOp(sk_sp<const GrCCPerFlushResources> resources, sk_sp<GrTextureProxy> copyProxy,
+    CopyAtlasOp(sk_sp<const GrCCPerFlushResources> resources, sk_sp<GrTextureProxy> srcProxy,
                 int baseInstance, int endInstance, const SkISize& drawBounds)
             : AtlasOp(ClassID(), std::move(resources), drawBounds)
-            , fStashedAtlasProxy(copyProxy)
+            , fSrcProxy(srcProxy)
             , fBaseInstance(baseInstance)
             , fEndInstance(endInstance) {
     }
-
-    sk_sp<GrTextureProxy> fStashedAtlasProxy;
+    sk_sp<GrTextureProxy> fSrcProxy;
     const int fBaseInstance;
     const int fEndInstance;
 };
@@ -161,9 +159,10 @@
         , fStroker(specs.fNumRenderedPaths[kStrokeIdx],
                    specs.fRenderedPathStats[kStrokeIdx].fNumTotalSkPoints,
                    specs.fRenderedPathStats[kStrokeIdx].fNumTotalSkVerbs)
-        , fCopyAtlasStack(kAlpha_8_GrPixelConfig, specs.fCopyAtlasSpecs, onFlushRP->caps())
-        , fRenderedAtlasStack(kAlpha_half_GrPixelConfig, specs.fRenderedAtlasSpecs,
-                              onFlushRP->caps())
+        , fCopyAtlasStack(GrCCAtlas::CoverageType::kA8_LiteralCoverage, specs.fCopyAtlasSpecs,
+                          onFlushRP->caps())
+        , fRenderedAtlasStack(GrCCAtlas::CoverageType::kFP16_CoverageCount,
+                              specs.fRenderedAtlasSpecs, onFlushRP->caps())
         , fIndexBuffer(GrCCPathProcessor::FindIndexBuffer(onFlushRP))
         , fVertexBuffer(GrCCPathProcessor::FindVertexBuffer(onFlushRP))
         , fInstanceBuffer(onFlushRP->makeBuffer(kVertex_GrBufferType,
@@ -190,21 +189,84 @@
     SkDEBUGCODE(fEndPathInstance = inst_buffer_count(specs));
 }
 
-GrCCAtlas* GrCCPerFlushResources::copyPathToCachedAtlas(const GrCCPathCacheEntry& entry,
-                                                        GrCCPathProcessor::DoEvenOddFill evenOdd,
-                                                        SkIVector* newAtlasOffset) {
+void GrCCPerFlushResources::upgradeEntryToLiteralCoverageAtlas(
+        GrCCPathCache* pathCache, GrOnFlushResourceProvider* onFlushRP, GrCCPathCacheEntry* entry,
+        GrCCPathProcessor::DoEvenOddFill evenOdd) {
+    using ReleaseAtlasResult = GrCCPathCacheEntry::ReleaseAtlasResult;
     SkASSERT(this->isMapped());
     SkASSERT(fNextCopyInstanceIdx < fEndCopyInstance);
-    SkASSERT(!entry.hasCachedAtlas());  // Unexpected, but not necessarily a problem.
 
-    if (GrCCAtlas* retiredAtlas = fCopyAtlasStack.addRect(entry.devIBounds(), newAtlasOffset)) {
-        // We did not fit in the previous copy atlas and it was retired. We will render the copies
-        // up until fNextCopyInstanceIdx into the retired atlas during finalize().
-        retiredAtlas->setFillBatchID(fNextCopyInstanceIdx);
+    const GrCCCachedAtlas* cachedAtlas = entry->cachedAtlas();
+    SkASSERT(cachedAtlas);
+    SkASSERT(cachedAtlas->getOnFlushProxy());
+
+    if (GrCCAtlas::CoverageType::kA8_LiteralCoverage == cachedAtlas->coverageType()) {
+        // This entry has already been upgraded to literal coverage. The path must have been drawn
+        // multiple times during the flush.
+        SkDEBUGCODE(--fEndCopyInstance);
+        return;
     }
 
-    fPathInstanceData[fNextCopyInstanceIdx++].set(entry, *newAtlasOffset, GrColor_WHITE, evenOdd);
-    return &fCopyAtlasStack.current();
+    SkIVector newAtlasOffset;
+    if (GrCCAtlas* retiredAtlas = fCopyAtlasStack.addRect(entry->devIBounds(), &newAtlasOffset)) {
+        // We did not fit in the previous copy atlas and it was retired. We will render the ranges
+        // up until fCopyPathRanges.count() into the retired atlas during finalize().
+        retiredAtlas->setFillBatchID(fCopyPathRanges.count());
+        fCurrCopyAtlasRangesIdx = fCopyPathRanges.count();
+    }
+
+    this->recordCopyPathInstance(*entry, newAtlasOffset, evenOdd,
+                                 sk_ref_sp(cachedAtlas->getOnFlushProxy()));
+
+    sk_sp<GrTexture> previousAtlasTexture =
+            sk_ref_sp(cachedAtlas->getOnFlushProxy()->peekTexture());
+    GrCCAtlas* newAtlas = &fCopyAtlasStack.current();
+    if (ReleaseAtlasResult::kDidInvalidateFromCache ==
+            entry->upgradeToLiteralCoverageAtlas(pathCache, onFlushRP, newAtlas, newAtlasOffset)) {
+        // This texture just got booted out of the cache. Keep it around, in case we might be able
+        // to recycle it for a new atlas. We can recycle it because copying happens before rendering
+        // new paths, and every path from the atlas that we're planning to use this flush will be
+        // copied to a new atlas. We'll never copy some and leave others.
+        fRecyclableAtlasTextures.push_back(std::move(previousAtlasTexture));
+    }
+}
+
+template<typename T, typename... Args>
+static void emplace_at_memcpy(SkTArray<T>* array, int idx, Args&&... args) {
+    if (int moveCount = array->count() - idx) {
+        array->push_back();
+        T* location = array->begin() + idx;
+        memcpy(location+1, location, moveCount * sizeof(T));
+        new (location) T(std::forward<Args>(args)...);
+    } else {
+        array->emplace_back(std::forward<Args>(args)...);
+    }
+}
+
+void GrCCPerFlushResources::recordCopyPathInstance(const GrCCPathCacheEntry& entry,
+                                                   const SkIVector& newAtlasOffset,
+                                                   GrCCPathProcessor::DoEvenOddFill evenOdd,
+                                                   sk_sp<GrTextureProxy> srcProxy) {
+    SkASSERT(fNextCopyInstanceIdx < fEndCopyInstance);
+
+    // Write the instance at the back of the array.
+    int currentInstanceIdx = fNextCopyInstanceIdx++;
+    fPathInstanceData[currentInstanceIdx].set(entry, newAtlasOffset, GrColor_WHITE, evenOdd);
+
+    // Percolate the instance forward until it's contiguous with other instances that share the same
+    // proxy.
+    for (int i = fCopyPathRanges.count() - 1; i >= fCurrCopyAtlasRangesIdx; --i) {
+        if (fCopyPathRanges[i].fSrcProxy == srcProxy) {
+            ++fCopyPathRanges[i].fCount;
+            return;
+        }
+        int rangeFirstInstanceIdx = currentInstanceIdx - fCopyPathRanges[i].fCount;
+        std::swap(fPathInstanceData[rangeFirstInstanceIdx], fPathInstanceData[currentInstanceIdx]);
+        currentInstanceIdx = rangeFirstInstanceIdx;
+    }
+
+    // An instance with this particular proxy did not yet exist in the array. Add a range for it.
+    emplace_at_memcpy(&fCopyPathRanges, fCurrCopyAtlasRangesIdx, std::move(srcProxy), 1);
 }
 
 static bool transform_path_pts(const SkMatrix& m, const SkPath& path,
@@ -263,7 +325,7 @@
     return true;
 }
 
-const GrCCAtlas* GrCCPerFlushResources::renderShapeInAtlas(
+GrCCAtlas* GrCCPerFlushResources::renderShapeInAtlas(
         const SkIRect& clipIBounds, const SkMatrix& m, const GrShape& shape, float strokeDevWidth,
         SkRect* devBounds, SkRect* devBounds45, SkIRect* devIBounds, SkIVector* devToAtlasOffset) {
     SkASSERT(this->isMapped());
@@ -361,17 +423,17 @@
 }
 
 bool GrCCPerFlushResources::finalize(GrOnFlushResourceProvider* onFlushRP,
-                                     sk_sp<GrTextureProxy> stashedAtlasProxy,
                                      SkTArray<sk_sp<GrRenderTargetContext>>* out) {
     SkASSERT(this->isMapped());
     SkASSERT(fNextPathInstanceIdx == fEndPathInstance);
-    // No assert for fEndCopyInstance because the caller may have detected and skipped duplicates.
+    SkASSERT(fNextCopyInstanceIdx == fEndCopyInstance);
 
     fInstanceBuffer->unmap();
     fPathInstanceData = nullptr;
 
     if (!fCopyAtlasStack.empty()) {
-        fCopyAtlasStack.current().setFillBatchID(fNextCopyInstanceIdx);
+        fCopyAtlasStack.current().setFillBatchID(fCopyPathRanges.count());
+        fCurrCopyAtlasRangesIdx = fCopyPathRanges.count();
     }
     if (!fRenderedAtlasStack.empty()) {
         fRenderedAtlasStack.current().setFillBatchID(fFiller.closeCurrentBatch());
@@ -387,38 +449,44 @@
         return false;
     }
 
-    // Draw the copies from the stashed atlas into 8-bit cached atlas(es).
+    // Draw the copies from 16-bit literal coverage atlas(es) into 8-bit cached atlas(es).
+    int copyRangeIdx = 0;
     int baseCopyInstance = 0;
     for (GrCCAtlasStack::Iter atlas(fCopyAtlasStack); atlas.next();) {
-        int endCopyInstance = atlas->getFillBatchID();
-        if (endCopyInstance <= baseCopyInstance) {
-            SkASSERT(endCopyInstance == baseCopyInstance);
-            continue;
+        int endCopyRange = atlas->getFillBatchID();
+        SkASSERT(endCopyRange > copyRangeIdx);
+
+        sk_sp<GrRenderTargetContext> rtc = atlas->makeRenderTargetContext(onFlushRP);
+        for (; copyRangeIdx < endCopyRange; ++copyRangeIdx) {
+            const CopyPathRange& copyRange = fCopyPathRanges[copyRangeIdx];
+            int endCopyInstance = baseCopyInstance + copyRange.fCount;
+            if (rtc) {
+                auto op = CopyAtlasOp::Make(rtc->surfPriv().getContext(), sk_ref_sp(this),
+                                            copyRange.fSrcProxy, baseCopyInstance, endCopyInstance,
+                                            atlas->drawBounds());
+                rtc->addDrawOp(GrNoClip(), std::move(op));
+            }
+            baseCopyInstance = endCopyInstance;
         }
-        if (auto rtc = atlas->makeRenderTargetContext(onFlushRP)) {
-            GrContext* ctx = rtc->surfPriv().getContext();
-            auto op = CopyAtlasOp::Make(ctx, sk_ref_sp(this), stashedAtlasProxy, baseCopyInstance,
-                                        endCopyInstance, atlas->drawBounds());
-            rtc->addDrawOp(GrNoClip(), std::move(op));
-            out->push_back(std::move(rtc));
-        }
-        baseCopyInstance = endCopyInstance;
+        out->push_back(std::move(rtc));
     }
+    SkASSERT(fCopyPathRanges.count() == copyRangeIdx);
+    SkASSERT(fNextCopyInstanceIdx == baseCopyInstance);
+    SkASSERT(baseCopyInstance == fEndCopyInstance);
 
     // Render the coverage count atlas(es).
     for (GrCCAtlasStack::Iter atlas(fRenderedAtlasStack); atlas.next();) {
-        // Copies will be finished by the time we get to this atlas. See if we can recycle the
-        // stashed atlas texture instead of creating a new one.
+        // Copies will be finished by the time we get to rendering new atlases. See if we can
+        // recycle any previous invalidated atlas textures instead of creating new ones.
         sk_sp<GrTexture> backingTexture;
-        if (stashedAtlasProxy && atlas->currentWidth() == stashedAtlasProxy->width() &&
-            atlas->currentHeight() == stashedAtlasProxy->height()) {
-            backingTexture = sk_ref_sp(stashedAtlasProxy->peekTexture());
+        for (sk_sp<GrTexture>& texture : fRecyclableAtlasTextures) {
+            if (texture && atlas->currentHeight() == texture->height() &&
+                    atlas->currentWidth() == texture->width()) {
+                backingTexture = skstd::exchange(texture, nullptr);
+                break;
+            }
         }
 
-        // Delete the stashed proxy here. That way, if we can't recycle the stashed atlas texture,
-        // we free this memory prior to allocating a new backing texture.
-        stashedAtlasProxy = nullptr;
-
         if (auto rtc = atlas->makeRenderTargetContext(onFlushRP, std::move(backingTexture))) {
             auto op = RenderAtlasOp::Make(rtc->surfPriv().getContext(), sk_ref_sp(this),
                                           atlas->getFillBatchID(), atlas->getStrokeBatchID(),
@@ -431,23 +499,10 @@
     return true;
 }
 
-void GrCCPerFlushResourceSpecs::convertCopiesToRenders() {
-    for (int i = 0; i < 2; ++i) {
-        fNumRenderedPaths[i] += fNumCopiedPaths[i];
-        fNumCopiedPaths[i] = 0;
-
-        fRenderedPathStats[i].fMaxPointsPerPath =
-               SkTMax(fRenderedPathStats[i].fMaxPointsPerPath, fCopyPathStats[i].fMaxPointsPerPath);
-        fRenderedPathStats[i].fNumTotalSkPoints += fCopyPathStats[i].fNumTotalSkPoints;
-        fRenderedPathStats[i].fNumTotalSkVerbs += fCopyPathStats[i].fNumTotalSkVerbs;
-        fRenderedPathStats[i].fNumTotalConicWeights += fCopyPathStats[i].fNumTotalConicWeights;
-        fCopyPathStats[i] = GrCCRenderedPathStats();
-    }
-
-    fRenderedAtlasSpecs.fApproxNumPixels += fCopyAtlasSpecs.fApproxNumPixels;
-    fRenderedAtlasSpecs.fMinWidth =
-            SkTMax(fRenderedAtlasSpecs.fMinWidth, fCopyAtlasSpecs.fMinWidth);
-    fRenderedAtlasSpecs.fMinHeight =
-            SkTMax(fRenderedAtlasSpecs.fMinHeight, fCopyAtlasSpecs.fMinHeight);
+void GrCCPerFlushResourceSpecs::cancelCopies() {
+    // Convert copies to cached draws.
+    fNumCachedPaths += fNumCopiedPaths[kFillIdx] + fNumCopiedPaths[kStrokeIdx];
+    fNumCopiedPaths[kFillIdx] = fNumCopiedPaths[kStrokeIdx] = 0;
+    fCopyPathStats[kFillIdx] = fCopyPathStats[kStrokeIdx] = GrCCRenderedPathStats();
     fCopyAtlasSpecs = GrCCAtlas::Specs();
 }
diff --git a/src/gpu/ccpr/GrCCPerFlushResources.h b/src/gpu/ccpr/GrCCPerFlushResources.h
index 132068f..f363c16 100644
--- a/src/gpu/ccpr/GrCCPerFlushResources.h
+++ b/src/gpu/ccpr/GrCCPerFlushResources.h
@@ -14,6 +14,7 @@
 #include "ccpr/GrCCStroker.h"
 #include "ccpr/GrCCPathProcessor.h"
 
+class GrCCPathCache;
 class GrCCPathCacheEntry;
 class GrOnFlushResourceProvider;
 class GrShape;
@@ -53,7 +54,8 @@
         return 0 == fNumCachedPaths + fNumCopiedPaths[kFillIdx] + fNumCopiedPaths[kStrokeIdx] +
                     fNumRenderedPaths[kFillIdx] + fNumRenderedPaths[kStrokeIdx] + fNumClipPaths;
     }
-    void convertCopiesToRenders();
+    // Converts the copies to normal cached draws.
+    void cancelCopies();
 };
 
 /**
@@ -67,22 +69,19 @@
 
     bool isMapped() const { return SkToBool(fPathInstanceData); }
 
-    // Copies a path out of the the previous flush's stashed mainline coverage count atlas, and into
-    // a cached, 8-bit, literal-coverage atlas. The actual source texture to copy from will be
-    // provided at the time finalize() is called.
-    GrCCAtlas* copyPathToCachedAtlas(const GrCCPathCacheEntry&, GrCCPathProcessor::DoEvenOddFill,
-                                     SkIVector* newAtlasOffset);
+    // Copies a coverage-counted path out of the given texture proxy, and into a cached, 8-bit,
+    // literal coverage atlas. Updates the cache entry to reference the new atlas.
+    void upgradeEntryToLiteralCoverageAtlas(GrCCPathCache*, GrOnFlushResourceProvider*,
+                                            GrCCPathCacheEntry*, GrCCPathProcessor::DoEvenOddFill);
 
     // These two methods render a path into a temporary coverage count atlas. See
-    // GrCCPathProcessor::Instance for a description of the outputs. The returned atlases are
-    // "const" to prevent the caller from assigning a unique key.
+    // GrCCPathProcessor::Instance for a description of the outputs.
     //
     // strokeDevWidth must be 0 for fills, 1 for hairlines, or the stroke width in device-space
     // pixels for non-hairline strokes (implicitly requiring a rigid-body transform).
-    const GrCCAtlas* renderShapeInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const GrShape&,
-                                        float strokeDevWidth, SkRect* devBounds,
-                                        SkRect* devBounds45, SkIRect* devIBounds,
-                                        SkIVector* devToAtlasOffset);
+    GrCCAtlas* renderShapeInAtlas(const SkIRect& clipIBounds, const SkMatrix&, const GrShape&,
+                                  float strokeDevWidth, SkRect* devBounds, SkRect* devBounds45,
+                                  SkIRect* devIBounds, SkIVector* devToAtlasOffset);
     const GrCCAtlas* renderDeviceSpacePathInAtlas(const SkIRect& clipIBounds, const SkPath& devPath,
                                                   const SkIRect& devPathIBounds,
                                                   SkIVector* devToAtlasOffset);
@@ -100,11 +99,8 @@
         return fPathInstanceData[fNextPathInstanceIdx++];
     }
 
-    // Finishes off the GPU buffers and renders the atlas(es). 'stashedAtlasProxy', if provided, is
-    // the mainline coverage count atlas from the previous flush. It will be used as the source
-    // texture for any copies setup by copyStashedPathToAtlas().
-    bool finalize(GrOnFlushResourceProvider*, sk_sp<GrTextureProxy> stashedAtlasProxy,
-                  SkTArray<sk_sp<GrRenderTargetContext>>* out);
+    // Finishes off the GPU buffers and renders the atlas(es).
+    bool finalize(GrOnFlushResourceProvider*, SkTArray<sk_sp<GrRenderTargetContext>>* out);
 
     // Accessors used by draw calls, once the resources have been finalized.
     const GrCCFiller& filler() const { SkASSERT(!this->isMapped()); return fFiller; }
@@ -113,23 +109,9 @@
     const GrBuffer* vertexBuffer() const { SkASSERT(!this->isMapped()); return fVertexBuffer.get();}
     GrBuffer* instanceBuffer() const { SkASSERT(!this->isMapped()); return fInstanceBuffer.get(); }
 
-    // Returns the mainline coverage count atlas that the client may stash for next flush, if any.
-    // The caller is responsible to call getOrAssignUniqueKey() on this atlas if they wish to
-    // actually stash it in order to copy paths into cached atlases.
-    GrCCAtlas* nextAtlasToStash() {
-        return fRenderedAtlasStack.empty() ? nullptr : &fRenderedAtlasStack.front();
-    }
-
-    // Returs true if the client has called getOrAssignUniqueKey() on our nextAtlasToStash().
-    bool hasStashedAtlas() const {
-        return !fRenderedAtlasStack.empty() && fRenderedAtlasStack.front().uniqueKey().isValid();
-    }
-    const GrUniqueKey& stashedAtlasKey() const  {
-        SkASSERT(this->hasStashedAtlas());
-        return fRenderedAtlasStack.front().uniqueKey();
-    }
-
 private:
+    void recordCopyPathInstance(const GrCCPathCacheEntry&, const SkIVector& newAtlasOffset,
+                                GrCCPathProcessor::DoEvenOddFill, sk_sp<GrTextureProxy> srcProxy);
     bool placeRenderedPathInAtlas(const SkIRect& clipIBounds, const SkIRect& pathIBounds,
                                   GrScissorTest*, SkIRect* clippedPathIBounds,
                                   SkIVector* devToAtlasOffset);
@@ -149,6 +131,30 @@
     SkDEBUGCODE(int fEndCopyInstance);
     int fNextPathInstanceIdx;
     SkDEBUGCODE(int fEndPathInstance);
+
+    // Represents a range of copy-path instances that all share the same source proxy. (i.e. Draw
+    // instances that copy a path mask from a 16-bit coverage count atlas into an 8-bit literal
+    // coverage atlas.)
+    struct CopyPathRange {
+        CopyPathRange() = default;
+        CopyPathRange(sk_sp<GrTextureProxy> srcProxy, int count)
+                : fSrcProxy(std::move(srcProxy)), fCount(count) {}
+        sk_sp<GrTextureProxy> fSrcProxy;
+        int fCount;
+    };
+
+    SkSTArray<4, CopyPathRange> fCopyPathRanges;
+    int fCurrCopyAtlasRangesIdx = 0;
+
+    // This is a list of coverage count atlas textures that have been invalidated due to us copying
+    // their paths into new 8-bit literal coverage atlases. Since copying is finished by the time
+    // we begin rendering new atlases, we can recycle these textures for the rendered atlases rather
+    // than allocating new texture objects upon instantiation.
+    SkSTArray<2, sk_sp<GrTexture>> fRecyclableAtlasTextures;
+
+public:
+    const GrTexture* testingOnly_frontCopyAtlasTexture() const;
+    const GrTexture* testingOnly_frontRenderedAtlasTexture() const;
 };
 
 inline void GrCCRenderedPathStats::statPath(const SkPath& path) {
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
index daf2cf1..901ca38 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -30,15 +30,16 @@
 }
 
 sk_sp<GrCoverageCountingPathRenderer> GrCoverageCountingPathRenderer::CreateIfSupported(
-        const GrCaps& caps, AllowCaching allowCaching) {
+        const GrCaps& caps, AllowCaching allowCaching, uint32_t contextUniqueID) {
     return sk_sp<GrCoverageCountingPathRenderer>((IsSupported(caps))
-            ? new GrCoverageCountingPathRenderer(allowCaching)
+            ? new GrCoverageCountingPathRenderer(allowCaching, contextUniqueID)
             : nullptr);
 }
 
-GrCoverageCountingPathRenderer::GrCoverageCountingPathRenderer(AllowCaching allowCaching) {
+GrCoverageCountingPathRenderer::GrCoverageCountingPathRenderer(AllowCaching allowCaching,
+                                                               uint32_t contextUniqueID) {
     if (AllowCaching::kYes == allowCaching) {
-        fPathCache = skstd::make_unique<GrCCPathCache>();
+        fPathCache = skstd::make_unique<GrCCPathCache>(contextUniqueID);
     }
 }
 
@@ -188,28 +189,16 @@
 void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlushRP,
                                               const uint32_t* opListIDs, int numOpListIDs,
                                               SkTArray<sk_sp<GrRenderTargetContext>>* out) {
-    using DoCopiesToCache = GrCCDrawPathsOp::DoCopiesToCache;
+    using DoCopiesToA8Coverage = GrCCDrawPathsOp::DoCopiesToA8Coverage;
     SkASSERT(!fFlushing);
     SkASSERT(fFlushingPaths.empty());
     SkDEBUGCODE(fFlushing = true);
 
-    // Dig up the stashed atlas from the previous flush (if any) so we can attempt to copy any
-    // reusable paths out of it and into the resource cache. We also need to clear its unique key.
-    sk_sp<GrTextureProxy> stashedAtlasProxy;
-    if (fStashedAtlasKey.isValid()) {
-        stashedAtlasProxy = onFlushRP->findOrCreateProxyByUniqueKey(fStashedAtlasKey,
-                                                                    GrCCAtlas::kTextureOrigin);
-        if (stashedAtlasProxy) {
-            // Instantiate the proxy so we can clear the underlying texture's unique key.
-            onFlushRP->instatiateProxy(stashedAtlasProxy.get());
-            onFlushRP->removeUniqueKeyFromProxy(fStashedAtlasKey, stashedAtlasProxy.get());
-        } else {
-            fStashedAtlasKey.reset();  // Indicate there is no stashed atlas to copy from.
-        }
+    if (fPathCache) {
+        fPathCache->doPreFlushProcessing();
     }
 
     if (fPendingPaths.empty()) {
-        fStashedAtlasKey.reset();
         return;  // Nothing to draw.
     }
 
@@ -233,13 +222,12 @@
         fPendingPaths.erase(iter);
 
         for (GrCCDrawPathsOp* op : fFlushingPaths.back()->fDrawOps) {
-            op->accountForOwnPaths(fPathCache.get(), onFlushRP, fStashedAtlasKey, &specs);
+            op->accountForOwnPaths(fPathCache.get(), onFlushRP, &specs);
         }
         for (const auto& clipsIter : fFlushingPaths.back()->fClipPaths) {
             clipsIter.second.accountForOwnPath(&specs);
         }
     }
-    fStashedAtlasKey.reset();
 
     if (specs.isEmpty()) {
         return;  // Nothing to draw.
@@ -249,12 +237,10 @@
     // copy them to cached atlas(es).
     int numCopies = specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kFillIdx] +
                     specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kStrokeIdx];
-    DoCopiesToCache doCopies = DoCopiesToCache(numCopies > 100 ||
-                                               specs.fCopyAtlasSpecs.fApproxNumPixels > 256 * 256);
-    if (numCopies && DoCopiesToCache::kNo == doCopies) {
-        specs.convertCopiesToRenders();
-        SkASSERT(!specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kFillIdx]);
-        SkASSERT(!specs.fNumCopiedPaths[GrCCPerFlushResourceSpecs::kStrokeIdx]);
+    auto doCopies = DoCopiesToA8Coverage(numCopies > 100 ||
+                                         specs.fCopyAtlasSpecs.fApproxNumPixels > 256 * 256);
+    if (numCopies && DoCopiesToA8Coverage::kNo == doCopies) {
+        specs.cancelCopies();
     }
 
     auto resources = sk_make_sp<GrCCPerFlushResources>(onFlushRP, specs);
@@ -265,19 +251,23 @@
     // Layout the atlas(es) and parse paths.
     for (const auto& flushingPaths : fFlushingPaths) {
         for (GrCCDrawPathsOp* op : flushingPaths->fDrawOps) {
-            op->setupResources(onFlushRP, resources.get(), doCopies);
+            op->setupResources(fPathCache.get(), onFlushRP, resources.get(), doCopies);
         }
         for (auto& clipsIter : flushingPaths->fClipPaths) {
             clipsIter.second.renderPathInAtlas(resources.get(), onFlushRP);
         }
     }
 
+    if (fPathCache) {
+        // Purge invalidated textures from previous atlases *before* calling finalize(). That way,
+        // the underlying textures objects can be freed up and reused for the next atlases.
+        fPathCache->purgeInvalidatedAtlasTextures(onFlushRP);
+    }
+
     // Allocate resources and then render the atlas(es).
-    if (!resources->finalize(onFlushRP, std::move(stashedAtlasProxy), out)) {
+    if (!resources->finalize(onFlushRP, out)) {
         return;
     }
-    // Verify the stashed atlas got released so its texture could be recycled.
-    SkASSERT(!stashedAtlasProxy);
 
     // Commit flushing paths to the resources once they are successfully completed.
     for (auto& flushingPaths : fFlushingPaths) {
@@ -289,15 +279,8 @@
 void GrCoverageCountingPathRenderer::postFlush(GrDeferredUploadToken, const uint32_t* opListIDs,
                                                int numOpListIDs) {
     SkASSERT(fFlushing);
-    SkASSERT(!fStashedAtlasKey.isValid());  // Should have been cleared in preFlush().
 
     if (!fFlushingPaths.empty()) {
-        // Note the stashed atlas's key for next flush, if any.
-        auto resources = fFlushingPaths.front()->fFlushResources.get();
-        if (resources && resources->hasStashedAtlas()) {
-            fStashedAtlasKey = resources->stashedAtlasKey();
-        }
-
         // In DDL mode these aren't guaranteed to be deleted so we must clear out the perFlush
         // resources manually.
         for (auto& flushingPaths : fFlushingPaths) {
@@ -308,17 +291,13 @@
         fFlushingPaths.reset();
     }
 
-    if (fPathCache) {
-        fPathCache->doPostFlushProcessing();
-    }
-
     SkDEBUGCODE(fFlushing = false);
 }
 
 void GrCoverageCountingPathRenderer::purgeCacheEntriesOlderThan(
-        const GrStdSteadyClock::time_point& purgeTime) {
+        GrProxyProvider* proxyProvider, const GrStdSteadyClock::time_point& purgeTime) {
     if (fPathCache) {
-        fPathCache->purgeEntriesOlderThan(purgeTime);
+        fPathCache->purgeEntriesOlderThan(proxyProvider, purgeTime);
     }
 }
 
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
index 554404d..b5fb321 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h
@@ -34,7 +34,8 @@
         kYes = true
     };
 
-    static sk_sp<GrCoverageCountingPathRenderer> CreateIfSupported(const GrCaps&, AllowCaching);
+    static sk_sp<GrCoverageCountingPathRenderer> CreateIfSupported(const GrCaps&, AllowCaching,
+                                                                   uint32_t contextUniqueID);
 
     using PendingPathsMap = std::map<uint32_t, sk_sp<GrCCPerOpListPaths>>;
 
@@ -65,10 +66,7 @@
                   SkTArray<sk_sp<GrRenderTargetContext>>* out) override;
     void postFlush(GrDeferredUploadToken, const uint32_t* opListIDs, int numOpListIDs) override;
 
-    void purgeCacheEntriesOlderThan(const GrStdSteadyClock::time_point& purgeTime);
-
-    void testingOnly_drawPathDirectly(const DrawPathArgs&);
-    const GrUniqueKey& testingOnly_getStashedAtlasKey() const;
+    void purgeCacheEntriesOlderThan(GrProxyProvider*, const GrStdSteadyClock::time_point&);
 
     // If a path spans more pixels than this, we need to crop it or else analytic AA can run out of
     // fp32 precision.
@@ -84,7 +82,7 @@
                                    float* inflationRadius = nullptr);
 
 private:
-    GrCoverageCountingPathRenderer(AllowCaching);
+    GrCoverageCountingPathRenderer(AllowCaching, uint32_t contextUniqueID);
 
     // GrPathRenderer overrides.
     StencilSupport onGetStencilSupport(const GrShape&) const override {
@@ -106,9 +104,13 @@
     SkSTArray<4, sk_sp<GrCCPerOpListPaths>> fFlushingPaths;
 
     std::unique_ptr<GrCCPathCache> fPathCache;
-    GrUniqueKey fStashedAtlasKey;
 
     SkDEBUGCODE(bool fFlushing = false);
+
+public:
+    void testingOnly_drawPathDirectly(const DrawPathArgs&);
+    const GrCCPerFlushResources* testingOnly_getCurrentFlushResources();
+    const GrCCPathCache* testingOnly_getPathCache() const;
 };
 
 #endif
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer_none.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer_none.cpp
index ebc12da..5de32aa 100644
--- a/src/gpu/ccpr/GrCoverageCountingPathRenderer_none.cpp
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer_none.cpp
@@ -12,7 +12,7 @@
 }
 
 sk_sp<GrCoverageCountingPathRenderer> GrCoverageCountingPathRenderer::CreateIfSupported(
-        const GrCaps& caps, AllowCaching allowCaching) {
+        const GrCaps& caps, AllowCaching allowCaching, uint32_t contextUniqueID) {
     return nullptr;
 }
 
diff --git a/src/gpu/effects/GrAtlasedShaderHelpers.h b/src/gpu/effects/GrAtlasedShaderHelpers.h
index caa81fb..a7d445a 100644
--- a/src/gpu/effects/GrAtlasedShaderHelpers.h
+++ b/src/gpu/effects/GrAtlasedShaderHelpers.h
@@ -20,19 +20,31 @@
                                      GrGLSLVarying *uv,
                                      GrGLSLVarying *texIdx,
                                      GrGLSLVarying *st) {
+    using Interpolation = GrGLSLVaryingHandler::Interpolation;
+
+    // This extracts the texture index and texel coordinates from the same variable
     // Packing structure: texel coordinates are multiplied by 2 (or shifted left 1)
     //                    texture index is stored as lower bits of both x and y
-    args.fVertBuilder->codeAppendf("float2 indexTexCoords = float2(%s.x, %s.y);",
-                                    inTexCoordsName, inTexCoordsName);
-    args.fVertBuilder->codeAppend("float2 unormTexCoords = floor(0.5*indexTexCoords);");
-    args.fVertBuilder->codeAppend("float2 diff = indexTexCoords - 2.0*unormTexCoords;");
-    args.fVertBuilder->codeAppend("float texIdx = 2.0*diff.x + diff.y;");
+    if (args.fShaderCaps->integerSupport()) {
+        args.fVertBuilder->codeAppendf("int2 signedCoords = int2(%s.x, %s.y);",
+                                       inTexCoordsName, inTexCoordsName);
+        args.fVertBuilder->codeAppend("int texIdx = 2*(signedCoords.x & 0x1) + (signedCoords.y & 0x1);");
+        args.fVertBuilder->codeAppend("float2 unormTexCoords = float2(signedCoords.x/2, signedCoords.y/2);");
+    } else {
+        args.fVertBuilder->codeAppendf("float2 indexTexCoords = float2(%s.x, %s.y);",
+                                       inTexCoordsName, inTexCoordsName);
+        args.fVertBuilder->codeAppend("float2 unormTexCoords = floor(0.5*indexTexCoords);");
+        args.fVertBuilder->codeAppend("float2 diff = indexTexCoords - 2.0*unormTexCoords;");
+        args.fVertBuilder->codeAppend("float texIdx = 2.0*diff.x + diff.y;");
+    }
 
     // Multiply by 1/atlasSize to get normalized texture coordinates
     args.fVaryingHandler->addVarying("TextureCoords", uv);
     args.fVertBuilder->codeAppendf("%s = unormTexCoords * %s;", uv->vsOut(), atlasSizeInvName);
 
-    args.fVaryingHandler->addVarying("TexIndex", texIdx);
+    args.fVaryingHandler->addVarying("TexIndex", texIdx, args.fShaderCaps->integerSupport()
+                                                                 ? Interpolation::kMustBeFlat
+                                                                 : Interpolation::kCanBeFlat);
     args.fVertBuilder->codeAppendf("%s = texIdx;", texIdx->vsOut());
 
     if (st) {
@@ -46,17 +58,15 @@
                                        const GrGLSLVarying &texIdx,
                                        const char* coordName,
                                        const char* colorName) {
-    // Conditionally load from the indexed texture sampler.
-    // We treat an interval of values around each int as corresponding to that int
-    // (basically round to int) to correct for floating point variation in the frag shader
-    for (int i = numTextureSamplers-1; i > 0; --i) {
-        args.fFragBuilder->codeAppendf("if (%s > %d - 0.5) { %s = ", texIdx.fsIn(), i, colorName);
+    // conditionally load from the indexed texture sampler
+    for (int i = 0; i < numTextureSamplers-1; ++i) {
+        args.fFragBuilder->codeAppendf("if (%s == %d) { %s = ", texIdx.fsIn(), i, colorName);
         args.fFragBuilder->appendTextureLookup(args.fTexSamplers[i], coordName,
                                                kFloat2_GrSLType);
         args.fFragBuilder->codeAppend("; } else ");
     }
     args.fFragBuilder->codeAppendf("{ %s = ", colorName);
-    args.fFragBuilder->appendTextureLookup(args.fTexSamplers[0], coordName,
+    args.fFragBuilder->appendTextureLookup(args.fTexSamplers[numTextureSamplers-1], coordName,
                                            kFloat2_GrSLType);
     args.fFragBuilder->codeAppend("; }");
 }
diff --git a/src/gpu/effects/GrBicubicEffect.cpp b/src/gpu/effects/GrBicubicEffect.cpp
index f17db09..a320b2e 100644
--- a/src/gpu/effects/GrBicubicEffect.cpp
+++ b/src/gpu/effects/GrBicubicEffect.cpp
@@ -106,22 +106,29 @@
 void GrGLBicubicEffect::onSetData(const GrGLSLProgramDataManager& pdman,
                                   const GrFragmentProcessor& processor) {
     const GrBicubicEffect& bicubicEffect = processor.cast<GrBicubicEffect>();
-    GrSurfaceProxy* proxy = processor.textureSampler(0).proxy();
+    GrTextureProxy* proxy = processor.textureSampler(0).proxy();
     GrTexture* texture = proxy->peekTexture();
 
     float imageIncrement[2];
     imageIncrement[0] = 1.0f / texture->width();
     imageIncrement[1] = 1.0f / texture->height();
     pdman.set2fv(fImageIncrementUni, 1, imageIncrement);
-    fDomain.setData(pdman, bicubicEffect.domain(), proxy);
+    fDomain.setData(pdman, bicubicEffect.domain(), proxy,
+                    processor.textureSampler(0).samplerState());
 }
 
 GrBicubicEffect::GrBicubicEffect(sk_sp<GrTextureProxy> proxy,
                                  const SkMatrix& matrix,
-                                 const GrSamplerState::WrapMode wrapModes[2])
-        : INHERITED{kGrBicubicEffect_ClassID, ModulateByConfigOptimizationFlags(proxy->config())}
+                                 const GrSamplerState::WrapMode wrapModes[2],
+                                 GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY)
+        : INHERITED{kGrBicubicEffect_ClassID,
+                    ModulateForSamplerOptFlags(proxy->config(),
+                            GrTextureDomain::IsDecalSampled(wrapModes, modeX,modeY))}
         , fCoordTransform(matrix, proxy.get())
-        , fDomain(GrTextureDomain::IgnoredDomain())
+        , fDomain(proxy.get(),
+                  GrTextureDomain::MakeTexelDomain(
+                          SkIRect::MakeWH(proxy->width(), proxy->height()), modeX, modeY),
+                  modeX, modeY)
         , fTextureSampler(std::move(proxy),
                           GrSamplerState(wrapModes, GrSamplerState::Filter::kNearest)) {
     this->addCoordTransform(&fCoordTransform);
@@ -131,10 +138,13 @@
 GrBicubicEffect::GrBicubicEffect(sk_sp<GrTextureProxy> proxy,
                                  const SkMatrix& matrix,
                                  const SkRect& domain)
-        : INHERITED(kGrBicubicEffect_ClassID, ModulateByConfigOptimizationFlags(proxy->config()))
+        : INHERITED(kGrBicubicEffect_ClassID, ModulateForClampedSamplerOptFlags(proxy->config()))
         , fCoordTransform(matrix, proxy.get())
-        , fDomain(proxy.get(), domain, GrTextureDomain::kClamp_Mode)
+        , fDomain(proxy.get(), domain, GrTextureDomain::kClamp_Mode, GrTextureDomain::kClamp_Mode)
         , fTextureSampler(std::move(proxy)) {
+    // Make sure the sampler's ctor uses the clamp wrap mode
+    SkASSERT(fTextureSampler.samplerState().wrapModeX() == GrSamplerState::WrapMode::kClamp &&
+             fTextureSampler.samplerState().wrapModeY() == GrSamplerState::WrapMode::kClamp);
     this->addCoordTransform(&fCoordTransform);
     this->setTextureSamplerCnt(1);
 }
diff --git a/src/gpu/effects/GrBicubicEffect.h b/src/gpu/effects/GrBicubicEffect.h
index 45c416f..68a63e1 100644
--- a/src/gpu/effects/GrBicubicEffect.h
+++ b/src/gpu/effects/GrBicubicEffect.h
@@ -34,8 +34,24 @@
     static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
                                                      const SkMatrix& matrix,
                                                      const GrSamplerState::WrapMode wrapModes[2]) {
+        // Ignore the domain on x and y, since this factory relies solely on the wrap mode of the
+        // sampler to constrain texture coordinates
+        return Make(std::move(proxy), matrix, wrapModes, GrTextureDomain::kIgnore_Mode,
+                    GrTextureDomain::kIgnore_Mode);
+    }
+
+    /**
+     * Create a Mitchell filter effect with specified texture matrix and x/y tile modes. This
+     * supports providing modes for the texture domain explicitly, in the event that it should
+     * override the behavior of the sampler's tile mode (e.g. clamp to border unsupported).
+     */
+    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy> proxy,
+                                                     const SkMatrix& matrix,
+                                                     const GrSamplerState::WrapMode wrapModes[2],
+                                                     GrTextureDomain::Mode modeX,
+                                                     GrTextureDomain::Mode modeY) {
         return std::unique_ptr<GrFragmentProcessor>(new GrBicubicEffect(std::move(proxy), matrix,
-                                                                        wrapModes));
+                                                                        wrapModes, modeX, modeY));
     }
 
     /**
@@ -60,7 +76,8 @@
 
 private:
     GrBicubicEffect(sk_sp<GrTextureProxy>, const SkMatrix& matrix,
-                    const GrSamplerState::WrapMode wrapModes[2]);
+                    const GrSamplerState::WrapMode wrapModes[2],
+                    GrTextureDomain::Mode modeX, GrTextureDomain::Mode modeY);
     GrBicubicEffect(sk_sp<GrTextureProxy>, const SkMatrix &matrix, const SkRect& domain);
     explicit GrBicubicEffect(const GrBicubicEffect&);
 
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.cpp b/src/gpu/effects/GrBitmapTextGeoProc.cpp
index 0b83c7b..370b0e5 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.cpp
+++ b/src/gpu/effects/GrBitmapTextGeoProc.cpp
@@ -40,7 +40,8 @@
                                                           &atlasSizeInvName);
 
         GrGLSLVarying uv(kFloat2_GrSLType);
-        GrGLSLVarying texIdx(kFloat_GrSLType);
+        GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType;
+        GrGLSLVarying texIdx(texIdxType);
         append_index_uv_varyings(args, btgp.inTextureCoords().name(), atlasSizeInvName, &uv,
                                  &texIdx, nullptr);
 
@@ -120,6 +121,7 @@
 
 GrBitmapTextGeoProc::GrBitmapTextGeoProc(const GrShaderCaps& caps,
                                          const SkPMColor4f& color,
+                                         bool wideColor,
                                          const sk_sp<GrTextureProxy>* proxies,
                                          int numActiveProxies,
                                          const GrSamplerState& params, GrMaskFormat format,
@@ -140,7 +142,7 @@
     bool hasVertexColor = kA8_GrMaskFormat == fMaskFormat ||
                           kA565_GrMaskFormat == fMaskFormat;
     if (hasVertexColor) {
-        fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
+        fInColor = MakeColorAttribute("inColor", wideColor);
     }
 
     fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
@@ -224,6 +226,7 @@
 
     return GrBitmapTextGeoProc::Make(*d->caps()->shaderCaps(),
                                      SkPMColor4f::FromBytes_RGBA(GrRandomColor(d->fRandom)),
+                                     d->fRandom->nextBool(),
                                      proxies, 1, samplerState, format,
                                      GrTest::TestMatrix(d->fRandom), d->fRandom->nextBool());
 }
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.h b/src/gpu/effects/GrBitmapTextGeoProc.h
index ca9b796..30cd38e 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.h
+++ b/src/gpu/effects/GrBitmapTextGeoProc.h
@@ -23,13 +23,14 @@
 public:
     static constexpr int kMaxTextures = 4;
 
-    static sk_sp<GrGeometryProcessor> Make(const GrShaderCaps& caps, const SkPMColor4f& color,
+    static sk_sp<GrGeometryProcessor> Make(const GrShaderCaps& caps,
+                                           const SkPMColor4f& color, bool wideColor,
                                            const sk_sp<GrTextureProxy>* proxies,
                                            int numActiveProxies,
                                            const GrSamplerState& p, GrMaskFormat format,
                                            const SkMatrix& localMatrix, bool usesW) {
         return sk_sp<GrGeometryProcessor>(
-            new GrBitmapTextGeoProc(caps, color, proxies, numActiveProxies, p, format,
+            new GrBitmapTextGeoProc(caps, color, wideColor, proxies, numActiveProxies, p, format,
                                     localMatrix, usesW));
     }
 
@@ -54,7 +55,7 @@
     GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override;
 
 private:
-    GrBitmapTextGeoProc(const GrShaderCaps&, const SkPMColor4f&,
+    GrBitmapTextGeoProc(const GrShaderCaps&, const SkPMColor4f&, bool wideColor,
                         const sk_sp<GrTextureProxy>* proxies, int numProxies,
                         const GrSamplerState& params, GrMaskFormat format,
                         const SkMatrix& localMatrix, bool usesW);
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
index 29c7c47..b0ddd91 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
@@ -68,7 +68,8 @@
 
         // add varyings
         GrGLSLVarying uv(kFloat2_GrSLType);
-        GrGLSLVarying texIdx(kFloat_GrSLType);
+        GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType;
+        GrGLSLVarying texIdx(texIdxType);
         GrGLSLVarying st(kFloat2_GrSLType);
         append_index_uv_varyings(args, dfTexEffect.inTextureCoords().name(), atlasSizeInvName, &uv,
                                  &texIdx, &st);
@@ -229,7 +230,7 @@
     } else {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
     }
-    fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
+    fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType };
     fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
                         caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType};
     this->setVertexAttributes(&fInPosition, 3);
@@ -344,7 +345,8 @@
                                                           &atlasSizeInvName);
 
         GrGLSLVarying uv(kFloat2_GrSLType);
-        GrGLSLVarying texIdx(kFloat_GrSLType);
+        GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType;
+        GrGLSLVarying texIdx(texIdxType);
         GrGLSLVarying st(kFloat2_GrSLType);
         append_index_uv_varyings(args, dfPathEffect.inTextureCoords().name(), atlasSizeInvName, &uv,
                                  &texIdx, &st);
@@ -509,6 +511,7 @@
 
 GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(const GrShaderCaps& caps,
                                                        const SkMatrix& matrix,
+                                                       bool wideColor,
                                                        const sk_sp<GrTextureProxy>* proxies,
                                                        int numProxies,
                                                        const GrSamplerState& params,
@@ -520,7 +523,7 @@
     SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
 
     fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
-    fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
+    fInColor = MakeColorAttribute("inColor", wideColor);
     fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
                         caps.integerSupport() ? kUShort2_GrSLType : kFloat2_GrSLType};
     this->setVertexAttributes(&fInPosition, 3);
@@ -596,6 +599,7 @@
 
     return GrDistanceFieldPathGeoProc::Make(*d->caps()->shaderCaps(),
                                             GrTest::TestMatrix(d->fRandom),
+                                            d->fRandom->nextBool(),
                                             proxies, 1,
                                             samplerState,
                                             flags);
@@ -646,7 +650,8 @@
 
         // set up varyings
         GrGLSLVarying uv(kFloat2_GrSLType);
-        GrGLSLVarying texIdx(kFloat_GrSLType);
+        GrSLType texIdxType = args.fShaderCaps->integerSupport() ? kInt_GrSLType : kFloat_GrSLType;
+        GrGLSLVarying texIdx(texIdxType);
         GrGLSLVarying st(kFloat2_GrSLType);
         append_index_uv_varyings(args, dfTexEffect.inTextureCoords().name(), atlasSizeInvName, &uv,
                                  &texIdx, &st);
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.h b/src/gpu/effects/GrDistanceFieldGeoProc.h
index 3529fe0..386b38a 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.h
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.h
@@ -137,11 +137,13 @@
     /** The local matrix should be identity if local coords are not required by the GrPipeline. */
     static sk_sp<GrGeometryProcessor> Make(const GrShaderCaps& caps,
                                            const SkMatrix& matrix,
+                                           bool wideColor,
                                            const sk_sp<GrTextureProxy>* proxies,
                                            int numActiveProxies,
                                            const GrSamplerState& params, uint32_t flags) {
         return sk_sp<GrGeometryProcessor>(
-            new GrDistanceFieldPathGeoProc(caps, matrix, proxies, numActiveProxies, params, flags));
+            new GrDistanceFieldPathGeoProc(caps, matrix, wideColor, proxies, numActiveProxies,
+                                           params, flags));
     }
 
     ~GrDistanceFieldPathGeoProc() override {}
@@ -164,6 +166,7 @@
 private:
     GrDistanceFieldPathGeoProc(const GrShaderCaps& caps,
                                const SkMatrix& matrix,
+                               bool wideColor,
                                const sk_sp<GrTextureProxy>* proxies,
                                int numActiveProxies,
                                const GrSamplerState&, uint32_t flags);
diff --git a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
index 57006de..9a4c69b 100644
--- a/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
+++ b/src/gpu/effects/GrGaussianConvolutionFragmentProcessor.cpp
@@ -216,12 +216,16 @@
                                                             GrTextureDomain::Mode mode,
                                                             int bounds[2])
         : INHERITED(kGrGaussianConvolutionFragmentProcessor_ClassID,
-                    ModulateByConfigOptimizationFlags(proxy->config()))
+                    ModulateForSamplerOptFlags(proxy->config(),
+                                               mode == GrTextureDomain::kDecal_Mode))
         , fCoordTransform(proxy.get())
         , fTextureSampler(std::move(proxy))
         , fRadius(radius)
         , fDirection(direction)
         , fMode(mode) {
+    // Make sure the sampler's ctor uses the clamp wrap mode
+    SkASSERT(fTextureSampler.samplerState().wrapModeX() == GrSamplerState::WrapMode::kClamp &&
+             fTextureSampler.samplerState().wrapModeY() == GrSamplerState::WrapMode::kClamp);
     this->addCoordTransform(&fCoordTransform);
     this->setTextureSamplerCnt(1);
     SkASSERT(radius <= kMaxKernelRadius);
diff --git a/src/gpu/effects/GrMatrixConvolutionEffect.cpp b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
index b3a3261..bd48c4b 100644
--- a/src/gpu/effects/GrMatrixConvolutionEffect.cpp
+++ b/src/gpu/effects/GrMatrixConvolutionEffect.cpp
@@ -125,7 +125,7 @@
 void GrGLMatrixConvolutionEffect::onSetData(const GrGLSLProgramDataManager& pdman,
                                             const GrFragmentProcessor& processor) {
     const GrMatrixConvolutionEffect& conv = processor.cast<GrMatrixConvolutionEffect>();
-    GrSurfaceProxy* proxy = conv.textureSampler(0).proxy();
+    GrTextureProxy* proxy = conv.textureSampler(0).proxy();
     GrTexture* texture = proxy->peekTexture();
 
     float imageIncrement[2];
@@ -140,7 +140,7 @@
     pdman.set4fv(fKernelUni, arrayCount, conv.kernel());
     pdman.set1f(fGainUni, conv.gain());
     pdman.set1f(fBiasUni, conv.bias());
-    fDomain.setData(pdman, conv.domain(), proxy);
+    fDomain.setData(pdman, conv.domain(), proxy, conv.textureSampler(0).samplerState());
 }
 
 GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(sk_sp<GrTextureProxy> srcProxy,
@@ -156,9 +156,8 @@
         // parameters.
         : INHERITED(kGrMatrixConvolutionEffect_ClassID, kNone_OptimizationFlags)
         , fCoordTransform(srcProxy.get())
-        , fDomain(srcProxy.get(),
-                  GrTextureDomain::MakeTexelDomainForMode(srcBounds, tileMode),
-                  tileMode)
+        , fDomain(srcProxy.get(), GrTextureDomain::MakeTexelDomain(srcBounds, tileMode),
+                  tileMode, tileMode)
         , fTextureSampler(std::move(srcProxy))
         , fKernelSize(kernelSize)
         , fGain(SkScalarToFloat(gain))
diff --git a/src/gpu/effects/GrRRectEffect.cpp b/src/gpu/effects/GrRRectEffect.cpp
index 6478306..5cd54b7 100644
--- a/src/gpu/effects/GrRRectEffect.cpp
+++ b/src/gpu/effects/GrRRectEffect.cpp
@@ -157,7 +157,7 @@
     // edges correspond to components x, y, z, and w, respectively. When a side of the rrect has
     // only rectangular corners, that side's value corresponds to the rect edge's value outset by
     // half a pixel.
-    fInnerRectUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
+    fInnerRectUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
                                                    "innerRect", &rectName);
     // x is (r + .5) and y is 1/(r + .5)
     fRadiusPlusHalfUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf2_GrSLType,
@@ -503,7 +503,7 @@
     GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
     const char *rectName;
     // The inner rect is the rrect bounds inset by the x/y radii
-    fInnerRectUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
+    fInnerRectUniform = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat4_GrSLType,
                                                    "innerRect", &rectName);
 
     GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
diff --git a/src/gpu/effects/GrSimpleTextureEffect.fp b/src/gpu/effects/GrSimpleTextureEffect.fp
index 18ce3a9..06d36d2 100644
--- a/src/gpu/effects/GrSimpleTextureEffect.fp
+++ b/src/gpu/effects/GrSimpleTextureEffect.fp
@@ -46,9 +46,9 @@
 }
 
 @optimizationFlags {
-    kCompatibleWithCoverageAsAlpha_OptimizationFlag |
-    (GrPixelConfigIsOpaque(image->config()) ? kPreservesOpaqueInput_OptimizationFlag :
-                                              kNone_OptimizationFlags)
+    ModulateForSamplerOptFlags(image->config(),
+            samplerParams.wrapModeX() == GrSamplerState::WrapMode::kClampToBorder ||
+            samplerParams.wrapModeY() == GrSamplerState::WrapMode::kClampToBorder)
 }
 
 void main() {
diff --git a/src/gpu/effects/GrSimpleTextureEffect.h b/src/gpu/effects/GrSimpleTextureEffect.h
index fb4e373..5e8730a 100644
--- a/src/gpu/effects/GrSimpleTextureEffect.h
+++ b/src/gpu/effects/GrSimpleTextureEffect.h
@@ -48,10 +48,12 @@
     GrSimpleTextureEffect(sk_sp<GrTextureProxy> image, SkMatrix44 matrix,
                           GrSamplerState samplerParams)
             : INHERITED(kGrSimpleTextureEffect_ClassID,
-                        (OptimizationFlags)kCompatibleWithCoverageAsAlpha_OptimizationFlag |
-                                (GrPixelConfigIsOpaque(image->config())
-                                         ? kPreservesOpaqueInput_OptimizationFlag
-                                         : kNone_OptimizationFlags))
+                        (OptimizationFlags)ModulateForSamplerOptFlags(
+                                image->config(),
+                                samplerParams.wrapModeX() ==
+                                                GrSamplerState::WrapMode::kClampToBorder ||
+                                        samplerParams.wrapModeY() ==
+                                                GrSamplerState::WrapMode::kClampToBorder))
             , fImage(std::move(image), samplerParams)
             , fMatrix(matrix)
             , fImageCoordTransform(matrix, fImage.proxy()) {
diff --git a/src/gpu/effects/GrSkSLFP.h b/src/gpu/effects/GrSkSLFP.h
index c4acfd0..912ba23 100644
--- a/src/gpu/effects/GrSkSLFP.h
+++ b/src/gpu/effects/GrSkSLFP.h
@@ -16,6 +16,7 @@
 #include "SkSLPipelineStageCodeGenerator.h"
 #include "SkRefCnt.h"
 #include "../private/GrSkSLFPFactoryCache.h"
+#include <atomic>
 
 #if GR_TEST_UTILS
 #define GR_FP_SRC_STRING const char*
@@ -33,8 +34,8 @@
      * NewIndex once, statically, and use this index for all calls to Make.
      */
     static int NewIndex() {
-        static int index = 0;
-        return sk_atomic_inc(&index);
+        static std::atomic<int> nextIndex{0};
+        return nextIndex++;
     }
 
     /**
diff --git a/src/gpu/effects/GrTextureDomain.cpp b/src/gpu/effects/GrTextureDomain.cpp
index d8df5ac..c710245 100644
--- a/src/gpu/effects/GrTextureDomain.cpp
+++ b/src/gpu/effects/GrTextureDomain.cpp
@@ -21,26 +21,14 @@
 
 #include <utility>
 
-static bool can_ignore_rect(GrTextureProxy* proxy, const SkRect& domain) {
-    if (GrProxyProvider::IsFunctionallyExact(proxy)) {
-        const SkIRect kFullRect = SkIRect::MakeWH(proxy->width(), proxy->height());
-
-        return domain.contains(kFullRect);
-    }
-
-    return false;
-}
-
-GrTextureDomain::GrTextureDomain(GrTextureProxy* proxy, const SkRect& domain, Mode mode, int index)
-    : fMode(mode)
+GrTextureDomain::GrTextureDomain(GrTextureProxy* proxy, const SkRect& domain, Mode modeX,
+                                 Mode modeY, int index)
+    : fModeX(modeX)
+    , fModeY(modeY)
     , fIndex(index) {
 
-    if (kIgnore_Mode == fMode) {
-        return;
-    }
-
-    if (kClamp_Mode == mode && can_ignore_rect(proxy, domain)) {
-        fMode = kIgnore_Mode;
+    if (!proxy) {
+        SkASSERT(modeX == kIgnore_Mode && modeY == kIgnore_Mode);
         return;
     }
 
@@ -61,6 +49,33 @@
 
 //////////////////////////////////////////////////////////////////////////////
 
+static SkString clamp_expression(GrTextureDomain::Mode mode, const char* inCoord,
+                                 const char* coordSwizzle, const char* domain,
+                                 const char* minSwizzle, const char* maxSwizzle) {
+    SkString clampedExpr;
+    switch(mode) {
+        case GrTextureDomain::kIgnore_Mode:
+            clampedExpr.printf("%s.%s\n", inCoord, coordSwizzle);
+            break;
+        case GrTextureDomain::kDecal_Mode:
+            // The lookup coordinate to use for decal will be clamped just like kClamp_Mode,
+            // it's just that the post-processing will be different, so fall through
+        case GrTextureDomain::kClamp_Mode:
+            clampedExpr.printf("clamp(%s.%s, %s.%s, %s.%s)",
+                               inCoord, coordSwizzle, domain, minSwizzle, domain, maxSwizzle);
+            break;
+        case GrTextureDomain::kRepeat_Mode:
+            clampedExpr.printf("mod(%s.%s - %s.%s, %s.%s - %s.%s) + %s.%s",
+                               inCoord, coordSwizzle, domain, minSwizzle, domain, maxSwizzle,
+                               domain, minSwizzle, domain, minSwizzle);
+            break;
+        default:
+            SkASSERTF(false, "Unknown texture domain mode: %u\n", (uint32_t) mode);
+            break;
+    }
+    return clampedExpr;
+}
+
 void GrTextureDomain::GLDomain::sampleTexture(GrGLSLShaderBuilder* builder,
                                               GrGLSLUniformHandler* uniformHandler,
                                               const GrShaderCaps* shaderCaps,
@@ -69,11 +84,14 @@
                                               const SkString& inCoords,
                                               GrGLSLFragmentProcessor::SamplerHandle sampler,
                                               const char* inModulateColor) {
-    SkASSERT(!fHasMode || textureDomain.mode() == fMode);
-    SkDEBUGCODE(fMode = textureDomain.mode();)
+    SkASSERT(!fHasMode || (textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY));
+    SkDEBUGCODE(fModeX = textureDomain.modeX();)
+    SkDEBUGCODE(fModeY = textureDomain.modeY();)
     SkDEBUGCODE(fHasMode = true;)
 
-    if (textureDomain.mode() != kIgnore_Mode && !fDomainUni.isValid()) {
+    if ((textureDomain.modeX() != kIgnore_Mode || textureDomain.modeY() != kIgnore_Mode) &&
+        !fDomainUni.isValid()) {
+        // Must include the domain uniform since at least one axis uses it
         const char* name;
         SkString uniName("TexDom");
         if (textureDomain.fIndex >= 0) {
@@ -84,89 +102,115 @@
         fDomainName = name;
     }
 
-    switch (textureDomain.mode()) {
-        case kIgnore_Mode: {
-            builder->codeAppendf("%s = ", outColor);
-            builder->appendTextureLookupAndModulate(inModulateColor, sampler, inCoords.c_str(),
-                                                    kFloat2_GrSLType);
-            builder->codeAppend(";");
-            break;
+    bool decalX = textureDomain.modeX() == kDecal_Mode;
+    bool decalY = textureDomain.modeY() == kDecal_Mode;
+    if ((decalX || decalY) && !fDecalUni.isValid()) {
+        const char* name;
+        SkString uniName("DecalParams");
+        if (textureDomain.fIndex >= 0) {
+            uniName.appendS32(textureDomain.fIndex);
         }
-        case kClamp_Mode: {
-            SkString clampedCoords;
-            clampedCoords.appendf("clamp(%s, %s.xy, %s.zw)",
-                                  inCoords.c_str(), fDomainName.c_str(), fDomainName.c_str());
+        // Half3 since this will hold texture width, height, and then a step function control param
+        fDecalUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf3_GrSLType,
+                                               uniName.c_str(), &name);
+        fDecalName = name;
+    }
 
-            builder->codeAppendf("%s = ", outColor);
-            builder->appendTextureLookupAndModulate(inModulateColor, sampler, clampedCoords.c_str(),
-                                                    kFloat2_GrSLType);
-            builder->codeAppend(";");
-            break;
+    // Add a block so that we can declare variables
+    GrGLSLShaderBuilder::ShaderBlock block(builder);
+    // Always use a local variable for the input coordinates; often callers pass in an expression
+    // and we want to cache it across all of its references in the code below
+    builder->codeAppendf("float2 origCoord = %s;", inCoords.c_str());
+    builder->codeAppend("float2 clampedCoord = ");
+    if (textureDomain.modeX() != textureDomain.modeY()) {
+        // The wrap modes differ on the two axes, so build up a coordinate that respects each axis'
+        // domain rule independently before sampling the texture.
+        SkString tcX = clamp_expression(textureDomain.modeX(), "origCoord", "x",
+                                        fDomainName.c_str(), "x", "z");
+        SkString tcY = clamp_expression(textureDomain.modeY(), "origCoord", "y",
+                                        fDomainName.c_str(), "y", "w");
+        builder->codeAppendf("float2(%s, %s)", tcX.c_str(), tcY.c_str());
+    } else {
+        // Since the x and y axis wrap modes are the same, they can be calculated together using
+        // more efficient vector operations
+        SkString tc = clamp_expression(textureDomain.modeX(), "origCoord", "xy",
+                                       fDomainName.c_str(), "xy", "zw");
+        builder->codeAppend(tc.c_str());
+    }
+    builder->codeAppend(";");
+
+    // Look up the texture sample at the clamped coordinate location
+    builder->codeAppend("half4 inside = ");
+    builder->appendTextureLookupAndModulate(inModulateColor, sampler, "clampedCoord",
+                                            kFloat2_GrSLType);
+    builder->codeAppend(";");
+
+    // Apply decal mode's transparency interpolation if needed
+    if (decalX || decalY) {
+        // The decal err is the max absoluate value between the clamped coordinate and the original
+        // pixel coordinate. This will then be clamped to 1.f if it's greater than the control
+        // parameter, which simulates kNearest and kBilerp behavior depending on if it's 0 or 1.
+        if (decalX && decalY) {
+            builder->codeAppendf("half err = max(abs(clampedCoord.x - origCoord.x) * %s.x, "
+                                                "abs(clampedCoord.y - origCoord.y) * %s.y);",
+                                 fDecalName.c_str(), fDecalName.c_str());
+        } else if (decalX) {
+            builder->codeAppendf("half err = abs(clampedCoord.x - origCoord.x) * %s.x;",
+                                 fDecalName.c_str());
+        } else {
+            SkASSERT(decalY);
+            builder->codeAppendf("half err = abs(clampedCoord.y - origCoord.y) * %s.y;",
+                                 fDecalName.c_str());
         }
-        case kDecal_Mode: {
-            // Add a block since we're going to declare variables.
-            GrGLSLShaderBuilder::ShaderBlock block(builder);
 
-            const char* domain = fDomainName.c_str();
-            if (!shaderCaps->canUseAnyFunctionInShader()) {
-                // On the NexusS and GalaxyNexus, the other path (with the 'any'
-                // call) causes the compilation error "Calls to any function that
-                // may require a gradient calculation inside a conditional block
-                // may return undefined results". This appears to be an issue with
-                // the 'any' call since even the simple "result=black; if (any())
-                // result=white;" code fails to compile.
-                builder->codeAppend("half4 outside = half4(0.0, 0.0, 0.0, 0.0);");
-                builder->codeAppend("half4 inside = ");
-                builder->appendTextureLookupAndModulate(inModulateColor, sampler, inCoords.c_str(),
-                                                        kFloat2_GrSLType);
-                builder->codeAppend(";");
-
-                builder->codeAppendf("float x = (%s).x;", inCoords.c_str());
-                builder->codeAppendf("float y = (%s).y;", inCoords.c_str());
-
-                builder->codeAppendf("x = abs(2.0*(x - %s.x)/(%s.z - %s.x) - 1.0);",
-                                     domain, domain, domain);
-                builder->codeAppendf("y = abs(2.0*(y - %s.y)/(%s.w - %s.y) - 1.0);",
-                                     domain, domain, domain);
-                builder->codeAppend("half blend = step(1.0, max(x, y));");
-                builder->codeAppendf("%s = mix(inside, outside, blend);", outColor);
-            } else {
-                builder->codeAppend("bool4 outside;\n");
-                builder->codeAppendf("outside.xy = lessThan(%s, %s.xy);", inCoords.c_str(),
-                                       domain);
-                builder->codeAppendf("outside.zw = greaterThan(%s, %s.zw);", inCoords.c_str(),
-                                       domain);
-                builder->codeAppendf("%s = any(outside) ? half4(0.0, 0.0, 0.0, 0.0) : ",
-                                       outColor);
-                builder->appendTextureLookupAndModulate(inModulateColor, sampler, inCoords.c_str(),
-                                                        kFloat2_GrSLType);
-                builder->codeAppend(";");
-            }
-            break;
-        }
-        case kRepeat_Mode: {
-            SkString clampedCoords;
-            clampedCoords.printf("mod(%s - %s.xy, %s.zw - %s.xy) + %s.xy",
-                                 inCoords.c_str(), fDomainName.c_str(), fDomainName.c_str(),
-                                 fDomainName.c_str(), fDomainName.c_str());
-
-            builder->codeAppendf("%s = ", outColor);
-            builder->appendTextureLookupAndModulate(inModulateColor, sampler, clampedCoords.c_str(),
-                                                    kFloat2_GrSLType);
-            builder->codeAppend(";");
-            break;
-        }
+        // Apply a transform to the error rate, which let's us simulate nearest or bilerp filtering
+        // in the same shader. When the texture is nearest filtered, fSizeName.z is set to 1/2 so
+        // this becomes a step function centered at .5 away from the clamped coordinate (but the
+        // domain for decal is inset by .5 so the edge lines up properly). When bilerp, fSizeName.z
+        // is set to 1 and it becomes a simple linear blend between texture and transparent.
+        builder->codeAppendf("if (err > %s.z) { err = 1.0; } else if (%s.z < 1) { err = 0.0; }",
+                             fDecalName.c_str(), fDecalName.c_str());
+        builder->codeAppendf("%s = mix(inside, half4(0, 0, 0, 0), err);", outColor);
+    } else {
+        // A simple look up
+        builder->codeAppendf("%s = inside;", outColor);
     }
 }
 
 void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
                                         const GrTextureDomain& textureDomain,
-                                        GrSurfaceProxy* proxy) {
+                                        GrTextureProxy* proxy,
+                                        const GrSamplerState& sampler) {
     GrTexture* tex = proxy->peekTexture();
-    SkASSERT(fHasMode && textureDomain.mode() == fMode);
-    if (kIgnore_Mode != textureDomain.mode()) {
-        SkScalar wInv = SK_Scalar1 / tex->width();
-        SkScalar hInv = SK_Scalar1 / tex->height();
+    SkASSERT(fHasMode && textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY);
+    if (kIgnore_Mode != textureDomain.modeX() || kIgnore_Mode != textureDomain.modeY()) {
+        bool sendDecalData = textureDomain.modeX() == kDecal_Mode ||
+                             textureDomain.modeY() == kDecal_Mode;
+
+        // If the texture is using nearest filtering, then the decal filter weight should step from
+        // 0 (texture) to 1 (transparent) one half pixel away from the domain. When doing any other
+        // form of filtering, the weight should be 1.0 so that it smoothly interpolates between the
+        // texture and transparent.
+        SkScalar decalFilterWeight = sampler.filter() == GrSamplerState::Filter::kNearest ?
+                SK_ScalarHalf : 1.0f;
+        SkScalar wInv, hInv, h;
+        if (proxy->textureType() == GrTextureType::kRectangle) {
+            wInv = hInv = 1.f;
+            h = tex->height();
+
+            // Don't do any scaling by texture size for decal filter rate, it's already in pixels
+            if (sendDecalData) {
+                pdman.set3f(fDecalUni, 1.f, 1.f, decalFilterWeight);
+            }
+        } else {
+            wInv = SK_Scalar1 / tex->width();
+            hInv = SK_Scalar1 / tex->height();
+            h = 1.f;
+
+            if (sendDecalData) {
+                pdman.set3f(fDecalUni, tex->width(), tex->height(), decalFilterWeight);
+            }
+        }
 
         float values[kPrevDomainCount] = {
             SkScalarToFloat(textureDomain.domain().fLeft * wInv),
@@ -175,15 +219,23 @@
             SkScalarToFloat(textureDomain.domain().fBottom * hInv)
         };
 
-        SkASSERT(values[0] >= 0.0f && values[0] <= 1.0f);
-        SkASSERT(values[1] >= 0.0f && values[1] <= 1.0f);
-        SkASSERT(values[2] >= 0.0f && values[2] <= 1.0f);
-        SkASSERT(values[3] >= 0.0f && values[3] <= 1.0f);
+        if (proxy->textureType() == GrTextureType::kRectangle) {
+            SkASSERT(values[0] >= 0.0f && values[0] <= proxy->height());
+            SkASSERT(values[1] >= 0.0f && values[1] <= proxy->height());
+            SkASSERT(values[2] >= 0.0f && values[2] <= proxy->height());
+            SkASSERT(values[3] >= 0.0f && values[3] <= proxy->height());
+        } else {
+            SkASSERT(values[0] >= 0.0f && values[0] <= 1.0f);
+            SkASSERT(values[1] >= 0.0f && values[1] <= 1.0f);
+            SkASSERT(values[2] >= 0.0f && values[2] <= 1.0f);
+            SkASSERT(values[3] >= 0.0f && values[3] <= 1.0f);
+        }
 
         // vertical flip if necessary
         if (kBottomLeft_GrSurfaceOrigin == proxy->origin()) {
-            values[1] = 1.0f - values[1];
-            values[3] = 1.0f - values[3];
+            values[1] = h - values[1];
+            values[3] = h - values[3];
+
             // The top and bottom were just flipped, so correct the ordering
             // of elements so that values = (l, t, r, b).
             using std::swap;
@@ -197,15 +249,6 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-inline GrFragmentProcessor::OptimizationFlags GrTextureDomainEffect::OptFlags(
-        GrPixelConfig config, GrTextureDomain::Mode mode) {
-    if (mode == GrTextureDomain::kDecal_Mode || !GrPixelConfigIsOpaque(config)) {
-        return GrFragmentProcessor::kCompatibleWithCoverageAsAlpha_OptimizationFlag;
-    } else {
-        return GrFragmentProcessor::kCompatibleWithCoverageAsAlpha_OptimizationFlag |
-               GrFragmentProcessor::kPreservesOpaqueInput_OptimizationFlag;
-    }
-}
 
 std::unique_ptr<GrFragmentProcessor> GrTextureDomainEffect::Make(
         sk_sp<GrTextureProxy> proxy,
@@ -213,26 +256,39 @@
         const SkRect& domain,
         GrTextureDomain::Mode mode,
         GrSamplerState::Filter filterMode) {
-    if (GrTextureDomain::kIgnore_Mode == mode ||
-        (GrTextureDomain::kClamp_Mode == mode && can_ignore_rect(proxy.get(), domain))) {
-        return GrSimpleTextureEffect::Make(std::move(proxy), matrix, filterMode);
-    } else {
-        return std::unique_ptr<GrFragmentProcessor>(new GrTextureDomainEffect(
-                std::move(proxy), matrix, domain, mode, filterMode));
-    }
+    return Make(std::move(proxy), matrix, domain, mode, mode,
+                GrSamplerState(GrSamplerState::WrapMode::kClamp, filterMode));
+}
+
+std::unique_ptr<GrFragmentProcessor> GrTextureDomainEffect::Make(
+        sk_sp<GrTextureProxy> proxy,
+        const SkMatrix& matrix,
+        const SkRect& domain,
+        GrTextureDomain::Mode modeX,
+        GrTextureDomain::Mode modeY,
+        const GrSamplerState& sampler) {
+    // If both domain modes happen to be ignore, it would be faster to just drop the domain logic
+    // entirely Technically, we could also use the simple texture effect if the domain modes agree
+    // with the sampler modes and the proxy is the same size as the domain. It's a lot easier for
+    // calling code to detect these cases and handle it themselves.
+    return std::unique_ptr<GrFragmentProcessor>(new GrTextureDomainEffect(
+            std::move(proxy), matrix, domain, modeX, modeY, sampler));
 }
 
 GrTextureDomainEffect::GrTextureDomainEffect(sk_sp<GrTextureProxy> proxy,
                                              const SkMatrix& matrix,
                                              const SkRect& domain,
-                                             GrTextureDomain::Mode mode,
-                                             GrSamplerState::Filter filterMode)
-        : INHERITED(kGrTextureDomainEffect_ClassID, OptFlags(proxy->config(), mode))
+                                             GrTextureDomain::Mode modeX,
+                                             GrTextureDomain::Mode modeY,
+                                             const GrSamplerState& sampler)
+        : INHERITED(kGrTextureDomainEffect_ClassID,
+                    ModulateForSamplerOptFlags(proxy->config(),
+                            GrTextureDomain::IsDecalSampled(sampler, modeX, modeY)))
         , fCoordTransform(matrix, proxy.get())
-        , fTextureDomain(proxy.get(), domain, mode)
-        , fTextureSampler(std::move(proxy), filterMode) {
-    SkASSERT(mode != GrTextureDomain::kRepeat_Mode ||
-             filterMode == GrSamplerState::Filter::kNearest);
+        , fTextureDomain(proxy.get(), domain, modeX, modeY)
+        , fTextureSampler(std::move(proxy), sampler) {
+    SkASSERT((modeX != GrTextureDomain::kRepeat_Mode && modeY != GrTextureDomain::kRepeat_Mode) ||
+             sampler.filter() == GrSamplerState::Filter::kNearest);
     this->addCoordTransform(&fCoordTransform);
     this->setTextureSamplerCnt(1);
 }
@@ -276,9 +332,9 @@
                        const GrFragmentProcessor& fp) override {
             const GrTextureDomainEffect& tde = fp.cast<GrTextureDomainEffect>();
             const GrTextureDomain& domain = tde.fTextureDomain;
-            GrSurfaceProxy* proxy = tde.textureSampler(0).proxy();
+            GrTextureProxy* proxy = tde.textureSampler(0).proxy();
 
-            fGLDomain.setData(pdman, domain, proxy);
+            fGLDomain.setData(pdman, domain, proxy, tde.textureSampler(0).samplerState());
         }
 
     private:
@@ -307,16 +363,21 @@
     domain.fRight = d->fRandom->nextRangeScalar(domain.fLeft, proxy->width());
     domain.fTop = d->fRandom->nextRangeScalar(0, proxy->height());
     domain.fBottom = d->fRandom->nextRangeScalar(domain.fTop, proxy->height());
-    GrTextureDomain::Mode mode =
+    GrTextureDomain::Mode modeX =
+        (GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount);
+    GrTextureDomain::Mode modeY =
         (GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount);
     const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
-    bool bilerp = mode != GrTextureDomain::kRepeat_Mode ? d->fRandom->nextBool() : false;
+    bool bilerp = modeX != GrTextureDomain::kRepeat_Mode && modeY != GrTextureDomain::kRepeat_Mode ?
+            d->fRandom->nextBool() : false;
     return GrTextureDomainEffect::Make(
             std::move(proxy),
             matrix,
             domain,
-            mode,
-            bilerp ? GrSamplerState::Filter::kBilerp : GrSamplerState::Filter::kNearest);
+            modeX,
+            modeY,
+            GrSamplerState(GrSamplerState::WrapMode::kClamp, bilerp ?
+                           GrSamplerState::Filter::kBilerp : GrSamplerState::Filter::kNearest));
 }
 #endif
 
@@ -332,8 +393,9 @@
         : INHERITED(kGrDeviceSpaceTextureDecalFragmentProcessor_ClassID,
                     kCompatibleWithCoverageAsAlpha_OptimizationFlag)
         , fTextureSampler(proxy, GrSamplerState::ClampNearest())
-        , fTextureDomain(proxy.get(), GrTextureDomain::MakeTexelDomain(subset),
-                         GrTextureDomain::kDecal_Mode) {
+        , fTextureDomain(proxy.get(),
+                         GrTextureDomain::MakeTexelDomain(subset, GrTextureDomain::kDecal_Mode),
+                         GrTextureDomain::kDecal_Mode, GrTextureDomain::kDecal_Mode) {
     this->setTextureSamplerCnt(1);
     fDeviceSpaceOffset.fX = deviceSpaceOffset.fX - subset.fLeft;
     fDeviceSpaceOffset.fY = deviceSpaceOffset.fY - subset.fTop;
@@ -382,10 +444,11 @@
                        const GrFragmentProcessor& fp) override {
             const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp =
                     fp.cast<GrDeviceSpaceTextureDecalFragmentProcessor>();
-            GrSurfaceProxy* proxy = dstdfp.textureSampler(0).proxy();
+            GrTextureProxy* proxy = dstdfp.textureSampler(0).proxy();
             GrTexture* texture = proxy->peekTexture();
 
-            fGLDomain.setData(pdman, dstdfp.fTextureDomain, proxy);
+            fGLDomain.setData(pdman, dstdfp.fTextureDomain, proxy,
+                              dstdfp.textureSampler(0).samplerState());
             float iw = 1.f / texture->width();
             float ih = 1.f / texture->height();
             float scaleAndTransData[4] = {
diff --git a/src/gpu/effects/GrTextureDomain.h b/src/gpu/effects/GrTextureDomain.h
index 35fb69d..7ff2721 100644
--- a/src/gpu/effects/GrTextureDomain.h
+++ b/src/gpu/effects/GrTextureDomain.h
@@ -45,7 +45,7 @@
 
     static const GrTextureDomain& IgnoredDomain() {
         static const GrTextureDomain gDomain((GrTextureProxy*)nullptr,
-                                             SkRect::MakeEmpty(), kIgnore_Mode);
+                                             SkRect::MakeEmpty(), kIgnore_Mode, kIgnore_Mode);
         return gDomain;
     }
 
@@ -53,28 +53,56 @@
      * @param index     Pass a value >= 0 if using multiple texture domains in the same effect.
      *                  It is used to keep inserted variables from causing name collisions.
      */
-    GrTextureDomain(GrTextureProxy*, const SkRect& domain, Mode, int index = -1);
+    GrTextureDomain(GrTextureProxy*, const SkRect& domain, Mode modeX, Mode modeY, int index = -1);
 
     GrTextureDomain(const GrTextureDomain&) = default;
 
     const SkRect& domain() const { return fDomain; }
-    Mode mode() const { return fMode; }
+    Mode modeX() const { return fModeX; }
+    Mode modeY() const { return fModeY; }
 
-    /* Computes a domain that bounds all the texels in texelRect. Note that with bilerp enabled
-       texels neighboring the domain may be read. */
-    static const SkRect MakeTexelDomain(const SkIRect& texelRect) {
-        return SkRect::Make(texelRect);
+    /*
+     * Computes a domain that bounds all the texels in texelRect, possibly insetting by half a pixel
+     * depending on the mode. The mode is used for both axes.
+     */
+    static const SkRect MakeTexelDomain(const SkIRect& texelRect, Mode mode) {
+        return MakeTexelDomain(texelRect, mode, mode);
     }
 
-    static const SkRect MakeTexelDomainForMode(const SkIRect& texelRect, Mode mode) {
-        // For Clamp mode, inset by half a texel.
-        SkScalar inset = (mode == kClamp_Mode && !texelRect.isEmpty()) ? SK_ScalarHalf : 0;
-        return SkRect::MakeLTRB(texelRect.fLeft + inset, texelRect.fTop + inset,
-                                texelRect.fRight - inset, texelRect.fBottom - inset);
+    static const SkRect MakeTexelDomain(const SkIRect& texelRect, Mode modeX, Mode modeY) {
+        // For Clamp and decal modes, inset by half a texel
+        SkScalar insetX = ((modeX == kClamp_Mode || modeX == kDecal_Mode) && texelRect.width() > 0)
+                ? SK_ScalarHalf : 0;
+        SkScalar insetY = ((modeY == kClamp_Mode || modeY == kDecal_Mode) && texelRect.height() > 0)
+                ? SK_ScalarHalf : 0;
+        return SkRect::MakeLTRB(texelRect.fLeft + insetX, texelRect.fTop + insetY,
+                                texelRect.fRight - insetX, texelRect.fBottom - insetY);
+    }
+
+    // Convenience to determine if any axis of a texture uses an explicit decal mode or the hardware
+    // clamp to border decal mode.
+    static bool IsDecalSampled(GrSamplerState::WrapMode wrapX, GrSamplerState::WrapMode wrapY,
+                               Mode modeX, Mode modeY) {
+        return wrapX == GrSamplerState::WrapMode::kClampToBorder ||
+               wrapY == GrSamplerState::WrapMode::kClampToBorder ||
+               modeX == kDecal_Mode ||
+               modeY == kDecal_Mode;
+    }
+
+    static bool IsDecalSampled(const GrSamplerState::WrapMode wraps[2], Mode modeX, Mode modeY) {
+        return IsDecalSampled(wraps[0], wraps[1], modeX, modeY);
+    }
+
+    static bool IsDecalSampled(const GrSamplerState& sampler, Mode modeX, Mode modeY) {
+        return IsDecalSampled(sampler.wrapModeX(), sampler.wrapModeY(), modeX, modeY);
     }
 
     bool operator==(const GrTextureDomain& that) const {
-        return fMode == that.fMode && (kIgnore_Mode == fMode || fDomain == that.fDomain);
+        return fModeX == that.fModeX && fModeY == that.fModeY &&
+               (kIgnore_Mode == fModeX || (fDomain.fLeft == that.fDomain.fLeft &&
+                                           fDomain.fRight == that.fDomain.fRight)) &&
+               (kIgnore_Mode == fModeY || (fDomain.fTop == that.fDomain.fTop &&
+                                           fDomain.fBottom == that.fDomain.fBottom));
     }
 
     /**
@@ -115,10 +143,12 @@
          * texture domain. The rectangle is automatically adjusted to account for the texture's
          * origin.
          */
-        void setData(const GrGLSLProgramDataManager&, const GrTextureDomain&, GrSurfaceProxy*);
+        void setData(const GrGLSLProgramDataManager&, const GrTextureDomain&, GrTextureProxy*,
+                     const GrSamplerState& sampler);
 
         enum {
-            kDomainKeyBits = 2, // See DomainKey().
+            kModeBits = 2, // See DomainKey().
+            kDomainKeyBits = 4
         };
 
         /**
@@ -126,21 +156,28 @@
          * computed key. The returned will be limited to the lower kDomainKeyBits bits.
          */
         static uint32_t DomainKey(const GrTextureDomain& domain) {
-            GR_STATIC_ASSERT(kModeCount <= (1 << kDomainKeyBits));
-            return domain.mode();
+            GR_STATIC_ASSERT(kModeCount <= (1 << kModeBits));
+            return domain.modeX() | (domain.modeY() << kModeBits);
         }
 
     private:
         static const int kPrevDomainCount = 4;
-        SkDEBUGCODE(Mode                        fMode;)
+        SkDEBUGCODE(Mode                        fModeX;)
+        SkDEBUGCODE(Mode                        fModeY;)
         SkDEBUGCODE(bool                        fHasMode = false;)
         GrGLSLProgramDataManager::UniformHandle fDomainUni;
         SkString                                fDomainName;
+
+        // Only initialized if the domain has at least one decal axis
+        GrGLSLProgramDataManager::UniformHandle fDecalUni;
+        SkString                                fDecalName;
+
         float                                   fPrevDomain[kPrevDomainCount];
     };
 
 protected:
-    Mode    fMode;
+    Mode    fModeX;
+    Mode    fModeY;
     SkRect  fDomain;
     int     fIndex;
 };
@@ -153,9 +190,16 @@
     static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy>,
                                                      const SkMatrix&,
                                                      const SkRect& domain,
-                                                     GrTextureDomain::Mode,
+                                                     GrTextureDomain::Mode mode,
                                                      GrSamplerState::Filter filterMode);
 
+    static std::unique_ptr<GrFragmentProcessor> Make(sk_sp<GrTextureProxy>,
+                                                     const SkMatrix&,
+                                                     const SkRect& domain,
+                                                     GrTextureDomain::Mode modeX,
+                                                     GrTextureDomain::Mode modeY,
+                                                     const GrSamplerState& sampler);
+
     const char* name() const override { return "TextureDomain"; }
 
     std::unique_ptr<GrFragmentProcessor> clone() const override {
@@ -181,13 +225,12 @@
     GrTextureDomainEffect(sk_sp<GrTextureProxy>,
                           const SkMatrix&,
                           const SkRect& domain,
-                          GrTextureDomain::Mode,
-                          GrSamplerState::Filter);
+                          GrTextureDomain::Mode modeX,
+                          GrTextureDomain::Mode modeY,
+                          const GrSamplerState&);
 
     explicit GrTextureDomainEffect(const GrTextureDomainEffect&);
 
-    static OptimizationFlags OptFlags(GrPixelConfig config, GrTextureDomain::Mode mode);
-
     GrGLSLFragmentProcessor* onCreateGLSLInstance() const override;
 
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
diff --git a/src/gpu/effects/GrYUVtoRGBEffect.h b/src/gpu/effects/GrYUVtoRGBEffect.h
index 6cdcce9..3d94402 100644
--- a/src/gpu/effects/GrYUVtoRGBEffect.h
+++ b/src/gpu/effects/GrYUVtoRGBEffect.h
@@ -42,7 +42,8 @@
             fSamplers[i].reset(std::move(proxies[i]),
                                GrSamplerState(GrSamplerState::WrapMode::kClamp, filterModes[i]));
             fSamplerTransforms[i] = SkMatrix::MakeScale(scales[i].width(), scales[i].height());
-            fSamplerCoordTransforms[i].reset(fSamplerTransforms[i], fSamplers[i].proxy(), true);
+            fSamplerCoordTransforms[i] =
+                    GrCoordTransform(fSamplerTransforms[i], fSamplers[i].proxy());
         }
 
         this->setTextureSamplerCnt(numPlanes);
diff --git a/src/gpu/gl/GrGLAssembleInterface.cpp b/src/gpu/gl/GrGLAssembleInterface.cpp
index c55ad74..138a4c1 100644
--- a/src/gpu/gl/GrGLAssembleInterface.cpp
+++ b/src/gpu/gl/GrGLAssembleInterface.cpp
@@ -208,6 +208,8 @@
         GET_PROC(TexBufferRange);
     }
     GET_PROC(TexImage2D);
+    GET_PROC(TexParameterf);
+    GET_PROC(TexParameterfv);
     GET_PROC(TexParameteri);
     GET_PROC(TexParameteriv);
     if (glVer >= GR_GL_VER(4,2) || extensions.has("GL_ARB_texture_storage")) {
@@ -386,10 +388,6 @@
         GET_EGL_PROC_SUFFIX(DestroyImage, KHR);
     }
 
-    if (glVer >= GR_GL_VER(4, 0) || extensions.has("GL_ARB_sample_shading")) {
-        GET_PROC(MinSampleShading);
-    }
-
     if (glVer >= GR_GL_VER(3, 2) || extensions.has("GL_ARB_sync")) {
         GET_PROC(FenceSync);
         GET_PROC(IsSync);
@@ -590,6 +588,8 @@
     }
 
     GET_PROC(TexImage2D);
+    GET_PROC(TexParameterf);
+    GET_PROC(TexParameterfv);
     GET_PROC(TexParameteri);
     GET_PROC(TexParameteriv);
     GET_PROC(TexSubImage2D);
@@ -827,10 +827,6 @@
         GET_EGL_PROC_SUFFIX(DestroyImage, KHR);
     }
 
-    if (extensions.has("GL_OES_sample_shading")) {
-        GET_PROC_SUFFIX(MinSampleShading, OES);
-    }
-
     if (version >= GR_GL_VER(3, 0)) {
         GET_PROC(FenceSync);
         GET_PROC(IsSync);
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 73e1f9d..f76810a 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -19,6 +19,12 @@
 #include "SkTSearch.h"
 #include "SkTSort.h"
 
+#if IS_WEBGL
+static constexpr bool kIsWebGL = true;
+#else
+static constexpr bool kIsWebGL = false;
+#endif
+
 GrGLCaps::GrGLCaps(const GrContextOptions& contextOptions,
                    const GrGLContextInfo& ctxInfo,
                    const GrGLInterface* glInterface) : INHERITED(contextOptions) {
@@ -31,7 +37,6 @@
     fTransferBufferType = kNone_TransferBufferType;
     fMaxFragmentUniformVectors = 0;
     fUnpackRowLengthSupport = false;
-    fUnpackFlipYSupport = false;
     fPackRowLengthSupport = false;
     fPackFlipYSupport = false;
     fTextureUsageSupport = false;
@@ -51,7 +56,7 @@
     fPartialFBOReadIsSlow = false;
     fMipMapLevelAndLodControlSupport = false;
     fRGBAToBGRAReadbackConversionsAreSlow = false;
-    fUseBufferDataNullHint = SkToBool(GR_GL_USE_BUFFER_DATA_NULL_HINT);
+    fUseBufferDataNullHint = false;
     fDoManualMipmapping = false;
     fClearToBoundaryValuesIsBroken = false;
     fClearTextureSupport = false;
@@ -101,13 +106,11 @@
 
     if (kGL_GrGLStandard == standard) {
         fUnpackRowLengthSupport = true;
-        fUnpackFlipYSupport = false;
         fPackRowLengthSupport = true;
         fPackFlipYSupport = false;
     } else {
         fUnpackRowLengthSupport = version >= GR_GL_VER(3,0) ||
                                   ctxInfo.hasExtension("GL_EXT_unpack_subimage");
-        fUnpackFlipYSupport = ctxInfo.hasExtension("GL_CHROMIUM_flipy");
         fPackRowLengthSupport = version >= GR_GL_VER(3,0) ||
                                 ctxInfo.hasExtension("GL_NV_pack_subimage");
         fPackFlipYSupport =
@@ -227,11 +230,7 @@
     if (kGL_GrGLStandard == standard) {
         if (version >= GR_GL_VER(3, 1) || ctxInfo.hasExtension("GL_ARB_texture_rectangle") ||
             ctxInfo.hasExtension("GL_ANGLE_texture_rectangle")) {
-            // We also require textureSize() support for rectangle 2D samplers which was added in
-            // GLSL 1.40.
-            if (ctxInfo.glslGeneration() >= k140_GrGLSLGeneration) {
-                fRectangleTextureSupport = true;
-            }
+            fRectangleTextureSupport = true;
         }
     } else {
         // Command buffer exposes this in GL ES context for Chromium reasons,
@@ -239,6 +238,21 @@
         // lacks TexImage2D support and ANGLE lacks GL ES 3.0 support.
     }
 
+    // GrCaps defaults fClampToBorderSupport to true, so disable when unsupported
+    if (kGL_GrGLStandard == standard) {
+        // Clamp to border added in 1.3
+        if (version < GR_GL_VER(1, 3) && !ctxInfo.hasExtension("GL_ARB_texture_border_clamp")) {
+            fClampToBorderSupport = false;
+        }
+    } else if (kGLES_GrGLStandard == standard) {
+        // GLES didn't have clamp to border until 3.2, but provides several alternative extensions
+        if (version < GR_GL_VER(3, 2) && !ctxInfo.hasExtension("GL_EXT_texture_border_clamp") &&
+            !ctxInfo.hasExtension("GL_NV_texture_border_clamp") &&
+            !ctxInfo.hasExtension("GL_OES_texture_border_clamp")) {
+            fClampToBorderSupport = false;
+        }
+    }
+
     if (kGL_GrGLStandard == standard) {
         if (version >= GR_GL_VER(3,3) || ctxInfo.hasExtension("GL_ARB_texture_swizzle")) {
             fTextureSwizzleSupport = true;
@@ -278,11 +292,9 @@
     // vis-versa.
     fRGBAToBGRAReadbackConversionsAreSlow = isMESA || isMAC;
 
-    if (GrContextOptions::Enable::kNo == contextOptions.fUseGLBufferDataNullHint) {
-        fUseBufferDataNullHint = false;
-    } else if (GrContextOptions::Enable::kYes == contextOptions.fUseGLBufferDataNullHint) {
-        fUseBufferDataNullHint = true;
-    }
+    // Chrome's command buffer will zero out a buffer if null is passed to glBufferData to
+    // avoid letting an application see uninitialized memory.
+    fUseBufferDataNullHint = !kIsWebGL && kChromium_GrGLDriver != ctxInfo.driver();
 
     if (kGL_GrGLStandard == standard) {
         if (version >= GR_GL_VER(4,4) || ctxInfo.hasExtension("GL_ARB_clear_texture")) {
@@ -365,15 +377,15 @@
     GR_GL_GetIntegerv(gli, GR_GL_MAX_TEXTURE_IMAGE_UNITS, &maxSamplers);
     shaderCaps->fMaxFragmentSamplers = SkTMin<GrGLint>(kMaxSaneSamplers, maxSamplers);
 
-    // SGX and Mali GPUs that are based on a tiled-deferred architecture that have trouble with
-    // frequently changing VBOs. We've measured a performance increase using non-VBO vertex
-    // data for dynamic content on these GPUs. Perhaps we should read the renderer string and
-    // limit this decision to specific GPU families rather than basing it on the vendor alone.
-    if (!GR_GL_MUST_USE_VBO &&
-        !fIsCoreProfile &&
-        (kARM_GrGLVendor == ctxInfo.vendor() ||
-         kImagination_GrGLVendor == ctxInfo.vendor() ||
-         kQualcomm_GrGLVendor == ctxInfo.vendor())) {
+    // SGX and Mali GPUs have tiled architectures that have trouble with frequently changing VBOs.
+    // We've measured a performance increase using non-VBO vertex data for dynamic content on these
+    // GPUs. Perhaps we should read the renderer string and limit this decision to specific GPU
+    // families rather than basing it on the vendor alone.
+    // The Chrome command buffer blocks the use of client side buffers (but may emulate VBOs with
+    // them). Client side buffers are not allowed in core profiles.
+    if (ctxInfo.driver() != kChromium_GrGLDriver && !fIsCoreProfile && !kIsWebGL &&
+        (ctxInfo.vendor() == kARM_GrGLVendor || ctxInfo.vendor() == kImagination_GrGLVendor ||
+         ctxInfo.vendor() == kQualcomm_GrGLVendor)) {
         fPreferClientSideDynamicBuffers = true;
     }
 
@@ -549,14 +561,6 @@
         fDrawRangeElementsSupport = version >= GR_GL_VER(3,0);
     }
 
-    if (kGL_GrGLStandard == standard) {
-        if ((version >= GR_GL_VER(4, 0) || ctxInfo.hasExtension("GL_ARB_sample_shading"))) {
-            fSampleShadingSupport = true;
-        }
-    } else if (ctxInfo.hasExtension("GL_OES_sample_shading")) {
-        fSampleShadingSupport = true;
-    }
-
     // TODO: support CHROMIUM_sync_point and maybe KHR_fence_sync
     if (kGL_GrGLStandard == standard) {
         if (version >= GR_GL_VER(3, 2) || ctxInfo.hasExtension("GL_ARB_sync")) {
@@ -1132,7 +1136,6 @@
     writer->appendString("Map Buffer Type", kMapBufferTypeStr[fMapBufferType]);
     writer->appendS32("Max FS Uniform Vectors", fMaxFragmentUniformVectors);
     writer->appendBool("Unpack Row length support", fUnpackRowLengthSupport);
-    writer->appendBool("Unpack Flip Y support", fUnpackFlipYSupport);
     writer->appendBool("Pack Row length support", fPackRowLengthSupport);
     writer->appendBool("Pack Flip Y support", fPackFlipYSupport);
 
@@ -1303,7 +1306,7 @@
 
           ES 2.0
             color renderable: RGBA4, RGB5_A1, RGB565
-            GL_EXT_texture_rg adds support for R8, RG5 as a color render target
+            GL_EXT_texture_rg adds support for R8, RG8 as a color render target
             GL_OES_rgb8_rgba8 adds support for RGB8 and RGBA8
             GL_ARM_rgba8 adds support for RGBA8 (but not RGB8)
             GL_EXT_texture_format_BGRA8888 does not add renderbuffer support
@@ -1418,12 +1421,8 @@
         // We require some form of FBO support and all GLs with FBO support can render to RGBA8
         fConfigTable[kRGBA_8888_GrPixelConfig].fFlags |= allRenderFlags;
     } else {
-        bool isWebGL = false;
-        // hack for skbug:8378
-        #if IS_WEBGL==1
-        isWebGL = true;
-        #endif
-        if (isWebGL || version >= GR_GL_VER(3,0) || ctxInfo.hasExtension("GL_OES_rgb8_rgba8") ||
+        // hack for skbug:8378 - assume support on WebGL.
+        if (kIsWebGL || version >= GR_GL_VER(3,0) || ctxInfo.hasExtension("GL_OES_rgb8_rgba8") ||
             ctxInfo.hasExtension("GL_ARM_rgba8")) {
             fConfigTable[kRGBA_8888_GrPixelConfig].fFlags |= allRenderFlags;
         }
@@ -1467,6 +1466,27 @@
         fConfigTable[kRGB_888_GrPixelConfig].fFlags = 0;
     }
 
+    // ES2 Command Buffer has several TexStorage restrictions. It appears to fail for any format
+    // not explicitly allowed by GL_EXT_texture_storage, particularly those from other extensions.
+    bool isCommandBufferES2 = kChromium_GrGLDriver == ctxInfo.driver() && version < GR_GL_VER(3, 0);
+
+    fConfigTable[kRG_88_GrPixelConfig].fFormats.fBaseInternalFormat = GR_GL_RG;
+    fConfigTable[kRG_88_GrPixelConfig].fFormats.fSizedInternalFormat = GR_GL_RG8;
+    fConfigTable[kRG_88_GrPixelConfig].fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] =
+        GR_GL_RG;
+    fConfigTable[kRG_88_GrPixelConfig].fFormats.fExternalType = GR_GL_UNSIGNED_BYTE;
+    fConfigTable[kRG_88_GrPixelConfig].fFormatType = kNormalizedFixedPoint_FormatType;
+    if (textureRedSupport) {
+        fConfigTable[kRG_88_GrPixelConfig].fFlags = ConfigInfo::kTextureable_Flag | allRenderFlags;
+        // ES2 Command Buffer does not allow TexStorage with RG8_EXT
+        if (texStorageSupported && !isCommandBufferES2) {
+            fConfigTable[kRG_88_GrPixelConfig].fFlags |= ConfigInfo::kCanUseTexStorage_Flag;
+        }
+    } else {
+        fConfigTable[kRG_88_GrPixelConfig].fFlags = 0;
+    }
+    fConfigTable[kRG_88_GrPixelConfig].fSwizzle = GrSwizzle::RGRG();
+
     fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fExternalFormat[kReadPixels_ExternalFormatUsage] =
         GR_GL_BGRA;
     fConfigTable[kBGRA_8888_GrPixelConfig].fFormats.fExternalType  = GR_GL_UNSIGNED_BYTE;
@@ -1560,10 +1580,6 @@
         fSRGBSupport = false;
     }
 
-    // ES2 Command Buffer has several TexStorage restrictions. It appears to fail for any format
-    // not explicitly allowed by GL_EXT_texture_storage, particularly those from other extensions.
-    bool isCommandBufferES2 = kChromium_GrGLDriver == ctxInfo.driver() && version < GR_GL_VER(3, 0);
-
     uint32_t srgbRenderFlags = allRenderFlags;
     if (disableSRGBRenderWithMSAAForMacAMD) {
         srgbRenderFlags &= ~ConfigInfo::kRenderableWithMSAA_Flag;
@@ -2176,8 +2192,8 @@
            !rt->rtPriv().glRTFBOIDIs0();
 }
 
-bool GrGLCaps::canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
-                              const SkIRect& srcRect, const SkIPoint& dstPoint) const {
+bool GrGLCaps::onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                                const SkIRect& srcRect, const SkIPoint& dstPoint) const {
     GrSurfaceOrigin dstOrigin = dst->origin();
     GrSurfaceOrigin srcOrigin = src->origin();
 
@@ -2493,10 +2509,6 @@
         fUseDrawInsteadOfAllRenderTargetWrites = true;
     }
 
-    if (kGL_GrGLStandard == ctxInfo.standard() && kIntel_GrGLVendor == ctxInfo.vendor() ) {
-        fSampleShadingSupport = false;
-    }
-
 #ifdef SK_BUILD_FOR_MAC
     static constexpr bool isMAC = true;
 #else
@@ -2760,7 +2772,7 @@
     }
 }
 
-bool GrGLCaps::surfaceSupportsWritePixels(const GrSurface* surface) const {
+bool GrGLCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const {
     if (fDisallowTexSubImageForUnormConfigTexturesEverBoundToFBO) {
         if (auto tex = static_cast<const GrGLTexture*>(surface->asTexture())) {
             if (tex->hasBaseLevelBeenBoundToFBO()) {
@@ -2855,160 +2867,132 @@
     return count;
 }
 
-bool validate_sized_format(GrGLenum format, SkColorType ct, GrPixelConfig* config,
-                           GrGLStandard standard) {
-    *config = kUnknown_GrPixelConfig;
-
+GrPixelConfig validate_sized_format(GrGLenum format, SkColorType ct, GrGLStandard standard) {
     switch (ct) {
         case kUnknown_SkColorType:
-            return false;
+            return kUnknown_GrPixelConfig;
         case kAlpha_8_SkColorType:
             if (GR_GL_ALPHA8 == format) {
-                *config = kAlpha_8_as_Alpha_GrPixelConfig;
+                return kAlpha_8_as_Alpha_GrPixelConfig;
             } else if (GR_GL_R8 == format) {
-                *config = kAlpha_8_as_Red_GrPixelConfig;
+                return kAlpha_8_as_Red_GrPixelConfig;
             }
             break;
         case kRGB_565_SkColorType:
             if (GR_GL_RGB565 == format) {
-                *config = kRGB_565_GrPixelConfig;
+                return kRGB_565_GrPixelConfig;
             }
             break;
         case kARGB_4444_SkColorType:
             if (GR_GL_RGBA4 == format) {
-                *config = kRGBA_4444_GrPixelConfig;
+                return kRGBA_4444_GrPixelConfig;
             }
             break;
         case kRGBA_8888_SkColorType:
             if (GR_GL_RGBA8 == format) {
-                *config = kRGBA_8888_GrPixelConfig;
+                return kRGBA_8888_GrPixelConfig;
             } else if (GR_GL_SRGB8_ALPHA8 == format) {
-                *config = kSRGBA_8888_GrPixelConfig;
+                return kSRGBA_8888_GrPixelConfig;
             }
             break;
         case kRGB_888x_SkColorType:
             if (GR_GL_RGB8 == format) {
-                *config = kRGB_888_GrPixelConfig;
+                return kRGB_888_GrPixelConfig;
             }
             break;
         case kBGRA_8888_SkColorType:
             if (GR_GL_RGBA8 == format) {
                 if (kGL_GrGLStandard == standard) {
-                    *config = kBGRA_8888_GrPixelConfig;
+                    return kBGRA_8888_GrPixelConfig;
                 }
             } else if (GR_GL_BGRA8 == format) {
                 if (kGLES_GrGLStandard == standard) {
-                    *config = kBGRA_8888_GrPixelConfig;
+                    return kBGRA_8888_GrPixelConfig;
                 }
             } else if (GR_GL_SRGB8_ALPHA8 == format) {
-                *config = kSBGRA_8888_GrPixelConfig;
+                return kSBGRA_8888_GrPixelConfig;
             }
             break;
         case kRGBA_1010102_SkColorType:
             if (GR_GL_RGB10_A2 == format) {
-                *config = kRGBA_1010102_GrPixelConfig;
+                return kRGBA_1010102_GrPixelConfig;
             }
             break;
         case kRGB_101010x_SkColorType:
-            return false;
+            break;
         case kGray_8_SkColorType:
             if (GR_GL_LUMINANCE8 == format) {
-                *config = kGray_8_as_Lum_GrPixelConfig;
+                return kGray_8_as_Lum_GrPixelConfig;
             } else if (GR_GL_R8 == format) {
-                *config = kGray_8_as_Red_GrPixelConfig;
+                return kGray_8_as_Red_GrPixelConfig;
             }
             break;
         case kRGBA_F16_SkColorType:
             if (GR_GL_RGBA16F == format) {
-                *config = kRGBA_half_GrPixelConfig;
+                return kRGBA_half_GrPixelConfig;
             }
             break;
         case kRGBA_F32_SkColorType:
             if (GR_GL_RGBA32F == format) {
-                *config = kRGBA_float_GrPixelConfig;
+                return kRGBA_float_GrPixelConfig;
             }
             break;
     }
 
-    return kUnknown_GrPixelConfig != *config;
+    return kUnknown_GrPixelConfig;
 }
 
-bool GrGLCaps::validateBackendTexture(const GrBackendTexture& tex, SkColorType ct,
-                                      GrPixelConfig* config) const {
-    GrGLTextureInfo texInfo;
-    if (!tex.getGLTextureInfo(&texInfo)) {
-        return false;
-    }
-    return validate_sized_format(texInfo.fFormat, ct, config, fStandard);
-}
-
-bool GrGLCaps::validateBackendRenderTarget(const GrBackendRenderTarget& rt, SkColorType ct,
-                                           GrPixelConfig* config) const {
+GrPixelConfig GrGLCaps::validateBackendRenderTarget(const GrBackendRenderTarget& rt,
+                                                    SkColorType ct) const {
     GrGLFramebufferInfo fbInfo;
     if (!rt.getGLFramebufferInfo(&fbInfo)) {
-        return false;
+        return kUnknown_GrPixelConfig;
     }
-    return validate_sized_format(fbInfo.fFormat, ct, config, fStandard);
+    return validate_sized_format(fbInfo.fFormat, ct, fStandard);
 }
 
-bool GrGLCaps::getConfigFromBackendFormat(const GrBackendFormat& format, SkColorType ct,
-                                          GrPixelConfig* config) const {
+GrPixelConfig GrGLCaps::getConfigFromBackendFormat(const GrBackendFormat& format,
+                                                   SkColorType ct) const {
     const GrGLenum* glFormat = format.getGLFormat();
     if (!glFormat) {
-        return false;
+        return kUnknown_GrPixelConfig;
     }
-    return validate_sized_format(*glFormat, ct, config, fStandard);
+    return validate_sized_format(*glFormat, ct, fStandard);
 }
 
-static bool get_yuva_config(GrGLenum format, GrPixelConfig* config) {
-    *config = kUnknown_GrPixelConfig;
+static GrPixelConfig get_yuva_config(GrGLenum format) {
+    GrPixelConfig config = kUnknown_GrPixelConfig;
 
     switch (format) {
-    case GR_GL_ALPHA8:
-        *config = kAlpha_8_as_Alpha_GrPixelConfig;
-        break;
-    case GR_GL_R8:
-        *config = kAlpha_8_as_Red_GrPixelConfig;
-        break;
-    case GR_GL_RGBA8:
-        *config = kRGBA_8888_GrPixelConfig;
-        break;
-    case GR_GL_RGB8:
-        *config = kRGB_888_GrPixelConfig;
-        break;
-    case GR_GL_BGRA8:
-        *config = kBGRA_8888_GrPixelConfig;
-        break;
-    default:
-        return false;
+        case GR_GL_ALPHA8:
+            config = kAlpha_8_as_Alpha_GrPixelConfig;
+            break;
+        case GR_GL_R8:
+            config = kAlpha_8_as_Red_GrPixelConfig;
+            break;
+        case GR_GL_RG8:
+            config = kRG_88_GrPixelConfig;
+            break;
+        case GR_GL_RGBA8:
+            config = kRGBA_8888_GrPixelConfig;
+            break;
+        case GR_GL_RGB8:
+            config = kRGB_888_GrPixelConfig;
+            break;
+        case GR_GL_BGRA8:
+            config = kBGRA_8888_GrPixelConfig;
+            break;
     }
 
-    return true;
+    return config;
 }
 
-bool GrGLCaps::getYUVAConfigFromBackendTexture(const GrBackendTexture& tex,
-                                               GrPixelConfig* config) const {
-    GrGLTextureInfo texInfo;
-    if (!tex.getGLTextureInfo(&texInfo)) {
-        return false;
-    }
-    return get_yuva_config(texInfo.fFormat, config);
-}
-
-bool GrGLCaps::getYUVAConfigFromBackendFormat(const GrBackendFormat& format,
-                                              GrPixelConfig* config) const {
+GrPixelConfig GrGLCaps::getYUVAConfigFromBackendFormat(const GrBackendFormat& format) const {
     const GrGLenum* glFormat = format.getGLFormat();
     if (!glFormat) {
-        return false;
+        return kUnknown_GrPixelConfig;
     }
-    return get_yuva_config(*glFormat, config);
-}
-
-GrBackendFormat GrGLCaps::onCreateFormatFromBackendTexture(
-        const GrBackendTexture& backendTex) const {
-    GrGLTextureInfo glInfo;
-    SkAssertResult(backendTex.getGLTextureInfo(&glInfo));
-    return GrBackendFormat::MakeGL(glInfo.fFormat, glInfo.fTarget);
+    return get_yuva_config(*glFormat);
 }
 
 GrBackendFormat GrGLCaps::getBackendFormatFromGrColorType(GrColorType ct,
diff --git a/src/gpu/gl/GrGLCaps.h b/src/gpu/gl/GrGLCaps.h
index 4cdd316..038a75c 100644
--- a/src/gpu/gl/GrGLCaps.h
+++ b/src/gpu/gl/GrGLCaps.h
@@ -264,9 +264,6 @@
     /// Is there support for GL_UNPACK_ROW_LENGTH
     bool unpackRowLengthSupport() const { return fUnpackRowLengthSupport; }
 
-    /// Is there support for GL_UNPACK_FLIP_Y
-    bool unpackFlipYSupport() const { return fUnpackFlipYSupport; }
-
     /// Is there support for GL_PACK_ROW_LENGTH
     bool packRowLengthSupport() const { return fPackRowLengthSupport; }
 
@@ -311,7 +308,6 @@
     /// Use indices or vertices in CPU arrays rather than VBOs for dynamic content.
     bool useNonVBOVertexAndIndexDynamicData() const { return fUseNonVBOVertexAndIndexDynamicData; }
 
-    bool surfaceSupportsWritePixels(const GrSurface*) const override;
     bool surfaceSupportsReadPixels(const GrSurface*) const override;
     GrColorType supportedReadPixelsColorType(GrPixelConfig, GrColorType) const override;
 
@@ -419,9 +415,6 @@
                        const SkIRect& srcRect, const SkIPoint& dstPoint) const;
     bool canCopyAsDraw(GrPixelConfig dstConfig, bool srcIsTextureable) const;
 
-    bool canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
-                        const SkIRect& srcRect, const SkIPoint& dstPoint) const override;
-
     bool initDescForDstCopy(const GrRenderTargetProxy* src, GrSurfaceDesc* desc, GrSurfaceOrigin*,
                             bool* rectsMustMatch, bool* disallowSubrect) const override;
 
@@ -429,17 +422,11 @@
 
     bool samplerObjectSupport() const { return fSamplerObjectSupport; }
 
-    bool validateBackendTexture(const GrBackendTexture&, SkColorType,
-                                GrPixelConfig*) const override;
-    bool validateBackendRenderTarget(const GrBackendRenderTarget&, SkColorType,
-                                     GrPixelConfig*) const override;
+    GrPixelConfig validateBackendRenderTarget(const GrBackendRenderTarget&,
+                                              SkColorType) const override;
 
-    bool getConfigFromBackendFormat(const GrBackendFormat&, SkColorType,
-                                    GrPixelConfig*) const override;
-    bool getYUVAConfigFromBackendTexture(const GrBackendTexture&,
-                                         GrPixelConfig*) const override;
-    bool getYUVAConfigFromBackendFormat(const GrBackendFormat&,
-                                        GrPixelConfig*) const override;
+    GrPixelConfig getConfigFromBackendFormat(const GrBackendFormat&, SkColorType) const override;
+    GrPixelConfig getYUVAConfigFromBackendFormat(const GrBackendFormat&) const override;
 
     GrBackendFormat getBackendFormatFromGrColorType(GrColorType ct,
                                                     GrSRGBEncoded srgbEncoded) const override;
@@ -469,8 +456,6 @@
 
     void onApplyOptionsOverrides(const GrContextOptions& options) override;
 
-    GrBackendFormat onCreateFormatFromBackendTexture(const GrBackendTexture&) const override;
-
     bool onIsWindowRectanglesSupportedForRT(const GrBackendRenderTarget&) const override;
 
     void initFSAASupport(const GrContextOptions& contextOptions, const GrGLContextInfo&,
@@ -480,6 +465,9 @@
     // This must be called after initFSAASupport().
     void initConfigTable(const GrContextOptions&, const GrGLContextInfo&, const GrGLInterface*,
                          GrShaderCaps*);
+    bool onSurfaceSupportsWritePixels(const GrSurface*) const override;
+    bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                          const SkIRect& srcRect, const SkIPoint& dstPoint) const override;
 
     GrGLStandard fStandard;
 
@@ -493,7 +481,6 @@
     TransferBufferType  fTransferBufferType;
 
     bool fUnpackRowLengthSupport : 1;
-    bool fUnpackFlipYSupport : 1;
     bool fPackRowLengthSupport : 1;
     bool fPackFlipYSupport : 1;
     bool fTextureUsageSupport : 1;
diff --git a/src/gpu/gl/GrGLDefines.h b/src/gpu/gl/GrGLDefines.h
index d861d86..a545e45 100644
--- a/src/gpu/gl/GrGLDefines.h
+++ b/src/gpu/gl/GrGLDefines.h
@@ -212,7 +212,6 @@
 #define GR_GL_COLOR_CLEAR_VALUE              0x0C22
 #define GR_GL_COLOR_WRITEMASK                0x0C23
 #define GR_GL_UNPACK_ALIGNMENT               0x0CF5
-#define GR_GL_UNPACK_FLIP_Y                  0x9240
 #define GR_GL_PACK_ALIGNMENT                 0x0D05
 #define GR_GL_PACK_REVERSE_ROW_ORDER         0x93A4
 #define GR_GL_MAX_TEXTURE_SIZE               0x0D33
@@ -670,6 +669,7 @@
 #define GR_GL_REPEAT                         0x2901
 #define GR_GL_CLAMP_TO_EDGE                  0x812F
 #define GR_GL_MIRRORED_REPEAT                0x8370
+#define GR_GL_CLAMP_TO_BORDER                0x812D
 
 /* Texture Swizzle */
 #define GR_GL_TEXTURE_SWIZZLE_R              0x8E42
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index df10ffd..03d2c44 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -202,11 +202,16 @@
     return 0;
 }
 
-static inline GrGLenum wrap_mode_to_gl_wrap(GrSamplerState::WrapMode wrapMode) {
+static inline GrGLenum wrap_mode_to_gl_wrap(GrSamplerState::WrapMode wrapMode,
+                                            const GrCaps& caps) {
     switch (wrapMode) {
         case GrSamplerState::WrapMode::kClamp:        return GR_GL_CLAMP_TO_EDGE;
         case GrSamplerState::WrapMode::kRepeat:       return GR_GL_REPEAT;
         case GrSamplerState::WrapMode::kMirrorRepeat: return GR_GL_MIRRORED_REPEAT;
+        case GrSamplerState::WrapMode::kClampToBorder:
+            // May not be supported but should have been caught earlier
+            SkASSERT(caps.clampToBorderSupport());
+            return GR_GL_CLAMP_TO_BORDER;
     }
     SK_ABORT("Unknown wrap mode");
     return 0;
@@ -242,8 +247,8 @@
             fSamplers[index] = s;
             auto minFilter = filter_to_gl_min_filter(state.filter());
             auto magFilter = filter_to_gl_mag_filter(state.filter());
-            auto wrapX = wrap_mode_to_gl_wrap(state.wrapModeX());
-            auto wrapY = wrap_mode_to_gl_wrap(state.wrapModeY());
+            auto wrapX = wrap_mode_to_gl_wrap(state.wrapModeX(), fGpu->glCaps());
+            auto wrapY = wrap_mode_to_gl_wrap(state.wrapModeY(), fGpu->glCaps());
             GR_GL_CALL(fGpu->glInterface(),
                        SamplerParameteri(s, GR_GL_TEXTURE_MIN_FILTER, minFilter));
             GR_GL_CALL(fGpu->glInterface(),
@@ -284,16 +289,16 @@
         int filter = static_cast<int>(state.filter());
         SkASSERT(filter >= 0 && filter < 3);
         int wrapX = static_cast<int>(state.wrapModeX());
-        SkASSERT(wrapX >= 0 && wrapX < 3);
+        SkASSERT(wrapX >= 0 && wrapX < 4);
         int wrapY = static_cast<int>(state.wrapModeY());
-        SkASSERT(wrapY >= 0 && wrapY < 3);
-        int idx = 9 * filter + 3 * wrapX + wrapY;
+        SkASSERT(wrapY >= 0 && wrapY < 4);
+        int idx = 16 * filter + 4 * wrapX + wrapY;
         SkASSERT(idx < kNumSamplers);
         return idx;
     }
 
     GrGLGpu* fGpu;
-    static constexpr int kNumSamplers = 27;
+    static constexpr int kNumSamplers = 48;
     std::unique_ptr<GrGLuint[]> fHWBoundSamplers;
     GrGLuint fSamplers[kNumSamplers];
     int fNumTextureUnits;
@@ -331,8 +336,7 @@
         , fHWProgramID(0)
         , fTempSrcFBOID(0)
         , fTempDstFBOID(0)
-        , fStencilClearFBOID(0)
-        , fHWMinSampleShading(0.0) {
+        , fStencilClearFBOID(0) {
     SkASSERT(fGLContext);
     GrGLClearErr(this->glInterface());
     fCaps = sk_ref_sp(fGLContext->caps());
@@ -614,9 +618,6 @@
         if (this->glCaps().packRowLengthSupport()) {
             GL_CALL(PixelStorei(GR_GL_PACK_ROW_LENGTH, 0));
         }
-        if (this->glCaps().unpackFlipYSupport()) {
-            GL_CALL(PixelStorei(GR_GL_UNPACK_FLIP_Y, GR_GL_FALSE));
-        }
         if (this->glCaps().packFlipYSupport()) {
             GL_CALL(PixelStorei(GR_GL_PACK_REVERSE_ROW_ORDER, GR_GL_FALSE));
         }
@@ -652,7 +653,8 @@
 }
 
 sk_sp<GrTexture> GrGLGpu::onWrapBackendTexture(const GrBackendTexture& backendTex,
-                                               GrWrapOwnership ownership, bool purgeImmediately) {
+                                               GrWrapOwnership ownership, GrIOType ioType,
+                                               bool purgeImmediately) {
     GrGLTexture::IDDesc idDesc;
     if (!check_backend_texture(backendTex, this->glCaps(), &idDesc)) {
         return nullptr;
@@ -676,7 +678,7 @@
     GrMipMapsStatus mipMapsStatus = backendTex.hasMipMaps() ? GrMipMapsStatus::kValid
                                                             : GrMipMapsStatus::kNotAllocated;
 
-    auto texture = GrGLTexture::MakeWrapped(this, surfDesc, mipMapsStatus, idDesc,
+    auto texture = GrGLTexture::MakeWrapped(this, surfDesc, mipMapsStatus, idDesc, ioType,
                                             purgeImmediately);
     // We don't know what parameters are already set on wrapped textures.
     texture->textureParamsModified();
@@ -833,6 +835,7 @@
             return 1;
         case kRGB_565_GrPixelConfig:
         case kRGBA_4444_GrPixelConfig:
+        case kRG_88_GrPixelConfig:
         case kAlpha_half_GrPixelConfig:
         case kAlpha_half_as_Red_GrPixelConfig:
         case kRGBA_half_GrPixelConfig:
@@ -1788,19 +1791,6 @@
 #endif
 }
 
-void GrGLGpu::flushMinSampleShading(float minSampleShading) {
-    if (fHWMinSampleShading != minSampleShading) {
-        if (minSampleShading > 0.0) {
-            GL_CALL(Enable(GR_GL_SAMPLE_SHADING));
-            GL_CALL(MinSampleShading(minSampleShading));
-        }
-        else {
-            GL_CALL(Disable(GR_GL_SAMPLE_SHADING));
-        }
-        fHWMinSampleShading = minSampleShading;
-    }
-}
-
 void GrGLGpu::resolveAndGenerateMipMapsForProcessorTextures(
         const GrPrimitiveProcessor& primProc,
         const GrPipeline& pipeline,
@@ -1869,7 +1859,6 @@
     pipeline.getXferProcessor().getBlendInfo(&blendInfo);
 
     this->flushColorWrite(blendInfo.fWriteColor);
-    this->flushMinSampleShading(primProc.getSampleShading());
 
     this->flushProgram(std::move(program));
 
@@ -2867,12 +2856,12 @@
         newSamplerParams.fMinFilter = filter_to_gl_min_filter(samplerState.filter());
         newSamplerParams.fMagFilter = filter_to_gl_mag_filter(samplerState.filter());
 
-        newSamplerParams.fWrapS = wrap_mode_to_gl_wrap(samplerState.wrapModeX());
-        newSamplerParams.fWrapT = wrap_mode_to_gl_wrap(samplerState.wrapModeY());
+        newSamplerParams.fWrapS = wrap_mode_to_gl_wrap(samplerState.wrapModeX(), this->glCaps());
+        newSamplerParams.fWrapT = wrap_mode_to_gl_wrap(samplerState.wrapModeY(), this->glCaps());
 
         // These are the OpenGL default values.
-        newSamplerParams.fMinLOD = -1000;
-        newSamplerParams.fMaxLOD = 1000;
+        newSamplerParams.fMinLOD = -1000.f;
+        newSamplerParams.fMaxLOD = 1000.f;
 
         if (setAll || newSamplerParams.fMagFilter != oldSamplerParams.fMagFilter) {
             this->setTextureUnit(unitIdx);
@@ -2883,15 +2872,13 @@
             GL_CALL(TexParameteri(target, GR_GL_TEXTURE_MIN_FILTER, newSamplerParams.fMinFilter));
         }
         if (this->glCaps().mipMapLevelAndLodControlSupport()) {
-            // Min and max LOD are actually floats. We don't curently support glTexParameterf.
-            // However, we only set these to integral floats (see above).
             if (setAll || newSamplerParams.fMinLOD != oldSamplerParams.fMinLOD) {
                 this->setTextureUnit(unitIdx);
-                GL_CALL(TexParameteri(target, GR_GL_TEXTURE_MIN_LOD, newSamplerParams.fMinLOD));
+                GL_CALL(TexParameterf(target, GR_GL_TEXTURE_MIN_LOD, newSamplerParams.fMinLOD));
             }
             if (setAll || newSamplerParams.fMaxLOD != oldSamplerParams.fMaxLOD) {
                 this->setTextureUnit(unitIdx);
-                GL_CALL(TexParameteri(target, GR_GL_TEXTURE_MAX_LOD, newSamplerParams.fMaxLOD));
+                GL_CALL(TexParameterf(target, GR_GL_TEXTURE_MAX_LOD, newSamplerParams.fMaxLOD));
             }
         }
         if (setAll || newSamplerParams.fWrapS != oldSamplerParams.fWrapS) {
@@ -2902,6 +2889,14 @@
             this->setTextureUnit(unitIdx);
             GL_CALL(TexParameteri(target, GR_GL_TEXTURE_WRAP_T, newSamplerParams.fWrapT));
         }
+        if (this->glCaps().clampToBorderSupport()) {
+            // Make sure the border color is transparent black (the default)
+            if (setAll || oldSamplerParams.fBorderColorInvalid) {
+                this->setTextureUnit(unitIdx);
+                static const GrGLfloat kTransparentBlack[4] = {0.f, 0.f, 0.f, 0.f};
+                GL_CALL(TexParameterfv(target, GR_GL_TEXTURE_BORDER_COLOR, kTransparentBlack));
+            }
+        }
     }
     GrGLTexture::NonSamplerParams newNonSamplerParams;
     newNonSamplerParams.fBaseMipMapLevel = 0;
@@ -3739,11 +3734,13 @@
         sy0 = sh - sy0;
         sy1 = sh - sy1;
     }
-    // src rect edges in normalized texture space (0 to 1)
-    sx0 /= sw;
-    sx1 /= sw;
-    sy0 /= sh;
-    sy1 /= sh;
+    if (srcTex->texturePriv().textureType() != GrTextureType::kRectangle) {
+        // src rect edges in normalized texture space (0 to 1)
+        sx0 /= sw;
+        sx1 /= sw;
+        sy0 /= sh;
+        sy1 /= sh;
+    }
 
     GL_CALL(Uniform4f(fCopyPrograms[progIdx].fPosXformUniform, dx1 - dx0, dy1 - dy0, dx0, dy0));
     GL_CALL(Uniform4f(fCopyPrograms[progIdx].fTexCoordXformUniform,
@@ -4242,7 +4239,7 @@
     SkColorType skColorType = GrColorTypeToSkColorType(colorType);
     if (skColorType != kUnknown_SkColorType) {
         SkASSERT(this->caps()->validateBackendRenderTarget(
-                beRT, GrColorTypeToSkColorType(colorType), &config));
+                         beRT, GrColorTypeToSkColorType(colorType)) != kUnknown_GrPixelConfig);
     }
 #endif
     return beRT;
@@ -4344,16 +4341,12 @@
     return GrGLSemaphore::MakeWrapped(this, semaphore.glSync(), ownership);
 }
 
-void GrGLGpu::insertSemaphore(sk_sp<GrSemaphore> semaphore, bool flush) {
+void GrGLGpu::insertSemaphore(sk_sp<GrSemaphore> semaphore) {
     GrGLSemaphore* glSem = static_cast<GrGLSemaphore*>(semaphore.get());
 
     GrGLsync sync;
     GL_CALL_RET(sync, FenceSync(GR_GL_SYNC_GPU_COMMANDS_COMPLETE, 0));
     glSem->setSync(sync);
-
-    if (flush) {
-        GL_CALL(Flush());
-    }
 }
 
 void GrGLGpu::waitSemaphore(sk_sp<GrSemaphore> semaphore) {
@@ -4373,7 +4366,9 @@
 sk_sp<GrSemaphore> GrGLGpu::prepareTextureForCrossContextUsage(GrTexture* texture) {
     // Set up a semaphore to be signaled once the data is ready, and flush GL
     sk_sp<GrSemaphore> semaphore = this->makeSemaphore(true);
-    this->insertSemaphore(semaphore, true);
+    this->insertSemaphore(semaphore);
+    // We must call flush here to make sure the GrGLSync object gets created and sent to the gpu.
+    GL_CALL(Flush());
 
     return semaphore;
 }
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index eb920db..bac9120 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -163,7 +163,7 @@
     sk_sp<GrSemaphore> wrapBackendSemaphore(const GrBackendSemaphore& semaphore,
                                             GrResourceProvider::SemaphoreWrapType wrapType,
                                             GrWrapOwnership ownership) override;
-    void insertSemaphore(sk_sp<GrSemaphore> semaphore, bool flush) override;
+    void insertSemaphore(sk_sp<GrSemaphore> semaphore) override;
     void waitSemaphore(sk_sp<GrSemaphore> semaphore) override;
 
     sk_sp<GrSemaphore> prepareTextureForCrossContextUsage(GrTexture*) override;
@@ -189,7 +189,7 @@
     GrBuffer* onCreateBuffer(size_t size, GrBufferType intendedType, GrAccessPattern,
                              const void* data) override;
 
-    sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership,
+    sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership, GrIOType,
                                           bool purgeImmediately) override;
     sk_sp<GrTexture> onWrapRenderableBackendTexture(const GrBackendTexture&,
                                                     int sampleCnt,
@@ -375,8 +375,6 @@
     // rt is used only if useHWAA is true.
     void flushHWAAState(GrRenderTarget* rt, bool useHWAA, bool stencilEnabled);
 
-    void flushMinSampleShading(float minSampleShading);
-
     void flushFramebufferSRGB(bool enable);
 
     // helper for onCreateTexture and writeTexturePixels
@@ -629,7 +627,6 @@
         return (wide ? 0x2 : 0x0) | (tall ? 0x1 : 0x0);
     }
 
-    float fHWMinSampleShading;
     GrPrimitiveType fLastPrimitiveType;
 
     class SamplerObjectCache;
diff --git a/src/gpu/gl/GrGLGpuProgramCache.cpp b/src/gpu/gl/GrGLGpuProgramCache.cpp
index 851640a..f3aa027 100644
--- a/src/gpu/gl/GrGLGpuProgramCache.cpp
+++ b/src/gpu/gl/GrGLGpuProgramCache.cpp
@@ -83,13 +83,11 @@
         GrCapsDebugf(gpu->caps(), "Failed to gl program descriptor!\n");
         return nullptr;
     }
-    desc.finalize();
     std::unique_ptr<Entry>* entry = fMap.find(desc);
     if (!entry) {
         // Didn't find an origin-independent version, check with the specific origin
         GrSurfaceOrigin origin = pipeline.proxy()->origin();
         desc.setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(origin));
-        desc.finalize();
         entry = fMap.find(desc);
     }
     if (!entry) {
diff --git a/src/gpu/gl/GrGLInterface.cpp b/src/gpu/gl/GrGLInterface.cpp
index f7157de..aa8fb63 100644
--- a/src/gpu/gl/GrGLInterface.cpp
+++ b/src/gpu/gl/GrGLInterface.cpp
@@ -96,6 +96,8 @@
         !fFunctions.fStencilOp ||
         !fFunctions.fStencilOpSeparate ||
         !fFunctions.fTexImage2D ||
+        !fFunctions.fTexParameterf ||
+        !fFunctions.fTexParameterfv ||
         !fFunctions.fTexParameteri ||
         !fFunctions.fTexParameteriv ||
         !fFunctions.fTexSubImage2D ||
@@ -581,17 +583,6 @@
         }
     }
 
-    if ((kGL_GrGLStandard == fStandard && glVer >= GR_GL_VER(4,0)) ||
-        fExtensions.has("GL_ARB_sample_shading")) {
-        if (!fFunctions.fMinSampleShading) {
-            RETURN_FALSE_INTERFACE;
-        }
-    } else if (kGLES_GrGLStandard == fStandard && fExtensions.has("GL_OES_sample_shading")) {
-        if (!fFunctions.fMinSampleShading) {
-            RETURN_FALSE_INTERFACE;
-        }
-    }
-
     if (kGL_GrGLStandard == fStandard) {
         if (glVer >= GR_GL_VER(3, 2) || fExtensions.has("GL_ARB_sync")) {
             if (!fFunctions.fFenceSync ||
diff --git a/src/gpu/gl/GrGLRenderTarget.cpp b/src/gpu/gl/GrGLRenderTarget.cpp
index 7344dd7..5e0a2fb 100644
--- a/src/gpu/gl/GrGLRenderTarget.cpp
+++ b/src/gpu/gl/GrGLRenderTarget.cpp
@@ -44,9 +44,6 @@
         SkASSERT(glCaps.usesMixedSamples() && idDesc.fRTFBOID); // FBO 0 can't be mixed sampled.
         this->setHasMixedSamples();
     }
-    if (glCaps.maxWindowRectangles() > 0 && idDesc.fRTFBOID) {
-        this->setSupportsWindowRects();
-    }
     if (!idDesc.fRTFBOID) {
         this->setGLRTFBOIDIs0();
     }
diff --git a/src/gpu/gl/GrGLTestInterface.cpp b/src/gpu/gl/GrGLTestInterface.cpp
index 9bb1747..c6539c1 100644
--- a/src/gpu/gl/GrGLTestInterface.cpp
+++ b/src/gpu/gl/GrGLTestInterface.cpp
@@ -120,7 +120,6 @@
     fFunctions.fMapBufferRange = bind_to_member(this, &GrGLTestInterface::mapBufferRange);
     fFunctions.fMapBufferSubData = bind_to_member(this, &GrGLTestInterface::mapBufferSubData);
     fFunctions.fMapTexSubImage2D = bind_to_member(this, &GrGLTestInterface::mapTexSubImage2D);
-    fFunctions.fMinSampleShading = bind_to_member(this, &GrGLTestInterface::minSampleShading);
     fFunctions.fPixelStorei = bind_to_member(this, &GrGLTestInterface::pixelStorei);
     fFunctions.fPolygonMode = bind_to_member(this, &GrGLTestInterface::polygonMode);
     fFunctions.fPopGroupMarker = bind_to_member(this, &GrGLTestInterface::popGroupMarker);
@@ -145,6 +144,8 @@
     fFunctions.fStencilOpSeparate = bind_to_member(this, &GrGLTestInterface::stencilOpSeparate);
     fFunctions.fTexBuffer = bind_to_member(this, &GrGLTestInterface::texBuffer);
     fFunctions.fTexImage2D = bind_to_member(this, &GrGLTestInterface::texImage2D);
+    fFunctions.fTexParameterf = bind_to_member(this, &GrGLTestInterface::texParameterf);
+    fFunctions.fTexParameterfv = bind_to_member(this, &GrGLTestInterface::texParameterfv);
     fFunctions.fTexParameteri = bind_to_member(this, &GrGLTestInterface::texParameteri);
     fFunctions.fTexParameteriv = bind_to_member(this, &GrGLTestInterface::texParameteriv);
     fFunctions.fTexStorage2D = bind_to_member(this, &GrGLTestInterface::texStorage2D);
diff --git a/src/gpu/gl/GrGLTestInterface.h b/src/gpu/gl/GrGLTestInterface.h
index 3c67be0..4c791f2 100644
--- a/src/gpu/gl/GrGLTestInterface.h
+++ b/src/gpu/gl/GrGLTestInterface.h
@@ -121,7 +121,6 @@
     virtual GrGLvoid* mapBufferRange(GrGLenum target, GrGLintptr offset, GrGLsizeiptr length, GrGLbitfield access) { return nullptr; }
     virtual GrGLvoid* mapBufferSubData(GrGLuint target, GrGLintptr offset, GrGLsizeiptr size, GrGLenum access) { return nullptr; }
     virtual GrGLvoid* mapTexSubImage2D(GrGLenum target, GrGLint level, GrGLint xoffset, GrGLint yoffset, GrGLsizei width, GrGLsizei height, GrGLenum format, GrGLenum type, GrGLenum access) { return nullptr; }
-    virtual GrGLvoid minSampleShading(GrGLfloat value) {}
     virtual GrGLvoid pixelStorei(GrGLenum pname, GrGLint param) {}
     virtual GrGLvoid polygonMode(GrGLenum face, GrGLenum mode) {}
     virtual GrGLvoid popGroupMarker() {}
@@ -137,11 +136,7 @@
     virtual GrGLvoid samplerParameteriv(GrGLuint sampler, GrGLenum pname, const GrGLint* param) {}
     virtual GrGLvoid scissor(GrGLint x, GrGLint y, GrGLsizei width, GrGLsizei height) {}
     virtual GrGLvoid bindUniformLocation(GrGLuint program, GrGLint location, const char* name) {}
-#if GR_GL_USE_NEW_SHADER_SOURCE_SIGNATURE
     virtual GrGLvoid shaderSource(GrGLuint shader, GrGLsizei count, const char* const * str, const GrGLint* length) {}
-#else
-    virtual GrGLvoid shaderSource(GrGLuint shader, GrGLsizei count, const char** str, const GrGLint* length) {}
-#endif
     virtual GrGLvoid stencilFunc(GrGLenum func, GrGLint ref, GrGLuint mask) {}
     virtual GrGLvoid stencilFuncSeparate(GrGLenum face, GrGLenum func, GrGLint ref, GrGLuint mask) {}
     virtual GrGLvoid stencilMask(GrGLuint mask) {}
@@ -150,6 +145,8 @@
     virtual GrGLvoid stencilOpSeparate(GrGLenum face, GrGLenum fail, GrGLenum zfail, GrGLenum zpass) {}
     virtual GrGLvoid texBuffer(GrGLenum target, GrGLenum internalformat, GrGLuint buffer) {}
     virtual GrGLvoid texImage2D(GrGLenum target, GrGLint level, GrGLint internalformat, GrGLsizei width, GrGLsizei height, GrGLint border, GrGLenum format, GrGLenum type, const GrGLvoid* pixels) {}
+    virtual GrGLvoid texParameterf(GrGLenum target, GrGLenum pname, GrGLfloat param) {}
+    virtual GrGLvoid texParameterfv(GrGLenum target, GrGLenum pname, const GrGLfloat* params) {}
     virtual GrGLvoid texParameteri(GrGLenum target, GrGLenum pname, GrGLint param) {}
     virtual GrGLvoid texParameteriv(GrGLenum target, GrGLenum pname, const GrGLint* params) {}
     virtual GrGLvoid texStorage2D(GrGLenum target, GrGLsizei levels, GrGLenum internalformat, GrGLsizei width, GrGLsizei height) {}
diff --git a/src/gpu/gl/GrGLTexture.cpp b/src/gpu/gl/GrGLTexture.cpp
index 2d432fe..06bf662 100644
--- a/src/gpu/gl/GrGLTexture.cpp
+++ b/src/gpu/gl/GrGLTexture.cpp
@@ -51,12 +51,15 @@
 }
 
 GrGLTexture::GrGLTexture(GrGLGpu* gpu, Wrapped, const GrSurfaceDesc& desc,
-                         GrMipMapsStatus mipMapsStatus, const IDDesc& idDesc,
+                         GrMipMapsStatus mipMapsStatus, const IDDesc& idDesc, GrIOType ioType,
                          bool purgeImmediately)
         : GrSurface(gpu, desc)
         , INHERITED(gpu, desc, TextureTypeFromTarget(idDesc.fInfo.fTarget), mipMapsStatus) {
     this->init(desc, idDesc);
     this->registerWithCacheWrapped(purgeImmediately);
+    if (ioType == kRead_GrIOType) {
+        this->setReadOnly();
+    }
 }
 
 GrGLTexture::GrGLTexture(GrGLGpu* gpu, const GrSurfaceDesc& desc, const IDDesc& idDesc,
@@ -111,9 +114,9 @@
 
 sk_sp<GrGLTexture> GrGLTexture::MakeWrapped(GrGLGpu* gpu, const GrSurfaceDesc& desc,
                                             GrMipMapsStatus mipMapsStatus, const IDDesc& idDesc,
-                                            bool purgeImmediately) {
-    return sk_sp<GrGLTexture>(new GrGLTexture(gpu, kWrapped, desc, mipMapsStatus, idDesc,
-                                              purgeImmediately));
+                                            GrIOType ioType, bool purgeImmediately) {
+    return sk_sp<GrGLTexture>(
+            new GrGLTexture(gpu, kWrapped, desc, mipMapsStatus, idDesc, ioType, purgeImmediately));
 }
 
 bool GrGLTexture::onStealBackendTexture(GrBackendTexture* backendTexture,
diff --git a/src/gpu/gl/GrGLTexture.h b/src/gpu/gl/GrGLTexture.h
index a9d7a38..db83eea 100644
--- a/src/gpu/gl/GrGLTexture.h
+++ b/src/gpu/gl/GrGLTexture.h
@@ -27,6 +27,10 @@
         GrGLenum fWrapT = GR_GL_REPEAT;
         GrGLfloat fMinLOD = -1000.f;
         GrGLfloat fMaxLOD = 1000.f;
+        // We always want the border color to be transparent black, so no need to store 4 floats.
+        // Just track if it's been invalidated and no longer the default
+        bool fBorderColorInvalid = false;
+
         void invalidate() {
             fMinFilter = ~0U;
             fMagFilter = ~0U;
@@ -34,6 +38,7 @@
             fWrapT = ~0U;
             fMinLOD = SK_ScalarNaN;
             fMaxLOD = SK_ScalarNaN;
+            fBorderColorInvalid = true;
         }
     };
 
@@ -77,6 +82,11 @@
         fReleaseHelper = std::move(releaseHelper);
     }
 
+    void setIdleProc(IdleProc proc, void* context) override {
+        fIdleProc = proc;
+        fIdleProcContext = context;
+    }
+
     // These functions are used to track the texture parameters associated with the texture.
     GrGpu::ResetTimestamp getCachedParamsTimestamp() const { return fParamsTimestamp; }
     const SamplerParams& getCachedSamplerParams() const { return fSamplerParams; }
@@ -100,7 +110,7 @@
     void baseLevelWasBoundToFBO() { fBaseLevelHasBeenBoundToFBO = true; }
 
     static sk_sp<GrGLTexture> MakeWrapped(GrGLGpu*, const GrSurfaceDesc&, GrMipMapsStatus,
-                                          const IDDesc&, bool purgeImmediately);
+                                          const IDDesc&, GrIOType, bool purgeImmediately);
 
     void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const override;
 
@@ -110,7 +120,7 @@
 
     enum Wrapped { kWrapped };
     // Constructor for instances wrapping backend objects.
-    GrGLTexture(GrGLGpu*, Wrapped, const GrSurfaceDesc&, GrMipMapsStatus, const IDDesc&,
+    GrGLTexture(GrGLGpu*, Wrapped, const GrSurfaceDesc&, GrMipMapsStatus, const IDDesc&, GrIOType,
                 bool purgeImmediately);
 
     void init(const GrSurfaceDesc&, const IDDesc&);
@@ -122,10 +132,16 @@
 
 private:
     void invokeReleaseProc() {
-        if (fReleaseHelper) {
-            // Depending on the ref count of fReleaseHelper this may or may not actually trigger the
-            // ReleaseProc to be called.
-            fReleaseHelper.reset();
+        // Depending on the ref count of fReleaseHelper this may or may not actually trigger the
+        // ReleaseProc to be called.
+        fReleaseHelper.reset();
+    }
+
+    void becamePurgeable() override {
+        if (fIdleProc) {
+            fIdleProc(fIdleProcContext);
+            fIdleProc = nullptr;
+            fIdleProcContext = nullptr;
         }
     }
 
@@ -133,6 +149,8 @@
     NonSamplerParams fNonSamplerParams;
     GrGpu::ResetTimestamp fParamsTimestamp;
     sk_sp<GrReleaseProcHelper> fReleaseHelper;
+    IdleProc* fIdleProc = nullptr;
+    void* fIdleProcContext = nullptr;
     GrGLuint fID;
     GrGLenum fFormat;
     GrBackendObjectOwnership fTextureIDOwnership;
diff --git a/src/gpu/gl/GrGLUtil.cpp b/src/gpu/gl/GrGLUtil.cpp
index 762c880..a031f95 100644
--- a/src/gpu/gl/GrGLUtil.cpp
+++ b/src/gpu/gl/GrGLUtil.cpp
@@ -307,7 +307,7 @@
 static bool is_renderer_angle(const char* rendererString) {
     static constexpr char kHeader[] = "ANGLE ";
     static constexpr size_t kHeaderLength = SK_ARRAY_COUNT(kHeader) - 1;
-    return 0 == strncmp(rendererString, kHeader, kHeaderLength);
+    return rendererString && 0 == strncmp(rendererString, kHeader, kHeaderLength);
 }
 
 GrGLRenderer GrGLGetRendererFromStrings(const char* rendererString,
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.cpp b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
index c98d47a..c724ab9 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.cpp
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
@@ -44,7 +44,7 @@
     GrGLProgramBuilder builder(gpu, pipeline, primProc, primProcProxies, desc);
 
     auto persistentCache = gpu->getContext()->contextPriv().getPersistentCache();
-    if (persistentCache && gpu->glCaps().programBinarySupport()) {
+    if (persistentCache) {
         sk_sp<SkData> key = SkData::MakeWithoutCopy(desc->asKey(), desc->keyLength());
         builder.fCached = persistentCache->load(*key);
         // the eventual end goal is to completely skip emitAndInstallProcs on a cache hit, but it's
@@ -101,7 +101,6 @@
         GrProgramDesc* d = this->desc();
         d->setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(
                                                      this->pipeline().proxy()->origin()));
-        d->finalize();
     }
 
     return true;
@@ -169,6 +168,44 @@
     }
 }
 
+void GrGLProgramBuilder::storeShaderInCache(const SkSL::Program::Inputs& inputs, GrGLuint programID,
+                                            const SkSL::String& glsl) {
+    if (!this->gpu()->getContext()->contextPriv().getPersistentCache()) {
+        return;
+    }
+    sk_sp<SkData> key = SkData::MakeWithoutCopy(desc()->asKey(), desc()->keyLength());
+    if (fGpu->glCaps().programBinarySupport()) {
+        // binary cache
+        GrGLsizei length = 0;
+        GL_CALL(GetProgramiv(programID, GL_PROGRAM_BINARY_LENGTH, &length));
+        if (length > 0) {
+            GrGLenum binaryFormat;
+            std::unique_ptr<char[]> binary(new char[length]);
+            GL_CALL(GetProgramBinary(programID, length, &length, &binaryFormat, binary.get()));
+            size_t dataLength = sizeof(inputs) + sizeof(binaryFormat) + length;
+            std::unique_ptr<uint8_t[]> data(new uint8_t[dataLength]);
+            size_t offset = 0;
+            memcpy(data.get() + offset, &inputs, sizeof(inputs));
+            offset += sizeof(inputs);
+            memcpy(data.get() + offset, &binaryFormat, sizeof(binaryFormat));
+            offset += sizeof(binaryFormat);
+            memcpy(data.get() + offset, binary.get(), length);
+            this->gpu()->getContext()->contextPriv().getPersistentCache()->store(
+                                            *key, *SkData::MakeWithoutCopy(data.get(), dataLength));
+        }
+    } else {
+        // source cache
+        size_t dataLength = sizeof(inputs) + glsl.length();
+        std::unique_ptr<uint8_t[]> data(new uint8_t[dataLength]);
+        size_t offset = 0;
+        memcpy(data.get() + offset, &inputs, sizeof(inputs));
+        offset += sizeof(inputs);
+        memcpy(data.get() + offset, glsl.data(), glsl.length());
+        this->gpu()->getContext()->contextPriv().getPersistentCache()->store(
+                                            *key, *SkData::MakeWithoutCopy(data.get(), dataLength));
+    }
+}
+
 GrGLProgram* GrGLProgramBuilder::finalize() {
     TRACE_EVENT0("skia", TRACE_FUNC);
 
@@ -196,49 +233,67 @@
 
     SkSL::Program::Inputs inputs;
     SkTDArray<GrGLuint> shadersToDelete;
-    bool cached = fGpu->glCaps().programBinarySupport() && nullptr != fCached.get();
+    // Calling GetProgramiv is expensive in Chromium. Assume success in release builds.
+    bool checkLinked = kChromium_GrGLDriver != fGpu->ctxInfo().driver();
+#ifdef SK_DEBUG
+    checkLinked = true;
+#endif
+    bool cached = fCached.get() != nullptr;
+    SkSL::String glsl;
     if (cached) {
-        this->bindProgramResourceLocations(programID);
-        // cache hit, just hand the binary to GL
         const uint8_t* bytes = fCached->bytes();
         size_t offset = 0;
         memcpy(&inputs, bytes + offset, sizeof(inputs));
         offset += sizeof(inputs);
-        int binaryFormat;
-        memcpy(&binaryFormat, bytes + offset, sizeof(binaryFormat));
-        offset += sizeof(binaryFormat);
-        GrGLClearErr(this->gpu()->glInterface());
-        GR_GL_CALL_NOERRCHECK(this->gpu()->glInterface(),
-                              ProgramBinary(programID, binaryFormat, (void*) (bytes + offset),
-                                            fCached->size() - offset));
-        if (GR_GL_GET_ERROR(this->gpu()->glInterface()) == GR_GL_NO_ERROR) {
-            cached = this->checkLinkStatus(programID);
-            if (cached) {
-                this->addInputVars(inputs);
-                this->computeCountsAndStrides(programID, primProc, false);
+        if (fGpu->glCaps().programBinarySupport()) {
+            // binary cache hit, just hand the binary to GL
+            int binaryFormat;
+            memcpy(&binaryFormat, bytes + offset, sizeof(binaryFormat));
+            offset += sizeof(binaryFormat);
+            GrGLClearErr(this->gpu()->glInterface());
+            GR_GL_CALL_NOERRCHECK(this->gpu()->glInterface(),
+                                  ProgramBinary(programID, binaryFormat, (void*) (bytes + offset),
+                                                fCached->size() - offset));
+            if (GR_GL_GET_ERROR(this->gpu()->glInterface()) == GR_GL_NO_ERROR) {
+                if (checkLinked) {
+                    cached = this->checkLinkStatus(programID);
+                }
+                if (cached) {
+                    this->addInputVars(inputs);
+                    this->computeCountsAndStrides(programID, primProc, false);
+                }
+            } else {
+                cached = false;
             }
         } else {
-            cached = false;
+            // source cache hit, we don't need to compile the SkSL->GLSL
+            glsl = SkSL::String(((const char*) bytes) + offset, fCached->size() - offset);
         }
     }
-    if (!cached) {
-        // cache miss, compile shaders
-        if (fFS.fForceHighPrecision) {
-            settings.fForceHighPrecision = true;
+    if (!cached || !fGpu->glCaps().programBinarySupport()) {
+        // either a cache miss, or we can't store binaries in the cache
+        if (glsl == "" || true) {
+            // don't have cached GLSL, need to compile SkSL->GLSL
+            if (fFS.fForceHighPrecision) {
+                settings.fForceHighPrecision = true;
+            }
+            std::unique_ptr<SkSL::Program> fs = GrSkSLtoGLSL(gpu()->glContext(),
+                                                             GR_GL_FRAGMENT_SHADER,
+                                                             fFS.fCompilerStrings.begin(),
+                                                             fFS.fCompilerStringLengths.begin(),
+                                                             fFS.fCompilerStrings.count(),
+                                                             settings,
+                                                             &glsl);
+            if (!fs) {
+                this->cleanupProgram(programID, shadersToDelete);
+                return nullptr;
+            }
+            inputs = fs->fInputs;
+        } else {
+            // we've pulled GLSL and inputs from the cache, but still need to do some setup
+            this->addInputVars(inputs);
+            this->computeCountsAndStrides(programID, primProc, false);
         }
-        SkSL::String glsl;
-        std::unique_ptr<SkSL::Program> fs = GrSkSLtoGLSL(gpu()->glContext(),
-                                                         GR_GL_FRAGMENT_SHADER,
-                                                         fFS.fCompilerStrings.begin(),
-                                                         fFS.fCompilerStringLengths.begin(),
-                                                         fFS.fCompilerStrings.count(),
-                                                         settings,
-                                                         &glsl);
-        if (!fs) {
-            this->cleanupProgram(programID, shadersToDelete);
-            return nullptr;
-        }
-        inputs = fs->fInputs;
         this->addInputVars(inputs);
         if (!this->compileAndAttachShaders(glsl.c_str(), glsl.size(), programID,
                                            GR_GL_FRAGMENT_SHADER, &shadersToDelete, settings,
@@ -286,55 +341,40 @@
         this->bindProgramResourceLocations(programID);
 
         GL_CALL(LinkProgram(programID));
-    }
-    // Calling GetProgramiv is expensive in Chromium. Assume success in release builds.
-    bool checkLinked = kChromium_GrGLDriver != fGpu->ctxInfo().driver();
-#ifdef SK_DEBUG
-    checkLinked = true;
-#endif
-    if (checkLinked) {
-        if (!this->checkLinkStatus(programID)) {
-            SkDebugf("VS:\n");
-            GrGLPrintShader(fGpu->glContext(), GR_GL_VERTEX_SHADER, fVS.fCompilerStrings.begin(),
-                            fVS.fCompilerStringLengths.begin(), fVS.fCompilerStrings.count(),
-                            settings);
-            if (primProc.willUseGeoShader()) {
-                SkDebugf("\nGS:\n");
-                GrGLPrintShader(fGpu->glContext(), GR_GL_GEOMETRY_SHADER,
-                                fGS.fCompilerStrings.begin(), fGS.fCompilerStringLengths.begin(),
-                                fGS.fCompilerStrings.count(), settings);
+        if (checkLinked) {
+            if (!this->checkLinkStatus(programID)) {
+                GL_CALL(DeleteProgram(programID));
+                SkDebugf("VS:\n");
+                GrGLPrintShader(fGpu->glContext(),
+                                GR_GL_VERTEX_SHADER,
+                                fVS.fCompilerStrings.begin(),
+                                fVS.fCompilerStringLengths.begin(),
+                                fVS.fCompilerStrings.count(),
+                                settings);
+                if (primProc.willUseGeoShader()) {
+                    SkDebugf("\nGS:\n");
+                    GrGLPrintShader(fGpu->glContext(),
+                                    GR_GL_GEOMETRY_SHADER,
+                                    fGS.fCompilerStrings.begin(),
+                                    fGS.fCompilerStringLengths.begin(),
+                                    fGS.fCompilerStrings.count(), settings);
+                }
+                SkDebugf("\nFS:\n");
+                GrGLPrintShader(fGpu->glContext(),
+                                GR_GL_FRAGMENT_SHADER,
+                                fFS.fCompilerStrings.begin(),
+                                fFS.fCompilerStringLengths.begin(),
+                                fFS.fCompilerStrings.count(),
+                                settings);
+                return nullptr;
             }
-            SkDebugf("\nFS:\n");
-            GrGLPrintShader(fGpu->glContext(), GR_GL_FRAGMENT_SHADER, fFS.fCompilerStrings.begin(),
-                            fFS.fCompilerStringLengths.begin(), fFS.fCompilerStrings.count(),
-                            settings);
-            return nullptr;
         }
     }
     this->resolveProgramResourceLocations(programID);
 
     this->cleanupShaders(shadersToDelete);
-    if (!cached && this->gpu()->getContext()->contextPriv().getPersistentCache() &&
-        fGpu->glCaps().programBinarySupport()) {
-        GrGLsizei length = 0;
-        GL_CALL(GetProgramiv(programID, GL_PROGRAM_BINARY_LENGTH, &length));
-        if (length > 0) {
-            // store shader in cache
-            sk_sp<SkData> key = SkData::MakeWithoutCopy(desc()->asKey(), desc()->keyLength());
-            GrGLenum binaryFormat;
-            std::unique_ptr<char[]> binary(new char[length]);
-            GL_CALL(GetProgramBinary(programID, length, &length, &binaryFormat, binary.get()));
-            size_t dataLength = sizeof(inputs) + sizeof(binaryFormat) + length;
-            std::unique_ptr<uint8_t[]> data(new uint8_t[dataLength]);
-            size_t offset = 0;
-            memcpy(data.get() + offset, &inputs, sizeof(inputs));
-            offset += sizeof(inputs);
-            memcpy(data.get() + offset, &binaryFormat, sizeof(binaryFormat));
-            offset += sizeof(binaryFormat);
-            memcpy(data.get() + offset, binary.get(), length);
-            this->gpu()->getContext()->contextPriv().getPersistentCache()->store(
-                                            *key, *SkData::MakeWithoutCopy(data.get(), dataLength));
-        }
+    if (!cached) {
+        this->storeShaderInCache(inputs, programID, glsl);
     }
     return this->createProgram(programID);
 }
@@ -383,8 +423,6 @@
                                       (char*)log.get()));
             SkDebugf("%s", (char*)log.get());
         }
-        GL_CALL(DeleteProgram(programID));
-        programID = 0;
     }
     return SkToBool(linked);
 }
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.h b/src/gpu/gl/builders/GrGLProgramBuilder.h
index 0ba7d49..9c090b7 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.h
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.h
@@ -66,6 +66,8 @@
                                  SkSL::Program::Inputs* outInputs);
     void computeCountsAndStrides(GrGLuint programID, const GrPrimitiveProcessor& primProc,
                                  bool bindAttribLocations);
+    void storeShaderInCache(const SkSL::Program::Inputs& inputs, GrGLuint programID,
+                            const SkSL::String& glsl);
     GrGLProgram* finalize();
     void bindProgramResourceLocations(GrGLuint programID);
     bool checkLinkStatus(GrGLuint programID);
diff --git a/src/gpu/gl/egl/GrGLMakeNativeInterface_egl.cpp b/src/gpu/gl/egl/GrGLMakeNativeInterface_egl.cpp
index de1b1bb..c14d703 100644
--- a/src/gpu/gl/egl/GrGLMakeNativeInterface_egl.cpp
+++ b/src/gpu/gl/egl/GrGLMakeNativeInterface_egl.cpp
@@ -19,111 +19,113 @@
     // https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_get_all_proc_addresses.txt
     // eglGetProcAddress() is not guaranteed to support the querying of non-extension EGL functions.
     #define M(X) if (0 == strcmp(#X, name)) { return (GrGLFuncPtr) X; }
-    M(eglGetCurrentDisplay);
-    M(eglQueryString);
-    M(glActiveTexture);
-    M(glAttachShader);
-    M(glBindAttribLocation);
-    M(glBindBuffer);
-    M(glBindFramebuffer);
-    M(glBindRenderbuffer);
-    M(glBindTexture);
-    M(glBlendColor);
-    M(glBlendEquation);
-    M(glBlendFunc);
-    M(glBufferData);
-    M(glBufferSubData);
-    M(glCheckFramebufferStatus);
-    M(glClear);
-    M(glClearColor);
-    M(glClearStencil);
-    M(glColorMask);
-    M(glCompileShader);
-    M(glCompressedTexImage2D);
-    M(glCompressedTexSubImage2D);
-    M(glCopyTexSubImage2D);
-    M(glCreateProgram);
-    M(glCreateShader);
-    M(glCullFace);
-    M(glDeleteBuffers);
-    M(glDeleteFramebuffers);
-    M(glDeleteProgram);
-    M(glDeleteRenderbuffers);
-    M(glDeleteShader);
-    M(glDeleteTextures);
-    M(glDepthMask);
-    M(glDisable);
-    M(glDisableVertexAttribArray);
-    M(glDrawArrays);
-    M(glDrawElements);
-    M(glEnable);
-    M(glEnableVertexAttribArray);
-    M(glFinish);
-    M(glFlush);
-    M(glFramebufferRenderbuffer);
-    M(glFramebufferTexture2D);
-    M(glFrontFace);
-    M(glGenBuffers);
-    M(glGenFramebuffers);
-    M(glGenRenderbuffers);
-    M(glGenTextures);
-    M(glGenerateMipmap);
-    M(glGetBufferParameteriv);
-    M(glGetError);
-    M(glGetFramebufferAttachmentParameteriv);
-    M(glGetIntegerv);
-    M(glGetProgramInfoLog);
-    M(glGetProgramiv);
-    M(glGetRenderbufferParameteriv);
-    M(glGetShaderInfoLog);
-    M(glGetShaderPrecisionFormat);
-    M(glGetShaderiv);
-    M(glGetString);
-    M(glGetUniformLocation);
-    M(glIsTexture);
-    M(glLineWidth);
-    M(glLinkProgram);
-    M(glPixelStorei);
-    M(glReadPixels);
-    M(glRenderbufferStorage);
-    M(glScissor);
-    M(glShaderSource);
-    M(glStencilFunc);
-    M(glStencilFuncSeparate);
-    M(glStencilMask);
-    M(glStencilMaskSeparate);
-    M(glStencilOp);
-    M(glStencilOpSeparate);
-    M(glTexImage2D);
-    M(glTexParameteri);
-    M(glTexParameteriv);
-    M(glTexSubImage2D);
-    M(glUniform1f);
-    M(glUniform1fv);
-    M(glUniform1i);
-    M(glUniform1iv);
-    M(glUniform2f);
-    M(glUniform2fv);
-    M(glUniform2i);
-    M(glUniform2iv);
-    M(glUniform3f);
-    M(glUniform3fv);
-    M(glUniform3i);
-    M(glUniform3iv);
-    M(glUniform4f);
-    M(glUniform4fv);
-    M(glUniform4i);
-    M(glUniform4iv);
-    M(glUniformMatrix2fv);
-    M(glUniformMatrix3fv);
-    M(glUniformMatrix4fv);
-    M(glUseProgram);
-    M(glVertexAttrib1f);
-    M(glVertexAttrib2fv);
-    M(glVertexAttrib3fv);
-    M(glVertexAttrib4fv);
-    M(glVertexAttribPointer);
-    M(glViewport);
+    M(eglGetCurrentDisplay)
+    M(eglQueryString)
+    M(glActiveTexture)
+    M(glAttachShader)
+    M(glBindAttribLocation)
+    M(glBindBuffer)
+    M(glBindFramebuffer)
+    M(glBindRenderbuffer)
+    M(glBindTexture)
+    M(glBlendColor)
+    M(glBlendEquation)
+    M(glBlendFunc)
+    M(glBufferData)
+    M(glBufferSubData)
+    M(glCheckFramebufferStatus)
+    M(glClear)
+    M(glClearColor)
+    M(glClearStencil)
+    M(glColorMask)
+    M(glCompileShader)
+    M(glCompressedTexImage2D)
+    M(glCompressedTexSubImage2D)
+    M(glCopyTexSubImage2D)
+    M(glCreateProgram)
+    M(glCreateShader)
+    M(glCullFace)
+    M(glDeleteBuffers)
+    M(glDeleteFramebuffers)
+    M(glDeleteProgram)
+    M(glDeleteRenderbuffers)
+    M(glDeleteShader)
+    M(glDeleteTextures)
+    M(glDepthMask)
+    M(glDisable)
+    M(glDisableVertexAttribArray)
+    M(glDrawArrays)
+    M(glDrawElements)
+    M(glEnable)
+    M(glEnableVertexAttribArray)
+    M(glFinish)
+    M(glFlush)
+    M(glFramebufferRenderbuffer)
+    M(glFramebufferTexture2D)
+    M(glFrontFace)
+    M(glGenBuffers)
+    M(glGenFramebuffers)
+    M(glGenRenderbuffers)
+    M(glGenTextures)
+    M(glGenerateMipmap)
+    M(glGetBufferParameteriv)
+    M(glGetError)
+    M(glGetFramebufferAttachmentParameteriv)
+    M(glGetIntegerv)
+    M(glGetProgramInfoLog)
+    M(glGetProgramiv)
+    M(glGetRenderbufferParameteriv)
+    M(glGetShaderInfoLog)
+    M(glGetShaderPrecisionFormat)
+    M(glGetShaderiv)
+    M(glGetString)
+    M(glGetUniformLocation)
+    M(glIsTexture)
+    M(glLineWidth)
+    M(glLinkProgram)
+    M(glPixelStorei)
+    M(glReadPixels)
+    M(glRenderbufferStorage)
+    M(glScissor)
+    M(glShaderSource)
+    M(glStencilFunc)
+    M(glStencilFuncSeparate)
+    M(glStencilMask)
+    M(glStencilMaskSeparate)
+    M(glStencilOp)
+    M(glStencilOpSeparate)
+    M(glTexImage2D)
+    M(glTexParameterf)
+    M(glTexParameterfv)
+    M(glTexParameteri)
+    M(glTexParameteriv)
+    M(glTexSubImage2D)
+    M(glUniform1f)
+    M(glUniform1fv)
+    M(glUniform1i)
+    M(glUniform1iv)
+    M(glUniform2f)
+    M(glUniform2fv)
+    M(glUniform2i)
+    M(glUniform2iv)
+    M(glUniform3f)
+    M(glUniform3fv)
+    M(glUniform3i)
+    M(glUniform3iv)
+    M(glUniform4f)
+    M(glUniform4fv)
+    M(glUniform4i)
+    M(glUniform4iv)
+    M(glUniformMatrix2fv)
+    M(glUniformMatrix3fv)
+    M(glUniformMatrix4fv)
+    M(glUseProgram)
+    M(glVertexAttrib1f)
+    M(glVertexAttrib2fv)
+    M(glVertexAttrib3fv)
+    M(glVertexAttrib4fv)
+    M(glVertexAttribPointer)
+    M(glViewport)
     #undef M
     return eglGetProcAddress(name);
 }
diff --git a/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp b/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp
index 3b5951f..cb69244 100644
--- a/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp
+++ b/src/gpu/gl/win/GrGLMakeNativeInterface_win.cpp
@@ -13,6 +13,12 @@
 #include "gl/GrGLAssembleInterface.h"
 #include "gl/GrGLUtil.h"
 
+#if defined(_M_ARM64)
+
+sk_sp<const GrGLInterface> GrGLMakeNativeInterface() { return nullptr; }
+
+#else
+
 class AutoLibraryUnload {
 public:
     AutoLibraryUnload(const char* moduleName) {
@@ -87,6 +93,8 @@
     return nullptr;
 }
 
+#endif // ARM64
+
 const GrGLInterface* GrGLCreateNativeInterface() { return GrGLMakeNativeInterface().release(); }
 
 #endif//defined(SK_BUILD_FOR_WIN)
diff --git a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
index 35f0fba..511df10 100644
--- a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
@@ -95,11 +95,6 @@
 const char* GrGLSLFragmentShaderBuilder::dstColor() {
     SkDEBUGCODE(fHasReadDstColor = true;)
 
-    const char* override = fProgramBuilder->primitiveProcessor().getDestColorOverride();
-    if (override != nullptr) {
-        return override;
-    }
-
     const GrShaderCaps* shaderCaps = fProgramBuilder->shaderCaps();
     if (shaderCaps->fbFetchSupport()) {
         this->addFeature(1 << kFramebufferFetch_GLSLPrivateFeature,
diff --git a/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp b/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp
index fb6e7f2..8e6099e 100644
--- a/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp
@@ -24,14 +24,26 @@
     }
 
     if (coordTransform.reverseY()) {
-        // combined.postScale(1,-1);
-        // combined.postTranslate(0,1);
-        combined.set(SkMatrix::kMSkewY,
-            combined[SkMatrix::kMPersp0] - combined[SkMatrix::kMSkewY]);
-        combined.set(SkMatrix::kMScaleY,
-            combined[SkMatrix::kMPersp1] - combined[SkMatrix::kMScaleY]);
-        combined.set(SkMatrix::kMTransY,
-            combined[SkMatrix::kMPersp2] - combined[SkMatrix::kMTransY]);
+        if (coordTransform.normalize()) {
+            // combined.postScale(1,-1);
+            // combined.postTranslate(0,1);
+            combined.set(SkMatrix::kMSkewY,
+                         combined[SkMatrix::kMPersp0] - combined[SkMatrix::kMSkewY]);
+            combined.set(SkMatrix::kMScaleY,
+                         combined[SkMatrix::kMPersp1] - combined[SkMatrix::kMScaleY]);
+            combined.set(SkMatrix::kMTransY,
+                         combined[SkMatrix::kMPersp2] - combined[SkMatrix::kMTransY]);
+        } else {
+            // combined.postScale(1, -1);
+            // combined.postTranslate(0,1);
+            SkScalar h = coordTransform.peekTexture()->height();
+            combined.set(SkMatrix::kMSkewY,
+                         h * combined[SkMatrix::kMPersp0] - combined[SkMatrix::kMSkewY]);
+            combined.set(SkMatrix::kMScaleY,
+                         h * combined[SkMatrix::kMPersp1] - combined[SkMatrix::kMScaleY]);
+            combined.set(SkMatrix::kMTransY,
+                         h * combined[SkMatrix::kMPersp2] - combined[SkMatrix::kMTransY]);
+        }
     }
     return combined;
 }
diff --git a/src/gpu/glsl/GrGLSLVarying.h b/src/gpu/glsl/GrGLSLVarying.h
index 0da88a0..73048e7 100644
--- a/src/gpu/glsl/GrGLSLVarying.h
+++ b/src/gpu/glsl/GrGLSLVarying.h
@@ -16,6 +16,22 @@
 
 class GrGLSLProgramBuilder;
 
+#ifdef SK_DEBUG
+static bool is_matrix(GrSLType type) {
+    switch (type) {
+        case kFloat2x2_GrSLType:
+        case kFloat3x3_GrSLType:
+        case kFloat4x4_GrSLType:
+        case kHalf2x2_GrSLType:
+        case kHalf3x3_GrSLType:
+        case kHalf4x4_GrSLType:
+            return true;
+        default:
+            return false;
+    }
+}
+#endif
+
 class GrGLSLVarying {
 public:
     enum class Scope {
@@ -25,9 +41,16 @@
     };
 
     GrGLSLVarying() = default;
-    GrGLSLVarying(GrSLType type, Scope scope = Scope::kVertToFrag) : fType(type), fScope(scope) {}
+    GrGLSLVarying(GrSLType type, Scope scope = Scope::kVertToFrag)
+        : fType(type)
+        , fScope(scope) {
+        // Metal doesn't support varying matrices, so we disallow them everywhere for consistency
+        SkASSERT(!is_matrix(type));
+    }
 
     void reset(GrSLType type, Scope scope = Scope::kVertToFrag) {
+        // Metal doesn't support varying matrices, so we disallow them everywhere for consistency
+        SkASSERT(!is_matrix(type));
         *this = GrGLSLVarying();
         fType = type;
         fScope = scope;
diff --git a/src/gpu/mock/GrMockCaps.h b/src/gpu/mock/GrMockCaps.h
index d24afa5..c58b9bb 100644
--- a/src/gpu/mock/GrMockCaps.h
+++ b/src/gpu/mock/GrMockCaps.h
@@ -66,64 +66,33 @@
         return 0;
     }
 
-    bool surfaceSupportsWritePixels(const GrSurface*) const override { return true; }
     bool surfaceSupportsReadPixels(const GrSurface*) const override { return true; }
 
-    bool canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
-                        const SkIRect& srcRect, const SkIPoint& dstPoint) const override {
-        return true;
-    }
-
     bool initDescForDstCopy(const GrRenderTargetProxy* src, GrSurfaceDesc* desc, GrSurfaceOrigin*,
                             bool* rectsMustMatch, bool* disallowSubrect) const override {
         return false;
     }
 
-    bool validateBackendTexture(const GrBackendTexture& tex, SkColorType,
-                                GrPixelConfig* config) const override {
-        GrMockTextureInfo texInfo;
-        if (!tex.getMockTextureInfo(&texInfo)) {
-            return false;
-        }
-
-        *config = texInfo.fConfig;
-        return true;
+    GrPixelConfig validateBackendRenderTarget(const GrBackendRenderTarget&,
+                                              SkColorType) const override {
+        return kUnknown_GrPixelConfig;
     }
 
-    bool validateBackendRenderTarget(const GrBackendRenderTarget& rt, SkColorType,
-                                     GrPixelConfig*) const override {
-        return false;
-    }
-
-    bool getConfigFromBackendFormat(const GrBackendFormat& format, SkColorType ct,
-                                    GrPixelConfig* config) const override {
+    GrPixelConfig getConfigFromBackendFormat(const GrBackendFormat& format,
+                                             SkColorType ct) const override {
         const GrPixelConfig* mockFormat = format.getMockFormat();
         if (!mockFormat) {
-            return false;
+            return kUnknown_GrPixelConfig;
         }
-        *config = *mockFormat;
-        return true;
+        return *mockFormat;
     }
 
-    bool getYUVAConfigFromBackendTexture(const GrBackendTexture& tex,
-                                         GrPixelConfig* config) const override {
-        GrMockTextureInfo texInfo;
-        if (!tex.getMockTextureInfo(&texInfo)) {
-            return false;
-        }
-
-        *config = texInfo.fConfig;
-        return true;
-    }
-
-    bool getYUVAConfigFromBackendFormat(const GrBackendFormat& format,
-                                        GrPixelConfig* config) const override {
+    GrPixelConfig getYUVAConfigFromBackendFormat(const GrBackendFormat& format) const override {
         const GrPixelConfig* mockFormat = format.getMockFormat();
         if (!mockFormat) {
-            return false;
+            return kUnknown_GrPixelConfig;
         }
-        *config = *mockFormat;
-        return true;
+        return *mockFormat;
     }
 
     GrBackendFormat getBackendFormatFromGrColorType(GrColorType ct,
@@ -136,11 +105,10 @@
     }
 
 private:
-    GrBackendFormat onCreateFormatFromBackendTexture(
-            const GrBackendTexture& backendTex) const override {
-        GrMockTextureInfo mockInfo;
-        SkAssertResult(backendTex.getMockTextureInfo(&mockInfo));
-        return GrBackendFormat::MakeMock(mockInfo.fConfig);
+    bool onSurfaceSupportsWritePixels(const GrSurface*) const override { return true; }
+    bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                          const SkIRect& srcRect, const SkIPoint& dstPoint) const override {
+        return true;
     }
 
     static const int kMaxSampleCnt = 16;
diff --git a/src/gpu/mock/GrMockGpu.cpp b/src/gpu/mock/GrMockGpu.cpp
index 5fa5350..b98af80 100644
--- a/src/gpu/mock/GrMockGpu.cpp
+++ b/src/gpu/mock/GrMockGpu.cpp
@@ -11,31 +11,36 @@
 #include "GrMockGpuCommandBuffer.h"
 #include "GrMockStencilAttachment.h"
 #include "GrMockTexture.h"
+#include <atomic>
 
 int GrMockGpu::NextInternalTextureID() {
-    static int gID = 0;
-    return sk_atomic_inc(&gID) + 1;
+    static std::atomic<int> nextID{1};
+    int id;
+    do {
+        id = nextID.fetch_add(1);
+    } while (0 == id);  // Reserve 0 for an invalid ID.
+    return id;
 }
 
 int GrMockGpu::NextExternalTextureID() {
     // We use negative ints for the "testing only external textures" so they can easily be
     // identified when debugging.
-    static int gID = 0;
-    return sk_atomic_dec(&gID) - 1;
+    static std::atomic<int> nextID{-1};
+    return nextID--;
 }
 
 int GrMockGpu::NextInternalRenderTargetID() {
-    // We start off with large numbers to differentiate from texture IDs, even though their
+    // We start off with large numbers to differentiate from texture IDs, even though they're
     // technically in a different space.
-    static int gID = SK_MaxS32;
-    return sk_atomic_dec(&gID);
+    static std::atomic<int> nextID{SK_MaxS32};
+    return nextID--;
 }
 
 int GrMockGpu::NextExternalRenderTargetID() {
     // We use large negative ints for the "testing only external render targets" so they can easily
     // be identified when debugging.
-    static int gID = SK_MinS32;
-    return sk_atomic_inc(&gID);
+    static std::atomic<int> nextID{SK_MinS32};
+    return nextID++;
 }
 
 sk_sp<GrGpu> GrMockGpu::Make(const GrMockOptions* mockOptions,
@@ -102,7 +107,8 @@
 }
 
 sk_sp<GrTexture> GrMockGpu::onWrapBackendTexture(const GrBackendTexture& tex,
-                                                 GrWrapOwnership ownership, bool purgeImmediately) {
+                                                 GrWrapOwnership ownership, GrIOType ioType,
+                                                 bool purgeImmediately) {
     GrSurfaceDesc desc;
     desc.fWidth = tex.width();
     desc.fHeight = tex.height();
@@ -115,7 +121,7 @@
                                                      : GrMipMapsStatus::kNotAllocated;
 
     return sk_sp<GrTexture>(new GrMockTexture(this, GrMockTexture::kWrapped, desc, mipMapsStatus,
-                                              info, purgeImmediately));
+                                              info, ioType, purgeImmediately));
 }
 
 sk_sp<GrTexture> GrMockGpu::onWrapRenderableBackendTexture(const GrBackendTexture& tex,
diff --git a/src/gpu/mock/GrMockGpu.h b/src/gpu/mock/GrMockGpu.h
index a39a82b..d3584b5 100644
--- a/src/gpu/mock/GrMockGpu.h
+++ b/src/gpu/mock/GrMockGpu.h
@@ -41,7 +41,7 @@
     sk_sp<GrSemaphore> wrapBackendSemaphore(const GrBackendSemaphore& semaphore,
                                             GrResourceProvider::SemaphoreWrapType wrapType,
                                             GrWrapOwnership ownership) override { return nullptr; }
-    void insertSemaphore(sk_sp<GrSemaphore> semaphore, bool flush) override {}
+    void insertSemaphore(sk_sp<GrSemaphore> semaphore) override {}
     void waitSemaphore(sk_sp<GrSemaphore> semaphore) override {}
     sk_sp<GrSemaphore> prepareTextureForCrossContextUsage(GrTexture*) override { return nullptr; }
 
@@ -59,7 +59,7 @@
     sk_sp<GrTexture> onCreateTexture(const GrSurfaceDesc&, SkBudgeted, const GrMipLevel[],
                                      int mipLevelCount) override;
 
-    sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership,
+    sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership, GrIOType,
                                           bool purgeImmediately) override;
 
     sk_sp<GrTexture> onWrapRenderableBackendTexture(const GrBackendTexture&,
diff --git a/src/gpu/mock/GrMockTexture.h b/src/gpu/mock/GrMockTexture.h
index 0565f81..ac5eab0 100644
--- a/src/gpu/mock/GrMockTexture.h
+++ b/src/gpu/mock/GrMockTexture.h
@@ -23,10 +23,12 @@
     }
 
     enum Wrapped { kWrapped };
-    GrMockTexture(GrMockGpu* gpu, Wrapped, const GrSurfaceDesc& desc,
-                  GrMipMapsStatus mipMapsStatus, const GrMockTextureInfo& info,
-                  bool purgeImmediately)
+    GrMockTexture(GrMockGpu* gpu, Wrapped, const GrSurfaceDesc& desc, GrMipMapsStatus mipMapsStatus,
+                  const GrMockTextureInfo& info, GrIOType ioType, bool purgeImmediately)
             : GrMockTexture(gpu, desc, mipMapsStatus, info) {
+        if (ioType == kRead_GrIOType) {
+            this->setReadOnly();
+        }
         this->registerWithCacheWrapped(purgeImmediately);
     }
 
@@ -46,6 +48,11 @@
         fReleaseHelper = std::move(releaseHelper);
     }
 
+    void setIdleProc(IdleProc proc, void* context) override {
+        fIdleProc = proc;
+        fIdleProcContext = context;
+    }
+
 protected:
     // constructor for subclasses
     GrMockTexture(GrMockGpu* gpu, const GrSurfaceDesc& desc, GrMipMapsStatus mipMapsStatus,
@@ -68,16 +75,27 @@
         return false;
     }
 
-private:
-    void invokeReleaseProc() {
-        if (fReleaseHelper) {
-            // Depending on the ref count of fReleaseHelper this may or may not actually trigger the
-            // ReleaseProc to be called.
-            fReleaseHelper.reset();
+    // protected so that GrMockTextureRenderTarget can call this to avoid "inheritance via
+    // dominance" warning.
+    void becamePurgeable() override {
+        if (fIdleProc) {
+            fIdleProc(fIdleProcContext);
+            fIdleProc = nullptr;
+            fIdleProcContext = nullptr;
         }
     }
-    GrMockTextureInfo          fInfo;
+
+private:
+    void invokeReleaseProc() {
+        // Depending on the ref count of fReleaseHelper this may or may not actually trigger the
+        // ReleaseProc to be called.
+        fReleaseHelper.reset();
+    }
+
+    GrMockTextureInfo fInfo;
     sk_sp<GrReleaseProcHelper> fReleaseHelper;
+    IdleProc* fIdleProc = nullptr;
+    void* fIdleProcContext = nullptr;
 
     typedef GrTexture INHERITED;
 };
@@ -177,6 +195,9 @@
         GrMockTexture::onRelease();
     }
 
+    // We implement this to avoid the inheritance via dominance warning.
+    void becamePurgeable() override { GrMockTexture::becamePurgeable(); }
+
     size_t onGpuMemorySize() const override {
         int numColorSamples = this->numColorSamples();
         if (numColorSamples > 1) {
diff --git a/src/gpu/mtl/GrMtlCaps.h b/src/gpu/mtl/GrMtlCaps.h
index 7edfa79..7822f60 100644
--- a/src/gpu/mtl/GrMtlCaps.h
+++ b/src/gpu/mtl/GrMtlCaps.h
@@ -33,7 +33,6 @@
     int getRenderTargetSampleCount(int requestedCount, GrPixelConfig) const override;
     int maxRenderTargetSampleCount(GrPixelConfig) const override;
 
-    bool surfaceSupportsWritePixels(const GrSurface*) const override { return true; }
     bool surfaceSupportsReadPixels(const GrSurface*) const override { return true; }
 
     bool isConfigCopyable(GrPixelConfig config) const override {
@@ -58,36 +57,22 @@
     bool canCopyAsDrawThenBlit(GrPixelConfig dstConfig, GrPixelConfig srcConfig,
                                bool srcIsTextureable) const;
 
-    bool canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
-                        const SkIRect& srcRect, const SkIPoint& dstPoint) const override;
-
     bool initDescForDstCopy(const GrRenderTargetProxy* src, GrSurfaceDesc* desc, GrSurfaceOrigin*,
                             bool* rectsMustMatch, bool* disallowSubrect) const override {
         return false;
     }
 
-    bool validateBackendTexture(const GrBackendTexture&, SkColorType,
-                                GrPixelConfig*) const override {
-        return false;
-    }
-    bool validateBackendRenderTarget(const GrBackendRenderTarget&, SkColorType,
-                                     GrPixelConfig*) const override {
-        return false;
+    GrPixelConfig validateBackendRenderTarget(const GrBackendRenderTarget&,
+                                              SkColorType) const override {
+        return kUnknown_GrPixelConfig;
     }
 
-    bool getConfigFromBackendFormat(const GrBackendFormat&, SkColorType,
-                                    GrPixelConfig*) const override {
-        return false;
+    GrPixelConfig getConfigFromBackendFormat(const GrBackendFormat&, SkColorType) const override {
+        return kUnknown_GrPixelConfig;
     }
 
-    bool getYUVAConfigFromBackendTexture(const GrBackendTexture&,
-                                         GrPixelConfig*) const override {
-        return false;
-    }
-
-    bool getYUVAConfigFromBackendFormat(const GrBackendFormat&,
-                                        GrPixelConfig*) const override {
-        return false;
+    GrPixelConfig getYUVAConfigFromBackendFormat(const GrBackendFormat&) const override {
+        return kUnknown_GrPixelConfig;
     }
 
     GrBackendFormat getBackendFormatFromGrColorType(GrColorType ct,
@@ -105,10 +90,12 @@
     void initGrCaps(const id<MTLDevice> device);
     void initShaderCaps();
 
-    GrBackendFormat onCreateFormatFromBackendTexture(const GrBackendTexture&) const override;
-
     void initConfigTable();
 
+    bool onSurfaceSupportsWritePixels(const GrSurface*) const override { return true; }
+    bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                          const SkIRect& srcRect, const SkIPoint& dstPoint) const override;
+
     struct ConfigInfo {
         ConfigInfo() : fFlags(0) {}
 
diff --git a/src/gpu/mtl/GrMtlCaps.mm b/src/gpu/mtl/GrMtlCaps.mm
index ca3779b..2f56e6b 100644
--- a/src/gpu/mtl/GrMtlCaps.mm
+++ b/src/gpu/mtl/GrMtlCaps.mm
@@ -166,8 +166,8 @@
     return true;
 }
 
-bool GrMtlCaps::canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
-                               const SkIRect& srcRect, const SkIPoint& dstPoint) const {
+bool GrMtlCaps::onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                                 const SkIRect& srcRect, const SkIPoint& dstPoint) const {
     GrSurfaceOrigin dstOrigin = dst->origin();
     GrSurfaceOrigin srcOrigin = src->origin();
 
@@ -221,6 +221,16 @@
         }
     }
 
+    // Clamp to border is supported on Mac 10.12 and higher (gpu family.version >= 1.2). It is not
+    // supported on iOS.
+    if (this->isMac()) {
+        if (fFamilyGroup == 1 && fVersion < 2) {
+            fClampToBorderSupport = false;
+        }
+    } else {
+        fClampToBorderSupport = false;
+    }
+
     // Starting with the assumption that there isn't a reason to not map small buffers.
     fBufferMapThreshold = 0;
 
@@ -229,10 +239,6 @@
 
     fOversizedStencilSupport = true;
 
-    // Looks like there is a field called rasterSampleCount labeled as beta in the Metal docs. This
-    // may be what we eventually need here, but it has no description.
-    fSampleShadingSupport = false;
-
     fSRGBSupport = true;   // always available in Metal
     fSRGBWriteControl = false;
     fMipMapSupport = true;   // always available in Metal
@@ -414,15 +420,6 @@
     fPreferredStencilFormat = StencilFormat{ MTLPixelFormatStencil8, 8, 8, true };
 }
 
-GrBackendFormat GrMtlCaps::onCreateFormatFromBackendTexture(
-        const GrBackendTexture& backendTex) const {
-    GrMtlTextureInfo mtlInfo;
-    SkAssertResult(backendTex.getMtlTextureInfo(&mtlInfo));
-    id<MTLTexture> mtlTexture = GrGetMTLTexture(mtlInfo.fTexture,
-                                                GrWrapOwnership::kBorrow_GrWrapOwnership);
-    return GrBackendFormat::MakeMtl(mtlTexture.pixelFormat);
-}
-
 GrBackendFormat GrMtlCaps::getBackendFormatFromGrColorType(GrColorType ct,
                                                            GrSRGBEncoded srgbEncoded) const {
     GrPixelConfig config = GrColorTypeToPixelConfig(ct, srgbEncoded);
diff --git a/src/gpu/mtl/GrMtlCppUtil.h b/src/gpu/mtl/GrMtlCppUtil.h
new file mode 100644
index 0000000..ed796fc
--- /dev/null
+++ b/src/gpu/mtl/GrMtlCppUtil.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrMtlCppUtil_DEFINED
+#define GrMtlCppUtil_DEFINED
+
+#include "mtl/GrMtlTypes.h"
+
+// Utilities that can be used from cpp files (rather than .mm).
+
+GrMTLPixelFormat GrGetMTLPixelFormatFromMtlTextureInfo(const GrMtlTextureInfo&);
+
+
+#endif
diff --git a/src/gpu/mtl/GrMtlGpu.h b/src/gpu/mtl/GrMtlGpu.h
index a727784..cc13645 100644
--- a/src/gpu/mtl/GrMtlGpu.h
+++ b/src/gpu/mtl/GrMtlGpu.h
@@ -108,7 +108,7 @@
     sk_sp<GrSemaphore> wrapBackendSemaphore(const GrBackendSemaphore& semaphore,
                                             GrResourceProvider::SemaphoreWrapType wrapType,
                                             GrWrapOwnership ownership) override { return nullptr; }
-    void insertSemaphore(sk_sp<GrSemaphore> semaphore, bool flush) override {}
+    void insertSemaphore(sk_sp<GrSemaphore> semaphore) override {}
     void waitSemaphore(sk_sp<GrSemaphore> semaphore) override {}
     sk_sp<GrSemaphore> prepareTextureForCrossContextUsage(GrTexture*) override { return nullptr; }
 
@@ -132,7 +132,7 @@
     sk_sp<GrTexture> onCreateTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted,
                                      const GrMipLevel texels[], int mipLevelCount) override;
 
-    sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership,
+    sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership, GrIOType,
                                           bool purgeImmediately) override;
 
     sk_sp<GrTexture> onWrapRenderableBackendTexture(const GrBackendTexture&,
diff --git a/src/gpu/mtl/GrMtlGpu.mm b/src/gpu/mtl/GrMtlGpu.mm
index 0661648..132f455 100644
--- a/src/gpu/mtl/GrMtlGpu.mm
+++ b/src/gpu/mtl/GrMtlGpu.mm
@@ -338,7 +338,8 @@
 }
 
 sk_sp<GrTexture> GrMtlGpu::onWrapBackendTexture(const GrBackendTexture& backendTex,
-                                                GrWrapOwnership ownership, bool purgeImmediately) {
+                                                GrWrapOwnership ownership, GrIOType ioType,
+                                                bool purgeImmediately) {
     id<MTLTexture> mtlTexture = get_texture_from_backend(backendTex, ownership);
     if (!mtlTexture) {
         return nullptr;
@@ -347,7 +348,7 @@
     GrSurfaceDesc surfDesc;
     init_surface_desc(&surfDesc, mtlTexture, false, backendTex.config());
 
-    return GrMtlTexture::MakeWrappedTexture(this, surfDesc, mtlTexture, purgeImmediately);
+    return GrMtlTexture::MakeWrappedTexture(this, surfDesc, mtlTexture, ioType, purgeImmediately);
 }
 
 sk_sp<GrTexture> GrMtlGpu::onWrapRenderableBackendTexture(const GrBackendTexture& backendTex,
diff --git a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
index 0eb4230..8cdd80c 100644
--- a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
+++ b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
@@ -122,7 +122,6 @@
     if (!GrProgramDesc::Build(&desc, primProc, hasPoints, pipeline, fGpu)) {
         return nullptr;
     }
-    desc.finalize();
 
     const GrTextureProxy* const* primProcProxies = nullptr;
     if (fixedDynamicState) {
diff --git a/src/gpu/mtl/GrMtlPipelineStateBuilder.mm b/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
index fb9cdf8..6954f10 100644
--- a/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
+++ b/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
@@ -78,7 +78,6 @@
     if (inputs.fFlipY) {
         desc->setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(
                                                                this->pipeline().proxy()->origin()));
-        desc->finalize();
     }
     return shaderLibrary;
 }
diff --git a/src/gpu/mtl/GrMtlSampler.mm b/src/gpu/mtl/GrMtlSampler.mm
index 02c6ecc..0b2c491 100644
--- a/src/gpu/mtl/GrMtlSampler.mm
+++ b/src/gpu/mtl/GrMtlSampler.mm
@@ -10,7 +10,7 @@
 #include "GrMtlGpu.h"
 
 static inline MTLSamplerAddressMode wrap_mode_to_mtl_sampler_address(
-        GrSamplerState::WrapMode wrapMode) {
+        GrSamplerState::WrapMode wrapMode, const GrCaps& caps) {
     switch (wrapMode) {
         case GrSamplerState::WrapMode::kClamp:
             return MTLSamplerAddressModeClampToEdge;
@@ -18,6 +18,16 @@
             return MTLSamplerAddressModeRepeat;
         case GrSamplerState::WrapMode::kMirrorRepeat:
             return MTLSamplerAddressModeMirrorRepeat;
+        case GrSamplerState::WrapMode::kClampToBorder:
+            // Must guard the reference to the clamp to border address mode by macro since iOS
+            // builds will fail if it's referenced, even if other code makes sure it's never used.
+#ifdef SK_BUILD_FOR_IOS
+            SkASSERT(false);
+            return MTLSamplerAddressModeClampToEdge;
+#else
+            SkASSERT(caps.clampToBorderSupport());
+            return MTLSamplerAddressModeClampToBorderColor;
+#endif
     }
     SK_ABORT("Unknown wrap mode.");
     return MTLSamplerAddressModeClampToEdge;
@@ -37,8 +47,10 @@
 
     auto samplerDesc = [[MTLSamplerDescriptor alloc] init];
     samplerDesc.rAddressMode = MTLSamplerAddressModeClampToEdge;
-    samplerDesc.sAddressMode = wrap_mode_to_mtl_sampler_address(samplerState.wrapModeX());
-    samplerDesc.tAddressMode = wrap_mode_to_mtl_sampler_address(samplerState.wrapModeY());
+    samplerDesc.sAddressMode = wrap_mode_to_mtl_sampler_address(samplerState.wrapModeX(),
+                                                                gpu->mtlCaps());
+    samplerDesc.tAddressMode = wrap_mode_to_mtl_sampler_address(samplerState.wrapModeY(),
+                                                                gpu->mtlCaps());
     samplerDesc.magFilter = mtlMinMagFilterModes[static_cast<int>(samplerState.filter())];
     samplerDesc.minFilter = mtlMinMagFilterModes[static_cast<int>(samplerState.filter())];
     samplerDesc.mipFilter = MTLSamplerMipFilterLinear;
diff --git a/src/gpu/mtl/GrMtlTexture.h b/src/gpu/mtl/GrMtlTexture.h
index 5f6234e..8037c68 100644
--- a/src/gpu/mtl/GrMtlTexture.h
+++ b/src/gpu/mtl/GrMtlTexture.h
@@ -21,8 +21,8 @@
                                                 MTLTextureDescriptor*,
                                                 GrMipMapsStatus);
 
-    static sk_sp<GrMtlTexture> MakeWrappedTexture(GrMtlGpu*, const GrSurfaceDesc&,
-                                                  id<MTLTexture>, bool purgeImmediately);
+    static sk_sp<GrMtlTexture> MakeWrappedTexture(GrMtlGpu*, const GrSurfaceDesc&, id<MTLTexture>,
+                                                  GrIOType, bool purgeImmediately);
 
     ~GrMtlTexture() override;
 
@@ -43,15 +43,22 @@
         fReleaseHelper = std::move(releaseHelper);
     }
 
+    void setIdleProc(IdleProc proc, void* context) override {
+        fIdleProc = proc;
+        fIdleProcContext = context;
+    }
+
 protected:
     GrMtlTexture(GrMtlGpu*, const GrSurfaceDesc&, id<MTLTexture>, GrMipMapsStatus);
 
     GrMtlGpu* getMtlGpu() const;
 
     void onAbandon() override {
+        this->invokeReleaseProc();
         fTexture = nil;
     }
     void onRelease() override {
+        this->invokeReleaseProc();
         fTexture = nil;
     }
 
@@ -61,15 +68,31 @@
 
 private:
     enum Wrapped { kWrapped };
+
+    void invokeReleaseProc() {
+        // Depending on the ref count of fReleaseHelper this may or may not actually trigger the
+        // ReleaseProc to be called.
+        fReleaseHelper.reset();
+    }
+
+    void becamePurgeable() override {
+        if (fIdleProc) {
+            fIdleProc(fIdleProcContext);
+            fIdleProc = nullptr;
+            fIdleProcContext = nullptr;
+        }
+    }
+
     GrMtlTexture(GrMtlGpu*, SkBudgeted, const GrSurfaceDesc&, id<MTLTexture>,
                  GrMipMapsStatus);
 
     GrMtlTexture(GrMtlGpu*, Wrapped, const GrSurfaceDesc&, id<MTLTexture>, GrMipMapsStatus,
-                 bool purgeImmediately);
+                 GrIOType, bool purgeImmediately);
 
     id<MTLTexture> fTexture;
-
-    sk_sp<GrReleaseProcHelper>        fReleaseHelper;
+    sk_sp<GrReleaseProcHelper> fReleaseHelper;
+    IdleProc* fIdleProc = nullptr;
+    void* fIdleProcContext = nullptr;
 
     typedef GrTexture INHERITED;
 };
diff --git a/src/gpu/mtl/GrMtlTexture.mm b/src/gpu/mtl/GrMtlTexture.mm
index 711d5ae..5e91746 100644
--- a/src/gpu/mtl/GrMtlTexture.mm
+++ b/src/gpu/mtl/GrMtlTexture.mm
@@ -28,11 +28,15 @@
                            const GrSurfaceDesc& desc,
                            id<MTLTexture> texture,
                            GrMipMapsStatus mipMapsStatus,
+                           GrIOType ioType,
                            bool purgeImmediately)
         : GrSurface(gpu, desc)
         , INHERITED(gpu, desc, GrTextureType::k2D, mipMapsStatus)
         , fTexture(texture) {
     SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == texture.mipmapLevelCount));
+    if (ioType == kRead_GrIOType) {
+        this->setReadOnly();
+    }
     this->registerWithCacheWrapped(purgeImmediately);
 }
 
@@ -63,6 +67,7 @@
 sk_sp<GrMtlTexture> GrMtlTexture::MakeWrappedTexture(GrMtlGpu* gpu,
                                                      const GrSurfaceDesc& desc,
                                                      id<MTLTexture> texture,
+                                                     GrIOType ioType,
                                                      bool purgeImmediately) {
     if (desc.fSampleCnt > 1) {
         SkASSERT(false); // Currently we don't support msaa
@@ -72,7 +77,7 @@
     SkASSERT(MTLTextureUsageShaderRead & texture.usage);
     GrMipMapsStatus mipMapsStatus = texture.mipmapLevelCount > 1 ? GrMipMapsStatus::kValid
                                                                  : GrMipMapsStatus::kNotAllocated;
-    return sk_sp<GrMtlTexture>(new GrMtlTexture(gpu, kWrapped, desc, texture, mipMapsStatus,
+    return sk_sp<GrMtlTexture>(new GrMtlTexture(gpu, kWrapped, desc, texture, mipMapsStatus, ioType,
                                                 purgeImmediately));
 }
 
diff --git a/src/gpu/mtl/GrMtlUtil.mm b/src/gpu/mtl/GrMtlUtil.mm
index f544421..6d80fbe 100644
--- a/src/gpu/mtl/GrMtlUtil.mm
+++ b/src/gpu/mtl/GrMtlUtil.mm
@@ -33,6 +33,9 @@
         case kRGB_888_GrPixelConfig:
             // TODO: MTLPixelFormatRGB8Unorm
             return false;
+        case kRG_88_GrPixelConfig:
+            // TODO: MTLPixelFormatRG8Unorm
+            return false;
         case kBGRA_8888_GrPixelConfig:
             *format = MTLPixelFormatBGRA8Unorm;
             return true;
@@ -107,6 +110,8 @@
         case MTLPixelFormatABGR4Unorm:
             return kRGBA_4444_GrPixelConfig;
 #endif
+        case MTLPixelFormatRG8Unorm:
+            return kRG_88_GrPixelConfig;
         case MTLPixelFormatR8Unorm:
             // We currently set this to be Alpha_8 and have no way to go to Gray_8
             return kAlpha_8_GrPixelConfig;
@@ -227,3 +232,13 @@
     }
     return mtlTexture;
 }
+
+
+//////////////////////////////////////////////////////////////////////////////
+// CPP Utils
+
+GrMTLPixelFormat GrGetMTLPixelFormatFromMtlTextureInfo(const GrMtlTextureInfo& info) {
+    id<MTLTexture> mtlTexture = GrGetMTLTexture(info.fTexture,
+                                                GrWrapOwnership::kBorrow_GrWrapOwnership);
+    return static_cast<GrMTLPixelFormat>(mtlTexture.pixelFormat);
+}
diff --git a/src/gpu/ops/GrAAConvexPathRenderer.cpp b/src/gpu/ops/GrAAConvexPathRenderer.cpp
index 1deb8be..6b8f6c6 100644
--- a/src/gpu/ops/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAAConvexPathRenderer.cpp
@@ -340,14 +340,6 @@
     }
 }
 
-struct QuadVertex {
-    SkPoint  fPos;
-    GrColor  fColor;
-    SkPoint  fUV;
-    SkScalar fD0;
-    SkScalar fD1;
-};
-
 struct Draw {
     Draw() : fVertexCnt(0), fIndexCnt(0) {}
     int fVertexCnt;
@@ -358,14 +350,16 @@
 
 static void create_vertices(const SegmentArray& segments,
                             const SkPoint& fanPt,
-                            GrColor color,
+                            const GrVertexColor& color,
                             DrawArray* draws,
-                            QuadVertex* verts,
-                            uint16_t* idxs) {
+                            GrVertexWriter& verts,
+                            uint16_t* idxs,
+                            size_t vertexStride) {
     Draw* draw = &draws->push_back();
     // alias just to make vert/index assignments easier to read.
     int* v = &draw->fVertexCnt;
     int* i = &draw->fIndexCnt;
+    const size_t uvOffset = sizeof(SkPoint) + color.size();
 
     int count = segments.count();
     for (int a = 0; a < count; ++a) {
@@ -382,30 +376,21 @@
             vCount += 6;
         }
         if (draw->fVertexCnt + vCount > (1 << 16)) {
-            verts += *v;
             idxs += *i;
             draw = &draws->push_back();
             v = &draw->fVertexCnt;
             i = &draw->fIndexCnt;
         }
 
+        const SkScalar negOneDists[2] = { -SK_Scalar1, -SK_Scalar1 };
+
         // FIXME: These tris are inset in the 1 unit arc around the corner
-        verts[*v + 0].fPos = sega.endPt();
-        verts[*v + 1].fPos = verts[*v + 0].fPos + sega.endNorm();
-        verts[*v + 2].fPos = verts[*v + 0].fPos + segb.fMid;
-        verts[*v + 3].fPos = verts[*v + 0].fPos + segb.fNorms[0];
-        verts[*v + 0].fColor = color;
-        verts[*v + 1].fColor = color;
-        verts[*v + 2].fColor = color;
-        verts[*v + 3].fColor = color;
-        verts[*v + 0].fUV.set(0,0);
-        verts[*v + 1].fUV.set(0,-SK_Scalar1);
-        verts[*v + 2].fUV.set(0,-SK_Scalar1);
-        verts[*v + 3].fUV.set(0,-SK_Scalar1);
-        verts[*v + 0].fD0 = verts[*v + 0].fD1 = -SK_Scalar1;
-        verts[*v + 1].fD0 = verts[*v + 1].fD1 = -SK_Scalar1;
-        verts[*v + 2].fD0 = verts[*v + 2].fD1 = -SK_Scalar1;
-        verts[*v + 3].fD0 = verts[*v + 3].fD1 = -SK_Scalar1;
+        SkPoint p0 = sega.endPt();
+        // Position, Color, UV, D0, D1
+        verts.write(p0,                  color, SkPoint{0, 0},           negOneDists);
+        verts.write(p0 + sega.endNorm(), color, SkPoint{0, -SK_Scalar1}, negOneDists);
+        verts.write(p0 + segb.fMid,      color, SkPoint{0, -SK_Scalar1}, negOneDists);
+        verts.write(p0 + segb.fNorms[0], color, SkPoint{0, -SK_Scalar1}, negOneDists);
 
         idxs[*i + 0] = *v + 0;
         idxs[*i + 1] = *v + 2;
@@ -418,34 +403,17 @@
         *i += 6;
 
         if (Segment::kLine == segb.fType) {
-            verts[*v + 0].fPos = fanPt;
-            verts[*v + 1].fPos = sega.endPt();
-            verts[*v + 2].fPos = segb.fPts[0];
-
-            verts[*v + 3].fPos = verts[*v + 1].fPos + segb.fNorms[0];
-            verts[*v + 4].fPos = verts[*v + 2].fPos + segb.fNorms[0];
-
-            verts[*v + 0].fColor = color;
-            verts[*v + 1].fColor = color;
-            verts[*v + 2].fColor = color;
-            verts[*v + 3].fColor = color;
-            verts[*v + 4].fColor = color;
-
             // we draw the line edge as a degenerate quad (u is 0, v is the
             // signed distance to the edge)
-            SkScalar dist = SkPointPriv::DistanceToLineBetween(fanPt, verts[*v + 1].fPos,
-                                                               verts[*v + 2].fPos);
-            verts[*v + 0].fUV.set(0, dist);
-            verts[*v + 1].fUV.set(0, 0);
-            verts[*v + 2].fUV.set(0, 0);
-            verts[*v + 3].fUV.set(0, -SK_Scalar1);
-            verts[*v + 4].fUV.set(0, -SK_Scalar1);
+            SkPoint v1Pos = sega.endPt();
+            SkPoint v2Pos = segb.fPts[0];
+            SkScalar dist = SkPointPriv::DistanceToLineBetween(fanPt, v1Pos, v2Pos);
 
-            verts[*v + 0].fD0 = verts[*v + 0].fD1 = -SK_Scalar1;
-            verts[*v + 1].fD0 = verts[*v + 1].fD1 = -SK_Scalar1;
-            verts[*v + 2].fD0 = verts[*v + 2].fD1 = -SK_Scalar1;
-            verts[*v + 3].fD0 = verts[*v + 3].fD1 = -SK_Scalar1;
-            verts[*v + 4].fD0 = verts[*v + 4].fD1 = -SK_Scalar1;
+            verts.write(fanPt,                  color, SkPoint{0, dist},        negOneDists);
+            verts.write(v1Pos,                  color, SkPoint{0, 0},           negOneDists);
+            verts.write(v2Pos,                  color, SkPoint{0, 0},           negOneDists);
+            verts.write(v1Pos + segb.fNorms[0], color, SkPoint{0, -SK_Scalar1}, negOneDists);
+            verts.write(v2Pos + segb.fNorms[0], color, SkPoint{0, -SK_Scalar1}, negOneDists);
 
             idxs[*i + 0] = *v + 3;
             idxs[*i + 1] = *v + 1;
@@ -470,43 +438,49 @@
 
             *v += 5;
         } else {
+            void* quadVertsBegin = verts.fPtr;
+
             SkPoint qpts[] = {sega.endPt(), segb.fPts[0], segb.fPts[1]};
 
+            SkScalar c0 = segb.fNorms[0].dot(qpts[0]);
+            SkScalar c1 = segb.fNorms[1].dot(qpts[2]);
+            GrVertexWriter::Skip<SkPoint> skipUVs;
+
+            verts.write(fanPt,
+                        color, skipUVs,
+                        -segb.fNorms[0].dot(fanPt) + c0,
+                        -segb.fNorms[1].dot(fanPt) + c1);
+
+            verts.write(qpts[0],
+                        color, skipUVs,
+                        0.0f,
+                        -segb.fNorms[1].dot(qpts[0]) + c1);
+
+            verts.write(qpts[2],
+                        color, skipUVs,
+                        -segb.fNorms[0].dot(qpts[2]) + c0,
+                        0.0f);
+
+            verts.write(qpts[0] + segb.fNorms[0],
+                        color, skipUVs,
+                        -SK_ScalarMax/100,
+                        -SK_ScalarMax/100);
+
+            verts.write(qpts[2] + segb.fNorms[1],
+                        color, skipUVs,
+                        -SK_ScalarMax/100,
+                        -SK_ScalarMax/100);
+
             SkVector midVec = segb.fNorms[0] + segb.fNorms[1];
             midVec.normalize();
 
-            verts[*v + 0].fPos = fanPt;
-            verts[*v + 1].fPos = qpts[0];
-            verts[*v + 2].fPos = qpts[2];
-            verts[*v + 3].fPos = qpts[0] + segb.fNorms[0];
-            verts[*v + 4].fPos = qpts[2] + segb.fNorms[1];
-            verts[*v + 5].fPos = qpts[1] + midVec;
-
-            verts[*v + 0].fColor = color;
-            verts[*v + 1].fColor = color;
-            verts[*v + 2].fColor = color;
-            verts[*v + 3].fColor = color;
-            verts[*v + 4].fColor = color;
-            verts[*v + 5].fColor = color;
-
-            SkScalar c = segb.fNorms[0].dot(qpts[0]);
-            verts[*v + 0].fD0 =  -segb.fNorms[0].dot(fanPt) + c;
-            verts[*v + 1].fD0 =  0.f;
-            verts[*v + 2].fD0 =  -segb.fNorms[0].dot(qpts[2]) + c;
-            verts[*v + 3].fD0 = -SK_ScalarMax/100;
-            verts[*v + 4].fD0 = -SK_ScalarMax/100;
-            verts[*v + 5].fD0 = -SK_ScalarMax/100;
-
-            c = segb.fNorms[1].dot(qpts[2]);
-            verts[*v + 0].fD1 =  -segb.fNorms[1].dot(fanPt) + c;
-            verts[*v + 1].fD1 =  -segb.fNorms[1].dot(qpts[0]) + c;
-            verts[*v + 2].fD1 =  0.f;
-            verts[*v + 3].fD1 = -SK_ScalarMax/100;
-            verts[*v + 4].fD1 = -SK_ScalarMax/100;
-            verts[*v + 5].fD1 = -SK_ScalarMax/100;
+            verts.write(qpts[1] + midVec,
+                        color, skipUVs,
+                        -SK_ScalarMax/100,
+                        -SK_ScalarMax/100);
 
             GrPathUtils::QuadUVMatrix toUV(qpts);
-            toUV.apply<6, sizeof(QuadVertex), offsetof(QuadVertex, fUV)>(verts + *v);
+            toUV.apply(quadVertsBegin, 6, vertexStride, uvOffset);
 
             idxs[*i + 0] = *v + 3;
             idxs[*i + 1] = *v + 1;
@@ -551,8 +525,10 @@
 
 class QuadEdgeEffect : public GrGeometryProcessor {
 public:
-    static sk_sp<GrGeometryProcessor> Make(const SkMatrix& localMatrix, bool usesLocalCoords) {
-        return sk_sp<GrGeometryProcessor>(new QuadEdgeEffect(localMatrix, usesLocalCoords));
+    static sk_sp<GrGeometryProcessor> Make(const SkMatrix& localMatrix, bool usesLocalCoords,
+                                           bool wideColor) {
+        return sk_sp<GrGeometryProcessor>(
+                new QuadEdgeEffect(localMatrix, usesLocalCoords, wideColor));
     }
 
     ~QuadEdgeEffect() override {}
@@ -640,12 +616,12 @@
     }
 
 private:
-    QuadEdgeEffect(const SkMatrix& localMatrix, bool usesLocalCoords)
+    QuadEdgeEffect(const SkMatrix& localMatrix, bool usesLocalCoords, bool wideColor)
             : INHERITED(kQuadEdgeEffect_ClassID)
             , fLocalMatrix(localMatrix)
             , fUsesLocalCoords(usesLocalCoords) {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
-        fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
+        fInColor = MakeColorAttribute("inColor", wideColor);
         fInQuadEdge = {"inQuadEdge", kFloat4_GrVertexAttribType, kHalf4_GrSLType};
         this->setVertexAttributes(&fInPosition, 3);
     }
@@ -668,7 +644,8 @@
 sk_sp<GrGeometryProcessor> QuadEdgeEffect::TestCreate(GrProcessorTestData* d) {
     // Doesn't work without derivative instructions.
     return d->caps()->shaderCaps()->shaderDerivativeSupport()
-                   ? QuadEdgeEffect::Make(GrTest::TestMatrix(d->fRandom), d->fRandom->nextBool())
+                   ? QuadEdgeEffect::Make(GrTest::TestMatrix(d->fRandom), d->fRandom->nextBool(),
+                                          d->fRandom->nextBool())
                    : nullptr;
 }
 #endif
@@ -709,6 +686,7 @@
             : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage, stencilSettings) {
         fPaths.emplace_back(PathData{viewMatrix, path, color});
         this->setTransformedBounds(path.getBounds(), viewMatrix, HasAABloat::kYes, IsZeroArea::kNo);
+        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "AAConvexPathOp"; }
@@ -746,7 +724,8 @@
 
         // Setup GrGeometryProcessor
         sk_sp<GrGeometryProcessor> quadProcessor(
-                QuadEdgeEffect::Make(invert, fHelper.usesLocalCoords()));
+                QuadEdgeEffect::Make(invert, fHelper.usesLocalCoords(), fWideColor));
+        const size_t kVertexStride = quadProcessor->vertexStride();
 
         // TODO generate all segments for all paths and use one vertex buffer
         for (int i = 0; i < instanceCount; i++) {
@@ -785,11 +764,10 @@
             const GrBuffer* vertexBuffer;
             int firstVertex;
 
-            SkASSERT(sizeof(QuadVertex) == quadProcessor->vertexStride());
-            QuadVertex* verts = reinterpret_cast<QuadVertex*>(target->makeVertexSpace(
-                    sizeof(QuadVertex), vertexCount, &vertexBuffer, &firstVertex));
+            GrVertexWriter verts{target->makeVertexSpace(kVertexStride, vertexCount,
+                                                         &vertexBuffer, &firstVertex)};
 
-            if (!verts) {
+            if (!verts.fPtr) {
                 SkDebugf("Could not allocate vertices\n");
                 return;
             }
@@ -804,8 +782,8 @@
             }
 
             SkSTArray<kPreallocDrawCnt, Draw, true> draws;
-            // TODO4F: Preserve float colors
-            create_vertices(segments, fanPt, args.fColor.toBytes_RGBA(), &draws, verts, idxs);
+            GrVertexColor color(args.fColor, fWideColor);
+            create_vertices(segments, fanPt, color, &draws, verts, idxs, kVertexStride);
 
             GrMesh* meshes = target->allocMeshes(draws.count());
             for (int j = 0; j < draws.count(); ++j) {
@@ -833,6 +811,7 @@
         }
 
         fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin());
+        fWideColor |= that->fWideColor;
         return CombineResult::kMerged;
     }
 
@@ -844,6 +823,7 @@
 
     Helper fHelper;
     SkSTArray<1, PathData, true> fPaths;
+    bool fWideColor;
 
     typedef GrMeshDrawOp INHERITED;
 };
diff --git a/src/gpu/ops/GrAAFillRectOp.cpp b/src/gpu/ops/GrAAFillRectOp.cpp
deleted file mode 100644
index 3aa05bb..0000000
--- a/src/gpu/ops/GrAAFillRectOp.cpp
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrColor.h"
-#include "GrDefaultGeoProcFactory.h"
-#include "GrMeshDrawOp.h"
-#include "GrOpFlushState.h"
-#include "GrRectOpFactory.h"
-#include "GrResourceKey.h"
-#include "GrResourceProvider.h"
-#include "GrTypes.h"
-#include "SkMatrix.h"
-#include "SkMatrixPriv.h"
-#include "SkRect.h"
-#include "SkPointPriv.h"
-#include "ops/GrSimpleMeshDrawOpHelper.h"
-#include <new>
-
-GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
-
-static inline bool view_matrix_ok_for_aa_fill_rect(const SkMatrix& viewMatrix) {
-    return viewMatrix.preservesRightAngles();
-}
-
-static inline void set_inset_fan(SkPoint* pts, size_t stride, const SkRect& r, SkScalar dx,
-                                 SkScalar dy) {
-    SkPointPriv::SetRectFan(pts, r.fLeft + dx, r.fTop + dy, r.fRight - dx, r.fBottom - dy, stride);
-}
-
-static const int kNumAAFillRectsInIndexBuffer = 256;
-static const int kVertsPerAAFillRect = 8;
-static const int kIndicesPerAAFillRect = 30;
-
-static sk_sp<const GrBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
-    GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
-
-    // clang-format off
-    static const uint16_t gFillAARectIdx[] = {
-        0, 1, 5, 5, 4, 0,
-        1, 2, 6, 6, 5, 1,
-        2, 3, 7, 7, 6, 2,
-        3, 0, 4, 4, 7, 3,
-        4, 5, 6, 6, 7, 4,
-    };
-    // clang-format on
-
-    GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect);
-    return resourceProvider->findOrCreatePatternedIndexBuffer(
-            gFillAARectIdx, kIndicesPerAAFillRect, kNumAAFillRectsInIndexBuffer,
-            kVertsPerAAFillRect, gAAFillRectIndexBufferKey);
-}
-
-static void generate_aa_fill_rect_geometry(intptr_t verts,
-                                           size_t vertexStride,
-                                           GrColor color,
-                                           const SkMatrix& viewMatrix,
-                                           const SkRect& rect,
-                                           const SkRect& devRect,
-                                           bool tweakAlphaForCoverage,
-                                           const SkMatrix* localMatrix) {
-    SkPoint* fan0Pos = reinterpret_cast<SkPoint*>(verts);
-    SkPoint* fan1Pos = reinterpret_cast<SkPoint*>(verts + 4 * vertexStride);
-
-    SkScalar inset;
-
-    if (viewMatrix.rectStaysRect()) {
-        inset = SkMinScalar(devRect.width(), SK_Scalar1);
-        inset = SK_ScalarHalf * SkMinScalar(inset, devRect.height());
-
-        set_inset_fan(fan0Pos, vertexStride, devRect, -SK_ScalarHalf, -SK_ScalarHalf);
-        set_inset_fan(fan1Pos, vertexStride, devRect, inset, inset);
-    } else {
-        // compute transformed (1, 0) and (0, 1) vectors
-        SkVector vec[2] = {{viewMatrix[SkMatrix::kMScaleX], viewMatrix[SkMatrix::kMSkewY]},
-                           {viewMatrix[SkMatrix::kMSkewX], viewMatrix[SkMatrix::kMScaleY]}};
-
-        SkScalar len1 = SkPoint::Normalize(&vec[0]);
-        vec[0].scale(SK_ScalarHalf);
-        SkScalar len2 = SkPoint::Normalize(&vec[1]);
-        vec[1].scale(SK_ScalarHalf);
-
-        inset = SkMinScalar(len1 * rect.width(), SK_Scalar1);
-        inset = SK_ScalarHalf * SkMinScalar(inset, len2 * rect.height());
-
-        // create the rotated rect
-        SkPointPriv::SetRectFan(fan0Pos, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom,
-                vertexStride);
-        SkMatrixPriv::MapPointsWithStride(viewMatrix, fan0Pos, vertexStride, 4);
-
-        // Now create the inset points and then outset the original
-        // rotated points
-
-        // TL
-        *((SkPoint*)((intptr_t)fan1Pos + 0 * vertexStride)) =
-                *((SkPoint*)((intptr_t)fan0Pos + 0 * vertexStride)) + vec[0] + vec[1];
-        *((SkPoint*)((intptr_t)fan0Pos + 0 * vertexStride)) -= vec[0] + vec[1];
-        // BL
-        *((SkPoint*)((intptr_t)fan1Pos + 1 * vertexStride)) =
-                *((SkPoint*)((intptr_t)fan0Pos + 1 * vertexStride)) + vec[0] - vec[1];
-        *((SkPoint*)((intptr_t)fan0Pos + 1 * vertexStride)) -= vec[0] - vec[1];
-        // BR
-        *((SkPoint*)((intptr_t)fan1Pos + 2 * vertexStride)) =
-                *((SkPoint*)((intptr_t)fan0Pos + 2 * vertexStride)) - vec[0] - vec[1];
-        *((SkPoint*)((intptr_t)fan0Pos + 2 * vertexStride)) += vec[0] + vec[1];
-        // TR
-        *((SkPoint*)((intptr_t)fan1Pos + 3 * vertexStride)) =
-                *((SkPoint*)((intptr_t)fan0Pos + 3 * vertexStride)) - vec[0] + vec[1];
-        *((SkPoint*)((intptr_t)fan0Pos + 3 * vertexStride)) += vec[0] - vec[1];
-    }
-
-    if (localMatrix) {
-        SkMatrix invViewMatrix;
-        if (!viewMatrix.invert(&invViewMatrix)) {
-            SkDebugf("View matrix is non-invertible, local coords will be wrong.");
-            invViewMatrix = SkMatrix::I();
-        }
-        SkMatrix localCoordMatrix;
-        localCoordMatrix.setConcat(*localMatrix, invViewMatrix);
-        SkPoint* fan0Loc = reinterpret_cast<SkPoint*>(verts + sizeof(SkPoint) + sizeof(GrColor));
-        SkMatrixPriv::MapPointsWithStride(localCoordMatrix, fan0Loc, vertexStride, fan0Pos,
-                                          vertexStride, 8);
-    }
-
-    // Make verts point to vertex color and then set all the color and coverage vertex attrs
-    // values.
-    verts += sizeof(SkPoint);
-
-    // The coverage offset is always the last vertex attribute
-    intptr_t coverageOffset = vertexStride - sizeof(GrColor) - sizeof(SkPoint);
-    for (int i = 0; i < 4; ++i) {
-        if (tweakAlphaForCoverage) {
-            *reinterpret_cast<GrColor*>(verts + i * vertexStride) = 0;
-        } else {
-            *reinterpret_cast<GrColor*>(verts + i * vertexStride) = color;
-            *reinterpret_cast<float*>(verts + i * vertexStride + coverageOffset) = 0;
-        }
-    }
-
-    int scale;
-    if (inset < SK_ScalarHalf) {
-        scale = SkScalarFloorToInt(512.0f * inset / (inset + SK_ScalarHalf));
-        SkASSERT(scale >= 0 && scale <= 255);
-    } else {
-        scale = 0xff;
-    }
-
-    verts += 4 * vertexStride;
-
-    float innerCoverage = GrNormalizeByteToFloat(scale);
-    GrColor scaledColor = (0xff == scale) ? color : SkAlphaMulQ(color, scale);
-
-    for (int i = 0; i < 4; ++i) {
-        if (tweakAlphaForCoverage) {
-            *reinterpret_cast<GrColor*>(verts + i * vertexStride) = scaledColor;
-        } else {
-            *reinterpret_cast<GrColor*>(verts + i * vertexStride) = color;
-            *reinterpret_cast<float*>(verts + i * vertexStride + coverageOffset) = innerCoverage;
-        }
-    }
-}
-
-namespace {
-
-class AAFillRectOp final : public GrMeshDrawOp {
-private:
-    using Helper = GrSimpleMeshDrawOpHelperWithStencil;
-
-public:
-    DEFINE_OP_CLASS_ID
-
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                                          GrPaint&& paint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkRect& rect,
-                                          const SkRect& devRect,
-                                          const SkMatrix* localMatrix,
-                                          const GrUserStencilSettings* stencil) {
-        SkASSERT(view_matrix_ok_for_aa_fill_rect(viewMatrix));
-        return Helper::FactoryHelper<AAFillRectOp>(context, std::move(paint), viewMatrix, rect,
-                                                   devRect, localMatrix, stencil);
-    }
-
-    AAFillRectOp(const Helper::MakeArgs& helperArgs,
-                 const SkPMColor4f& color,
-                 const SkMatrix& viewMatrix,
-                 const SkRect& rect,
-                 const SkRect& devRect,
-                 const SkMatrix* localMatrix,
-                 const GrUserStencilSettings* stencil)
-            : INHERITED(ClassID()), fHelper(helperArgs, GrAAType::kCoverage, stencil) {
-        if (localMatrix) {
-            void* mem = fRectData.push_back_n(sizeof(RectWithLocalMatrixInfo));
-            new (mem) RectWithLocalMatrixInfo(color, viewMatrix, rect, devRect, *localMatrix);
-        } else {
-            void* mem = fRectData.push_back_n(sizeof(RectInfo));
-            new (mem) RectInfo(color, viewMatrix, rect, devRect);
-        }
-        IsZeroArea zeroArea =
-                (!rect.width() || !rect.height()) ? IsZeroArea::kYes : IsZeroArea::kNo;
-        this->setBounds(devRect, HasAABloat::kYes, zeroArea);
-        fRectCnt = 1;
-    }
-
-    const char* name() const override { return "AAFillRectOp"; }
-
-    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
-        fHelper.visitProxies(func);
-    }
-
-#ifdef SK_DEBUG
-    SkString dumpInfo() const override {
-        SkString str;
-        str.append(INHERITED::dumpInfo());
-        str.appendf("# combined: %d\n", fRectCnt);
-        const RectInfo* info = this->first();
-        for (int i = 0; i < fRectCnt; ++i) {
-            const SkRect& rect = info->rect();
-            str.appendf("%d: Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", i,
-                        info->color().toBytes_RGBA(), rect.fLeft, rect.fTop, rect.fRight,
-                        rect.fBottom);
-            info = this->next(info);
-        }
-        str += fHelper.dumpInfo();
-        str += INHERITED::dumpInfo();
-        return str;
-    }
-#endif
-
-    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
-
-    RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        SkPMColor4f color = this->first()->color();
-        auto result = fHelper.xpRequiresDstTexture(
-                caps, clip, GrProcessorAnalysisCoverage::kSingleChannel, &color);
-        this->first()->setColor(color);
-        return result;
-    }
-
-private:
-    void onPrepareDraws(Target* target) override {
-        using namespace GrDefaultGeoProcFactory;
-
-        Color color(Color::kPremulGrColorAttribute_Type);
-        Coverage::Type coverageType = Coverage::kSolid_Type;
-        if (!fHelper.compatibleWithAlphaAsCoverage()) {
-            coverageType = Coverage::kAttribute_Type;
-        }
-        LocalCoords lc = LocalCoords::kUnused_Type;
-        if (fHelper.usesLocalCoords()) {
-            lc = LocalCoords::kHasExplicit_Type;
-        }
-
-        sk_sp<GrGeometryProcessor> gp =
-                GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), color, coverageType,
-                                              lc, SkMatrix::I());
-        if (!gp) {
-            SkDebugf("Couldn't create GrGeometryProcessor\n");
-            return;
-        }
-
-        size_t vertexStride = gp->vertexStride();
-
-        sk_sp<const GrBuffer> indexBuffer = get_index_buffer(target->resourceProvider());
-        PatternHelper helper(target, GrPrimitiveType::kTriangles, vertexStride, indexBuffer.get(),
-                             kVertsPerAAFillRect, kIndicesPerAAFillRect, fRectCnt);
-        void* vertices = helper.vertices();
-        if (!vertices || !indexBuffer) {
-            SkDebugf("Could not allocate vertices\n");
-            return;
-        }
-
-        const RectInfo* info = this->first();
-        const SkMatrix* localMatrix = nullptr;
-        for (int i = 0; i < fRectCnt; i++) {
-            intptr_t verts =
-                    reinterpret_cast<intptr_t>(vertices) + i * kVertsPerAAFillRect * vertexStride;
-            if (fHelper.usesLocalCoords()) {
-                if (info->hasLocalMatrix()) {
-                    localMatrix = &static_cast<const RectWithLocalMatrixInfo*>(info)->localMatrix();
-                } else {
-                    localMatrix = &SkMatrix::I();
-                }
-            }
-            // TODO4F: Preserve float colors
-            generate_aa_fill_rect_geometry(verts, vertexStride, info->color().toBytes_RGBA(),
-                                           info->viewMatrix(), info->rect(), info->devRect(),
-                                           fHelper.compatibleWithAlphaAsCoverage(), localMatrix);
-            info = this->next(info);
-        }
-        auto pipe = fHelper.makePipeline(target);
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
-    }
-
-    CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
-        AAFillRectOp* that = t->cast<AAFillRectOp>();
-        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
-            return CombineResult::kCannotCombine;
-        }
-
-        fRectData.push_back_n(that->fRectData.count(), that->fRectData.begin());
-        fRectCnt += that->fRectCnt;
-        return CombineResult::kMerged;
-    }
-
-    struct RectInfo {
-    public:
-        RectInfo(const SkPMColor4f& color, const SkMatrix& viewMatrix, const SkRect& rect,
-                 const SkRect& devRect)
-                : RectInfo(color, viewMatrix, rect, devRect, HasLocalMatrix::kNo) {}
-        bool hasLocalMatrix() const { return HasLocalMatrix::kYes == fHasLocalMatrix; }
-        const SkPMColor4f& color() const { return fColor; }
-        const SkMatrix& viewMatrix() const { return fViewMatrix; }
-        const SkRect& rect() const { return fRect; }
-        const SkRect& devRect() const { return fDevRect; }
-
-        void setColor(const SkPMColor4f& color) { fColor = color; }
-
-    protected:
-        enum class HasLocalMatrix : uint32_t { kNo, kYes };
-
-        RectInfo(const SkPMColor4f& color, const SkMatrix& viewMatrix, const SkRect& rect,
-                 const SkRect& devRect, HasLocalMatrix hasLM)
-                : fHasLocalMatrix(hasLM)
-                , fColor(color)
-                , fViewMatrix(viewMatrix)
-                , fRect(rect)
-                , fDevRect(devRect) {}
-
-        HasLocalMatrix fHasLocalMatrix;
-        SkPMColor4f fColor;
-        SkMatrix fViewMatrix;
-        SkRect fRect;
-        SkRect fDevRect;
-    };
-
-    struct RectWithLocalMatrixInfo : public RectInfo {
-    public:
-        RectWithLocalMatrixInfo(const SkPMColor4f& color, const SkMatrix& viewMatrix,
-                                const SkRect& rect, const SkRect& devRect,
-                                const SkMatrix& localMatrix)
-                : RectInfo(color, viewMatrix, rect, devRect, HasLocalMatrix::kYes)
-                , fLocalMatrix(localMatrix) {}
-        const SkMatrix& localMatrix() const { return fLocalMatrix; }
-
-    private:
-        SkMatrix fLocalMatrix;
-    };
-
-    RectInfo* first() { return reinterpret_cast<RectInfo*>(fRectData.begin()); }
-    const RectInfo* first() const { return reinterpret_cast<const RectInfo*>(fRectData.begin()); }
-    const RectInfo* next(const RectInfo* prev) const {
-        intptr_t next =
-                reinterpret_cast<intptr_t>(prev) +
-                (prev->hasLocalMatrix() ? sizeof(RectWithLocalMatrixInfo) : sizeof(RectInfo));
-        return reinterpret_cast<const RectInfo*>(next);
-    }
-
-    SkSTArray<4 * sizeof(RectWithLocalMatrixInfo), uint8_t, true> fRectData;
-    Helper fHelper;
-    int fRectCnt;
-
-    typedef GrMeshDrawOp INHERITED;
-};
-
-}  // anonymous namespace
-
-namespace GrRectOpFactory {
-
-std::unique_ptr<GrDrawOp> MakeAAFill(GrContext* context,
-                                     GrPaint&& paint,
-                                     const SkMatrix& viewMatrix,
-                                     const SkRect& rect,
-                                     const GrUserStencilSettings* stencil) {
-    if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
-        return nullptr;
-    }
-    SkRect devRect;
-    viewMatrix.mapRect(&devRect, rect);
-    return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, devRect,
-                              nullptr, stencil);
-}
-
-std::unique_ptr<GrDrawOp> MakeAAFillWithLocalMatrix(GrContext* context,
-                                                    GrPaint&& paint,
-                                                    const SkMatrix& viewMatrix,
-                                                    const SkMatrix& localMatrix,
-                                                    const SkRect& rect) {
-    if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
-        return nullptr;
-    }
-    SkRect devRect;
-    viewMatrix.mapRect(&devRect, rect);
-    return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, devRect,
-                              &localMatrix, nullptr);
-}
-
-std::unique_ptr<GrDrawOp> MakeAAFillWithLocalRect(GrContext* context,
-                                                  GrPaint&& paint,
-                                                  const SkMatrix& viewMatrix,
-                                                  const SkRect& rect,
-                                                  const SkRect& localRect) {
-    if (!view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
-        return nullptr;
-    }
-    SkRect devRect;
-    viewMatrix.mapRect(&devRect, rect);
-    SkMatrix localMatrix;
-    if (!localMatrix.setRectToRect(rect, localRect, SkMatrix::kFill_ScaleToFit)) {
-        return nullptr;
-    }
-    return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, devRect,
-                              &localMatrix, nullptr);
-}
-
-}  // namespace GrRectOpFactory
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#if GR_TEST_UTILS
-
-#include "GrDrawOpTest.h"
-
-GR_DRAW_OP_TEST_DEFINE(AAFillRectOp) {
-    SkMatrix viewMatrix;
-    do {
-        viewMatrix = GrTest::TestMatrixInvertible(random);
-    } while (!view_matrix_ok_for_aa_fill_rect(viewMatrix));
-    SkRect rect = GrTest::TestRect(random);
-    SkRect devRect;
-    viewMatrix.mapRect(&devRect, rect);
-    const SkMatrix* localMatrix = nullptr;
-    SkMatrix m;
-    if (random->nextBool()) {
-        m = GrTest::TestMatrix(random);
-    }
-    const GrUserStencilSettings* stencil = random->nextBool() ? nullptr
-                                                              : GrGetRandomStencil(random, context);
-    return AAFillRectOp::Make(context, std::move(paint), viewMatrix, rect,
-                              devRect, localMatrix, stencil);
-}
-
-#endif
diff --git a/src/gpu/ops/GrAAHairLinePathRenderer.cpp b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
index 8f4c2f9..e03ce3f 100644
--- a/src/gpu/ops/GrAAHairLinePathRenderer.cpp
+++ b/src/gpu/ops/GrAAHairLinePathRenderer.cpp
@@ -514,7 +514,7 @@
 static void set_uv_quad(const SkPoint qpts[3], BezierVertex verts[kQuadNumVertices]) {
     // this should be in the src space, not dev coords, when we have perspective
     GrPathUtils::QuadUVMatrix DevToUV(qpts);
-    DevToUV.apply<kQuadNumVertices, sizeof(BezierVertex), sizeof(SkPoint)>(verts);
+    DevToUV.apply(verts, kQuadNumVertices, sizeof(BezierVertex), sizeof(SkPoint));
 }
 
 static void bloat_quad(const SkPoint qpts[3], const SkMatrix* toDevice,
diff --git a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
index 52785eb..1b36698 100644
--- a/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAALinearizingConvexPathRenderer.cpp
@@ -82,13 +82,17 @@
 // extract the result vertices and indices from the GrAAConvexTessellator
 static void extract_verts(const GrAAConvexTessellator& tess,
                           void* vertData,
-                          size_t vertexStride,
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
                           GrColor color,
+#else
+                          const GrVertexColor& color,
+#endif
                           uint16_t firstIndex,
                           uint16_t* idxs,
                           bool tweakAlphaForCoverage) {
     GrVertexWriter verts{vertData};
     for (int i = 0; i < tess.numPts(); ++i) {
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
         verts.write(tess.point(i));
         if (tweakAlphaForCoverage) {
             SkASSERT(SkScalarRoundToInt(255.0f * tess.coverage(i)) <= 255);
@@ -98,6 +102,9 @@
         } else {
             verts.write(color, tess.coverage(i));
         }
+#else
+        verts.write(tess.point(i), color, tess.coverage(i));
+#endif
     }
 
     for (int i = 0; i < tess.numIndices(); ++i) {
@@ -108,22 +115,23 @@
 static sk_sp<GrGeometryProcessor> create_lines_only_gp(const GrShaderCaps* shaderCaps,
                                                        bool tweakAlphaForCoverage,
                                                        const SkMatrix& viewMatrix,
-                                                       bool usesLocalCoords) {
+                                                       bool usesLocalCoords,
+                                                       bool wideColor) {
     using namespace GrDefaultGeoProcFactory;
 
-    Coverage::Type coverageType;
-    if (tweakAlphaForCoverage) {
-        coverageType = Coverage::kSolid_Type;
-    } else {
-        coverageType = Coverage::kAttribute_Type;
-    }
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
+    Coverage::Type coverageType =
+        tweakAlphaForCoverage ? Coverage::kSolid_Type : Coverage::kAttribute_Type;
+#else
+    Coverage::Type coverageType =
+        tweakAlphaForCoverage ? Coverage::kAttributeTweakAlpha_Type : Coverage::kAttribute_Type;
+#endif
     LocalCoords::Type localCoordsType =
-            usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type;
-    return MakeForDeviceSpace(shaderCaps,
-                              Color::kPremulGrColorAttribute_Type,
-                              coverageType,
-                              localCoordsType,
-                              viewMatrix);
+        usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type;
+    Color::Type colorType =
+        wideColor ? Color::kPremulWideColorAttribute_Type : Color::kPremulGrColorAttribute_Type;
+
+    return MakeForDeviceSpace(shaderCaps, colorType, coverageType, localCoordsType, viewMatrix);
 }
 
 namespace {
@@ -174,6 +182,11 @@
             bounds.outset(w, w);
         }
         this->setTransformedBounds(bounds, viewMatrix, HasAABloat::kYes, IsZeroArea::kNo);
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
+        fWideColor = false;
+#else
+        fWideColor = !SkPMColor4fFitsInBytes(color);
+#endif
     }
 
     const char* name() const override { return "AAFlatteningConvexPathOp"; }
@@ -243,7 +256,8 @@
         sk_sp<GrGeometryProcessor> gp(create_lines_only_gp(target->caps().shaderCaps(),
                                                            fHelper.compatibleWithAlphaAsCoverage(),
                                                            this->viewMatrix(),
-                                                           fHelper.usesLocalCoords()));
+                                                           fHelper.usesLocalCoords(),
+                                                           fWideColor));
         if (!gp) {
             SkDebugf("Couldn't create a GrGeometryProcessor\n");
             return;
@@ -296,9 +310,13 @@
                 indices = (uint16_t*) sk_realloc_throw(indices, maxIndices * sizeof(uint16_t));
             }
 
-            // TODO4F: Preserve float colors
-            extract_verts(tess, vertices + vertexStride * vertexCount, vertexStride,
-                          args.fColor.toBytes_RGBA(), vertexCount, indices + indexCount,
+            extract_verts(tess, vertices + vertexStride * vertexCount,
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
+                          args.fColor.toBytes_RGBA(),
+#else
+                          GrVertexColor(args.fColor, fWideColor),
+#endif
+                          vertexCount, indices + indexCount,
                           fHelper.compatibleWithAlphaAsCoverage());
             vertexCount += currentVertices;
             indexCount += currentIndices;
@@ -318,6 +336,7 @@
         }
 
         fPaths.push_back_n(that->fPaths.count(), that->fPaths.begin());
+        fWideColor |= that->fWideColor;
         return CombineResult::kMerged;
     }
 
@@ -335,6 +354,7 @@
 
     SkSTArray<1, PathData, true> fPaths;
     Helper fHelper;
+    bool fWideColor;
 
     typedef GrMeshDrawOp INHERITED;
 };
diff --git a/src/gpu/ops/GrAtlasTextOp.cpp b/src/gpu/ops/GrAtlasTextOp.cpp
index 7263dfb..368c625 100644
--- a/src/gpu/ops/GrAtlasTextOp.cpp
+++ b/src/gpu/ops/GrAtlasTextOp.cpp
@@ -321,8 +321,8 @@
         GrSamplerState samplerState = fNeedsGlyphTransform ? GrSamplerState::ClampBilerp()
                                                            : GrSamplerState::ClampNearest();
         flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
-            *target->caps().shaderCaps(), this->color(), proxies, numActiveProxies, samplerState,
-            maskFormat, localMatrix, vmPerspective);
+            *target->caps().shaderCaps(), this->color(), false, proxies, numActiveProxies,
+            samplerState, maskFormat, localMatrix, vmPerspective);
     }
 
     flushInfo.fGlyphsToFlush = 0;
diff --git a/src/gpu/ops/GrDefaultPathRenderer.cpp b/src/gpu/ops/GrDefaultPathRenderer.cpp
index a30fe3b..54e98f1 100644
--- a/src/gpu/ops/GrDefaultPathRenderer.cpp
+++ b/src/gpu/ops/GrDefaultPathRenderer.cpp
@@ -9,6 +9,7 @@
 #include "GrContext.h"
 #include "GrDefaultGeoProcFactory.h"
 #include "GrDrawOpTest.h"
+#include "GrFillRectOp.h"
 #include "GrFixedClip.h"
 #include "GrMesh.h"
 #include "GrOpFlushState.h"
@@ -23,7 +24,6 @@
 #include "SkTLazy.h"
 #include "SkTraceEvent.h"
 #include "ops/GrMeshDrawOp.h"
-#include "ops/GrRectOpFactory.h"
 
 GrDefaultPathRenderer::GrDefaultPathRenderer() {
 }
@@ -611,11 +611,12 @@
             }
             const SkMatrix& viewM = (reverse && viewMatrix.hasPerspective()) ? SkMatrix::I() :
                                                                                viewMatrix;
+            // This is a non-coverage aa rect op since we assert aaType != kCoverage at the start
+            assert_alive(paint);
             renderTargetContext->addDrawOp(
                     clip,
-                    GrRectOpFactory::MakeNonAAFillWithLocalMatrix(
-                            context, std::move(paint), viewM, localMatrix,
-                            bounds, aaType, passes[p]));
+                    GrFillRectOp::MakeWithLocalMatrix(context, std::move(paint), aaType, viewM,
+                                                      localMatrix, bounds, passes[p]));
         } else {
             bool stencilPass = stencilOnly || passCount > 1;
             std::unique_ptr<GrDrawOp> op;
@@ -626,6 +627,7 @@
                                          newCoverage, viewMatrix, isHairline, aaType, devBounds,
                                          passes[p]);
             } else {
+                assert_alive(paint);
                 op = DefaultPathOp::Make(context, std::move(paint), path, srcSpaceTol, newCoverage,
                                          viewMatrix, isHairline, aaType, devBounds, passes[p]);
             }
@@ -639,7 +641,8 @@
 GrDefaultPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
     bool isHairline = IsStrokeHairlineOrEquivalent(args.fShape->style(), *args.fViewMatrix, nullptr);
     // If we aren't a single_pass_shape or hairline, we require stencil buffers.
-    if (!(single_pass_shape(*args.fShape) || isHairline) && args.fCaps->avoidStencilBuffers()) {
+    if (!(single_pass_shape(*args.fShape) || isHairline) &&
+        (args.fCaps->avoidStencilBuffers() || args.fTargetIsWrappedVkSecondaryCB)) {
         return CanDrawPath::kNo;
     }
     // This can draw any path with any simple fill style but doesn't do coverage-based antialiasing.
diff --git a/src/gpu/ops/GrFillRectOp.cpp b/src/gpu/ops/GrFillRectOp.cpp
index 337d476..0ff0322 100644
--- a/src/gpu/ops/GrFillRectOp.cpp
+++ b/src/gpu/ops/GrFillRectOp.cpp
@@ -24,54 +24,32 @@
 using VertexSpec = GrQuadPerEdgeAA::VertexSpec;
 using ColorType = GrQuadPerEdgeAA::ColorType;
 
-// NOTE: This info structure is intentionally modeled after GrTextureOps' Quad so that they can
-// more easily be integrated together in the future.
-class TransformedQuad {
-public:
-    TransformedQuad(const GrPerspQuad& deviceQuad, const GrPerspQuad& localQuad,
-                    const SkPMColor4f& color, GrQuadAAFlags aaFlags)
-            : fDeviceQuad(deviceQuad)
-            , fLocalQuad(localQuad)
-            , fColor(color)
-            , fAAFlags(aaFlags) {}
-
-    const GrPerspQuad& deviceQuad() const { return fDeviceQuad; }
-    const GrPerspQuad& localQuad() const { return fLocalQuad; }
-    const SkPMColor4f& color() const { return fColor; }
-    GrQuadAAFlags aaFlags() const { return fAAFlags; }
-
-    void setColor(const SkPMColor4f& color) { fColor = color; }
-
-    SkString dumpInfo(int index) const {
-        SkString str;
-        str.appendf("%d: Color: [%.2f, %.2f, %.2f, %.2f], Edge AA: l%u_t%u_r%u_b%u, \n"
-                    "  device quad: [(%.2f, %2.f, %.2f), (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f), "
-                    "(%.2f, %.2f, %.2f)],\n"
-                    "  local quad: [(%.2f, %2.f, %.2f), (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f), "
-                    "(%.2f, %.2f, %.2f)]\n",
-                    index, fColor.fR, fColor.fG, fColor.fB, fColor.fA,
-                    (uint32_t) (fAAFlags & GrQuadAAFlags::kLeft),
-                    (uint32_t) (fAAFlags & GrQuadAAFlags::kTop),
-                    (uint32_t) (fAAFlags & GrQuadAAFlags::kRight),
-                    (uint32_t) (fAAFlags & GrQuadAAFlags::kBottom),
-                    fDeviceQuad.x(0), fDeviceQuad.y(0), fDeviceQuad.w(0),
-                    fDeviceQuad.x(1), fDeviceQuad.y(1), fDeviceQuad.w(1),
-                    fDeviceQuad.x(2), fDeviceQuad.y(2), fDeviceQuad.w(2),
-                    fDeviceQuad.x(3), fDeviceQuad.y(3), fDeviceQuad.w(3),
-                    fLocalQuad.x(0), fLocalQuad.y(0), fLocalQuad.w(0),
-                    fLocalQuad.x(1), fLocalQuad.y(1), fLocalQuad.w(1),
-                    fLocalQuad.x(2), fLocalQuad.y(2), fLocalQuad.w(2),
-                    fLocalQuad.x(3), fLocalQuad.y(3), fLocalQuad.w(3));
-        return str;
-    }
-private:
-    // NOTE: The TransformedQuad does not store the types for device and local. The owning op tracks
-    // the most general type for device and local across all of its merged quads.
-    GrPerspQuad fDeviceQuad; // In device space, allowing rects to be combined across view matrices
-    GrPerspQuad fLocalQuad; // Original rect transformed by its local matrix
-    SkPMColor4f fColor;
-    GrQuadAAFlags fAAFlags;
-};
+#ifdef SK_DEBUG
+static SkString dump_quad_info(int index, const GrPerspQuad& deviceQuad,
+                               const GrPerspQuad& localQuad, const SkPMColor4f& color,
+                               GrQuadAAFlags aaFlags) {
+    SkString str;
+    str.appendf("%d: Color: [%.2f, %.2f, %.2f, %.2f], Edge AA: l%u_t%u_r%u_b%u, \n"
+                "  device quad: [(%.2f, %2.f, %.2f), (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f), "
+                "(%.2f, %.2f, %.2f)],\n"
+                "  local quad: [(%.2f, %2.f, %.2f), (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f), "
+                "(%.2f, %.2f, %.2f)]\n",
+                index, color.fR, color.fG, color.fB, color.fA,
+                (uint32_t) (aaFlags & GrQuadAAFlags::kLeft),
+                (uint32_t) (aaFlags & GrQuadAAFlags::kTop),
+                (uint32_t) (aaFlags & GrQuadAAFlags::kRight),
+                (uint32_t) (aaFlags & GrQuadAAFlags::kBottom),
+                deviceQuad.x(0), deviceQuad.y(0), deviceQuad.w(0),
+                deviceQuad.x(1), deviceQuad.y(1), deviceQuad.w(1),
+                deviceQuad.x(2), deviceQuad.y(2), deviceQuad.w(2),
+                deviceQuad.x(3), deviceQuad.y(3), deviceQuad.w(3),
+                localQuad.x(0), localQuad.y(0), localQuad.w(0),
+                localQuad.x(1), localQuad.y(1), localQuad.w(1),
+                localQuad.x(2), localQuad.y(2), localQuad.w(2),
+                localQuad.x(3), localQuad.y(3), localQuad.w(3));
+    return str;
+}
+#endif
 
 class FillRectOp final : public GrMeshDrawOp {
 private:
@@ -89,53 +67,31 @@
                                           GrQuadType localQuadType) {
         // Clean up deviations between aaType and edgeAA
         GrResolveAATypeForQuad(aaType, edgeAA, deviceQuad, deviceQuadType, &aaType, &edgeAA);
-
-        // Analyze the paint to see if it is compatible with scissor-clearing
-        SkPMColor4f color = paint.getColor4f();
-        // Only non-null if the paint can be turned into a clear, it can be a local pointer since
-        // the op ctor consumes the value right away if it's provided
-        SkPMColor4f* clearColor = nullptr;
-        if (paint.isTrivial() || paint.isConstantBlendedColor(&color)) {
-            clearColor = &color;
-        }
-
-        return Helper::FactoryHelper<FillRectOp>(context, std::move(paint), clearColor, aaType,
-                edgeAA, stencilSettings, deviceQuad, deviceQuadType, localQuad, localQuadType);
+        return Helper::FactoryHelper<FillRectOp>(context, std::move(paint), aaType, edgeAA,
+                stencilSettings, deviceQuad, deviceQuadType, localQuad, localQuadType);
     }
 
-    // Analysis of the GrPaint to determine the const blend color must be done before, passing
-    // nullptr for constBlendColor disables all scissor-clear optimizations (must keep the
-    // paintColor argument because it is assumed by the GrSimpleMeshDrawOpHelper). Similarly, aaType
-    // is passed to Helper in the initializer list, so incongruities between aaType and edgeFlags
-    // must be resolved prior to calling this constructor.
-    FillRectOp(Helper::MakeArgs args, SkPMColor4f paintColor, const SkPMColor4f* constBlendColor,
-               GrAAType aaType, GrQuadAAFlags edgeFlags, const GrUserStencilSettings* stencil,
+    // aaType is passed to Helper in the initializer list, so incongruities between aaType and
+    // edgeFlags must be resolved prior to calling this constructor.
+    FillRectOp(Helper::MakeArgs args, SkPMColor4f paintColor, GrAAType aaType,
+               GrQuadAAFlags edgeFlags, const GrUserStencilSettings* stencil,
                const GrPerspQuad& deviceQuad, GrQuadType deviceQuadType,
                const GrPerspQuad& localQuad, GrQuadType localQuadType)
             : INHERITED(ClassID())
             , fHelper(args, aaType, stencil)
-            , fDeviceQuadType(static_cast<unsigned>(deviceQuadType))
-            , fLocalQuadType(static_cast<unsigned>(localQuadType)) {
-        if (constBlendColor) {
-            // The GrPaint is compatible with clearing, and the constant blend color overrides the
-            // paint color (although in most cases they are probably the same)
-            paintColor = *constBlendColor;
-            // However, just because the paint is compatible, the device quad must also be a rect
-            // that is non-AA (AA aligned with pixel bounds should have already been turned into
-            // non-AA).
-            fClearCompatible = deviceQuadType == GrQuadType::kRect && aaType == GrAAType::kNone;
-        } else {
-            // Paint isn't clear compatible
-            fClearCompatible = false;
-        }
-
-        fWideColor = !SkPMColor4fFitsInBytes(paintColor);
-
+            , fWideColor(!SkPMColor4fFitsInBytes(paintColor)) {
         // The color stored with the quad is the clear color if a scissor-clear is decided upon
         // when executing the op.
-        fQuads.emplace_back(deviceQuad, localQuad, paintColor, edgeFlags);
-        this->setBounds(deviceQuad.bounds(), HasAABloat(aaType == GrAAType::kCoverage),
-                        IsZeroArea::kNo);
+        fDeviceQuads.push_back(deviceQuad, deviceQuadType, { paintColor, edgeFlags });
+
+        if (!fHelper.isTrivial()) {
+            // Conservatively keep track of the local coordinates; it may be that the paint doesn't
+            // need them after analysis is finished. If the paint is known to be solid up front they
+            // can be skipped entirely.
+            fLocalQuads.push_back(localQuad, localQuadType);
+        }
+        this->setBounds(deviceQuad.bounds(deviceQuadType),
+                        HasAABloat(aaType == GrAAType::kCoverage), IsZeroArea::kNo);
     }
 
     const char* name() const override { return "FillRectOp"; }
@@ -147,14 +103,18 @@
 #ifdef SK_DEBUG
     SkString dumpInfo() const override {
         SkString str;
-        str.appendf("# draws: %d\n", fQuads.count());
-        str.appendf("Clear compatible: %u\n", static_cast<bool>(fClearCompatible));
+        str.appendf("# draws: %u\n", this->quadCount());
         str.appendf("Device quad type: %u, local quad type: %u\n",
-                    fDeviceQuadType, fLocalQuadType);
+                    (uint32_t) fDeviceQuads.quadType(), (uint32_t) fLocalQuads.quadType());
         str += fHelper.dumpInfo();
-        for (int i = 0; i < fQuads.count(); i++) {
-            str += fQuads[i].dumpInfo(i);
-
+        GrPerspQuad device, local;
+        for (int i = 0; i < this->quadCount(); i++) {
+            device = fDeviceQuads[i];
+            const ColorAndAA& info = fDeviceQuads.metadata(i);
+            if (!fHelper.isTrivial()) {
+                local = fLocalQuads[i];
+            }
+            str += dump_quad_info(i, device, local, info.fColor, info.fAAFlags);
         }
         str += INHERITED::dumpInfo();
         return str;
@@ -163,20 +123,31 @@
 
     RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
         // Initialize aggregate color analysis with the first quad's color (which always exists)
-        SkASSERT(fQuads.count() > 0);
-        GrProcessorAnalysisColor quadColors(fQuads[0].color());
+        SkASSERT(this->quadCount() > 0);
+        GrProcessorAnalysisColor quadColors(fDeviceQuads.metadata(0).fColor);
         // Then combine the colors of any additional quads (e.g. from MakeSet)
-        for (int i = 1; i < fQuads.count(); ++i) {
-            quadColors = GrProcessorAnalysisColor::Combine(quadColors, fQuads[i].color());
+        for (int i = 1; i < this->quadCount(); ++i) {
+            quadColors = GrProcessorAnalysisColor::Combine(quadColors,
+                                                           fDeviceQuads.metadata(i).fColor);
+            if (quadColors.isUnknown()) {
+                // No point in accumulating additional starting colors, combining cannot make it
+                // less unknown.
+                break;
+            }
         }
-        auto result = fHelper.xpRequiresDstTexture(
-                caps, clip, GrProcessorAnalysisCoverage::kSingleChannel, &quadColors);
+
+        // If the AA type is coverage, it will be a single value per pixel; if it's not coverage AA
+        // then the coverage is always 1.0, so specify kNone for more optimal blending.
+        GrProcessorAnalysisCoverage coverage = fHelper.aaType() == GrAAType::kCoverage ?
+                GrProcessorAnalysisCoverage::kSingleChannel :
+                GrProcessorAnalysisCoverage::kNone;
+        auto result = fHelper.xpRequiresDstTexture(caps, clip, coverage, &quadColors);
         // If there is a constant color after analysis, that means all of the quads should be set
         // to the same color (even if they started out with different colors).
         SkPMColor4f colorOverride;
         if (quadColors.isConstant(&colorOverride)) {
-            for (int i = 0; i < fQuads.count(); ++i) {
-                fQuads[i].setColor(colorOverride);
+            for (int i = 0; i < this->quadCount(); ++i) {
+                fDeviceQuads.metadata(i).fColor = colorOverride;
             }
         }
 
@@ -193,22 +164,24 @@
 
 private:
     // For GrFillRectOp::MakeSet's use of addQuad
-    // FIXME(reviewer): better to just make addQuad public?
     friend std::unique_ptr<GrDrawOp> GrFillRectOp::MakeSet(GrContext* context, GrPaint&& paint,
             GrAAType aaType, const SkMatrix& viewMatrix,
             const GrRenderTargetContext::QuadSetEntry quads[], int quadCount,
             const GrUserStencilSettings* stencilSettings);
 
-   void onPrepareDraws(Target* target) override {
+    void onPrepareDraws(Target* target) override {
         TRACE_EVENT0("skia", TRACE_FUNC);
 
         using Domain = GrQuadPerEdgeAA::Domain;
         static constexpr SkRect kEmptyDomain = SkRect::MakeEmpty();
 
-        VertexSpec vertexSpec(this->deviceQuadType(),
+        VertexSpec vertexSpec(fDeviceQuads.quadType(),
                               fWideColor ? ColorType::kHalf : ColorType::kByte,
-                              this->localQuadType(), fHelper.usesLocalCoords(), Domain::kNo,
-                              fHelper.aaType());
+                              fLocalQuads.quadType(), fHelper.usesLocalCoords(), Domain::kNo,
+                              fHelper.aaType(), fHelper.compatibleWithAlphaAsCoverage());
+        // Make sure that if the op thought it was a solid color, the vertex spec does not use
+        // local coords.
+        SkASSERT(!fHelper.isTrivial() || !fHelper.usesLocalCoords());
 
         sk_sp<GrGeometryProcessor> gp = GrQuadPerEdgeAA::MakeProcessor(vertexSpec);
         size_t vertexSize = gp->vertexStride();
@@ -217,8 +190,9 @@
         int vertexOffsetInBuffer = 0;
 
         // Fill the allocated vertex data
-        void* vdata = target->makeVertexSpace(vertexSize, fQuads.count() * 4, &vbuffer,
-                                              &vertexOffsetInBuffer);
+        void* vdata = target->makeVertexSpace(
+                vertexSize, this->quadCount() * vertexSpec.verticesPerQuad(),
+                &vbuffer, &vertexOffsetInBuffer);
         if (!vdata) {
             SkDebugf("Could not allocate vertices\n");
             return;
@@ -226,26 +200,28 @@
 
         // vertices pointer advances through vdata based on Tessellate's return value
         void* vertices = vdata;
-        for (int i = 0; i < fQuads.count(); ++i) {
-            const auto& q = fQuads[i];
-            vertices = GrQuadPerEdgeAA::Tessellate(vertices, vertexSpec, q.deviceQuad(), q.color(),
-                                                   q.localQuad(), kEmptyDomain, q.aaFlags());
+        if (fHelper.isTrivial()) {
+            SkASSERT(fLocalQuads.count() == 0); // No local coords, so send an ignored dummy quad
+            static const GrPerspQuad kIgnoredLocal(SkRect::MakeEmpty(), SkMatrix::I());
+            for (int i = 0; i < this->quadCount(); ++i) {
+                const ColorAndAA& info = fDeviceQuads.metadata(i);
+                vertices = GrQuadPerEdgeAA::Tessellate(vertices, vertexSpec, fDeviceQuads[i],
+                        info.fColor, kIgnoredLocal, kEmptyDomain, info.fAAFlags);
+            }
+        } else {
+            SkASSERT(fLocalQuads.count() == fDeviceQuads.count());
+            for (int i = 0; i < this->quadCount(); ++i) {
+                const ColorAndAA& info = fDeviceQuads.metadata(i);
+                vertices = GrQuadPerEdgeAA::Tessellate(vertices, vertexSpec, fDeviceQuads[i],
+                        info.fColor, fLocalQuads[i], kEmptyDomain, info.fAAFlags);
+            }
         }
 
         // Configure the mesh for the vertex data
-        GrMesh* mesh;
-        if (fQuads.count() > 1) {
-            mesh = target->allocMesh(GrPrimitiveType::kTriangles);
-            sk_sp<const GrBuffer> ibuffer = target->resourceProvider()->refQuadIndexBuffer();
-            if (!ibuffer) {
-                SkDebugf("Could not allocate quad indices\n");
-                return;
-            }
-            mesh->setIndexedPatterned(ibuffer.get(), 6, 4, fQuads.count(),
-                                      GrResourceProvider::QuadCountOfQuadBuffer());
-        } else {
-            mesh = target->allocMesh(GrPrimitiveType::kTriangleStrip);
-            mesh->setNonIndexedNonInstanced(4);
+        GrMesh* mesh = target->allocMeshes(1);
+        if (!GrQuadPerEdgeAA::ConfigureMeshIndices(target, mesh, vertexSpec, this->quadCount())) {
+            SkDebugf("Could not allocate indices\n");
+            return;
         }
         mesh->setVertexData(vbuffer, vertexOffsetInBuffer);
 
@@ -257,23 +233,25 @@
         TRACE_EVENT0("skia", TRACE_FUNC);
         const auto* that = t->cast<FillRectOp>();
 
-        // Unlike most users of the draw op helper, this op can merge none-aa and coverage-aa
-        // draw ops together, so pass true as the last argument.
+        if ((fHelper.aaType() == GrAAType::kCoverage ||
+             that->fHelper.aaType() == GrAAType::kCoverage) &&
+            this->quadCount() + that->quadCount() > GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer) {
+            // This limit on batch size seems to help on Adreno devices
+            return CombineResult::kCannotCombine;
+        }
+
+        // Unlike most users of the draw op helper, this op can merge none-aa and coverage-aa draw
+        // ops together, so pass true as the last argument.
         if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds(), true)) {
             return CombineResult::kCannotCombine;
         }
 
-        // If the processor sets are compatible, the two ops are always compatible; it just needs
-        // to adjust the state of the op to be the more general quad and aa types of the two ops.
+        // If the paints were compatible, the trivial/solid-color state should be the same
+        SkASSERT(fHelper.isTrivial() == that->fHelper.isTrivial());
 
-        // The GrQuadType enum is ordered such that higher values are more general quad types
-        if (that->fDeviceQuadType > fDeviceQuadType) {
-            fDeviceQuadType = that->fDeviceQuadType;
-        }
-        if (that->fLocalQuadType > fLocalQuadType) {
-            fLocalQuadType = that->fLocalQuadType;
-        }
-        fClearCompatible &= that->fClearCompatible;
+        // If the processor sets are compatible, the two ops are always compatible; it just needs to
+        // adjust the state of the op to be the more general quad and aa types of the two ops and
+        // then concatenate the per-quad data.
         fWideColor |= that->fWideColor;
 
         // The helper stores the aa type, but isCompatible(with true arg) allows the two ops' aa
@@ -283,7 +261,10 @@
             fHelper.setAAType(GrAAType::kCoverage);
         }
 
-        fQuads.push_back_n(that->fQuads.count(), that->fQuads.begin());
+        fDeviceQuads.concat(that->fDeviceQuads);
+        if (!fHelper.isTrivial()) {
+            fLocalQuads.concat(that->fLocalQuads);
+        }
         return CombineResult::kMerged;
     }
 
@@ -291,8 +272,10 @@
     // But since it's avoiding the op list management, it must update the op's bounds. This is only
     // used with quad sets, which uses the same view matrix for each quad so this assumes that the
     // device quad type of the new quad is the same as the op's.
-    void addQuad(TransformedQuad&& quad, GrQuadType localQuadType, GrAAType aaType) {
-        SkASSERT(quad.deviceQuad().quadType() <= this->deviceQuadType());
+    void addQuad(const GrPerspQuad& deviceQuad, const GrPerspQuad& localQuad,
+                 GrQuadType localQuadType, const SkPMColor4f& color, GrQuadAAFlags edgeAA,
+                 GrAAType aaType) {
+        SkASSERT(deviceQuad.quadType() <= fDeviceQuads.quadType());
 
         // The new quad's aa type should be the same as the first quad's or none, except when the
         // first quad's aa type was already downgraded to none, in which case the stored type must
@@ -307,39 +290,40 @@
             // reset the op's accumulated aa type.
         }
 
-        // The new quad's local coordinates could differ
-        if (localQuadType > this->localQuadType()) {
-            fLocalQuadType = static_cast<unsigned>(localQuadType);
-        }
-
         // clear compatible won't need to be updated, since device quad type and paint is the same,
         // but this quad has a new color, so maybe update wide color
-        fWideColor |= !SkPMColor4fFitsInBytes(quad.color());
+        fWideColor |= !SkPMColor4fFitsInBytes(color);
 
         // Update the bounds and add the quad to this op's storage
         SkRect newBounds = this->bounds();
-        newBounds.joinPossiblyEmptyRect(quad.deviceQuad().bounds());
+        newBounds.joinPossiblyEmptyRect(deviceQuad.bounds(fDeviceQuads.quadType()));
         this->setBounds(newBounds, HasAABloat(fHelper.aaType() == GrAAType::kCoverage),
                         IsZeroArea::kNo);
-        fQuads.push_back(std::move(quad));
+        fDeviceQuads.push_back(deviceQuad, fDeviceQuads.quadType(), { color, edgeAA });
+        if (!fHelper.isTrivial()) {
+            fLocalQuads.push_back(localQuad, localQuadType);
+        }
     }
 
-    GrQuadType deviceQuadType() const { return static_cast<GrQuadType>(fDeviceQuadType); }
-    GrQuadType localQuadType() const { return static_cast<GrQuadType>(fLocalQuadType); }
+    int quadCount() const {
+        // Sanity check that the parallel arrays for quad properties all have the same size
+        SkASSERT(fDeviceQuads.count() == fLocalQuads.count() ||
+                 (fLocalQuads.count() == 0 && fHelper.isTrivial()));
+        return fDeviceQuads.count();
+    }
+
+    struct ColorAndAA {
+        SkPMColor4f fColor;
+        GrQuadAAFlags fAAFlags;
+    };
 
     Helper fHelper;
-    SkSTArray<1, TransformedQuad, true> fQuads;
+    GrTQuadList<ColorAndAA> fDeviceQuads;
+    // No metadata attached to the local quads; this list is empty when local coords are not needed.
+    GrQuadList fLocalQuads;
 
-    // While we always store full GrPerspQuads in memory, if the type is known to be simpler we can
-    // optimize our geometry generation.
-    unsigned fDeviceQuadType: 2;
-    unsigned fLocalQuadType: 2;
     unsigned fWideColor: 1;
 
-    // True if fQuad produced by a rectangle-preserving view matrix, is pixel aligned or non-AA,
-    // and its paint is a constant blended color.
-    unsigned fClearCompatible: 1;
-
     typedef GrMeshDrawOp INHERITED;
 };
 
@@ -347,40 +331,40 @@
 
 namespace GrFillRectOp {
 
-std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                               GrPaint&& paint,
-                               GrAAType aaType,
-                               GrQuadAAFlags edgeAA,
-                               const SkMatrix& viewMatrix,
-                               const SkRect& rect,
-                               const GrUserStencilSettings* stencilSettings) {
+std::unique_ptr<GrDrawOp> MakePerEdge(GrContext* context,
+                                      GrPaint&& paint,
+                                      GrAAType aaType,
+                                      GrQuadAAFlags edgeAA,
+                                      const SkMatrix& viewMatrix,
+                                      const SkRect& rect,
+                                      const GrUserStencilSettings* stencilSettings) {
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
                             GrPerspQuad(rect, viewMatrix), GrQuadTypeForTransformedRect(viewMatrix),
                             GrPerspQuad(rect, SkMatrix::I()), GrQuadType::kRect);
 }
 
-std::unique_ptr<GrDrawOp> MakeWithLocalMatrix(GrContext* context,
-                                              GrPaint&& paint,
-                                              GrAAType aaType,
-                                              GrQuadAAFlags edgeAA,
-                                              const SkMatrix& viewMatrix,
-                                              const SkMatrix& localMatrix,
-                                              const SkRect& rect,
-                                              const GrUserStencilSettings* stencilSettings) {
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalMatrix(GrContext* context,
+                                                     GrPaint&& paint,
+                                                     GrAAType aaType,
+                                                     GrQuadAAFlags edgeAA,
+                                                     const SkMatrix& viewMatrix,
+                                                     const SkMatrix& localMatrix,
+                                                     const SkRect& rect,
+                                                     const GrUserStencilSettings* stencilSettings) {
     GrQuadType localQuadType = GrQuadTypeForTransformedRect(localMatrix);
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
                             GrPerspQuad(rect, viewMatrix), GrQuadTypeForTransformedRect(viewMatrix),
                             GrPerspQuad(rect, localMatrix), localQuadType);
 }
 
-std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrContext* context,
-                                            GrPaint&& paint,
-                                            GrAAType aaType,
-                                            GrQuadAAFlags edgeAA,
-                                            const SkMatrix& viewMatrix,
-                                            const SkRect& rect,
-                                            const SkRect& localRect,
-                                            const GrUserStencilSettings* stencilSettings) {
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalRect(GrContext* context,
+                                                   GrPaint&& paint,
+                                                   GrAAType aaType,
+                                                   GrQuadAAFlags edgeAA,
+                                                   const SkMatrix& viewMatrix,
+                                                   const SkRect& rect,
+                                                   const SkRect& localRect,
+                                                   const GrUserStencilSettings* stencilSettings) {
     return FillRectOp::Make(context, std::move(paint), aaType, edgeAA, stencilSettings,
                             GrPerspQuad(rect, viewMatrix), GrQuadTypeForTransformedRect(viewMatrix),
                             GrPerspQuad(localRect, SkMatrix::I()), GrQuadType::kRect);
@@ -413,14 +397,49 @@
         GrResolveAATypeForQuad(aaType, quads[i].fAAFlags, deviceQuad, deviceQuadType,
                                &resolvedAA, &resolvedEdgeFlags);
 
-        fillRects->addQuad({ deviceQuad, GrPerspQuad(quads[i].fRect, quads[i].fLocalMatrix),
-                             quads[i].fColor, resolvedEdgeFlags },
-                           GrQuadTypeForTransformedRect(quads[i].fLocalMatrix), resolvedAA);
+        fillRects->addQuad(deviceQuad, GrPerspQuad(quads[i].fRect, quads[i].fLocalMatrix),
+                           GrQuadTypeForTransformedRect(quads[i].fLocalMatrix), quads[i].fColor,
+                           resolvedEdgeFlags,resolvedAA);
     }
 
     return op;
 }
 
+std::unique_ptr<GrDrawOp> Make(GrContext* context,
+                               GrPaint&& paint,
+                               GrAAType aaType,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const GrUserStencilSettings* stencil) {
+    return MakePerEdge(context, std::move(paint), aaType,
+            aaType == GrAAType::kCoverage ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
+            viewMatrix, rect, stencil);
+}
+
+std::unique_ptr<GrDrawOp> MakeWithLocalMatrix(GrContext* context,
+                                              GrPaint&& paint,
+                                              GrAAType aaType,
+                                              const SkMatrix& viewMatrix,
+                                              const SkMatrix& localMatrix,
+                                              const SkRect& rect,
+                                              const GrUserStencilSettings* stencil) {
+    return MakePerEdgeWithLocalMatrix(context, std::move(paint), aaType,
+            aaType == GrAAType::kCoverage ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
+            viewMatrix, localMatrix, rect, stencil);
+}
+
+std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrContext* context,
+                                            GrPaint&& paint,
+                                            GrAAType aaType,
+                                            const SkMatrix& viewMatrix,
+                                            const SkRect& rect,
+                                            const SkRect& localRect,
+                                            const GrUserStencilSettings* stencil) {
+    return MakePerEdgeWithLocalRect(context, std::move(paint), aaType,
+            aaType == GrAAType::kCoverage ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone,
+            viewMatrix, rect, localRect, stencil);
+}
+
 } // namespace GrFillRectOp
 
 #if GR_TEST_UTILS
@@ -472,19 +491,21 @@
             } else {
                 // Single local matrix
                 SkMatrix localMatrix = GrTest::TestMatrixInvertible(random);
-                return GrFillRectOp::MakeWithLocalMatrix(context, std::move(paint), aaType, aaFlags,
-                                                         viewMatrix, localMatrix, rect, stencil);
+                return GrFillRectOp::MakePerEdgeWithLocalMatrix(context, std::move(paint), aaType,
+                                                                aaFlags, viewMatrix, localMatrix,
+                                                                rect, stencil);
             }
         } else {
             // Pass local rect directly
             SkRect localRect = GrTest::TestRect(random);
-            return GrFillRectOp::MakeWithLocalRect(context, std::move(paint), aaType, aaFlags,
-                                                   viewMatrix, rect, localRect, stencil);
+            return GrFillRectOp::MakePerEdgeWithLocalRect(context, std::move(paint), aaType,
+                                                          aaFlags, viewMatrix, rect, localRect,
+                                                          stencil);
         }
     } else {
         // The simplest constructor
-        return GrFillRectOp::Make(context, std::move(paint), aaType, aaFlags, viewMatrix, rect,
-                                  stencil);
+        return GrFillRectOp::MakePerEdge(context, std::move(paint), aaType, aaFlags, viewMatrix,
+                                         rect, stencil);
     }
 }
 
diff --git a/src/gpu/ops/GrFillRectOp.h b/src/gpu/ops/GrFillRectOp.h
index 8d287bb..175c9f3 100644
--- a/src/gpu/ops/GrFillRectOp.h
+++ b/src/gpu/ops/GrFillRectOp.h
@@ -17,12 +17,55 @@
 class SkMatrix;
 struct SkRect;
 
+/**
+ * A set of factory functions for drawing filled rectangles either coverage-antialiased, or
+ * non-antialiased. The non-antialiased ops can be used with MSAA. As with other GrDrawOp factories,
+ * the GrPaint is only consumed by these methods if a valid op is returned. If null is returned then
+ * the paint is unmodified and may still be used.
+ */
 namespace GrFillRectOp {
 
+// General purpose factory functions that handle per-edge anti-aliasing
+std::unique_ptr<GrDrawOp> MakePerEdge(GrContext* context,
+                                      GrPaint&& paint,
+                                      GrAAType aaType,
+                                      GrQuadAAFlags edgeAA,
+                                      const SkMatrix& viewMatrix,
+                                      const SkRect& rect,
+                                      const GrUserStencilSettings* stencil = nullptr);
+
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalMatrix(GrContext* context,
+                                                     GrPaint&& paint,
+                                                     GrAAType aaType,
+                                                     GrQuadAAFlags edgeAA,
+                                                     const SkMatrix& viewMatrix,
+                                                     const SkMatrix& localMatrix,
+                                                     const SkRect& rect,
+                                                     const GrUserStencilSettings* stl = nullptr);
+
+std::unique_ptr<GrDrawOp> MakePerEdgeWithLocalRect(GrContext* context,
+                                                   GrPaint&& paint,
+                                                   GrAAType aaType,
+                                                   GrQuadAAFlags edgeAA,
+                                                   const SkMatrix& viewMatrix,
+                                                   const SkRect& rect,
+                                                   const SkRect& localRect,
+                                                   const GrUserStencilSettings* stencil = nullptr);
+
+// Bulk API for drawing quads with a single op
+std::unique_ptr<GrDrawOp> MakeSet(GrContext* context,
+                                  GrPaint&& paint,
+                                  GrAAType aaType,
+                                  const SkMatrix& viewMatrix,
+                                  const GrRenderTargetContext::QuadSetEntry quads[],
+                                  int quadCount,
+                                  const GrUserStencilSettings* stencil = nullptr);
+
+// Specializations where all edges are treated the same. If the aa type is coverage, then the
+// edges will be anti-aliased, otherwise per-edge AA will be disabled.
 std::unique_ptr<GrDrawOp> Make(GrContext* context,
                                GrPaint&& paint,
                                GrAAType aaType,
-                               GrQuadAAFlags edgeAA,
                                const SkMatrix& viewMatrix,
                                const SkRect& rect,
                                const GrUserStencilSettings* stencil = nullptr);
@@ -30,7 +73,6 @@
 std::unique_ptr<GrDrawOp> MakeWithLocalMatrix(GrContext* context,
                                               GrPaint&& paint,
                                               GrAAType aaType,
-                                              GrQuadAAFlags edgeAA,
                                               const SkMatrix& viewMatrix,
                                               const SkMatrix& localMatrix,
                                               const SkRect& rect,
@@ -39,19 +81,11 @@
 std::unique_ptr<GrDrawOp> MakeWithLocalRect(GrContext* context,
                                             GrPaint&& paint,
                                             GrAAType aaType,
-                                            GrQuadAAFlags edgeAA,
                                             const SkMatrix& viewMatrix,
                                             const SkRect& rect,
                                             const SkRect& localRect,
                                             const GrUserStencilSettings* stencil = nullptr);
 
-std::unique_ptr<GrDrawOp> MakeSet(GrContext* context,
-                                  GrPaint&& paint,
-                                  GrAAType aaType,
-                                  const SkMatrix& viewMatrix,
-                                  const GrRenderTargetContext::QuadSetEntry quads[],
-                                  int quadCount,
-                                  const GrUserStencilSettings* stencil = nullptr);
-}
+} // namespace GrFillRectOp
 
 #endif // GrFillRectOp_DEFINED
diff --git a/src/gpu/ops/GrLatticeOp.cpp b/src/gpu/ops/GrLatticeOp.cpp
index 0975025..68b15bc 100644
--- a/src/gpu/ops/GrLatticeOp.cpp
+++ b/src/gpu/ops/GrLatticeOp.cpp
@@ -30,8 +30,10 @@
     static sk_sp<GrGeometryProcessor> Make(GrGpu* gpu,
                                            const GrTextureProxy* proxy,
                                            sk_sp<GrColorSpaceXform> csxf,
-                                           GrSamplerState::Filter filter) {
-        return sk_sp<GrGeometryProcessor>(new LatticeGP(gpu, proxy, std::move(csxf), filter));
+                                           GrSamplerState::Filter filter,
+                                           bool wideColor) {
+        return sk_sp<GrGeometryProcessor>(
+                new LatticeGP(gpu, proxy, std::move(csxf), filter, wideColor));
     }
 
     const char* name() const override { return "LatticeGP"; }
@@ -90,7 +92,7 @@
 
 private:
     LatticeGP(GrGpu* gpu, const GrTextureProxy* proxy, sk_sp<GrColorSpaceXform> csxf,
-              GrSamplerState::Filter filter)
+              GrSamplerState::Filter filter, bool wideColor)
             : INHERITED(kLatticeGP_ClassID), fColorSpaceXform(std::move(csxf)) {
 
         GrSamplerState samplerState = GrSamplerState(GrSamplerState::WrapMode::kClamp,
@@ -104,7 +106,7 @@
         fInPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
         fInTextureCoords = {"textureCoords", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
         fInTextureDomain = {"textureDomain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
-        fInColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
+        fInColor = MakeColorAttribute("color", wideColor);
         this->setVertexAttributes(&fInPosition, 4);
     }
 
@@ -163,6 +165,7 @@
 
         // setup bounds
         this->setTransformedBounds(patch.fDst, viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
+        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "NonAALatticeOp"; }
@@ -204,7 +207,7 @@
 private:
     void onPrepareDraws(Target* target) override {
         GrGpu* gpu = target->resourceProvider()->priv().gpu();
-        auto gp = LatticeGP::Make(gpu, fProxy.get(), fColorSpaceXform, fFilter);
+        auto gp = LatticeGP::Make(gpu, fProxy.get(), fColorSpaceXform, fFilter, fWideColor);
         if (!gp) {
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
@@ -233,8 +236,7 @@
         for (int i = 0; i < patchCnt; i++) {
             const Patch& patch = fPatches[i];
 
-            // TODO4F: Preserve float colors
-            GrColor patchColor = patch.fColor.toBytes_RGBA();
+            GrVertexColor patchColor(patch.fColor, fWideColor);
 
             // Apply the view matrix here if it is scale-translate.  Otherwise, we need to
             // wait until we've created the dst rects.
@@ -299,6 +301,7 @@
         }
 
         fPatches.move_back_n(that->fPatches.count(), that->fPatches.begin());
+        fWideColor |= that->fWideColor;
         return CombineResult::kMerged;
     }
 
@@ -314,6 +317,7 @@
     sk_sp<GrTextureProxy> fProxy;
     sk_sp<GrColorSpaceXform> fColorSpaceXform;
     GrSamplerState::Filter fFilter;
+    bool fWideColor;
 
     typedef GrMeshDrawOp INHERITED;
 };
diff --git a/src/gpu/ops/GrNonAAFillRectOp.cpp b/src/gpu/ops/GrNonAAFillRectOp.cpp
deleted file mode 100644
index ae0c9b7..0000000
--- a/src/gpu/ops/GrNonAAFillRectOp.cpp
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrAppliedClip.h"
-#include "GrColor.h"
-#include "GrDefaultGeoProcFactory.h"
-#include "GrDrawOpTest.h"
-#include "GrMeshDrawOp.h"
-#include "GrOpFlushState.h"
-#include "GrPrimitiveProcessor.h"
-#include "GrQuad.h"
-#include "GrRectOpFactory.h"
-#include "GrResourceProvider.h"
-#include "GrSimpleMeshDrawOpHelper.h"
-#include "SkMatrixPriv.h"
-
-static const int kVertsPerRect = 4;
-static const int kIndicesPerRect = 6;
-
-/** We always use per-vertex colors so that rects can be combined across color changes. Sometimes
-    we  have explicit local coords and sometimes not. We *could* always provide explicit local
-    coords and just duplicate the positions when the caller hasn't provided a local coord rect,
-    but we haven't seen a use case which frequently switches between local rect and no local
-    rect draws.
-
-    The vertex attrib order is always pos, color, [local coords].
- */
-static sk_sp<GrGeometryProcessor> make_gp(const GrShaderCaps* shaderCaps) {
-    using namespace GrDefaultGeoProcFactory;
-    return GrDefaultGeoProcFactory::Make(shaderCaps,
-                                         Color::kPremulGrColorAttribute_Type,
-                                         Coverage::kSolid_Type,
-                                         LocalCoords::kHasExplicit_Type,
-                                         SkMatrix::I());
-}
-
-static sk_sp<GrGeometryProcessor> make_perspective_gp(const GrShaderCaps* shaderCaps,
-                                                      const SkMatrix& viewMatrix,
-                                                      bool hasExplicitLocalCoords,
-                                                      const SkMatrix* localMatrix) {
-    SkASSERT(viewMatrix.hasPerspective() || (localMatrix && localMatrix->hasPerspective()));
-
-    using namespace GrDefaultGeoProcFactory;
-
-    // If we have perspective on the viewMatrix then we won't map on the CPU, nor will we map
-    // the local rect on the cpu (in case the localMatrix also has perspective).
-    // Otherwise, if we have a local rect, then we apply the localMatrix directly to the localRect
-    // to generate vertex local coords
-    if (viewMatrix.hasPerspective()) {
-        LocalCoords localCoords(hasExplicitLocalCoords ? LocalCoords::kHasExplicit_Type
-                                                       : LocalCoords::kUsePosition_Type,
-                                localMatrix);
-        return GrDefaultGeoProcFactory::Make(shaderCaps, Color::kPremulGrColorAttribute_Type,
-                                             Coverage::kSolid_Type, localCoords, viewMatrix);
-    } else if (hasExplicitLocalCoords) {
-        LocalCoords localCoords(LocalCoords::kHasExplicit_Type, localMatrix);
-        return GrDefaultGeoProcFactory::Make(shaderCaps, Color::kPremulGrColorAttribute_Type,
-                                             Coverage::kSolid_Type, localCoords, SkMatrix::I());
-    } else {
-        LocalCoords localCoords(LocalCoords::kUsePosition_Type, localMatrix);
-        return GrDefaultGeoProcFactory::MakeForDeviceSpace(shaderCaps,
-                                                           Color::kPremulGrColorAttribute_Type,
-                                                           Coverage::kSolid_Type, localCoords,
-                                                           viewMatrix);
-    }
-}
-
-static void tesselate(intptr_t vertices,
-                      size_t vertexStride,
-                      GrColor color,
-                      const SkMatrix* viewMatrix,
-                      const SkRect& rect,
-                      const GrQuad* localQuad) {
-    SkPoint* positions = reinterpret_cast<SkPoint*>(vertices);
-
-    SkPointPriv::SetRectTriStrip(positions, rect, vertexStride);
-
-    if (viewMatrix) {
-        SkMatrixPriv::MapPointsWithStride(*viewMatrix, positions, vertexStride, kVertsPerRect);
-    }
-
-    // Setup local coords
-    // TODO we should only do this if local coords are being read
-    if (localQuad) {
-        static const int kLocalOffset = sizeof(SkPoint) + sizeof(GrColor);
-        for (int i = 0; i < kVertsPerRect; i++) {
-            SkPoint* coords =
-                    reinterpret_cast<SkPoint*>(vertices + kLocalOffset + i * vertexStride);
-            *coords = localQuad->point(i);
-        }
-    }
-
-    static const int kColorOffset = sizeof(SkPoint);
-    GrColor* vertColor = reinterpret_cast<GrColor*>(vertices + kColorOffset);
-    for (int j = 0; j < 4; ++j) {
-        *vertColor = color;
-        vertColor = (GrColor*)((intptr_t)vertColor + vertexStride);
-    }
-}
-
-namespace {
-
-class NonAAFillRectOp final : public GrMeshDrawOp {
-private:
-    using Helper = GrSimpleMeshDrawOpHelperWithStencil;
-
-public:
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                                          GrPaint&& paint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkRect& rect,
-                                          const SkRect* localRect,
-                                          const SkMatrix* localMatrix,
-                                          GrAAType aaType,
-                                          const GrUserStencilSettings* stencilSettings) {
-        SkASSERT(GrAAType::kCoverage != aaType);
-        return Helper::FactoryHelper<NonAAFillRectOp>(context, std::move(paint), viewMatrix, rect,
-                                                      localRect, localMatrix, aaType,
-                                                      stencilSettings);
-    }
-
-    NonAAFillRectOp() = delete;
-
-    NonAAFillRectOp(const Helper::MakeArgs& args, const SkPMColor4f& color,
-                    const SkMatrix& viewMatrix, const SkRect& rect, const SkRect* localRect,
-                    const SkMatrix* localMatrix, GrAAType aaType,
-                    const GrUserStencilSettings* stencilSettings)
-            : INHERITED(ClassID()), fHelper(args, aaType, stencilSettings) {
-
-        SkASSERT(!viewMatrix.hasPerspective() && (!localMatrix || !localMatrix->hasPerspective()));
-        RectInfo& info = fRects.push_back();
-        info.fColor = color;
-        info.fViewMatrix = viewMatrix;
-        info.fRect = rect;
-        if (localRect && localMatrix) {
-            info.fLocalQuad = GrQuad(*localRect, *localMatrix);
-        } else if (localRect) {
-            info.fLocalQuad = GrQuad(*localRect);
-        } else if (localMatrix) {
-            info.fLocalQuad = GrQuad(rect, *localMatrix);
-        } else {
-            info.fLocalQuad = GrQuad(rect);
-        }
-        this->setTransformedBounds(fRects[0].fRect, viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
-    }
-
-    const char* name() const override { return "NonAAFillRectOp"; }
-
-    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
-        fHelper.visitProxies(func);
-    }
-
-#ifdef SK_DEBUG
-    SkString dumpInfo() const override {
-        SkString str;
-        str.append(GrMeshDrawOp::dumpInfo());
-        str.appendf("# combined: %d\n", fRects.count());
-        for (int i = 0; i < fRects.count(); ++i) {
-            const RectInfo& info = fRects[i];
-            str.appendf("%d: Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", i,
-                        info.fColor.toBytes_RGBA(), info.fRect.fLeft, info.fRect.fTop,
-                        info.fRect.fRight, info.fRect.fBottom);
-        }
-        str += fHelper.dumpInfo();
-        str += INHERITED::dumpInfo();
-        return str;
-    }
-#endif
-
-    RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        SkPMColor4f* color = &fRects.front().fColor;
-        return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kNone, color);
-    }
-
-    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
-
-    DEFINE_OP_CLASS_ID
-
-private:
-    void onPrepareDraws(Target* target) override {
-        sk_sp<GrGeometryProcessor> gp = make_gp(target->caps().shaderCaps());
-        if (!gp) {
-            SkDebugf("Couldn't create GrGeometryProcessor\n");
-            return;
-        }
-
-        size_t kVertexStride = gp->vertexStride();
-        int rectCount = fRects.count();
-
-        sk_sp<const GrBuffer> indexBuffer = target->resourceProvider()->refQuadIndexBuffer();
-        PatternHelper helper(target, GrPrimitiveType::kTriangles, kVertexStride, indexBuffer.get(),
-                             kVertsPerRect, kIndicesPerRect, rectCount);
-        void* vertices = helper.vertices();
-        if (!vertices || !indexBuffer) {
-            SkDebugf("Could not allocate vertices\n");
-            return;
-        }
-
-        for (int i = 0; i < rectCount; i++) {
-            intptr_t verts =
-                    reinterpret_cast<intptr_t>(vertices) + i * kVertsPerRect * kVertexStride;
-            // TODO4F: Preserve float colors
-            tesselate(verts, kVertexStride, fRects[i].fColor.toBytes_RGBA(), &fRects[i].fViewMatrix,
-                      fRects[i].fRect, &fRects[i].fLocalQuad);
-        }
-        auto pipe = fHelper.makePipeline(target);
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
-    }
-
-    CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
-        NonAAFillRectOp* that = t->cast<NonAAFillRectOp>();
-        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
-            return CombineResult::kCannotCombine;
-        }
-        fRects.push_back_n(that->fRects.count(), that->fRects.begin());
-        return CombineResult::kMerged;
-    }
-
-    struct RectInfo {
-        SkPMColor4f fColor;
-        SkMatrix fViewMatrix;
-        SkRect fRect;
-        GrQuad fLocalQuad;
-    };
-
-    Helper fHelper;
-    SkSTArray<1, RectInfo, true> fRects;
-    typedef GrMeshDrawOp INHERITED;
-};
-
-// We handle perspective in the local matrix or viewmatrix with special ops.
-class NonAAFillRectPerspectiveOp final : public GrMeshDrawOp {
-private:
-    using Helper = GrSimpleMeshDrawOpHelperWithStencil;
-
-public:
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                                          GrPaint&& paint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkRect& rect,
-                                          const SkRect* localRect,
-                                          const SkMatrix* localMatrix,
-                                          GrAAType aaType,
-                                          const GrUserStencilSettings* stencilSettings) {
-        SkASSERT(GrAAType::kCoverage != aaType);
-        return Helper::FactoryHelper<NonAAFillRectPerspectiveOp>(context, std::move(paint),
-                                                                 viewMatrix, rect,
-                                                                 localRect, localMatrix, aaType,
-                                                                 stencilSettings);
-    }
-
-    NonAAFillRectPerspectiveOp() = delete;
-
-    NonAAFillRectPerspectiveOp(const Helper::MakeArgs& args, const SkPMColor4f& color,
-                               const SkMatrix& viewMatrix, const SkRect& rect,
-                               const SkRect* localRect, const SkMatrix* localMatrix,
-                               GrAAType aaType, const GrUserStencilSettings* stencilSettings)
-            : INHERITED(ClassID())
-            , fHelper(args, aaType, stencilSettings)
-            , fViewMatrix(viewMatrix) {
-        SkASSERT(viewMatrix.hasPerspective() || (localMatrix && localMatrix->hasPerspective()));
-        RectInfo& info = fRects.push_back();
-        info.fColor = color;
-        info.fRect = rect;
-        fHasLocalRect = SkToBool(localRect);
-        fHasLocalMatrix = SkToBool(localMatrix);
-        if (fHasLocalMatrix) {
-            fLocalMatrix = *localMatrix;
-        }
-        if (fHasLocalRect) {
-            info.fLocalRect = *localRect;
-        }
-        this->setTransformedBounds(rect, viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
-    }
-
-    const char* name() const override { return "NonAAFillRectPerspectiveOp"; }
-
-    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
-        fHelper.visitProxies(func);
-    }
-
-#ifdef SK_DEBUG
-    SkString dumpInfo() const override {
-        SkString str;
-        str.appendf("# combined: %d\n", fRects.count());
-        for (int i = 0; i < fRects.count(); ++i) {
-            const RectInfo& geo = fRects[i];
-            str.appendf("%d: Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f]\n", i,
-                        geo.fColor.toBytes_RGBA(), geo.fRect.fLeft, geo.fRect.fTop,
-                        geo.fRect.fRight, geo.fRect.fBottom);
-        }
-        str += fHelper.dumpInfo();
-        str += INHERITED::dumpInfo();
-        return str;
-    }
-#endif
-
-    RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        SkPMColor4f* color = &fRects.front().fColor;
-        return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kNone, color);
-    }
-
-    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
-
-    DEFINE_OP_CLASS_ID
-
-private:
-    void onPrepareDraws(Target* target) override {
-        sk_sp<GrGeometryProcessor> gp = make_perspective_gp(
-                target->caps().shaderCaps(),
-                fViewMatrix,
-                fHasLocalRect,
-                fHasLocalMatrix ? &fLocalMatrix : nullptr);
-        if (!gp) {
-            SkDebugf("Couldn't create GrGeometryProcessor\n");
-            return;
-        }
-        size_t vertexStride = gp->vertexStride();
-        int rectCount = fRects.count();
-
-        sk_sp<const GrBuffer> indexBuffer = target->resourceProvider()->refQuadIndexBuffer();
-        PatternHelper helper(target, GrPrimitiveType::kTriangles, vertexStride, indexBuffer.get(),
-                             kVertsPerRect, kIndicesPerRect, rectCount);
-        void* vertices = helper.vertices();
-        if (!vertices || !indexBuffer) {
-            SkDebugf("Could not allocate vertices\n");
-            return;
-        }
-
-        for (int i = 0; i < rectCount; i++) {
-            const RectInfo& info = fRects[i];
-            // TODO4F: Preserve float colors
-            GrColor color = info.fColor.toBytes_RGBA();
-            intptr_t verts =
-                    reinterpret_cast<intptr_t>(vertices) + i * kVertsPerRect * vertexStride;
-            if (fHasLocalRect) {
-                GrQuad quad(info.fLocalRect);
-                tesselate(verts, vertexStride, color, nullptr, info.fRect, &quad);
-            } else {
-                tesselate(verts, vertexStride, color, nullptr, info.fRect, nullptr);
-            }
-        }
-        auto pipe = fHelper.makePipeline(target);
-        helper.recordDraw(target, std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState);
-    }
-
-    CombineResult onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
-        NonAAFillRectPerspectiveOp* that = t->cast<NonAAFillRectPerspectiveOp>();
-        if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
-            return CombineResult::kCannotCombine;
-        }
-
-        // We could combine across perspective vm changes if we really wanted to.
-        if (!fViewMatrix.cheapEqualTo(that->fViewMatrix)) {
-            return CombineResult::kCannotCombine;
-        }
-        if (fHasLocalRect != that->fHasLocalRect) {
-            return CombineResult::kCannotCombine;
-        }
-        if (fHasLocalMatrix && !fLocalMatrix.cheapEqualTo(that->fLocalMatrix)) {
-            return CombineResult::kCannotCombine;
-        }
-
-        fRects.push_back_n(that->fRects.count(), that->fRects.begin());
-        return CombineResult::kMerged;
-    }
-
-    struct RectInfo {
-        SkRect fRect;
-        SkPMColor4f fColor;
-        SkRect fLocalRect;
-    };
-
-    SkSTArray<1, RectInfo, true> fRects;
-    Helper fHelper;
-    bool fHasLocalMatrix;
-    bool fHasLocalRect;
-    SkMatrix fLocalMatrix;
-    SkMatrix fViewMatrix;
-
-    typedef GrMeshDrawOp INHERITED;
-};
-
-}  // anonymous namespace
-
-namespace GrRectOpFactory {
-
-std::unique_ptr<GrDrawOp> MakeNonAAFill(GrContext* context,
-                                        GrPaint&& paint,
-                                        const SkMatrix& viewMatrix,
-                                        const SkRect& rect,
-                                        GrAAType aaType,
-                                        const GrUserStencilSettings* stencilSettings) {
-    if (viewMatrix.hasPerspective()) {
-        return NonAAFillRectPerspectiveOp::Make(context, std::move(paint), viewMatrix, rect,
-                                                nullptr, nullptr, aaType, stencilSettings);
-    } else {
-        return NonAAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, nullptr, nullptr,
-                                     aaType, stencilSettings);
-    }
-}
-
-std::unique_ptr<GrDrawOp> MakeNonAAFillWithLocalMatrix(
-                                                    GrContext* context,
-                                                    GrPaint&& paint,
-                                                    const SkMatrix& viewMatrix,
-                                                    const SkMatrix& localMatrix,
-                                                    const SkRect& rect,
-                                                    GrAAType aaType,
-                                                    const GrUserStencilSettings* stencilSettings) {
-    if (viewMatrix.hasPerspective() || localMatrix.hasPerspective()) {
-        return NonAAFillRectPerspectiveOp::Make(context, std::move(paint), viewMatrix, rect,
-                                                nullptr, &localMatrix, aaType, stencilSettings);
-    } else {
-        return NonAAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, nullptr,
-                                     &localMatrix, aaType, stencilSettings);
-    }
-}
-
-std::unique_ptr<GrDrawOp> MakeNonAAFillWithLocalRect(GrContext* context,
-                                                     GrPaint&& paint,
-                                                     const SkMatrix& viewMatrix,
-                                                     const SkRect& rect,
-                                                     const SkRect& localRect,
-                                                     GrAAType aaType) {
-    if (viewMatrix.hasPerspective()) {
-        return NonAAFillRectPerspectiveOp::Make(context, std::move(paint), viewMatrix, rect,
-                                                &localRect, nullptr, aaType, nullptr);
-    } else {
-        return NonAAFillRectOp::Make(context, std::move(paint), viewMatrix, rect, &localRect,
-                                     nullptr, aaType, nullptr);
-    }
-}
-
-}  // namespace GrRectOpFactory
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#if GR_TEST_UTILS
-
-GR_DRAW_OP_TEST_DEFINE(NonAAFillRectOp) {
-    SkRect rect = GrTest::TestRect(random);
-    SkRect localRect = GrTest::TestRect(random);
-    SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random);
-    SkMatrix localMatrix = GrTest::TestMatrix(random);
-    const GrUserStencilSettings* stencil = GrGetRandomStencil(random, context);
-    GrAAType aaType = GrAAType::kNone;
-    if (fsaaType == GrFSAAType::kUnifiedMSAA) {
-        aaType = random->nextBool() ? GrAAType::kMSAA : GrAAType::kNone;
-    }
-    const SkRect* lr = random->nextBool() ? &localRect : nullptr;
-    const SkMatrix* lm = random->nextBool() ? &localMatrix : nullptr;
-    if (viewMatrix.hasPerspective() || (lm && lm->hasPerspective())) {
-        return NonAAFillRectPerspectiveOp::Make(context, std::move(paint), viewMatrix, rect,
-                                                lr, lm, aaType, stencil);
-    } else {
-        return NonAAFillRectOp::Make(context, std::move(paint), viewMatrix, rect,
-                                     lr, lm, aaType, stencil);
-    }
-}
-
-#endif
diff --git a/src/gpu/ops/GrNonAAStrokeRectOp.cpp b/src/gpu/ops/GrNonAAStrokeRectOp.cpp
deleted file mode 100644
index c2786ef..0000000
--- a/src/gpu/ops/GrNonAAStrokeRectOp.cpp
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "GrColor.h"
-#include "GrDefaultGeoProcFactory.h"
-#include "GrDrawOpTest.h"
-#include "GrMeshDrawOp.h"
-#include "GrOpFlushState.h"
-#include "GrRectOpFactory.h"
-#include "GrSimpleMeshDrawOpHelper.h"
-#include "SkRandom.h"
-#include "SkStrokeRec.h"
-
-/*  create a triangle strip that strokes the specified rect. There are 8
-    unique vertices, but we repeat the last 2 to close up. Alternatively we
-    could use an indices array, and then only send 8 verts, but not sure that
-    would be faster.
-    */
-static void init_stroke_rect_strip(SkPoint verts[10], const SkRect& rect, SkScalar width) {
-    const SkScalar rad = SkScalarHalf(width);
-
-    verts[0].set(rect.fLeft + rad, rect.fTop + rad);
-    verts[1].set(rect.fLeft - rad, rect.fTop - rad);
-    verts[2].set(rect.fRight - rad, rect.fTop + rad);
-    verts[3].set(rect.fRight + rad, rect.fTop - rad);
-    verts[4].set(rect.fRight - rad, rect.fBottom - rad);
-    verts[5].set(rect.fRight + rad, rect.fBottom + rad);
-    verts[6].set(rect.fLeft + rad, rect.fBottom - rad);
-    verts[7].set(rect.fLeft - rad, rect.fBottom + rad);
-    verts[8] = verts[0];
-    verts[9] = verts[1];
-
-    // TODO: we should be catching this higher up the call stack and just draw a single
-    // non-AA rect
-    if (2*rad >= rect.width()) {
-        verts[0].fX = verts[2].fX = verts[4].fX = verts[6].fX = verts[8].fX = rect.centerX();
-    }
-    if (2*rad >= rect.height()) {
-        verts[0].fY = verts[2].fY = verts[4].fY = verts[6].fY = verts[8].fY = rect.centerY();
-    }
-}
-
-// Allow all hairlines and all miters, so long as the miter limit doesn't produce beveled corners.
-inline static bool allowed_stroke(const SkStrokeRec& stroke) {
-    SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style ||
-             stroke.getStyle() == SkStrokeRec::kHairline_Style);
-    return !stroke.getWidth() ||
-           (stroke.getJoin() == SkPaint::kMiter_Join && stroke.getMiter() > SK_ScalarSqrt2);
-}
-
-namespace {
-
-class NonAAStrokeRectOp final : public GrMeshDrawOp {
-private:
-    using Helper = GrSimpleMeshDrawOpHelper;
-
-public:
-    DEFINE_OP_CLASS_ID
-
-    const char* name() const override { return "NonAAStrokeRectOp"; }
-
-    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
-        fHelper.visitProxies(func);
-    }
-
-#ifdef SK_DEBUG
-    SkString dumpInfo() const override {
-        SkString string;
-        string.appendf(
-                "Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
-                "StrokeWidth: %.2f\n",
-                fColor.toBytes_RGBA(), fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom,
-                fStrokeWidth);
-        string += fHelper.dumpInfo();
-        string += INHERITED::dumpInfo();
-        return string;
-    }
-#endif
-
-    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
-                                          GrPaint&& paint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkRect& rect,
-                                          const SkStrokeRec& stroke,
-                                          GrAAType aaType) {
-        if (!allowed_stroke(stroke)) {
-            return nullptr;
-        }
-        Helper::Flags flags = Helper::Flags::kNone;
-        // Depending on sub-pixel coordinates and the particular GPU, we may lose a corner of
-        // hairline rects. We jam all the vertices to pixel centers to avoid this, but not
-        // when MSAA is enabled because it can cause ugly artifacts.
-        if (stroke.getStyle() == SkStrokeRec::kHairline_Style && aaType != GrAAType::kMSAA) {
-            flags |= Helper::Flags::kSnapVerticesToPixelCenters;
-        }
-        return Helper::FactoryHelper<NonAAStrokeRectOp>(context, std::move(paint), flags,
-                                                        viewMatrix, rect,
-                                                        stroke, aaType);
-    }
-
-    NonAAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
-                      Helper::Flags flags, const SkMatrix& viewMatrix, const SkRect& rect,
-                      const SkStrokeRec& stroke, GrAAType aaType)
-            : INHERITED(ClassID()), fHelper(helperArgs, aaType, flags) {
-        fColor = color;
-        fViewMatrix = viewMatrix;
-        fRect = rect;
-        // Sort the rect for hairlines
-        fRect.sort();
-        fStrokeWidth = stroke.getWidth();
-
-        SkScalar rad = SkScalarHalf(fStrokeWidth);
-        SkRect bounds = rect;
-        bounds.outset(rad, rad);
-
-        // If our caller snaps to pixel centers then we have to round out the bounds
-        if (flags & Helper::Flags::kSnapVerticesToPixelCenters) {
-            viewMatrix.mapRect(&bounds);
-            // We want to be consistent with how we snap non-aa lines. To match what we do in
-            // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a
-            // pixel to force us to pixel centers.
-            bounds.set(SkScalarFloorToScalar(bounds.fLeft),
-                       SkScalarFloorToScalar(bounds.fTop),
-                       SkScalarFloorToScalar(bounds.fRight),
-                       SkScalarFloorToScalar(bounds.fBottom));
-            bounds.offset(0.5f, 0.5f);
-            this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
-        } else {
-            this->setTransformedBounds(bounds, fViewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
-        }
-    }
-
-    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
-
-    RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
-        return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kNone,
-                                            &fColor);
-    }
-
-private:
-    void onPrepareDraws(Target* target) override {
-        sk_sp<GrGeometryProcessor> gp;
-        {
-            using namespace GrDefaultGeoProcFactory;
-            Color color(fColor);
-            LocalCoords::Type localCoordsType = fHelper.usesLocalCoords()
-                                                        ? LocalCoords::kUsePosition_Type
-                                                        : LocalCoords::kUnused_Type;
-            gp = GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), color,
-                                               Coverage::kSolid_Type, localCoordsType,
-                                               fViewMatrix);
-        }
-
-        size_t kVertexStride = gp->vertexStride();
-        int vertexCount = kVertsPerHairlineRect;
-        if (fStrokeWidth > 0) {
-            vertexCount = kVertsPerStrokeRect;
-        }
-
-        const GrBuffer* vertexBuffer;
-        int firstVertex;
-
-        void* verts =
-                target->makeVertexSpace(kVertexStride, vertexCount, &vertexBuffer, &firstVertex);
-
-        if (!verts) {
-            SkDebugf("Could not allocate vertices\n");
-            return;
-        }
-
-        SkPoint* vertex = reinterpret_cast<SkPoint*>(verts);
-
-        GrPrimitiveType primType;
-        if (fStrokeWidth > 0) {
-            primType = GrPrimitiveType::kTriangleStrip;
-            init_stroke_rect_strip(vertex, fRect, fStrokeWidth);
-        } else {
-            // hairline
-            primType = GrPrimitiveType::kLineStrip;
-            vertex[0].set(fRect.fLeft, fRect.fTop);
-            vertex[1].set(fRect.fRight, fRect.fTop);
-            vertex[2].set(fRect.fRight, fRect.fBottom);
-            vertex[3].set(fRect.fLeft, fRect.fBottom);
-            vertex[4].set(fRect.fLeft, fRect.fTop);
-        }
-
-        GrMesh* mesh = target->allocMesh(primType);
-        mesh->setNonIndexedNonInstanced(vertexCount);
-        mesh->setVertexData(vertexBuffer, firstVertex);
-        auto pipe = fHelper.makePipeline(target);
-        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
-    }
-
-    // TODO: override onCombineIfPossible
-
-    Helper fHelper;
-    SkPMColor4f fColor;
-    SkMatrix fViewMatrix;
-    SkRect fRect;
-    SkScalar fStrokeWidth;
-
-    const static int kVertsPerHairlineRect = 5;
-    const static int kVertsPerStrokeRect = 10;
-
-    typedef GrMeshDrawOp INHERITED;
-};
-
-}  // anonymous namespace
-
-namespace GrRectOpFactory {
-std::unique_ptr<GrDrawOp> MakeNonAAStroke(GrContext* context,
-                                          GrPaint&& paint,
-                                          const SkMatrix& viewMatrix,
-                                          const SkRect& rect,
-                                          const SkStrokeRec& stroke,
-                                          GrAAType aaType) {
-    return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke, aaType);
-}
-}  // namespace GrRectOpFactory
-
-#if GR_TEST_UTILS
-
-GR_DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp) {
-    SkMatrix viewMatrix = GrTest::TestMatrix(random);
-    SkRect rect = GrTest::TestRect(random);
-    SkScalar strokeWidth = random->nextBool() ? 0.0f : 2.0f;
-    SkPaint strokePaint;
-    strokePaint.setStrokeWidth(strokeWidth);
-    strokePaint.setStyle(SkPaint::kStroke_Style);
-    strokePaint.setStrokeJoin(SkPaint::kMiter_Join);
-    SkStrokeRec strokeRec(strokePaint);
-    GrAAType aaType = GrAAType::kNone;
-    if (fsaaType == GrFSAAType::kUnifiedMSAA) {
-        aaType = random->nextBool() ? GrAAType::kMSAA : GrAAType::kNone;
-    }
-    return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, strokeRec, aaType);
-}
-
-#endif
diff --git a/src/gpu/ops/GrOp.cpp b/src/gpu/ops/GrOp.cpp
index 3a36d73..b8a0031 100644
--- a/src/gpu/ops/GrOp.cpp
+++ b/src/gpu/ops/GrOp.cpp
@@ -7,9 +7,8 @@
 
 #include "GrOp.h"
 
-int32_t GrOp::gCurrOpClassID = GrOp::kIllegalOpID;
-
-int32_t GrOp::gCurrOpUniqueID = GrOp::kIllegalOpID;
+std::atomic<uint32_t> GrOp::gCurrOpClassID {GrOp::kIllegalOpID + 1};
+std::atomic<uint32_t> GrOp::gCurrOpUniqueID{GrOp::kIllegalOpID + 1};
 
 #ifdef SK_DEBUG
 void* GrOp::operator new(size_t size) {
diff --git a/src/gpu/ops/GrOp.h b/src/gpu/ops/GrOp.h
index 8cf05a8..f3d0db0 100644
--- a/src/gpu/ops/GrOp.h
+++ b/src/gpu/ops/GrOp.h
@@ -8,8 +8,6 @@
 #ifndef GrOp_DEFINED
 #define GrOp_DEFINED
 
-#include <new>
-#include "../private/SkAtomics.h"
 #include "GrGpuResource.h"
 #include "GrNonAtomicRef.h"
 #include "GrTracing.h"
@@ -17,6 +15,8 @@
 #include "SkMatrix.h"
 #include "SkRect.h"
 #include "SkString.h"
+#include <atomic>
+#include <new>
 
 class GrCaps;
 class GrGpuCommandBuffer;
@@ -303,11 +303,9 @@
     // Otherwise, this op's bounds.
     virtual void onExecute(GrOpFlushState*, const SkRect& chainBounds) = 0;
 
-    static uint32_t GenID(int32_t* idCounter) {
-        // The atomic inc returns the old value not the incremented value. So we add
-        // 1 to the returned value.
-        uint32_t id = static_cast<uint32_t>(sk_atomic_inc(idCounter)) + 1;
-        if (!id) {
+    static uint32_t GenID(std::atomic<uint32_t>* idCounter) {
+        uint32_t id = (*idCounter)++;
+        if (id == 0) {
             SK_ABORT("This should never wrap as it should only be called once for each GrOp "
                    "subclass.");
         }
@@ -339,8 +337,8 @@
     mutable uint32_t                    fUniqueID = SK_InvalidUniqueID;
     SkRect                              fBounds;
 
-    static int32_t                      gCurrOpUniqueID;
-    static int32_t                      gCurrOpClassID;
+    static std::atomic<uint32_t> gCurrOpUniqueID;
+    static std::atomic<uint32_t> gCurrOpClassID;
 };
 
 #endif
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index c392364..7e2a9c8 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -30,22 +30,8 @@
 
 namespace {
 
-struct EllipseVertex {
-    SkPoint fPos;
-    GrColor fColor;
-    SkPoint fOffset;
-    SkPoint fOuterRadii;
-    SkPoint fInnerRadii;
-};
-
 static inline bool circle_stays_circle(const SkMatrix& m) { return m.isSimilarity(); }
 
-static inline GrPrimitiveProcessor::Attribute color_attribute(bool wideColor) {
-    return { "inColor",
-             wideColor ? kHalf4_GrVertexAttribType : kUByte4_norm_GrVertexAttribType,
-             kHalf4_GrSLType };
-}
-
 // Produces TriStrip vertex data for an origin-centered rectangle from [-x, -y] to [x, y]
 static inline GrVertexWriter::TriStrip<float> origin_centered_tri_strip(float x, float y) {
     return GrVertexWriter::TriStrip<float>{ -x, -y, x, y };
@@ -82,7 +68,7 @@
             , fLocalMatrix(localMatrix)
             , fStroke(stroke) {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
-        fInColor = color_attribute(wideColor);
+        fInColor = MakeColorAttribute("inColor", wideColor);
         fInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
 
         if (clipPlane) {
@@ -274,7 +260,7 @@
     ButtCapDashedCircleGeometryProcessor(bool wideColor, const SkMatrix& localMatrix)
             : INHERITED(kButtCapStrokedCircleGeometryProcessor_ClassID), fLocalMatrix(localMatrix) {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
-        fInColor = color_attribute(wideColor);
+        fInColor = MakeColorAttribute("inColor", wideColor);
         fInCircleEdge = {"inCircleEdge", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
         fInDashParams = {"inDashParams", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
         this->setVertexAttributes(&fInPosition, 4);
@@ -521,7 +507,7 @@
     : INHERITED(kEllipseGeometryProcessor_ClassID)
     , fLocalMatrix(localMatrix) {
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
-        fInColor = color_attribute(wideColor);
+        fInColor = MakeColorAttribute("inColor", wideColor);
         fInEllipseOffset = {"inEllipseOffset", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
         fInEllipseRadii = {"inEllipseRadii", kFloat4_GrVertexAttribType, kHalf4_GrSLType};
         this->setVertexAttributes(&fInPosition, 4);
@@ -673,7 +659,7 @@
             , fViewMatrix(viewMatrix) {
         fStyle = style;
         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
-        fInColor = color_attribute(wideColor);
+        fInColor = MakeColorAttribute("inColor", wideColor);
         fInEllipseOffsets0 = {"inEllipseOffsets0", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
         fInEllipseOffsets1 = {"inEllipseOffsets1", kFloat2_GrVertexAttribType, kHalf2_GrSLType};
         this->setVertexAttributes(&fInPosition, 4);
@@ -1282,7 +1268,9 @@
                 if (fClipPlaneUnion) {
                     vertices.write(circle.fUnionPlane);
                 }
-                SkASSERT(!fRoundCaps);
+                if (fRoundCaps) {
+                    vertices.write(circle.fRoundCapCenters);
+                }
             }
 
             const uint16_t* primIndices = circle_type_to_indices(circle.fStroked);
@@ -2272,6 +2260,7 @@
         fVertCount = rrect_type_to_vert_count(type);
         fIndexCount = rrect_type_to_index_count(type);
         fAllFill = (kFill_RRectType == type);
+        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "CircularRRectOp"; }
@@ -2307,79 +2296,54 @@
     FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
 
 private:
-    struct CircleVertex {
-        SkPoint fPos;
-        GrColor fColor;
-        SkPoint fOffset;
-        SkScalar fOuterRadius;
-        SkScalar fInnerRadius;
-        // No half plane, we don't use it here.
-    };
-
-    static void FillInOverstrokeVerts(CircleVertex** verts, const SkRect& bounds, SkScalar smInset,
+    static void FillInOverstrokeVerts(GrVertexWriter& verts, const SkRect& bounds, SkScalar smInset,
                                       SkScalar bigInset, SkScalar xOffset, SkScalar outerRadius,
-                                      SkScalar innerRadius, GrColor color) {
+                                      SkScalar innerRadius, const GrVertexColor& color) {
         SkASSERT(smInset < bigInset);
 
         // TL
-        (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fTop + smInset);
-        (*verts)->fColor = color;
-        (*verts)->fOffset = SkPoint::Make(xOffset, 0);
-        (*verts)->fOuterRadius = outerRadius;
-        (*verts)->fInnerRadius = innerRadius;
-        (*verts)++;
+        verts.write(bounds.fLeft + smInset, bounds.fTop + smInset,
+                    color,
+                    xOffset, 0.0f,
+                    outerRadius, innerRadius);
 
         // TR
-        (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fTop + smInset);
-        (*verts)->fColor = color;
-        (*verts)->fOffset = SkPoint::Make(xOffset, 0);
-        (*verts)->fOuterRadius = outerRadius;
-        (*verts)->fInnerRadius = innerRadius;
-        (*verts)++;
+        verts.write(bounds.fRight - smInset, bounds.fTop + smInset,
+                    color,
+                    xOffset, 0.0f,
+                    outerRadius, innerRadius);
 
-        (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fTop + bigInset);
-        (*verts)->fColor = color;
-        (*verts)->fOffset = SkPoint::Make(0, 0);
-        (*verts)->fOuterRadius = outerRadius;
-        (*verts)->fInnerRadius = innerRadius;
-        (*verts)++;
+        verts.write(bounds.fLeft + bigInset, bounds.fTop + bigInset,
+                    color,
+                    0.0f, 0.0f,
+                    outerRadius, innerRadius);
 
-        (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fTop + bigInset);
-        (*verts)->fColor = color;
-        (*verts)->fOffset = SkPoint::Make(0, 0);
-        (*verts)->fOuterRadius = outerRadius;
-        (*verts)->fInnerRadius = innerRadius;
-        (*verts)++;
+        verts.write(bounds.fRight - bigInset, bounds.fTop + bigInset,
+                    color,
+                    0.0f, 0.0f,
+                    outerRadius, innerRadius);
 
-        (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fBottom - bigInset);
-        (*verts)->fColor = color;
-        (*verts)->fOffset = SkPoint::Make(0, 0);
-        (*verts)->fOuterRadius = outerRadius;
-        (*verts)->fInnerRadius = innerRadius;
-        (*verts)++;
+        verts.write(bounds.fLeft + bigInset, bounds.fBottom - bigInset,
+                    color,
+                    0.0f, 0.0f,
+                    outerRadius, innerRadius);
 
-        (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fBottom - bigInset);
-        (*verts)->fColor = color;
-        (*verts)->fOffset = SkPoint::Make(0, 0);
-        (*verts)->fOuterRadius = outerRadius;
-        (*verts)->fInnerRadius = innerRadius;
-        (*verts)++;
+        verts.write(bounds.fRight - bigInset, bounds.fBottom - bigInset,
+                    color,
+                    0.0f, 0.0f,
+                    outerRadius, innerRadius);
 
         // BL
-        (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fBottom - smInset);
-        (*verts)->fColor = color;
-        (*verts)->fOffset = SkPoint::Make(xOffset, 0);
-        (*verts)->fOuterRadius = outerRadius;
-        (*verts)->fInnerRadius = innerRadius;
-        (*verts)++;
+        verts.write(bounds.fLeft + smInset, bounds.fBottom - smInset,
+                    color,
+                    xOffset, 0.0f,
+                    outerRadius, innerRadius);
 
         // BR
-        (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fBottom - smInset);
-        (*verts)->fColor = color;
-        (*verts)->fOffset = SkPoint::Make(xOffset, 0);
-        (*verts)->fOuterRadius = outerRadius;
-        (*verts)->fInnerRadius = innerRadius;
-        (*verts)++;
+        verts.write(bounds.fRight - smInset, bounds.fBottom - smInset,
+                    color,
+                    xOffset, 0.0f,
+                    outerRadius, innerRadius);
     }
 
     void onPrepareDraws(Target* target) override {
@@ -2391,17 +2355,15 @@
 
         // Setup geometry processor
         sk_sp<GrGeometryProcessor> gp(
-                new CircleGeometryProcessor(!fAllFill, false, false, false, false, false,
+                new CircleGeometryProcessor(!fAllFill, false, false, false, false, fWideColor,
                                             localMatrix));
 
-        SkASSERT(sizeof(CircleVertex) == gp->vertexStride());
-
         const GrBuffer* vertexBuffer;
         int firstVertex;
 
-        CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(
-                sizeof(CircleVertex), fVertCount, &vertexBuffer, &firstVertex);
-        if (!verts) {
+        GrVertexWriter verts{target->makeVertexSpace(gp->vertexStride(), fVertCount,
+                                                     &vertexBuffer, &firstVertex)};
+        if (!verts.fPtr) {
             SkDebugf("Could not allocate vertices\n");
             return;
         }
@@ -2416,8 +2378,7 @@
 
         int currStartVertex = 0;
         for (const auto& rrect : fRRects) {
-            // TODO4F: Preserve float colors
-            GrColor color = rrect.fColor.toBytes_RGBA();
+            GrVertexColor color(rrect.fColor, fWideColor);
             SkScalar outerRadius = rrect.fOuterRadius;
             const SkRect& bounds = rrect.fDevBounds;
 
@@ -2431,33 +2392,25 @@
                                            ? rrect.fInnerRadius / rrect.fOuterRadius
                                            : -1.0f / rrect.fOuterRadius;
             for (int i = 0; i < 4; ++i) {
-                verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
-                verts->fColor = color;
-                verts->fOffset = SkPoint::Make(-1, yOuterRadii[i]);
-                verts->fOuterRadius = outerRadius;
-                verts->fInnerRadius = innerRadius;
-                verts++;
+                verts.write(bounds.fLeft, yCoords[i],
+                            color,
+                            -1.0f, yOuterRadii[i],
+                            outerRadius, innerRadius);
 
-                verts->fPos = SkPoint::Make(bounds.fLeft + outerRadius, yCoords[i]);
-                verts->fColor = color;
-                verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
-                verts->fOuterRadius = outerRadius;
-                verts->fInnerRadius = innerRadius;
-                verts++;
+                verts.write(bounds.fLeft + outerRadius, yCoords[i],
+                            color,
+                            0.0f, yOuterRadii[i],
+                            outerRadius, innerRadius);
 
-                verts->fPos = SkPoint::Make(bounds.fRight - outerRadius, yCoords[i]);
-                verts->fColor = color;
-                verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
-                verts->fOuterRadius = outerRadius;
-                verts->fInnerRadius = innerRadius;
-                verts++;
+                verts.write(bounds.fRight - outerRadius, yCoords[i],
+                            color,
+                            0.0f, yOuterRadii[i],
+                            outerRadius, innerRadius);
 
-                verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
-                verts->fColor = color;
-                verts->fOffset = SkPoint::Make(1, yOuterRadii[i]);
-                verts->fOuterRadius = outerRadius;
-                verts->fInnerRadius = innerRadius;
-                verts++;
+                verts.write(bounds.fRight, yCoords[i],
+                            color,
+                            1.0f, yOuterRadii[i],
+                            outerRadius, innerRadius);
             }
             // Add the additional vertices for overstroked rrects.
             // Effectively this is an additional stroked rrect, with its
@@ -2475,7 +2428,7 @@
                 // geometry to the outer edge
                 SkScalar maxOffset = -rrect.fInnerRadius / overstrokeOuterRadius;
 
-                FillInOverstrokeVerts(&verts, bounds, outerRadius, overstrokeOuterRadius, maxOffset,
+                FillInOverstrokeVerts(verts, bounds, outerRadius, overstrokeOuterRadius, maxOffset,
                                       overstrokeOuterRadius, 0.0f, color);
             }
 
@@ -2517,6 +2470,7 @@
         fVertCount += that->fVertCount;
         fIndexCount += that->fIndexCount;
         fAllFill = fAllFill && that->fAllFill;
+        fWideColor = fWideColor || that->fWideColor;
         return CombineResult::kMerged;
     }
 
@@ -2533,6 +2487,7 @@
     int fVertCount;
     int fIndexCount;
     bool fAllFill;
+    bool fWideColor;
     SkSTArray<1, RRect, true> fRRects;
 
     typedef GrMeshDrawOp INHERITED;
@@ -2638,6 +2593,7 @@
         this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
         // Expand the rect for aa in order to generate the correct vertices.
         bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+        fWideColor = !SkPMColor4fFitsInBytes(color);
         fRRects.emplace_back(
                 RRect{color, devXRadius, devYRadius, innerXRadius, innerYRadius, bounds});
     }
@@ -2682,32 +2638,32 @@
         }
 
         // Setup geometry processor
-        sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, false, localMatrix));
-
-        SkASSERT(sizeof(EllipseVertex) == gp->vertexStride());
+        sk_sp<GrGeometryProcessor> gp(new EllipseGeometryProcessor(fStroked, fWideColor,
+                                                                   localMatrix));
 
         // drop out the middle quad if we're stroked
         int indicesPerInstance = fStroked ? kIndicesPerStrokeRRect : kIndicesPerFillRRect;
         sk_sp<const GrBuffer> indexBuffer = get_rrect_index_buffer(
                 fStroked ? kStroke_RRectType : kFill_RRectType, target->resourceProvider());
 
-        PatternHelper helper(target, GrPrimitiveType::kTriangles, sizeof(EllipseVertex),
+        PatternHelper helper(target, GrPrimitiveType::kTriangles, gp->vertexStride(),
                              indexBuffer.get(), kVertsPerStandardRRect, indicesPerInstance,
                              fRRects.count());
-        EllipseVertex* verts = reinterpret_cast<EllipseVertex*>(helper.vertices());
-        if (!verts || !indexBuffer) {
+        GrVertexWriter verts{helper.vertices()};
+        if (!verts.fPtr || !indexBuffer) {
             SkDebugf("Could not allocate vertices\n");
             return;
         }
 
         for (const auto& rrect : fRRects) {
-            // TODO4F: Preserve float colors
-            GrColor color = rrect.fColor.toBytes_RGBA();
+            GrVertexColor color(rrect.fColor, fWideColor);
             // Compute the reciprocals of the radii here to save time in the shader
-            SkScalar xRadRecip = SkScalarInvert(rrect.fXRadius);
-            SkScalar yRadRecip = SkScalarInvert(rrect.fYRadius);
-            SkScalar xInnerRadRecip = SkScalarInvert(rrect.fInnerXRadius);
-            SkScalar yInnerRadRecip = SkScalarInvert(rrect.fInnerYRadius);
+            float reciprocalRadii[4] = {
+                SkScalarInvert(rrect.fXRadius),
+                SkScalarInvert(rrect.fYRadius),
+                SkScalarInvert(rrect.fInnerXRadius),
+                SkScalarInvert(rrect.fInnerYRadius)
+            };
 
             // Extend the radii out half a pixel to antialias.
             SkScalar xOuterRadius = rrect.fXRadius + SK_ScalarHalf;
@@ -2732,33 +2688,25 @@
                                          SK_ScalarNearlyZero, yMaxOffset};
 
             for (int i = 0; i < 4; ++i) {
-                verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
-                verts->fColor = color;
-                verts->fOffset = SkPoint::Make(xMaxOffset, yOuterOffsets[i]);
-                verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-                verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
-                verts++;
+                verts.write(bounds.fLeft, yCoords[i],
+                            color,
+                            xMaxOffset, yOuterOffsets[i],
+                            reciprocalRadii);
 
-                verts->fPos = SkPoint::Make(bounds.fLeft + xOuterRadius, yCoords[i]);
-                verts->fColor = color;
-                verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]);
-                verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-                verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
-                verts++;
+                verts.write(bounds.fLeft + xOuterRadius, yCoords[i],
+                            color,
+                            SK_ScalarNearlyZero, yOuterOffsets[i],
+                            reciprocalRadii);
 
-                verts->fPos = SkPoint::Make(bounds.fRight - xOuterRadius, yCoords[i]);
-                verts->fColor = color;
-                verts->fOffset = SkPoint::Make(SK_ScalarNearlyZero, yOuterOffsets[i]);
-                verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-                verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
-                verts++;
+                verts.write(bounds.fRight - xOuterRadius, yCoords[i],
+                            color,
+                            SK_ScalarNearlyZero, yOuterOffsets[i],
+                            reciprocalRadii);
 
-                verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
-                verts->fColor = color;
-                verts->fOffset = SkPoint::Make(xMaxOffset, yOuterOffsets[i]);
-                verts->fOuterRadii = SkPoint::Make(xRadRecip, yRadRecip);
-                verts->fInnerRadii = SkPoint::Make(xInnerRadRecip, yInnerRadRecip);
-                verts++;
+                verts.write(bounds.fRight, yCoords[i],
+                            color,
+                            xMaxOffset, yOuterOffsets[i],
+                            reciprocalRadii);
             }
         }
         auto pipe = fHelper.makePipeline(target);
@@ -2782,6 +2730,7 @@
         }
 
         fRRects.push_back_n(that->fRRects.count(), that->fRRects.begin());
+        fWideColor = fWideColor || that->fWideColor;
         return CombineResult::kMerged;
     }
 
@@ -2797,6 +2746,7 @@
     SkMatrix fViewMatrixIfUsingLocalCoords;
     Helper fHelper;
     bool fStroked;
+    bool fWideColor;
     SkSTArray<1, RRect, true> fRRects;
 
     typedef GrMeshDrawOp INHERITED;
@@ -3020,6 +2970,7 @@
         if (op) {
             return op;
         }
+        assert_alive(paint);
     } while (true);
 }
 
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp
index f893a91..8fa067f 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.cpp
+++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp
@@ -54,190 +54,181 @@
     *ydiff *= *invLengths;
 }
 
-static AI void outset_masked_vertices(const Sk4f& xdiff, const Sk4f& ydiff, const Sk4f& invLengths,
-                                      const Sk4f& mask, Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r,
-                                      int uvrCount) {
-    auto halfMask = 0.5f * mask;
-    auto maskCW = nextCW(halfMask);
-    *x += maskCW * -xdiff + halfMask * nextCW(xdiff);
-    *y += maskCW * -ydiff + halfMask * nextCW(ydiff);
+// outset and outsetCW are provided separately to allow for different magnitude outsets for
+// with-edge and "perpendicular" edge shifts. This is needed when one axis cannot be inset the full
+// half pixel without crossing over the other side.
+static AI void outset_masked_vertices(const Sk4f& outset, const Sk4f& outsetCW, const Sk4f& xdiff,
+                                      const Sk4f& ydiff, const Sk4f& invLengths, const Sk4f& mask,
+                                      Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount) {
+    // The mask is rotated compared to the outsets and edge vectors, since if the edge is "on"
+    // both its points need to be moved along their other edge vectors.
+    auto maskedOutset = -outset * nextCW(mask);
+    auto maskedOutsetCW = outsetCW * mask;
+    // x = x + outsetCW * mask * nextCW(xdiff) - outset * nextCW(mask) * xdiff
+    *x += fma(maskedOutsetCW, nextCW(xdiff), maskedOutset * xdiff);
+    *y += fma(maskedOutsetCW, nextCW(ydiff), maskedOutset * ydiff);
     if (uvrCount > 0) {
         // We want to extend the texture coords by the same proportion as the positions.
-        maskCW *= invLengths;
-        halfMask *= nextCW(invLengths);
+        maskedOutset *= invLengths;
+        maskedOutsetCW *= nextCW(invLengths);
         Sk4f udiff = nextCCW(*u) - *u;
         Sk4f vdiff = nextCCW(*v) - *v;
-        *u += maskCW * -udiff + halfMask * nextCW(udiff);
-        *v += maskCW * -vdiff + halfMask * nextCW(vdiff);
+        *u += fma(maskedOutsetCW, nextCW(udiff), maskedOutset * udiff);
+        *v += fma(maskedOutsetCW, nextCW(vdiff), maskedOutset * vdiff);
         if (uvrCount == 3) {
             Sk4f rdiff = nextCCW(*r) - *r;
-            *r += maskCW * -rdiff + halfMask * nextCW(rdiff);
+            *r += fma(maskedOutsetCW, nextCW(rdiff), maskedOutset * rdiff);
         }
     }
 }
 
-static AI void outset_vertices(const Sk4f& xdiff, const Sk4f& ydiff, const Sk4f& invLengths,
+static AI void outset_vertices(const Sk4f& outset, const Sk4f& outsetCW, const Sk4f& xdiff,
+                               const Sk4f& ydiff, const Sk4f& invLengths,
                                Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount) {
-    *x += 0.5f * (-xdiff + nextCW(xdiff));
-    *y += 0.5f * (-ydiff + nextCW(ydiff));
+    // x = x + outsetCW * nextCW(xdiff) - outset * xdiff (as above, but where mask = (1,1,1,1))
+    *x += fma(outsetCW, nextCW(xdiff), -outset * xdiff);
+    *y += fma(outsetCW, nextCW(ydiff), -outset * ydiff);
     if (uvrCount > 0) {
-        Sk4f t = 0.5f * invLengths;
+        Sk4f t = -outset * invLengths; // Bake minus sign in here
+        Sk4f tCW = outsetCW * nextCW(invLengths);
         Sk4f udiff = nextCCW(*u) - *u;
         Sk4f vdiff = nextCCW(*v) - *v;
-        *u += t * -udiff + nextCW(t) * nextCW(udiff);
-        *v += t * -vdiff + nextCW(t) * nextCW(vdiff);
+        *u += fma(tCW, nextCW(udiff), t * udiff);
+        *v += fma(tCW, nextCW(vdiff), t * vdiff);
         if (uvrCount == 3) {
             Sk4f rdiff = nextCCW(*r) - *r;
-            *r += t * -rdiff + nextCW(t) * nextCW(rdiff);
+            *r += fma(tCW, nextCW(rdiff), t * rdiff);
         }
     }
 }
 
-static AI void compute_edge_distances(const Sk4f& a, const Sk4f& b, const Sk4f& c, const Sk4f& x,
-                                      const Sk4f& y, const Sk4f& w, Sk4f edgeDistances[]) {
-    for (int i = 0; i < 4; ++i) {
-        edgeDistances[i] = a * x[i] + b * y[i] + c * w[i];
-    }
+// Updates outset in place to account for non-90 degree angles of the quad edges stored in
+// xdiff, ydiff (which are assumed to be normalized).
+static void adjust_non_rectilinear_outset(const Sk4f& xdiff, const Sk4f& ydiff, Sk4f* outset) {
+    // The distance the point needs to move is outset/sqrt(1-cos^2(theta)), where theta is the angle
+    // between the two edges at that point. cos(theta) is equal to dot(xydiff, nextCW(xydiff)),
+    Sk4f cosTheta = fma(xdiff, nextCW(xdiff), ydiff * nextCW(ydiff));
+    *outset *= (1.f - cosTheta * cosTheta).rsqrt();
+    // But clamp to make sure we don't expand by a giant amount if the sheer is really high
+    *outset = Sk4f::Max(-3.f, Sk4f::Min(*outset, 3.f));
 }
 
-static AI float get_max_coverage(const Sk4f& lengths) {
-    float minWidth = SkMinScalar(lengths[0], lengths[3]);
-    float minHeight = SkMinScalar(lengths[1], lengths[2]);
-    // Calculate approximate area of the quad, pinning dimensions to 1 in case the quad is larger
-    // than a pixel. Sub-pixel quads that are rotated may in fact have a different true maximum
-    // coverage than this calculation, but this will be close and is stable.
-    return SkMinScalar(minWidth, 1.f) * SkMinScalar(minHeight, 1.f);
-}
-
-// This computes the four edge equations for a quad, then outsets them and optionally computes a new
-// quad as the intersection points of the outset edges. 'x' and 'y' contain the original points as
-// input and the outset points as output. In order to be used as a component of perspective edge
-// distance calculation, this exports edge equations in 'a', 'b', and 'c'. Use
-// compute_edge_distances to turn these equations into the distances needed by the shader. The
-// values in x, y, u, v, and r are possibly updated if outsetting is needed. r is the local
-// position's w component if it exists.
-//
-// Returns maximum coverage allowed for any given pixel.
-static float compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y,
-        Sk4f* a, Sk4f* b, Sk4f* c, Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount, bool outset) {
-    SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3);
+// Computes the vertices for the two nested quads used to create AA edges. The original single quad
+// should be duplicated as input in x1 and x2, y1 and y2, and possibly u1|u2, v1|v2, [r1|r2]
+// (controlled by uvrChannelCount).  While the values should be duplicated, they should be separate
+// pointers. The outset quad is written in-place back to x1, y1, etc. and the inset inner quad is
+// written to x2, y2, etc.
+static float compute_nested_quad_vertices(GrQuadAAFlags aaFlags, Sk4f* x1, Sk4f* y1,
+        Sk4f* u1, Sk4f* v1, Sk4f* r1, Sk4f* x2, Sk4f* y2, Sk4f* u2, Sk4f* v2, Sk4f* r2,
+        int uvrCount, bool rectilinear) {
+    SkASSERT(uvrCount == 0 || uvrCount == 2 || uvrCount == 3);
 
     // Compute edge vectors for the quad.
-    auto xnext = nextCCW(*x);
-    auto ynext = nextCCW(*y);
+    auto xnext = nextCCW(*x1);
+    auto ynext = nextCCW(*y1);
     // xdiff and ydiff will comprise the normalized vectors pointing along each quad edge.
     Sk4f xdiff, ydiff, invLengths;
-    compute_edge_vectors(*x, *y, xnext, ynext, &xdiff, &ydiff, &invLengths);
+    compute_edge_vectors(*x1, *y1, xnext, ynext, &xdiff, &ydiff, &invLengths);
 
-    // Use above vectors to compute edge equations (importantly before we outset positions).
-    *c = fma(xnext, *y,  -ynext * *x) * invLengths;
-    // Make sure the edge equations have their normals facing into the quad in device space.
-    auto test = fma(ydiff, nextCW(*x), fma(-xdiff, nextCW(*y), *c));
-    if ((test < Sk4f(0)).anyTrue()) {
-        *a = -ydiff;
-        *b = xdiff;
-        *c = -*c;
-    } else {
-        *a = ydiff;
-        *b = -xdiff;
-    }
-    // Outset the edge equations so aa coverage evaluates to zero half a pixel away from the
-    // original quad edge.
-    *c += 0.5f;
-
-    if (aaFlags != GrQuadAAFlags::kAll) {
-        // This order is the same order the edges appear in xdiff/ydiff and therefore as the
-        // edges in a/b/c.
-        Sk4f mask = compute_edge_mask(aaFlags);
-
-        // Outset edge equations for masked out edges another pixel so that they always evaluate
-        // >= 1.
-        *c += (1.f - mask);
-        if (outset) {
-            outset_masked_vertices(xdiff, ydiff, invLengths, mask, x, y, u, v, r, uvrChannelCount);
-        }
-    } else if (outset) {
-        outset_vertices(xdiff, ydiff, invLengths, x, y, u, v, r, uvrChannelCount);
+    // When outsetting, we want the new edge to be .5px away from the old line, which means the
+    // corners may need to be adjusted by more than .5px if the matrix had sheer.
+    Sk4f outset = 0.5f;
+    if (!rectilinear) {
+        adjust_non_rectilinear_outset(xdiff, ydiff, &outset);
     }
 
-    return get_max_coverage(invLengths.invert());
-}
-
-// A specialization of the above function that can compute edge distances very quickly when it knows
-// that the edges intersect at right angles, i.e. any transform other than skew and perspective
-// (GrQuadType::kRectilinear). Unlike the above function, this always outsets the corners since it
-// cannot be reused in the perspective case.
-static float compute_rectilinear_dists_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x,
-        Sk4f* y,  Sk4f edgeDistances[4], Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount) {
-    SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3);
-    // xdiff and ydiff will comprise the normalized vectors pointing along each quad edge.
-    Sk4f xdiff, ydiff, invLengths;
-    compute_edge_vectors(*x, *y, nextCCW(*x), nextCCW(*y), &xdiff, &ydiff, &invLengths);
+    // When insetting, cap the inset amount to be half of the edge length, except that each edge
+    // has to remain parallel, so we separately limit LR and TB to half of the smallest of the
+    // opposing edges.
     Sk4f lengths = invLengths.invert();
+    Sk2f sides(SkMinScalar(lengths[0], lengths[3]), SkMinScalar(lengths[1], lengths[2]));
+    Sk4f edgeLimits = 0.5f * SkNx_shuffle<0, 1, 1, 0>(sides);
 
-    // Since the quad is rectilinear, the edge distances are predictable and independent of the
-    // actual orientation of the quad. The lengths vector stores |p1-p0|, |p3-p1|, |p0-p2|, |p2-p3|,
-    // matching the CCW order. For instance, edge distances for p0 are 0 for e0 and e2 since they
-    // intersect at p0. Distance to e1 is the same as p0 to p1. Distance to e3 is p0 to p2 since
-    // e3 goes through p2 and since the quad is rectilinear, we know that's the shortest distance.
-    edgeDistances[0] = Sk4f(0.f, lengths[0], 0.f, lengths[2]);
-    edgeDistances[1] = Sk4f(0.f, 0.f, lengths[0], lengths[1]);
-    edgeDistances[2] = Sk4f(lengths[2], lengths[3], 0.f, 0.f);
-    edgeDistances[3] = Sk4f(lengths[1], 0.f, lengths[3], 0.f);
+    if ((edgeLimits < 0.5f).anyTrue()) {
+        // Dealing with a subpixel rectangle, so must calculate clamped insets and padded outsets.
+        // The outsets are padded to ensure that the quad spans 2 pixels for improved interpolation.
+        Sk4f inset = -Sk4f::Min(outset, edgeLimits);
+        Sk4f insetCW = -Sk4f::Min(outset, nextCW(edgeLimits));
 
-    if (aaFlags != GrQuadAAFlags::kAll) {
-        // This order is the same order the edges appear in xdiff/ydiff and therefore as the
-        // edges in a/b/c.
-        Sk4f mask = compute_edge_mask(aaFlags);
+        // The parallel distance shift caused by outset is currently 0.5, but need to scale it up to
+        // 0.5*(2 - side) so that (side + 2*shift) = 2px. Thus scale outsets for thin edges by
+        // (2 - side) since it already has the 1/2.
+        Sk4f outsetScale = 2.f - 2.f * Sk4f::Min(edgeLimits, 0.5f); // == 1 for non-thin edges
+        Sk4f outsetCW = outset * nextCW(outsetScale);
+        outset *= outsetScale;
 
-        // Update opposite corner distances by 1 (when enabled by the mask). The distance
-        // calculations used in compute_quad_edges_... calculates the edge equations from original
-        // positions and then shifts the coefficient by 0.5. If the opposite edges are also outset
-        // then must add an additional 0.5 to account for its shift away from that edge.
-        Sk4f maskWithOpposites = mask + SkNx_shuffle<3, 2, 1, 0>(mask);
-        edgeDistances[0] += Sk4f(0.f, 0.5f, 0.f, 0.5f) * maskWithOpposites;
-        edgeDistances[1] += Sk4f(0.f, 0.f, 0.5f, 0.5f) * maskWithOpposites;
-        edgeDistances[2] += Sk4f(0.5f, 0.5f, 0.f, 0.f) * maskWithOpposites;
-        edgeDistances[3] += Sk4f(0.5f, 0.f, 0.5f, 0.f) * maskWithOpposites;
-
-        // Outset edge equations for masked out edges another pixel so that they always evaluate
-        // So add 1-mask to each point's edge distances vector so that coverage >= 1 on non-aa
-        for (int i = 0; i < 4; ++i) {
-            edgeDistances[i] += (1.f - mask);
+        if (aaFlags != GrQuadAAFlags::kAll) {
+            Sk4f mask = compute_edge_mask(aaFlags);
+            outset_masked_vertices(outset, outsetCW, xdiff, ydiff, invLengths, mask, x1, y1,
+                                   u1, v1, r1, uvrCount);
+            outset_masked_vertices(inset, insetCW, xdiff, ydiff, invLengths, mask, x2, y2,
+                                   u2, v2, r2, uvrCount);
+        } else {
+            outset_vertices(outset, outsetCW, xdiff, ydiff, invLengths, x1, y1, u1, v1, r1, uvrCount);
+            outset_vertices(inset, insetCW, xdiff, ydiff, invLengths, x2, y2, u2, v2, r2, uvrCount);
         }
-        outset_masked_vertices(xdiff, ydiff, invLengths, mask, x, y, u, v, r, uvrChannelCount);
     } else {
-        // Update opposite corner distances by 0.5 pixel and 0.5 edge shift, skipping the need for
-        // mask since that's 1s
-        edgeDistances[0] += Sk4f(0.f, 1.f, 0.f, 1.f);
-        edgeDistances[1] += Sk4f(0.f, 0.f, 1.f, 1.f);
-        edgeDistances[2] += Sk4f(1.f, 1.f, 0.f, 0.f);
-        edgeDistances[3] += Sk4f(1.f, 0.f, 1.f, 0.f);
-
-        outset_vertices(xdiff, ydiff, invLengths, x, y, u, v, r, uvrChannelCount);
+        // Since it's not subpixel, the inset is just the opposite of the outset and there's no
+        // difference between CCW and CW behavior.
+        Sk4f inset = -outset;
+        if (aaFlags != GrQuadAAFlags::kAll) {
+            Sk4f mask = compute_edge_mask(aaFlags);
+            outset_masked_vertices(outset, outset, xdiff, ydiff, invLengths, mask, x1, y1,
+                                   u1, v1, r1, uvrCount);
+            outset_masked_vertices(inset, inset, xdiff, ydiff, invLengths, mask, x2, y2,
+                                   u2, v2, r2, uvrCount);
+        } else {
+            outset_vertices(outset, outset, xdiff, ydiff, invLengths, x1, y1, u1, v1, r1, uvrCount);
+            outset_vertices(inset, inset, xdiff, ydiff, invLengths, x2, y2, u2, v2, r2, uvrCount);
+        }
     }
 
-    return get_max_coverage(lengths);
+    // An approximation of the pixel area covered by the quad
+    sides = Sk2f::Min(1.f, sides);
+    return sides[0] * sides[1];
 }
 
-// Generalizes compute_quad_edge_distances_and_outset_vertices to extrapolate local coords such that
+// For each device space corner, devP, label its left/right or top/bottom opposite device space
+// point opDevPt. The new device space point is opDevPt + s (devPt - opDevPt) where s is
+// (length(devPt - opDevPt) + outset) / length(devPt - opDevPt); This returns the interpolant s,
+// adjusted for any subpixel corrections. If subpixel, it also updates the max coverage.
+static Sk4f get_projected_interpolant(const Sk4f& len, const Sk4f& outsets, float* maxCoverage) {
+    if ((len < 1.f).anyTrue()) {
+        *maxCoverage *= len.min();
+
+        // When insetting, the amount is clamped to be half the minimum edge length to prevent
+        // overlap. When outsetting, the amount is padded to cover 2 pixels.
+        if ((outsets < 0.f).anyTrue()) {
+            return (len - 0.5f * len.min()) / len;
+        } else {
+            return (len + outsets * (2.f - len.min())) / len;
+        }
+    } else {
+        return (len + outsets) / len;
+    }
+}
+
+// Generalizes compute_nested_quad_vertices to extrapolate local coords such that
 // after perspective division of the device coordinate, the original local coordinate value is at
-// the original un-outset device position. r is the local coordinate's w component.
-static float compute_quad_dists_and_outset_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x,
-        Sk4f* y, Sk4f* w, Sk4f edgeDistances[4], Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount) {
-    SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3);
+// the original un-outset device position. r is the local coordinate's w component. However, since
+// the projected edges will be different for inner and outer quads, there isn't much reuse between
+// the calculations, so it's easier to just have this operate on one quad a time.
+static float compute_quad_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y,
+        Sk4f* w, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount, bool inset) {
+    SkASSERT(uvrCount == 0 || uvrCount == 2 || uvrCount == 3);
 
     auto iw = (*w).invert();
     auto x2d = (*x) * iw;
     auto y2d = (*y) * iw;
-    Sk4f a, b, c;
-    // Don't compute outset corners in the normalized space, which means u, v, and r don't need
-    // to be provided here (outset separately below). Since this is computing distances for a
-    // projected quad, there is a very good chance it's not rectilinear so use the general 2D path.
-    float maxProjectedCoverage = compute_quad_edges_and_outset_vertices(aaFlags, &x2d, &y2d,
-            &a, &b, &c, nullptr, nullptr, nullptr, /* uvr ct */ 0, /* outsetCorners */ false);
 
-    static const float kOutset = 0.5f;
+    // Must compute non-rectilinear outset quantity using the projected 2d edge vectors
+    Sk4f xdiff, ydiff, invLengths;
+    compute_edge_vectors(x2d, y2d, nextCCW(x2d), nextCCW(y2d), &xdiff, &ydiff, &invLengths);
+    Sk4f outset = inset ? -0.5f : 0.5f;
+    adjust_non_rectilinear_outset(xdiff, ydiff, &outset);
+
+    float maxProjectedCoverage = 1.f;
+
     if ((GrQuadAAFlags::kLeft | GrQuadAAFlags::kRight) & aaFlags) {
         // For each entry in x the equivalent entry in opX is the left/right opposite and so on.
         Sk4f opX = SkNx_shuffle<2, 3, 0, 1>(*x);
@@ -246,13 +237,11 @@
         // vx/vy holds the device space left-to-right vectors along top and bottom of the quad.
         Sk2f vx = SkNx_shuffle<2, 3>(x2d) - SkNx_shuffle<0, 1>(x2d);
         Sk2f vy = SkNx_shuffle<2, 3>(y2d) - SkNx_shuffle<0, 1>(y2d);
-        Sk2f len = SkNx_fma(vx, vx, vy * vy).sqrt();
-        // For each device space corner, devP, label its left/right opposite device space point
-        // opDevPt. The new device space point is opDevPt + s (devPt - opDevPt) where s is
-        // (length(devPt - opDevPt) + 0.5) / length(devPt - opDevPt);
-        Sk4f s = SkNx_shuffle<0, 1, 0, 1>((len + kOutset) / len);
+        Sk4f len = SkNx_shuffle<0, 1, 0, 1>(SkNx_fma(vx, vx, vy * vy).sqrt());
+
         // Compute t in homogeneous space from s using similar triangles so that we can produce
         // homogeneous outset vertices for perspective-correct interpolation.
+        Sk4f s = get_projected_interpolant(len, outset, &maxProjectedCoverage);
         Sk4f sOpW = s * opW;
         Sk4f t = sOpW / (sOpW + (1.f - s) * (*w));
         // mask is used to make the t values be 1 when the left/right side is not antialiased.
@@ -265,12 +254,12 @@
         *y = opY + t * (*y - opY);
         *w = opW + t * (*w - opW);
 
-        if (uvrChannelCount > 0) {
+        if (uvrCount > 0) {
             Sk4f opU = SkNx_shuffle<2, 3, 0, 1>(*u);
             Sk4f opV = SkNx_shuffle<2, 3, 0, 1>(*v);
             *u = opU + t * (*u - opU);
             *v = opV + t * (*v - opV);
-            if (uvrChannelCount == 3) {
+            if (uvrCount == 3) {
                 Sk4f opR = SkNx_shuffle<2, 3, 0, 1>(*r);
                 *r = opR + t * (*r - opR);
             }
@@ -292,10 +281,9 @@
 
         Sk2f vx = SkNx_shuffle<1, 3>(x2d) - SkNx_shuffle<0, 2>(x2d);
         Sk2f vy = SkNx_shuffle<1, 3>(y2d) - SkNx_shuffle<0, 2>(y2d);
-        Sk2f len = SkNx_fma(vx, vx, vy * vy).sqrt();
+        Sk4f len = SkNx_shuffle<0, 0, 1, 1>(SkNx_fma(vx, vx, vy * vy).sqrt());
 
-        Sk4f s = SkNx_shuffle<0, 0, 1, 1>((len + kOutset) / len);
-
+        Sk4f s = get_projected_interpolant(len, outset, &maxProjectedCoverage);
         Sk4f sOpW = s * opW;
         Sk4f t = sOpW / (sOpW + (1.f - s) * (*w));
 
@@ -308,58 +296,101 @@
         *y = opY + t * (*y - opY);
         *w = opW + t * (*w - opW);
 
-        if (uvrChannelCount > 0) {
+        if (uvrCount > 0) {
             Sk4f opU = SkNx_shuffle<1, 0, 3, 2>(*u);
             Sk4f opV = SkNx_shuffle<1, 0, 3, 2>(*v);
             *u = opU + t * (*u - opU);
             *v = opV + t * (*v - opV);
-            if (uvrChannelCount == 3) {
+            if (uvrCount == 3) {
                 Sk4f opR = SkNx_shuffle<1, 0, 3, 2>(*r);
                 *r = opR + t * (*r - opR);
             }
         }
     }
 
-    // Use the original edge equations with the outset homogeneous coordinates to get the edge
-    // distance (technically multiplied by w, so that the fragment shader can do perspective
-    // interpolation when it multiplies by 1/w later).
-    compute_edge_distances(a, b, c, *x, *y, *w, edgeDistances);
-
     return maxProjectedCoverage;
 }
 
-// Calculate safe edge distances for non-aa quads that have been batched with aa quads. Since the
-// fragment shader multiples by 1/w, so the edge distance cannot just be set to 1. It cannot just
-// be set to w either due to interpolation across the triangle. If iA, iB, and iC are the
-// barycentric weights of the triangle, and we set the edge distance to w, the fragment shader
-// actually sees d = (iA*wA + iB*wB + iC*wC) * (iA/wA + iB/wB + iC/wC). Without perspective this
-// simplifies to 1 as necessary, but we must choose something other than w when there is perspective
-// to ensure that d >= 1 and the edge shows as non-aa.
-static float compute_nonaa_edge_distances(const Sk4f& w, bool hasPersp, Sk4f edgeDistances[4]) {
-    // Let n = min(w1,w2,w3,w4) and m = max(w1,w2,w3,w4) and rewrite
-    //   d = (iA*wA + iB*wB + iC*wC) * (iA*wB*wC + iB*wA*wC + iC*wA*wB) / (wA*wB*wC)
-    //       |   e=attr from VS    |   |         fragCoord.w = 1/w                 |
-    // Since the weights are the interior of the primitive then we have:
-    //   n <= (iA*wA + iB*wB + iC*wC) <= m and
-    //   n^2 <= (iA*wB*wC + iB*wA*wC + iC*wA*wB) <= m^2 and
-    //   n^3 <= wA*wB*wC <= m^3 regardless of the choice of A, B, and C verts in the quad
-    // Thus if we set e = m^3/n^3, it guarantees d >= 1 for any perspective.
-    float e;
-    if (hasPersp) {
-        float m = w.max();
-        float n = w.min();
-        e = (m * m * m) / (n * n * n);
+enum class CoverageMode {
+    kNone,
+    kWithPosition,
+    kWithColor
+};
+
+static CoverageMode get_mode_for_spec(const GrQuadPerEdgeAA::VertexSpec& spec) {
+    if (spec.usesCoverageAA()) {
+        if (spec.compatibleWithAlphaAsCoverage() && spec.hasVertexColors()) {
+            return CoverageMode::kWithColor;
+        } else {
+            return CoverageMode::kWithPosition;
+        }
     } else {
-        e = 1.f;
+        return CoverageMode::kNone;
     }
+}
 
-    // All edge distances set to the same
+// Writes four vertices in triangle strip order, including the additional data for local
+// coordinates, domain, color, and coverage as needed to satisfy the vertex spec.
+static void write_quad(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
+                       CoverageMode mode, float coverage,
+                       SkPMColor4f color4f, bool wideColor,
+                       const SkRect& domain,
+                       const Sk4f& x, const Sk4f& y, const Sk4f& w,
+                       const Sk4f& u, const Sk4f& v, const Sk4f& r) {
+    static constexpr auto If = GrVertexWriter::If<float>;
+
+    if (mode == CoverageMode::kWithColor) {
+        // Multiply the color by the coverage up front
+        SkASSERT(spec.hasVertexColors());
+        color4f = color4f * coverage;
+    }
+    GrVertexColor color(color4f, wideColor);
+
     for (int i = 0; i < 4; ++i) {
-        edgeDistances[i] = e;
-    }
+        // save position, this is a float2 or float3 or float4 depending on the combination of
+        // perspective and coverage mode.
+        vb->write(x[i], y[i], If(spec.deviceQuadType() == GrQuadType::kPerspective, w[i]),
+                  If(mode == CoverageMode::kWithPosition, coverage));
 
-    // Non-aa, so always use full coverage
-    return 1.f;
+        // save color
+        if (spec.hasVertexColors()) {
+            vb->write(color);
+        }
+
+        // save local position
+        if (spec.hasLocalCoords()) {
+            vb->write(u[i], v[i], If(spec.localQuadType() == GrQuadType::kPerspective, r[i]));
+        }
+
+        // save the domain
+        if (spec.hasDomain()) {
+            vb->write(domain);
+        }
+    }
+}
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
+
+static const int kVertsPerAAFillRect = 8;
+static const int kIndicesPerAAFillRect = 30;
+
+static sk_sp<const GrBuffer> get_index_buffer(GrResourceProvider* resourceProvider) {
+    GR_DEFINE_STATIC_UNIQUE_KEY(gAAFillRectIndexBufferKey);
+
+    // clang-format off
+    static const uint16_t gFillAARectIdx[] = {
+        0, 1, 2, 1, 3, 2,
+        0, 4, 1, 4, 5, 1,
+        0, 6, 4, 0, 2, 6,
+        2, 3, 6, 3, 7, 6,
+        1, 5, 3, 3, 5, 7,
+    };
+    // clang-format on
+
+    GR_STATIC_ASSERT(SK_ARRAY_COUNT(gFillAARectIdx) == kIndicesPerAAFillRect);
+    return resourceProvider->findOrCreatePatternedIndexBuffer(
+            gFillAARectIdx, kIndicesPerAAFillRect, GrQuadPerEdgeAA::kNumAAQuadsInIndexBuffer,
+            kVertsPerAAFillRect, gAAFillRectIndexBufferKey);
 }
 
 } // anonymous namespace
@@ -371,86 +402,99 @@
 void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad,
                  const SkPMColor4f& color4f, const GrPerspQuad& localQuad, const SkRect& domain,
                  GrQuadAAFlags aaFlags) {
-    bool deviceHasPerspective = spec.deviceQuadType() == GrQuadType::kPerspective;
-    bool localHasPerspective = spec.localQuadType() == GrQuadType::kPerspective;
-    GrVertexColor color(color4f, GrQuadPerEdgeAA::ColorType::kHalf == spec.colorType());
+    bool wideColor = GrQuadPerEdgeAA::ColorType::kHalf == spec.colorType();
+    CoverageMode mode = get_mode_for_spec(spec);
 
     // Load position data into Sk4fs (always x, y, and load w to avoid branching down the road)
-    Sk4f x = deviceQuad.x4f();
-    Sk4f y = deviceQuad.y4f();
-    Sk4f w = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
+    Sk4f oX = deviceQuad.x4f();
+    Sk4f oY = deviceQuad.y4f();
+    Sk4f oW = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
 
     // Load local position data into Sk4fs (either none, just u,v or all three)
-    Sk4f u, v, r;
+    Sk4f oU, oV, oR;
     if (spec.hasLocalCoords()) {
-        u = localQuad.x4f();
-        v = localQuad.y4f();
-
-        if (localHasPerspective) {
-            r = localQuad.w4f();
-        }
+        oU = localQuad.x4f();
+        oV = localQuad.y4f();
+        oR = localQuad.w4f(); // Will be ignored if the local quad type isn't perspective
     }
 
-    // Index into array refers to vertex. Index into particular Sk4f refers to edge.
-    Sk4f edgeDistances[4];
-    float maxCoverage = 1.f;
-    if (spec.usesCoverageAA()) {
-        // Must calculate edges and possibly outside the positions
-        if (aaFlags == GrQuadAAFlags::kNone) {
-            // A non-AA quad that got batched into an AA group, so it should have full coverage
-            maxCoverage = compute_nonaa_edge_distances(w, deviceHasPerspective, edgeDistances);
-        } else if (deviceHasPerspective) {
-            // For simplicity, pointers to u, v, and r are always provided, but the local dim param
-            // ensures that only loaded Sk4fs are modified in the compute functions.
-            maxCoverage = compute_quad_dists_and_outset_persp_vertices(aaFlags, &x, &y, &w,
-                    edgeDistances, &u, &v, &r, spec.localDimensionality());
-        } else if (spec.deviceQuadType() <= GrQuadType::kRectilinear) {
-            maxCoverage = compute_rectilinear_dists_and_outset_vertices(aaFlags, &x, &y,
-                    edgeDistances, &u, &v, &r, spec.localDimensionality());
-        } else {
-            Sk4f a, b, c;
-            maxCoverage = compute_quad_edges_and_outset_vertices(aaFlags, &x, &y, &a, &b, &c,
-                    &u, &v, &r, spec.localDimensionality(), /*outset*/ true);
-            compute_edge_distances(a, b, c, x, y, w, edgeDistances); // w holds 1.f as desired
-        }
-    }
-
-    // Now rearrange the Sk4fs into the interleaved vertex layout:
-    //  i.e. x1x2x3x4 y1y2y3y4 -> x1y1 x2y2 x3y3 x4y
     GrVertexWriter vb{vertices};
-    for (int i = 0; i < 4; ++i) {
-        // save position, always send a vec4 because we embed max coverage in the last component.
-        // For 2D quads, we know w holds the correct 1.f, so just write it out without branching
-        vb.write(x[i], y[i], w[i], maxCoverage);
+    if (spec.usesCoverageAA()) {
+        SkASSERT(mode == CoverageMode::kWithPosition || mode == CoverageMode::kWithColor);
 
-        // save color
-        if (spec.hasVertexColors()) {
-            vb.write(color);
-        }
+        // Must calculate two new quads, an outset and inset by .5 in projected device space, so
+        // duplicate the original quad into new Sk4fs for the inset.
+        Sk4f iX = oX, iY = oY, iW = oW;
+        Sk4f iU = oU, iV = oV, iR = oR;
 
-        // save local position
-        if (spec.hasLocalCoords()) {
-            if (localHasPerspective) {
-                vb.write<SkPoint3>({u[i], v[i], r[i]});
+        float maxCoverage = 1.f;
+        if (aaFlags != GrQuadAAFlags::kNone) {
+            if (spec.deviceQuadType() == GrQuadType::kPerspective) {
+                // Outset and inset the quads independently because perspective makes each shift
+                // unique. Since iX copied pre-outset oX, this will compute the proper inset too.
+                compute_quad_persp_vertices(aaFlags, &oX, &oY, &oW, &oU, &oV, &oW,
+                                            spec.localDimensionality(), /* inset */ false);
+                // Save coverage limit when computing inset quad
+                maxCoverage = compute_quad_persp_vertices(aaFlags, &iX, &iY, &iW, &iU, &iV, &iW,
+                                                          spec.localDimensionality(), true);
             } else {
-                vb.write<SkPoint>({u[i], v[i]});
+                // In the 2D case, insetting and outsetting can reuse the edge vectors, so the
+                // nested quads are computed together
+                maxCoverage = compute_nested_quad_vertices(aaFlags, &oX, &oY, &oU, &oV, &oR,
+                        &iX, &iY, &iU, &iV, &iR, spec.localDimensionality(),
+                        spec.deviceQuadType() <= GrQuadType::kRectilinear);
             }
-        }
+            // NOTE: could provide an even more optimized tessellation function for axis-aligned
+            // rects since the positions can be outset by constants without doing vector math,
+            // except it must handle identifying the winding of the quad vertices if the transform
+            // applied a mirror, etc. The current 2D case is already adequately fast.
+        } // else don't adjust any positions, let the outer quad form degenerate triangles
 
-        // save the domain
-        if (spec.hasDomain()) {
-            vb.write(domain);
-        }
-
-        // save the edges
-        if (spec.usesCoverageAA()) {
-            vb.write(edgeDistances[i]);
-        }
+        // Write two quads for inner and outer, inner will use the
+        write_quad(&vb, spec, mode, maxCoverage, color4f, wideColor, domain,
+                   iX, iY, iW, iU, iV, iR);
+        write_quad(&vb, spec, mode, 0.f, color4f, wideColor, domain, oX, oY, oW, oU, oV, oR);
+    } else {
+        // No outsetting needed, just write a single quad with full coverage
+        SkASSERT(mode == CoverageMode::kNone);
+        write_quad(&vb, spec, mode, 1.f, color4f, wideColor, domain, oX, oY, oW, oU, oV, oR);
     }
 
     return vb.fPtr;
 }
 
+bool ConfigureMeshIndices(GrMeshDrawOp::Target* target, GrMesh* mesh, const VertexSpec& spec,
+                          int quadCount) {
+    if (spec.usesCoverageAA()) {
+        // AA quads use 8 vertices, basically nested rectangles
+        sk_sp<const GrBuffer> ibuffer = get_index_buffer(target->resourceProvider());
+        if (!ibuffer) {
+            return false;
+        }
+
+        mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
+        mesh->setIndexedPatterned(ibuffer.get(), kIndicesPerAAFillRect, kVertsPerAAFillRect,
+                quadCount, kNumAAQuadsInIndexBuffer);
+    } else {
+        // Non-AA quads use 4 vertices, and regular triangle strip layout
+        if (quadCount > 1) {
+            sk_sp<const GrBuffer> ibuffer = target->resourceProvider()->refQuadIndexBuffer();
+            if (!ibuffer) {
+                return false;
+            }
+
+            mesh->setPrimitiveType(GrPrimitiveType::kTriangles);
+            mesh->setIndexedPatterned(ibuffer.get(), 6, 4, quadCount,
+                                      GrResourceProvider::QuadCountOfQuadBuffer());
+        } else {
+            mesh->setPrimitiveType(GrPrimitiveType::kTriangleStrip);
+            mesh->setNonIndexedNonInstanced(4);
+        }
+    }
+
+    return true;
+}
+
 ////////////////// VertexSpec Implementation
 
 int VertexSpec::deviceDimensionality() const {
@@ -483,19 +527,21 @@
     const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
 
     void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
-        // aa, domain, texturing are single bit flags
-        uint32_t x = fAAEdgeDistances.isInitialized() ? 0 : 1;
-        x |= fDomain.isInitialized() ? 0 : 2;
-        x |= fSampler.isInitialized() ? 0 : 4;
-        // regular position has two options as well
-        x |= fNeedsPerspective ? 0 : 8;
+        // domain, texturing, device-dimensions are single bit flags
+        uint32_t x = fDomain.isInitialized() ? 0 : 1;
+        x |= fSampler.isInitialized() ? 0 : 2;
+        x |= fNeedsPerspective ? 0 : 4;
         // local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d
         if (fLocalCoord.isInitialized()) {
-            x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 16 : 32;
+            x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 8 : 16;
         }
         // similar for colors, 00 for none, 01 for bytes, 10 for half-floats
-        if (this->fColor.isInitialized()) {
-            x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 64 : 128;
+        if (fColor.isInitialized()) {
+            x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 32 : 64;
+        }
+        // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor
+        if (fCoverageMode != CoverageMode::kNone) {
+            x |= CoverageMode::kWithPosition == fCoverageMode ? 128 : 256;
         }
 
         b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
@@ -524,17 +570,24 @@
 
                 args.fVaryingHandler->emitAttributes(gp);
 
-                // Extract effective position out of vec4 as a local variable in the vertex shader
-                if (gp.fNeedsPerspective) {
-                    args.fVertBuilder->codeAppendf("float3 position = %s.xyz;",
-                                                   gp.fPositionWithCoverage.name());
+                if (gp.fCoverageMode == CoverageMode::kWithPosition) {
+                    // Strip last channel from the vertex attribute to remove coverage and get the
+                    // actual position
+                    if (gp.fNeedsPerspective) {
+                        args.fVertBuilder->codeAppendf("float3 position = %s.xyz;",
+                                                       gp.fPosition.name());
+                    } else {
+                        args.fVertBuilder->codeAppendf("float2 position = %s.xy;",
+                                                       gp.fPosition.name());
+                    }
+                    gpArgs->fPositionVar = {"position",
+                                            gp.fNeedsPerspective ? kFloat3_GrSLType
+                                                                 : kFloat2_GrSLType,
+                                            GrShaderVar::kNone_TypeModifier};
                 } else {
-                    args.fVertBuilder->codeAppendf("float2 position = %s.xy;",
-                                                   gp.fPositionWithCoverage.name());
+                    // No coverage to eliminate
+                    gpArgs->fPositionVar = gp.fPosition.asShaderVar();
                 }
-                gpArgs->fPositionVar = {"position",
-                                        gp.fNeedsPerspective ? kFloat3_GrSLType : kFloat2_GrSLType,
-                                        GrShaderVar::kNone_TypeModifier};
 
                 // Handle local coordinates if they exist
                 if (gp.fLocalCoord.isInitialized()) {
@@ -550,8 +603,13 @@
 
                 // Solid color before any texturing gets modulated in
                 if (gp.fColor.isInitialized()) {
+                    // The color cannot be flat if the varying coverage has been modulated into it
                     args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor,
-                                                                  Interpolation::kCanBeFlat);
+                            gp.fCoverageMode == CoverageMode::kWithColor ?
+                            Interpolation::kInterpolated : Interpolation::kCanBeFlat);
+                } else {
+                    // Output color must be initialized to something
+                    args.fFragBuilder->codeAppendf("%s = half4(1);", args.fOutputColor);
                 }
 
                 // If there is a texture, must also handle texture coordinates and reading from
@@ -590,32 +648,22 @@
                 }
 
                 // And lastly, output the coverage calculation code
-                if (gp.fAAEdgeDistances.isInitialized()) {
-                    GrGLSLVarying maxCoverage(kFloat_GrSLType);
-                    args.fVaryingHandler->addVarying("maxCoverage", &maxCoverage);
-                    args.fVertBuilder->codeAppendf("%s = %s.w;",
-                                                   maxCoverage.vsOut(), gp.fPositionWithCoverage.name());
-
-                    args.fFragBuilder->codeAppend("float4 edgeDists;");
-                    args.fVaryingHandler->addPassThroughAttribute(gp.fAAEdgeDistances, "edgeDists");
-
-                    args.fFragBuilder->codeAppend(
-                            "float minDist = min(min(edgeDists.x, edgeDists.y),"
-                            " min(edgeDists.z, edgeDists.w));");
+                if (gp.fCoverageMode == CoverageMode::kWithPosition) {
+                    GrGLSLVarying coverage(kFloat_GrSLType);
+                    args.fVaryingHandler->addVarying("coverage", &coverage);
                     if (gp.fNeedsPerspective) {
-                        // The distance from edge equation e to homogeneous point p=sk_Position is
-                        // e.x*p.x/p.w + e.y*p.y/p.w + e.z. However, we want screen space
-                        // interpolation of this distance. We can do this by multiplying the vertex
-                        // attribute by p.w and then multiplying by sk_FragCoord.w in the FS. So we
-                        // output e.x*p.x + e.y*p.y + e.z * p.w
-                        args.fFragBuilder->codeAppend("minDist *= sk_FragCoord.w;");
+                        args.fVertBuilder->codeAppendf("%s = %s.w;",
+                                                       coverage.vsOut(), gp.fPosition.name());
+                    } else {
+                        args.fVertBuilder->codeAppendf("%s = %s.z;",
+                                                       coverage.vsOut(), gp.fPosition.name());
                     }
-                    // Clamp to max coverage after the perspective divide since perspective quads
-                    // calculated the max coverage in projected space.
-                    args.fFragBuilder->codeAppendf("%s = float4(clamp(minDist, 0.0, %s));",
-                                                   args.fOutputCoverage, maxCoverage.fsIn());
+
+                    args.fFragBuilder->codeAppendf("%s = float4(%s);",
+                                                   args.fOutputCoverage, coverage.fsIn());
                 } else {
-                    // Set coverage to 1
+                    // Set coverage to 1, since it's either non-AA or the coverage was already
+                    // folded into the output color
                     args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage);
                 }
             }
@@ -628,7 +676,7 @@
     QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec)
             : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
             , fTextureColorSpaceXform(nullptr) {
-        SkASSERT(spec.hasVertexColors() && !spec.hasDomain());
+        SkASSERT(!spec.hasDomain());
         this->initializeAttrs(spec);
         this->setTextureSamplerCnt(0);
     }
@@ -641,14 +689,28 @@
             : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
             , fSampler(textureType, textureConfig, samplerState, extraSamplerKey) {
-        SkASSERT(spec.hasVertexColors() && spec.hasLocalCoords());
+        SkASSERT(spec.hasLocalCoords());
         this->initializeAttrs(spec);
         this->setTextureSamplerCnt(1);
     }
 
     void initializeAttrs(const VertexSpec& spec) {
         fNeedsPerspective = spec.deviceDimensionality() == 3;
-        fPositionWithCoverage = {"posAndCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+        fCoverageMode = get_mode_for_spec(spec);
+
+        if (fCoverageMode == CoverageMode::kWithPosition) {
+            if (fNeedsPerspective) {
+                fPosition = {"positionWithCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
+            } else {
+                fPosition = {"positionWithCoverage", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
+            }
+        } else {
+            if (fNeedsPerspective) {
+                fPosition = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
+            } else {
+                fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
+            }
+        }
 
         int localDim = spec.localDimensionality();
         if (localDim == 3) {
@@ -667,22 +729,20 @@
             fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
         }
 
-        if (spec.usesCoverageAA()) {
-            fAAEdgeDistances = {"aaEdgeDist", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
-        }
-        this->setVertexAttributes(&fPositionWithCoverage, 5);
+        this->setVertexAttributes(&fPosition, 4);
     }
 
     const TextureSampler& onTextureSampler(int) const override { return fSampler; }
 
-    Attribute fPositionWithCoverage;
-    Attribute fColor;
+    Attribute fPosition; // May contain coverage as last channel
+    Attribute fColor; // May have coverage modulated in if the FPs support it
     Attribute fLocalCoord;
     Attribute fDomain;
-    Attribute fAAEdgeDistances;
 
-    // The positions attribute is always a vec4 and can't be used to encode perspectiveness
+    // The positions attribute may have coverage built into it, so float3 is an ambiguous type
+    // and may mean 2d with coverage, or 3d with no coverage
     bool fNeedsPerspective;
+    CoverageMode fCoverageMode;
 
     // Color space will be null and fSampler.isInitialized() returns false when the GP is configured
     // to skip texturing.
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.h b/src/gpu/ops/GrQuadPerEdgeAA.h
index 68e798c..232a10d 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.h
+++ b/src/gpu/ops/GrQuadPerEdgeAA.h
@@ -10,6 +10,7 @@
 
 #include "GrColor.h"
 #include "GrGeometryProcessor.h"
+#include "GrMeshDrawOp.h"
 #include "GrQuad.h"
 #include "GrSamplerState.h"
 #include "GrTypesPriv.h"
@@ -32,13 +33,14 @@
     struct VertexSpec {
     public:
         VertexSpec(GrQuadType deviceQuadType, ColorType colorType, GrQuadType localQuadType,
-                   bool hasLocalCoords, Domain domain, GrAAType aa)
+                   bool hasLocalCoords, Domain domain, GrAAType aa, bool alphaAsCoverage)
                 : fDeviceQuadType(static_cast<unsigned>(deviceQuadType))
                 , fLocalQuadType(static_cast<unsigned>(localQuadType))
                 , fHasLocalCoords(hasLocalCoords)
                 , fColorType(static_cast<unsigned>(colorType))
                 , fHasDomain(static_cast<unsigned>(domain))
-                , fUsesCoverageAA(aa == GrAAType::kCoverage) { }
+                , fUsesCoverageAA(aa == GrAAType::kCoverage)
+                , fCompatibleWithAlphaAsCoverage(alphaAsCoverage) { }
 
         GrQuadType deviceQuadType() const { return static_cast<GrQuadType>(fDeviceQuadType); }
         GrQuadType localQuadType() const { return static_cast<GrQuadType>(fLocalQuadType); }
@@ -47,12 +49,14 @@
         bool hasVertexColors() const { return ColorType::kNone != this->colorType(); }
         bool hasDomain() const { return fHasDomain; }
         bool usesCoverageAA() const { return fUsesCoverageAA; }
+        bool compatibleWithAlphaAsCoverage() const { return fCompatibleWithAlphaAsCoverage; }
 
         // Will always be 2 or 3
         int deviceDimensionality() const;
         // Will always be 0 if hasLocalCoords is false, otherwise will be 2 or 3
         int localDimensionality() const;
 
+        int verticesPerQuad() const { return fUsesCoverageAA ? 8 : 4; }
     private:
         static_assert(kGrQuadTypeCount <= 4, "GrQuadType doesn't fit in 2 bits");
         static_assert(kColorTypeCount <= 4, "Color doesn't fit in 2 bits");
@@ -63,6 +67,7 @@
         unsigned fColorType : 2;
         unsigned fHasDomain: 1;
         unsigned fUsesCoverageAA: 1;
+        unsigned fCompatibleWithAlphaAsCoverage: 1;
     };
 
     sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec);
@@ -82,6 +87,16 @@
                      const SkPMColor4f& color, const GrPerspQuad& localQuad, const SkRect& domain,
                      GrQuadAAFlags aa);
 
+    // The mesh will have its index data configured to meet the expectations of the Tessellate()
+    // function, but it the calling code must handle filling a vertex buffer via Tessellate() and
+    // then assigning it to the returned mesh.
+    //
+    // Returns false if the index data could not be allocated.
+    bool ConfigureMeshIndices(GrMeshDrawOp::Target* target, GrMesh* mesh, const VertexSpec& spec,
+                              int quadCount);
+
+    static constexpr int kNumAAQuadsInIndexBuffer = 512;
+
 } // namespace GrQuadPerEdgeAA
 
 #endif // GrQuadPerEdgeAA_DEFINED
diff --git a/src/gpu/ops/GrRectOpFactory.h b/src/gpu/ops/GrRectOpFactory.h
deleted file mode 100644
index 29ac10e..0000000
--- a/src/gpu/ops/GrRectOpFactory.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrRectOpFactory_DEFINED
-#define GrRectOpFactory_DEFINED
-
-#include <memory>
-#include "GrTypes.h"
-
-enum class GrAAType : unsigned;
-class GrDrawOp;
-class GrPaint;
-struct GrUserStencilSettings;
-class SkMatrix;
-struct SkRect;
-class SkStrokeRec;
-
-/**
- * A set of factory functions for drawing rectangles including fills, strokes, coverage-antialiased,
- * and non-antialiased. The non-antialiased ops can be used with MSAA. As with other GrDrawOp
- * factories, the GrPaint is only consumed by these methods if a valid op is returned. If null is
- * returned then the paint is unmodified and may still be used.
- */
-namespace GrRectOpFactory {
-/** AA Fill */
-
-std::unique_ptr<GrDrawOp> MakeAAFill(GrContext*,
-                                     GrPaint&&,
-                                     const SkMatrix&,
-                                     const SkRect&,
-                                     const GrUserStencilSettings* = nullptr);
-
-std::unique_ptr<GrDrawOp> MakeAAFillWithLocalMatrix(GrContext*,
-                                                    GrPaint&&,
-                                                    const SkMatrix& viewMatrix,
-                                                    const SkMatrix& localMatrix,
-                                                    const SkRect&);
-
-std::unique_ptr<GrDrawOp> MakeAAFillWithLocalRect(GrContext*,
-                                                  GrPaint&&,
-                                                  const SkMatrix&,
-                                                  const SkRect& rect,
-                                                  const SkRect& localRect);
-
-/** Non-AA Fill - GrAAType must be either kNone or kMSAA. */
-
-std::unique_ptr<GrDrawOp> MakeNonAAFill(GrContext*,
-                                        GrPaint&&,
-                                        const SkMatrix& viewMatrix,
-                                        const SkRect& rect,
-                                        GrAAType,
-                                        const GrUserStencilSettings* = nullptr);
-
-std::unique_ptr<GrDrawOp> MakeNonAAFillWithLocalMatrix(GrContext*,
-                                                       GrPaint&&,
-                                                       const SkMatrix& viewMatrix,
-                                                       const SkMatrix& localMatrix,
-                                                       const SkRect&,
-                                                       GrAAType,
-                                                       const GrUserStencilSettings* = nullptr);
-
-std::unique_ptr<GrDrawOp> MakeNonAAFillWithLocalRect(GrContext*,
-                                                     GrPaint&&,
-                                                     const SkMatrix&,
-                                                     const SkRect& rect,
-                                                     const SkRect& localRect,
-                                                     GrAAType);
-
-/** AA Stroke */
-
-std::unique_ptr<GrDrawOp> MakeAAStroke(GrContext*,
-                                       GrPaint&&,
-                                       const SkMatrix&,
-                                       const SkRect&,
-                                       const SkStrokeRec&);
-
-// rects[0] == outer rectangle, rects[1] == inner rectangle. Null return means there is nothing to
-// draw rather than failure.
-std::unique_ptr<GrDrawOp> MakeAAFillNestedRects(GrContext*,
-                                                GrPaint&&,
-                                                const SkMatrix&,
-                                                const SkRect rects[2]);
-
-/** Non-AA Stroke - GrAAType must be either kNone or kMSAA. */
-
-std::unique_ptr<GrDrawOp> MakeNonAAStroke(GrContext*,
-                                          GrPaint&&,
-                                          const SkMatrix&,
-                                          const SkRect&,
-                                          const SkStrokeRec&,
-                                          GrAAType);
-
-}  // namespace GrRectOpFactory
-
-#endif
diff --git a/src/gpu/ops/GrRegionOp.cpp b/src/gpu/ops/GrRegionOp.cpp
index 7f1ad81..356f3a7 100644
--- a/src/gpu/ops/GrRegionOp.cpp
+++ b/src/gpu/ops/GrRegionOp.cpp
@@ -20,11 +20,13 @@
 static const int kIndicesPerInstance = 6;
 
 static sk_sp<GrGeometryProcessor> make_gp(const GrShaderCaps* shaderCaps,
-                                          const SkMatrix& viewMatrix) {
+                                          const SkMatrix& viewMatrix,
+                                          bool wideColor) {
     using namespace GrDefaultGeoProcFactory;
-    return GrDefaultGeoProcFactory::Make(shaderCaps, Color::kPremulGrColorAttribute_Type,
-                                         Coverage::kSolid_Type, LocalCoords::kUsePosition_Type,
-                                         viewMatrix);
+    Color::Type colorType =
+        wideColor ? Color::kPremulWideColorAttribute_Type : Color::kPremulGrColorAttribute_Type;
+    return GrDefaultGeoProcFactory::Make(shaderCaps, colorType, Coverage::kSolid_Type,
+                                         LocalCoords::kUsePosition_Type, viewMatrix);
 }
 
 namespace {
@@ -58,6 +60,7 @@
 
         SkRect bounds = SkRect::Make(region.getBounds());
         this->setTransformedBounds(bounds, viewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
+        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     const char* name() const override { return "GrRegionOp"; }
@@ -90,7 +93,8 @@
 
 private:
     void onPrepareDraws(Target* target) override {
-        sk_sp<GrGeometryProcessor> gp = make_gp(target->caps().shaderCaps(), fViewMatrix);
+        sk_sp<GrGeometryProcessor> gp = make_gp(target->caps().shaderCaps(), fViewMatrix,
+                                                fWideColor);
         if (!gp) {
             SkDebugf("Couldn't create GrGeometryProcessor\n");
             return;
@@ -115,9 +119,7 @@
         }
 
         for (int i = 0; i < numRegions; i++) {
-            // TODO4F: Preserve float colors
-            GrColor color = fRegions[i].fColor.toBytes_RGBA();
-
+            GrVertexColor color(fRegions[i].fColor, fWideColor);
             SkRegion::Iterator iter(fRegions[i].fRegion);
             while (!iter.done()) {
                 SkRect rect = SkRect::Make(iter.rect());
@@ -140,6 +142,7 @@
         }
 
         fRegions.push_back_n(that->fRegions.count(), that->fRegions.begin());
+        fWideColor |= that->fWideColor;
         return CombineResult::kMerged;
     }
 
@@ -151,6 +154,7 @@
     Helper fHelper;
     SkMatrix fViewMatrix;
     SkSTArray<1, RegionInfo, true> fRegions;
+    bool fWideColor;
 
     typedef GrMeshDrawOp INHERITED;
 };
diff --git a/src/gpu/ops/GrSemaphoreOp.cpp b/src/gpu/ops/GrSemaphoreOp.cpp
index 01bf86c..b0a96eb 100644
--- a/src/gpu/ops/GrSemaphoreOp.cpp
+++ b/src/gpu/ops/GrSemaphoreOp.cpp
@@ -13,37 +13,6 @@
 #include "GrMemoryPool.h"
 #include "GrOpFlushState.h"
 
-class GrSignalSemaphoreOp final : public GrSemaphoreOp {
-public:
-    DEFINE_OP_CLASS_ID
-
-    static std::unique_ptr<GrOp> Make(GrContext* context,
-                                      sk_sp<GrSemaphore> semaphore,
-                                      GrRenderTargetProxy* proxy,
-                                      bool forceFlush) {
-        GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
-
-        return pool->allocate<GrSignalSemaphoreOp>(std::move(semaphore), proxy, forceFlush);
-    }
-
-    const char* name() const override { return "SignalSemaphore"; }
-
-private:
-    friend class GrOpMemoryPool; // for ctor
-
-    explicit GrSignalSemaphoreOp(sk_sp<GrSemaphore> semaphore, GrRenderTargetProxy* proxy,
-                                 bool forceFlush)
-            : INHERITED(ClassID(), std::move(semaphore), proxy), fForceFlush(forceFlush) {}
-
-    void onExecute(GrOpFlushState* state, const SkRect& chainBounds) override {
-        state->gpu()->insertSemaphore(fSemaphore, fForceFlush);
-    }
-
-    bool fForceFlush;
-
-    typedef GrSemaphoreOp INHERITED;
-};
-
 class GrWaitSemaphoreOp final : public GrSemaphoreOp {
 public:
     DEFINE_OP_CLASS_ID
@@ -73,13 +42,6 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-std::unique_ptr<GrOp> GrSemaphoreOp::MakeSignal(GrContext* context,
-                                                sk_sp<GrSemaphore> semaphore,
-                                                GrRenderTargetProxy* proxy,
-                                                bool forceFlush) {
-    return GrSignalSemaphoreOp::Make(context, std::move(semaphore), proxy, forceFlush);
-}
-
 std::unique_ptr<GrOp> GrSemaphoreOp::MakeWait(GrContext* context,
                                               sk_sp<GrSemaphore> semaphore,
                                               GrRenderTargetProxy* proxy) {
diff --git a/src/gpu/ops/GrSemaphoreOp.h b/src/gpu/ops/GrSemaphoreOp.h
index 53c76c3..9876d62 100644
--- a/src/gpu/ops/GrSemaphoreOp.h
+++ b/src/gpu/ops/GrSemaphoreOp.h
@@ -16,11 +16,6 @@
 
 class GrSemaphoreOp : public GrOp {
 public:
-    static std::unique_ptr<GrOp> MakeSignal(GrContext*,
-                                            sk_sp<GrSemaphore>,
-                                            GrRenderTargetProxy*,
-                                            bool forceFlush);
-
     static std::unique_ptr<GrOp> MakeWait(GrContext*,
                                           sk_sp<GrSemaphore>,
                                           GrRenderTargetProxy*);
diff --git a/src/gpu/ops/GrSimpleMeshDrawOpHelper.h b/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
index 69232e9..25cdb86 100644
--- a/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
+++ b/src/gpu/ops/GrSimpleMeshDrawOpHelper.h
@@ -81,6 +81,10 @@
                                                       GrProcessorAnalysisCoverage geometryCoverage,
                                                       SkPMColor4f* geometryColor);
 
+    bool isTrivial() const {
+      return fProcessors == nullptr;
+    }
+
     bool usesLocalCoords() const {
         SkASSERT(fDidAnalysis);
         return fUsesLocalCoords;
@@ -166,6 +170,9 @@
 
     GrDrawOp::FixedFunctionFlags fixedFunctionFlags() const;
 
+    using GrSimpleMeshDrawOpHelper::aaType;
+    using GrSimpleMeshDrawOpHelper::setAAType;
+    using GrSimpleMeshDrawOpHelper::isTrivial;
     using GrSimpleMeshDrawOpHelper::xpRequiresDstTexture;
     using GrSimpleMeshDrawOpHelper::usesLocalCoords;
     using GrSimpleMeshDrawOpHelper::compatibleWithAlphaAsCoverage;
@@ -180,8 +187,6 @@
 #ifdef SK_DEBUG
     SkString dumpInfo() const;
 #endif
-    GrAAType aaType() const { return INHERITED::aaType(); }
-    void setAAType(GrAAType aaType) { INHERITED::setAAType(aaType); }
 
 private:
     const GrUserStencilSettings* fStencilSettings;
@@ -202,9 +207,9 @@
     } else {
         char* mem = (char*) pool->allocate(sizeof(Op) + sizeof(GrProcessorSet));
         char* setMem = mem + sizeof(Op);
+        auto color = paint.getColor4f();
         makeArgs.fProcessorSet = new (setMem) GrProcessorSet(std::move(paint));
-
-        return std::unique_ptr<GrDrawOp>(new (mem) Op(makeArgs, paint.getColor4f(),
+        return std::unique_ptr<GrDrawOp>(new (mem) Op(makeArgs, color,
                                                       std::forward<OpArgs>(opArgs)...));
     }
 }
diff --git a/src/gpu/ops/GrSmallPathRenderer.cpp b/src/gpu/ops/GrSmallPathRenderer.cpp
index 4f902e0..acb82af 100644
--- a/src/gpu/ops/GrSmallPathRenderer.cpp
+++ b/src/gpu/ops/GrSmallPathRenderer.cpp
@@ -267,6 +267,7 @@
         fShapeCache = shapeCache;
         fShapeList = shapeList;
         fGammaCorrect = gammaCorrect;
+        fWideColor = !SkPMColor4fFitsInBytes(color);
 
     }
 
@@ -351,7 +352,7 @@
                 matrix = &SkMatrix::I();
             }
             flushInfo.fGeometryProcessor = GrDistanceFieldPathGeoProc::Make(
-                    *target->caps().shaderCaps(), *matrix, fAtlas->getProxies(),
+                    *target->caps().shaderCaps(), *matrix, fWideColor, fAtlas->getProxies(),
                     fAtlas->numActivePages(), GrSamplerState::ClampBilerp(), flags);
         } else {
             SkMatrix invert;
@@ -362,7 +363,7 @@
             }
 
             flushInfo.fGeometryProcessor = GrBitmapTextGeoProc::Make(
-                    *target->caps().shaderCaps(), this->color(), fAtlas->getProxies(),
+                    *target->caps().shaderCaps(), this->color(), fWideColor, fAtlas->getProxies(),
                     fAtlas->numActivePages(), GrSamplerState::ClampNearest(), kA8_GrMaskFormat,
                     invert, false);
         }
@@ -489,9 +490,8 @@
             auto uploadTarget = target->deferredUploadTarget();
             fAtlas->setLastUseToken(shapeData->fID, uploadTarget->tokenTracker()->nextDrawToken());
 
-            // TODO4F: Preserve float colors
-            this->writePathVertices(fAtlas, vertices, args.fColor.toBytes_RGBA(), args.fViewMatrix,
-                                    shapeData);
+            this->writePathVertices(fAtlas, vertices, GrVertexColor(args.fColor, fWideColor),
+                                    args.fViewMatrix, shapeData);
             flushInfo.fInstancesToFlush++;
         }
 
@@ -745,7 +745,7 @@
 
     void writePathVertices(GrDrawOpAtlas* atlas,
                            GrVertexWriter& vertices,
-                           GrColor color,
+                           const GrVertexColor& color,
                            const SkMatrix& ctm,
                            const ShapeData* shapeData) const {
         SkRect translatedBounds(shapeData->fBounds);
@@ -844,6 +844,7 @@
         }
 
         fShapes.push_back_n(that->fShapes.count(), that->fShapes.begin());
+        fWideColor |= that->fWideColor;
         return CombineResult::kMerged;
     }
 
@@ -861,6 +862,7 @@
     ShapeCache* fShapeCache;
     ShapeDataList* fShapeList;
     bool fGammaCorrect;
+    bool fWideColor;
 
     typedef GrMeshDrawOp INHERITED;
 };
@@ -880,7 +882,7 @@
                                      format,
                                      kAlpha_8_GrPixelConfig,
                                      ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
-                                     NUM_PLOTS_X, NUM_PLOTS_Y,
+                                     PLOT_WIDTH, PLOT_HEIGHT,
                                      GrDrawOpAtlas::AllowMultitexturing::kYes,
                                      &GrSmallPathRenderer::HandleEviction,
                                      (void*)this);
@@ -969,7 +971,7 @@
         gTestStruct.fAtlas = GrDrawOpAtlas::Make(context->contextPriv().proxyProvider(),
                                                  format, kAlpha_8_GrPixelConfig,
                                                  ATLAS_TEXTURE_WIDTH, ATLAS_TEXTURE_HEIGHT,
-                                                 NUM_PLOTS_X, NUM_PLOTS_Y,
+                                                 PLOT_WIDTH, PLOT_HEIGHT,
                                                  GrDrawOpAtlas::AllowMultitexturing::kYes,
                                                  &PathTestStruct::HandleEviction,
                                                  (void*)&gTestStruct);
diff --git a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
index a0ef219..16db3b3 100644
--- a/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
+++ b/src/gpu/ops/GrStencilAndCoverPathRenderer.cpp
@@ -18,7 +18,7 @@
 #include "GrStencilClip.h"
 #include "GrStencilPathOp.h"
 #include "GrStyle.h"
-#include "ops/GrRectOpFactory.h"
+#include "ops/GrFillRectOp.h"
 
 GrPathRenderer* GrStencilAndCoverPathRenderer::Create(GrResourceProvider* resourceProvider,
                                                       const GrCaps& caps) {
@@ -35,6 +35,7 @@
 
 GrPathRenderer::CanDrawPath
 GrStencilAndCoverPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
+    SkASSERT(!args.fTargetIsWrappedVkSecondaryCB);
     // GrPath doesn't support hairline paths. An arbitrary path effect could produce a hairline
     // path.
     if (args.fShape->style().strokeRec().isHairlineStyle() ||
@@ -156,10 +157,12 @@
             if (GrAAType::kMixedSamples == coverAAType) {
                 coverAAType = GrAAType::kNone;
             }
-            std::unique_ptr<GrDrawOp> op = GrRectOpFactory::MakeNonAAFillWithLocalMatrix(
+            // This is a non-coverage aa rect operation
+            SkASSERT(coverAAType == GrAAType::kNone || coverAAType == GrAAType::kMSAA);
+            std::unique_ptr<GrDrawOp> op = GrFillRectOp::MakeWithLocalMatrix(
                                                          args.fContext, std::move(args.fPaint),
-                                                         coverMatrix, localMatrix, coverBounds,
-                                                         coverAAType, &kInvertedCoverPass);
+                                                         coverAAType, coverMatrix, localMatrix,
+                                                         coverBounds, &kInvertedCoverPass);
 
             args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));
         }
diff --git a/src/gpu/ops/GrAAStrokeRectOp.cpp b/src/gpu/ops/GrStrokeRectOp.cpp
similarity index 62%
rename from src/gpu/ops/GrAAStrokeRectOp.cpp
rename to src/gpu/ops/GrStrokeRectOp.cpp
index d6c4d97..250c46b 100644
--- a/src/gpu/ops/GrAAStrokeRectOp.cpp
+++ b/src/gpu/ops/GrStrokeRectOp.cpp
@@ -1,25 +1,31 @@
 /*
- * Copyright 2015 Google Inc.
+ * Copyright 2018 Google Inc.
  *
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
 
+#include "GrStrokeRectOp.h"
+
+#include "GrColor.h"
 #include "GrDefaultGeoProcFactory.h"
+#include "GrDrawOpTest.h"
+#include "GrMeshDrawOp.h"
 #include "GrOpFlushState.h"
-#include "GrRectOpFactory.h"
 #include "GrResourceKey.h"
 #include "GrResourceProvider.h"
 #include "GrSimpleMeshDrawOpHelper.h"
 #include "GrVertexWriter.h"
+#include "ops/GrFillRectOp.h"
+#include "SkRandom.h"
 #include "SkStrokeRec.h"
 
-GR_DECLARE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey);
-GR_DECLARE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey);
+namespace {
 
 // We support all hairlines, bevels, and miters, but not round joins. Also, check whether the miter
-// limit makes a miter join effectively beveled.
-inline static bool allowed_stroke(const SkStrokeRec& stroke, bool* isMiter) {
+// limit makes a miter join effectively beveled. If the miter is effectively beveled, it is only
+// supported when using an AA stroke.
+inline static bool allowed_stroke(const SkStrokeRec& stroke, GrAA aa, bool* isMiter) {
     SkASSERT(stroke.getStyle() == SkStrokeRec::kStroke_Style ||
              stroke.getStyle() == SkStrokeRec::kHairline_Style);
     // For hairlines, make bevel and round joins appear the same as mitered ones.
@@ -29,18 +35,216 @@
     }
     if (stroke.getJoin() == SkPaint::kBevel_Join) {
         *isMiter = false;
-        return true;
+        return aa == GrAA::kYes; // bevel only supported with AA
     }
     if (stroke.getJoin() == SkPaint::kMiter_Join) {
         *isMiter = stroke.getMiter() >= SK_ScalarSqrt2;
-        return true;
+        // Supported under non-AA only if it remains mitered
+        return aa == GrAA::kYes || *isMiter;
     }
     return false;
 }
 
-static void compute_rects(SkRect* devOutside, SkRect* devOutsideAssist, SkRect* devInside,
-                          bool* isDegenerate, const SkMatrix& viewMatrix, const SkRect& rect,
-                          SkScalar strokeWidth, bool miterStroke) {
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Non-AA Stroking
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+/*  create a triangle strip that strokes the specified rect. There are 8
+    unique vertices, but we repeat the last 2 to close up. Alternatively we
+    could use an indices array, and then only send 8 verts, but not sure that
+    would be faster.
+    */
+static void init_nonaa_stroke_rect_strip(SkPoint verts[10], const SkRect& rect, SkScalar width) {
+    const SkScalar rad = SkScalarHalf(width);
+
+    verts[0].set(rect.fLeft + rad, rect.fTop + rad);
+    verts[1].set(rect.fLeft - rad, rect.fTop - rad);
+    verts[2].set(rect.fRight - rad, rect.fTop + rad);
+    verts[3].set(rect.fRight + rad, rect.fTop - rad);
+    verts[4].set(rect.fRight - rad, rect.fBottom - rad);
+    verts[5].set(rect.fRight + rad, rect.fBottom + rad);
+    verts[6].set(rect.fLeft + rad, rect.fBottom - rad);
+    verts[7].set(rect.fLeft - rad, rect.fBottom + rad);
+    verts[8] = verts[0];
+    verts[9] = verts[1];
+
+    // TODO: we should be catching this higher up the call stack and just draw a single
+    // non-AA rect
+    if (2*rad >= rect.width()) {
+        verts[0].fX = verts[2].fX = verts[4].fX = verts[6].fX = verts[8].fX = rect.centerX();
+    }
+    if (2*rad >= rect.height()) {
+        verts[0].fY = verts[2].fY = verts[4].fY = verts[6].fY = verts[8].fY = rect.centerY();
+    }
+}
+
+class NonAAStrokeRectOp final : public GrMeshDrawOp {
+private:
+    using Helper = GrSimpleMeshDrawOpHelper;
+
+public:
+    DEFINE_OP_CLASS_ID
+
+    const char* name() const override { return "NonAAStrokeRectOp"; }
+
+    void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
+        fHelper.visitProxies(func);
+    }
+
+#ifdef SK_DEBUG
+    SkString dumpInfo() const override {
+        SkString string;
+        string.appendf(
+                "Color: 0x%08x, Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
+                "StrokeWidth: %.2f\n",
+                fColor.toBytes_RGBA(), fRect.fLeft, fRect.fTop, fRect.fRight, fRect.fBottom,
+                fStrokeWidth);
+        string += fHelper.dumpInfo();
+        string += INHERITED::dumpInfo();
+        return string;
+    }
+#endif
+
+    static std::unique_ptr<GrDrawOp> Make(GrContext* context,
+                                          GrPaint&& paint,
+                                          const SkMatrix& viewMatrix,
+                                          const SkRect& rect,
+                                          const SkStrokeRec& stroke,
+                                          GrAAType aaType) {
+        bool isMiter;
+        if (!allowed_stroke(stroke, GrAA::kNo, &isMiter)) {
+            return nullptr;
+        }
+        Helper::Flags flags = Helper::Flags::kNone;
+        // Depending on sub-pixel coordinates and the particular GPU, we may lose a corner of
+        // hairline rects. We jam all the vertices to pixel centers to avoid this, but not
+        // when MSAA is enabled because it can cause ugly artifacts.
+        if (stroke.getStyle() == SkStrokeRec::kHairline_Style && aaType != GrAAType::kMSAA) {
+            flags |= Helper::Flags::kSnapVerticesToPixelCenters;
+        }
+        return Helper::FactoryHelper<NonAAStrokeRectOp>(context, std::move(paint), flags,
+                                                        viewMatrix, rect,
+                                                        stroke, aaType);
+    }
+
+    NonAAStrokeRectOp(const Helper::MakeArgs& helperArgs, const SkPMColor4f& color,
+                      Helper::Flags flags, const SkMatrix& viewMatrix, const SkRect& rect,
+                      const SkStrokeRec& stroke, GrAAType aaType)
+            : INHERITED(ClassID()), fHelper(helperArgs, aaType, flags) {
+        fColor = color;
+        fViewMatrix = viewMatrix;
+        fRect = rect;
+        // Sort the rect for hairlines
+        fRect.sort();
+        fStrokeWidth = stroke.getWidth();
+
+        SkScalar rad = SkScalarHalf(fStrokeWidth);
+        SkRect bounds = rect;
+        bounds.outset(rad, rad);
+
+        // If our caller snaps to pixel centers then we have to round out the bounds
+        if (flags & Helper::Flags::kSnapVerticesToPixelCenters) {
+            viewMatrix.mapRect(&bounds);
+            // We want to be consistent with how we snap non-aa lines. To match what we do in
+            // GrGLSLVertexShaderBuilder, we first floor all the vertex values and then add half a
+            // pixel to force us to pixel centers.
+            bounds.set(SkScalarFloorToScalar(bounds.fLeft),
+                       SkScalarFloorToScalar(bounds.fTop),
+                       SkScalarFloorToScalar(bounds.fRight),
+                       SkScalarFloorToScalar(bounds.fBottom));
+            bounds.offset(0.5f, 0.5f);
+            this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
+        } else {
+            this->setTransformedBounds(bounds, fViewMatrix, HasAABloat::kNo, IsZeroArea::kNo);
+        }
+    }
+
+    FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
+
+    RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip) override {
+        return fHelper.xpRequiresDstTexture(caps, clip, GrProcessorAnalysisCoverage::kNone,
+                                            &fColor);
+    }
+
+private:
+    void onPrepareDraws(Target* target) override {
+        sk_sp<GrGeometryProcessor> gp;
+        {
+            using namespace GrDefaultGeoProcFactory;
+            Color color(fColor);
+            LocalCoords::Type localCoordsType = fHelper.usesLocalCoords()
+                                                        ? LocalCoords::kUsePosition_Type
+                                                        : LocalCoords::kUnused_Type;
+            gp = GrDefaultGeoProcFactory::Make(target->caps().shaderCaps(), color,
+                                               Coverage::kSolid_Type, localCoordsType,
+                                               fViewMatrix);
+        }
+
+        size_t kVertexStride = gp->vertexStride();
+        int vertexCount = kVertsPerHairlineRect;
+        if (fStrokeWidth > 0) {
+            vertexCount = kVertsPerStrokeRect;
+        }
+
+        const GrBuffer* vertexBuffer;
+        int firstVertex;
+
+        void* verts =
+                target->makeVertexSpace(kVertexStride, vertexCount, &vertexBuffer, &firstVertex);
+
+        if (!verts) {
+            SkDebugf("Could not allocate vertices\n");
+            return;
+        }
+
+        SkPoint* vertex = reinterpret_cast<SkPoint*>(verts);
+
+        GrPrimitiveType primType;
+        if (fStrokeWidth > 0) {
+            primType = GrPrimitiveType::kTriangleStrip;
+            init_nonaa_stroke_rect_strip(vertex, fRect, fStrokeWidth);
+        } else {
+            // hairline
+            primType = GrPrimitiveType::kLineStrip;
+            vertex[0].set(fRect.fLeft, fRect.fTop);
+            vertex[1].set(fRect.fRight, fRect.fTop);
+            vertex[2].set(fRect.fRight, fRect.fBottom);
+            vertex[3].set(fRect.fLeft, fRect.fBottom);
+            vertex[4].set(fRect.fLeft, fRect.fTop);
+        }
+
+        GrMesh* mesh = target->allocMesh(primType);
+        mesh->setNonIndexedNonInstanced(vertexCount);
+        mesh->setVertexData(vertexBuffer, firstVertex);
+        auto pipe = fHelper.makePipeline(target);
+        target->draw(std::move(gp), pipe.fPipeline, pipe.fFixedDynamicState, mesh);
+    }
+
+    // TODO: override onCombineIfPossible
+
+    Helper fHelper;
+    SkPMColor4f fColor;
+    SkMatrix fViewMatrix;
+    SkRect fRect;
+    SkScalar fStrokeWidth;
+
+    const static int kVertsPerHairlineRect = 5;
+    const static int kVertsPerStrokeRect = 10;
+
+    typedef GrMeshDrawOp INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// AA Stroking
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+GR_DECLARE_STATIC_UNIQUE_KEY(gMiterIndexBufferKey);
+GR_DECLARE_STATIC_UNIQUE_KEY(gBevelIndexBufferKey);
+
+static void compute_aa_rects(SkRect* devOutside, SkRect* devOutsideAssist, SkRect* devInside,
+                             bool* isDegenerate, const SkMatrix& viewMatrix, const SkRect& rect,
+                             SkScalar strokeWidth, bool miterStroke) {
     SkRect devRect;
     viewMatrix.mapRect(&devRect, rect);
 
@@ -90,28 +294,22 @@
     }
 }
 
-static sk_sp<GrGeometryProcessor> create_stroke_rect_gp(const GrShaderCaps* shaderCaps,
-                                                        bool tweakAlphaForCoverage,
-                                                        const SkMatrix& viewMatrix,
-                                                        bool usesLocalCoords) {
+static sk_sp<GrGeometryProcessor> create_aa_stroke_rect_gp(const GrShaderCaps* shaderCaps,
+                                                           bool tweakAlphaForCoverage,
+                                                           const SkMatrix& viewMatrix,
+                                                           bool usesLocalCoords,
+                                                           bool wideColor) {
     using namespace GrDefaultGeoProcFactory;
 
-    Coverage::Type coverageType;
-    if (tweakAlphaForCoverage) {
-        coverageType = Coverage::kSolid_Type;
-    } else {
-        coverageType = Coverage::kAttribute_Type;
-    }
+    Coverage::Type coverageType =
+        tweakAlphaForCoverage ? Coverage::kSolid_Type : Coverage::kAttribute_Type;
     LocalCoords::Type localCoordsType =
-            usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type;
-    return MakeForDeviceSpace(shaderCaps,
-                              Color::kPremulGrColorAttribute_Type,
-                              coverageType,
-                              localCoordsType,
-                              viewMatrix);
-}
+        usesLocalCoords ? LocalCoords::kUsePosition_Type : LocalCoords::kUnused_Type;
+    Color::Type colorType =
+        wideColor ? Color::kPremulWideColorAttribute_Type: Color::kPremulGrColorAttribute_Type;
 
-namespace {
+    return MakeForDeviceSpace(shaderCaps, colorType, coverageType, localCoordsType, viewMatrix);
+}
 
 class AAStrokeRectOp final : public GrMeshDrawOp {
 private:
@@ -140,6 +338,7 @@
         fRects.emplace_back(RectInfo{color, devOutside, devOutside, devInside, false});
         this->setBounds(devOutside, HasAABloat::kYes, IsZeroArea::kNo);
         fMiterStroke = true;
+        fWideColor = !SkPMColor4fFitsInBytes(color);
     }
 
     static std::unique_ptr<GrDrawOp> Make(GrContext* context,
@@ -148,7 +347,7 @@
                                           const SkRect& rect,
                                           const SkStrokeRec& stroke) {
         bool isMiter;
-        if (!allowed_stroke(stroke, &isMiter)) {
+        if (!allowed_stroke(stroke, GrAA::kYes, &isMiter)) {
             return nullptr;
         }
         return Helper::FactoryHelper<AAStrokeRectOp>(context, std::move(paint), viewMatrix, rect,
@@ -162,9 +361,10 @@
             , fHelper(helperArgs, GrAAType::kCoverage)
             , fViewMatrix(viewMatrix) {
         fMiterStroke = isMiter;
+        fWideColor = !SkPMColor4fFitsInBytes(color);
         RectInfo& info = fRects.push_back();
-        compute_rects(&info.fDevOutside, &info.fDevOutsideAssist, &info.fDevInside,
-                      &info.fDegenerate, viewMatrix, rect, stroke.getWidth(), isMiter);
+        compute_aa_rects(&info.fDevOutside, &info.fDevOutsideAssist, &info.fDevInside,
+                         &info.fDegenerate, viewMatrix, rect, stroke.getWidth(), isMiter);
         info.fColor = color;
         if (isMiter) {
             this->setBounds(info.fDevOutside, HasAABloat::kYes, IsZeroArea::kNo);
@@ -229,7 +429,8 @@
     CombineResult onCombineIfPossible(GrOp* t, const GrCaps&) override;
 
     void generateAAStrokeRectGeometry(GrVertexWriter& vertices,
-                                      GrColor color,
+                                      const SkPMColor4f& color,
+                                      bool wideColor,
                                       const SkRect& devOutside,
                                       const SkRect& devOutsideAssist,
                                       const SkRect& devInside,
@@ -250,17 +451,17 @@
     SkSTArray<1, RectInfo, true> fRects;
     SkMatrix fViewMatrix;
     bool fMiterStroke;
+    bool fWideColor;
 
     typedef GrMeshDrawOp INHERITED;
 };
 
-}  // anonymous namespace
-
 void AAStrokeRectOp::onPrepareDraws(Target* target) {
-    sk_sp<GrGeometryProcessor> gp(create_stroke_rect_gp(target->caps().shaderCaps(),
-                                                        fHelper.compatibleWithAlphaAsCoverage(),
-                                                        this->viewMatrix(),
-                                                        fHelper.usesLocalCoords()));
+    sk_sp<GrGeometryProcessor> gp(create_aa_stroke_rect_gp(target->caps().shaderCaps(),
+                                                           fHelper.compatibleWithAlphaAsCoverage(),
+                                                           this->viewMatrix(),
+                                                           fHelper.usesLocalCoords(),
+                                                           fWideColor));
     if (!gp) {
         SkDebugf("Couldn't create GrGeometryProcessor\n");
         return;
@@ -285,7 +486,8 @@
     for (int i = 0; i < instanceCount; i++) {
         const RectInfo& info = fRects[i];
         this->generateAAStrokeRectGeometry(vertices,
-                                           info.fColor.toBytes_RGBA(), // TODO4F
+                                           info.fColor,
+                                           fWideColor,
                                            info.fDevOutside,
                                            info.fDevOutsideAssist,
                                            info.fDevInside,
@@ -410,6 +612,7 @@
     }
 
     fRects.push_back_n(that->fRects.count(), that->fRects.begin());
+    fWideColor |= that->fWideColor;
     return CombineResult::kMerged;
 }
 
@@ -423,7 +626,8 @@
 }
 
 void AAStrokeRectOp::generateAAStrokeRectGeometry(GrVertexWriter& vertices,
-                                                  GrColor color,
+                                                  const SkPMColor4f& color,
+                                                  bool wideColor,
                                                   const SkRect& devOutside,
                                                   const SkRect& devOutsideAssist,
                                                   const SkRect& devInside,
@@ -462,7 +666,7 @@
         return GrVertexWriter::If(!tweakAlphaForCoverage, coverage);
     };
 
-    GrColor outerColor = tweakAlphaForCoverage ? 0 : color;
+    GrVertexColor outerColor(tweakAlphaForCoverage ? SK_PMColor4fTRANSPARENT : color, wideColor);
 
     // Outermost rect
     vertices.writeQuad(inset_fan(devOutside, -SK_ScalarHalf, -SK_ScalarHalf),
@@ -481,8 +685,8 @@
     setup_scale(&scale, inset);
 
     float innerCoverage = GrNormalizeByteToFloat(scale);
-    GrColor scaledColor = (0xff == scale) ? color : SkAlphaMulQ(color, scale);
-    GrColor innerColor = tweakAlphaForCoverage ? scaledColor : color;
+    SkPMColor4f scaledColor = color * innerCoverage;
+    GrVertexColor innerColor(tweakAlphaForCoverage ? scaledColor : color, wideColor);
 
     // Inner rect
     vertices.writeQuad(inset_fan(devOutside, inset, inset),
@@ -503,7 +707,7 @@
 
         // The innermost rect has 0 coverage...
         vertices.writeQuad(inset_fan(devInside, SK_ScalarHalf, SK_ScalarHalf),
-                           (GrColor)0,
+                           GrVertexColor(SK_PMColor4fTRANSPARENT, wideColor),
                            maybe_coverage(0.0f));
     } else {
         // When the interior rect has become degenerate we smoosh to a single point
@@ -520,12 +724,31 @@
     }
 }
 
-namespace GrRectOpFactory {
+}  // anonymous namespace
 
-std::unique_ptr<GrDrawOp> MakeAAFillNestedRects(GrContext* context,
-                                                GrPaint&& paint,
-                                                const SkMatrix& viewMatrix,
-                                                const SkRect rects[2]) {
+namespace GrStrokeRectOp {
+
+std::unique_ptr<GrDrawOp> Make(GrContext* context,
+                               GrPaint&& paint,
+                               GrAAType aaType,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkStrokeRec& stroke) {
+    if (aaType == GrAAType::kCoverage) {
+        // The AA op only supports axis-aligned rectangles
+        if (!viewMatrix.rectStaysRect()) {
+            return nullptr;
+        }
+        return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke);
+    } else {
+        return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke, aaType);
+    }
+}
+
+std::unique_ptr<GrDrawOp> MakeNested(GrContext* context,
+                                     GrPaint&& paint,
+                                     const SkMatrix& viewMatrix,
+                                     const SkRect rects[2]) {
     SkASSERT(viewMatrix.rectStaysRect());
     SkASSERT(!rects[0].isEmpty() && !rects[1].isEmpty());
 
@@ -536,28 +759,35 @@
         if (devOutside.isEmpty()) {
             return nullptr;
         }
-        return MakeAAFill(context, std::move(paint), viewMatrix, rects[0]);
+        return GrFillRectOp::Make(context, std::move(paint), GrAAType::kCoverage, viewMatrix,
+                                  rects[0]);
     }
 
     return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, devOutside, devInside);
 }
 
-std::unique_ptr<GrDrawOp> MakeAAStroke(GrContext* context,
-                                       GrPaint&& paint,
-                                       const SkMatrix& viewMatrix,
-                                       const SkRect& rect,
-                                       const SkStrokeRec& stroke) {
-    return AAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, stroke);
-}
-
-}  // namespace GrRectOpFactory
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
+}  // namespace GrStrokeRectOp
 
 #if GR_TEST_UTILS
 
 #include "GrDrawOpTest.h"
 
+GR_DRAW_OP_TEST_DEFINE(NonAAStrokeRectOp) {
+    SkMatrix viewMatrix = GrTest::TestMatrix(random);
+    SkRect rect = GrTest::TestRect(random);
+    SkScalar strokeWidth = random->nextBool() ? 0.0f : 2.0f;
+    SkPaint strokePaint;
+    strokePaint.setStrokeWidth(strokeWidth);
+    strokePaint.setStyle(SkPaint::kStroke_Style);
+    strokePaint.setStrokeJoin(SkPaint::kMiter_Join);
+    SkStrokeRec strokeRec(strokePaint);
+    GrAAType aaType = GrAAType::kNone;
+    if (fsaaType == GrFSAAType::kUnifiedMSAA) {
+        aaType = random->nextBool() ? GrAAType::kMSAA : GrAAType::kNone;
+    }
+    return NonAAStrokeRectOp::Make(context, std::move(paint), viewMatrix, rect, strokeRec, aaType);
+}
+
 GR_DRAW_OP_TEST_DEFINE(AAStrokeRectOp) {
     bool miterStroke = random->nextBool();
 
@@ -572,7 +802,7 @@
     rec.setStrokeParams(SkPaint::kButt_Cap,
                         miterStroke ? SkPaint::kMiter_Join : SkPaint::kBevel_Join, 1.f);
     SkMatrix matrix = GrTest::TestMatrixRectStaysRect(random);
-    return GrRectOpFactory::MakeAAStroke(context, std::move(paint), matrix, rect, rec);
+    return AAStrokeRectOp::Make(context, std::move(paint), matrix, rect, rec);
 }
 
 #endif
diff --git a/src/gpu/ops/GrStrokeRectOp.h b/src/gpu/ops/GrStrokeRectOp.h
new file mode 100644
index 0000000..97ea865
--- /dev/null
+++ b/src/gpu/ops/GrStrokeRectOp.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrStrokeRectOp_DEFINED
+#define GrStrokeRectOp_DEFINED
+
+#include "GrTypesPriv.h"
+
+class GrDrawOp;
+class GrPaint;
+class SkMatrix;
+struct SkRect;
+class SkStrokeRec;
+
+/**
+ * A set of factory functions for drawing stroked rectangles either coverage-antialiased, or
+ * non-antialiased. The non-antialiased ops can be used with MSAA. As with other GrDrawOp factories,
+ * the GrPaint is only consumed by these methods if a valid op is returned. If null is returned then
+ * the paint is unmodified and may still be used.
+ */
+namespace GrStrokeRectOp {
+
+std::unique_ptr<GrDrawOp> Make(GrContext* context,
+                               GrPaint&& paint,
+                               GrAAType aaType,
+                               const SkMatrix& viewMatrix,
+                               const SkRect& rect,
+                               const SkStrokeRec& stroke);
+
+// rects[0] == outer rectangle, rects[1] == inner rectangle. Null return means there is nothing to
+// draw rather than failure. The area between the rectangles will be filled by the paint, and it
+// will be anti-aliased with coverage AA. viewMatrix.rectStaysRect() must be true.
+std::unique_ptr<GrDrawOp> MakeNested(GrContext* context,
+                                     GrPaint&& paint,
+                                     const SkMatrix& viewMatrix,
+                                     const SkRect rects[2]);
+
+}  // namespace GrStrokeRectOp
+
+#endif // GrStrokeRectOp_DEFINED
diff --git a/src/gpu/ops/GrTessellatingPathRenderer.cpp b/src/gpu/ops/GrTessellatingPathRenderer.cpp
index a59c161..28ad083 100644
--- a/src/gpu/ops/GrTessellatingPathRenderer.cpp
+++ b/src/gpu/ops/GrTessellatingPathRenderer.cpp
@@ -277,8 +277,11 @@
         bool isLinear;
         bool canMapVB = GrCaps::kNone_MapFlags != target->caps().mapBufferFlags();
         StaticVertexAllocator allocator(vertexStride, rp, canMapVB);
-        int count = GrTessellator::PathToTriangles(getPath(), tol, clipBounds, &allocator,
-                                                   false, GrColor(), false, &isLinear);
+        int count = GrTessellator::PathToTriangles(getPath(), tol, clipBounds, &allocator, false,
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
+                                                   GrColor(), false,
+#endif
+                                                   &isLinear);
         if (count == 0) {
             return;
         }
@@ -302,11 +305,12 @@
         SkScalar tol = GrPathUtils::kDefaultTolerance;
         bool isLinear;
         DynamicVertexAllocator allocator(vertexStride, target);
-        // TODO4F: Preserve float colors
-        int count =
-                GrTessellator::PathToTriangles(path, tol, clipBounds, &allocator, true,
-                                               fColor.toBytes_RGBA(),
-                                               fHelper.compatibleWithAlphaAsCoverage(), &isLinear);
+        int count = GrTessellator::PathToTriangles(path, tol, clipBounds, &allocator, true,
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
+                                                   fColor.toBytes_RGBA(),
+                                                   fHelper.compatibleWithAlphaAsCoverage(),
+#endif
+                                                   &isLinear);
         if (count == 0) {
             return;
         }
@@ -325,9 +329,15 @@
                                                         : LocalCoords::kUnused_Type;
             Coverage::Type coverageType;
             if (fAntiAlias) {
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
                 color = Color(Color::kPremulGrColorAttribute_Type);
+#endif
                 if (fHelper.compatibleWithAlphaAsCoverage()) {
+#ifdef SK_LEGACY_TESSELLATOR_CPU_COVERAGE
                     coverageType = Coverage::kSolid_Type;
+#else
+                    coverageType = Coverage::kAttributeTweakAlpha_Type;
+#endif
                 } else {
                     coverageType = Coverage::kAttribute_Type;
                 }
diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp
index 255f937..1e49f5f 100644
--- a/src/gpu/ops/GrTextureOp.cpp
+++ b/src/gpu/ops/GrTextureOp.cpp
@@ -54,9 +54,11 @@
            SkScalarFraction(qt) != SkScalarFraction(srcRect.fTop);
 }
 
-static SkRect compute_domain(Domain domain, GrSamplerState::Filter filter,
-                             GrSurfaceOrigin origin, const SkRect& srcRect, float iw, float ih) {
-    static constexpr SkRect kLargeRect = {-2, -2, 2, 2};
+// if normalizing the domain then pass 1/width, 1/height, 1 for iw, ih, h. Otherwise pass
+// 1, 1, and height.
+static SkRect compute_domain(Domain domain, GrSamplerState::Filter filter, GrSurfaceOrigin origin,
+                             const SkRect& srcRect, float iw, float ih, float h) {
+    static constexpr SkRect kLargeRect = {-100000, -100000, 1000000, 1000000};
     if (domain == Domain::kNo) {
         // Either the quad has no domain constraint and is batched with a domain constrained op
         // (in which case we want a domain that doesn't restrict normalized tex coords), or the
@@ -75,7 +77,7 @@
     ltrb *= Sk4f(iw, ih, iw, ih);
     if (origin == kBottomLeft_GrSurfaceOrigin) {
         static const Sk4f kMul = {1.f, -1.f, 1.f, -1.f};
-        static const Sk4f kAdd = {0.f, 1.f, 0.f, 1.f};
+        const Sk4f kAdd = {0.f, h, 0.f, h};
         ltrb = SkNx_shuffle<0, 3, 2, 1>(kMul * ltrb + kAdd);
     }
 
@@ -84,8 +86,10 @@
     return domainRect;
 }
 
-static GrPerspQuad compute_src_quad(GrSurfaceOrigin origin, const SkRect& srcRect,
-                                    float iw, float ih) {
+// If normalizing the src quad then pass 1/width, 1/height, 1 for iw, ih, h. Otherwise pass
+// 1, 1, and height.
+static GrPerspQuad compute_src_quad(GrSurfaceOrigin origin, const SkRect& srcRect, float iw,
+                                    float ih, float h) {
     // Convert the pixel-space src rectangle into normalized texture coordinates
     SkRect texRect = {
         iw * srcRect.fLeft,
@@ -94,8 +98,8 @@
         ih * srcRect.fBottom
     };
     if (origin == kBottomLeft_GrSurfaceOrigin) {
-        texRect.fTop = 1.f - texRect.fTop;
-        texRect.fBottom = 1.f - texRect.fBottom;
+        texRect.fTop = h - texRect.fTop;
+        texRect.fBottom = h - texRect.fBottom;
     }
     return GrPerspQuad(texRect, SkMatrix::I());
 }
@@ -165,15 +169,16 @@
             str.appendf("Proxy ID: %d, Filter: %d\n", fProxies[p].fProxy->uniqueID().asUInt(),
                         static_cast<int>(fFilter));
             for (int i = 0; i < fProxies[p].fQuadCnt; ++i, ++q) {
-                const Quad& quad = fQuads[q];
+                GrPerspQuad quad = fQuads[q];
+                const ColorDomainAndAA& info = fQuads.metadata(i);
                 str.appendf(
                         "%d: Color: 0x%08x, TexRect [L: %.2f, T: %.2f, R: %.2f, B: %.2f] "
                         "Quad [(%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f), (%.2f, %.2f)]\n",
-                        i, quad.color().toBytes_RGBA(), quad.srcRect().fLeft, quad.srcRect().fTop,
-                        quad.srcRect().fRight, quad.srcRect().fBottom, quad.quad().point(0).fX,
-                        quad.quad().point(0).fY, quad.quad().point(1).fX, quad.quad().point(1).fY,
-                        quad.quad().point(2).fX, quad.quad().point(2).fY, quad.quad().point(3).fX,
-                        quad.quad().point(3).fY);
+                        i, info.fColor.toBytes_RGBA(), info.fSrcRect.fLeft, info.fSrcRect.fTop,
+                        info.fSrcRect.fRight, info.fSrcRect.fBottom, quad.point(0).fX,
+                        quad.point(0).fY, quad.point(1).fX, quad.point(1).fY,
+                        quad.point(2).fX, quad.point(2).fY, quad.point(3).fX,
+                        quad.point(3).fY);
             }
         }
         str += INHERITED::dumpInfo();
@@ -217,7 +222,6 @@
         GrResolveAATypeForQuad(aaType, aaFlags, quad, quadType, &aaType, &aaFlags);
         fAAType = static_cast<unsigned>(aaType);
 
-        fQuadType = static_cast<unsigned>(quadType);
         // We expect our caller to have already caught this optimization.
         SkASSERT(!srcRect.contains(proxy->getWorstCaseBoundsRect()) ||
                  constraint == SkCanvas::kFast_SrcRectConstraint);
@@ -237,12 +241,15 @@
             aaType != GrAAType::kCoverage) {
             constraint = SkCanvas::kFast_SrcRectConstraint;
         }
-        const auto& draw = fQuads.emplace_back(srcRect, quad, aaFlags, constraint, color);
+
+        Domain domain = constraint == SkCanvas::kStrict_SrcRectConstraint ? Domain::kYes
+                                                                          : Domain::kNo;
+        fQuads.push_back(quad, quadType, {color, srcRect, domain, aaFlags});
         fProxyCnt = 1;
         fProxies[0] = {proxy.release(), 1};
-        auto bounds = quad.bounds();
+        auto bounds = quad.bounds(quadType);
         this->setBounds(bounds, HasAABloat(aaType == GrAAType::kCoverage), IsZeroArea::kNo);
-        fDomain = static_cast<unsigned>(draw.domain());
+        fDomain = static_cast<unsigned>(domain);
         fWideColor = !SkPMColor4fFitsInBytes(color);
         fCanSkipAllocatorGather =
                 static_cast<unsigned>(fProxies[0].fProxy->canSkipResourceAllocator());
@@ -254,7 +261,6 @@
             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
             , fFilter(static_cast<unsigned>(filter))
             , fFinalized(0) {
-        fQuads.reserve(cnt);
         fProxyCnt = SkToUInt(cnt);
         SkRect bounds = SkRectPriv::MakeLargestInverted();
         GrAAType overallAAType = GrAAType::kNone; // aa type maximally compatible with all dst rects
@@ -262,6 +268,8 @@
         fCanSkipAllocatorGather = static_cast<unsigned>(true);
         // All dst rects are transformed by the same view matrix, so their quad types are identical
         GrQuadType quadType = GrQuadTypeForTransformedRect(viewMatrix);
+        fQuads.reserve(cnt, quadType);
+
         for (unsigned p = 0; p < fProxyCnt; ++p) {
             fProxies[p].fProxy = SkRef(set[p].fProxy.get());
             fProxies[p].fQuadCnt = 1;
@@ -271,7 +279,7 @@
                 fCanSkipAllocatorGather = static_cast<unsigned>(false);
             }
             auto quad = GrPerspQuad(set[p].fDstRect, viewMatrix);
-            bounds.joinPossiblyEmptyRect(quad.bounds());
+            bounds.joinPossiblyEmptyRect(quad.bounds(quadType));
             GrQuadAAFlags aaFlags;
             // Don't update the overall aaType, might be inappropriate for some of the quads
             GrAAType aaForQuad;
@@ -287,15 +295,13 @@
             }
             float alpha = SkTPin(set[p].fAlpha, 0.f, 1.f);
             SkPMColor4f color{alpha, alpha, alpha, alpha};
-            fQuads.emplace_back(set[p].fSrcRect, quad, aaFlags, SkCanvas::kFast_SrcRectConstraint,
-                                color);
+            fQuads.push_back(quad, quadType, {color, set[p].fSrcRect, Domain::kNo, aaFlags});
         }
         fAAType = static_cast<unsigned>(overallAAType);
         if (!mustFilter) {
             fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest);
         }
         this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), IsZeroArea::kNo);
-        fQuadType = static_cast<unsigned>(quadType);
         fDomain = static_cast<unsigned>(false);
         fWideColor = static_cast<unsigned>(false);
     }
@@ -305,15 +311,25 @@
         TRACE_EVENT0("skia", TRACE_FUNC);
         auto origin = proxy->origin();
         const auto* texture = proxy->peekTexture();
-        float iw = 1.f / texture->width();
-        float ih = 1.f / texture->height();
+        float iw, ih, h;
+        if (proxy->textureType() == GrTextureType::kRectangle) {
+            iw = ih = 1.f;
+            h = texture->height();
+        } else {
+            iw = 1.f / texture->width();
+            ih = 1.f / texture->height();
+            h = 1.f;
+        }
 
         for (int i = start; i < start + cnt; ++i) {
-            const auto q = fQuads[i];
-            GrPerspQuad srcQuad = compute_src_quad(origin, q.srcRect(), iw, ih);
-            SkRect domain = compute_domain(q.domain(), this->filter(), origin, q.srcRect(), iw, ih);
-            v = GrQuadPerEdgeAA::Tessellate(v, spec, q.quad(), q.color(), srcQuad, domain,
-                                            q.aaFlags());
+            const GrPerspQuad& device = fQuads[i];
+            const ColorDomainAndAA& info = fQuads.metadata(i);
+
+            GrPerspQuad srcQuad = compute_src_quad(origin, info.fSrcRect, iw, ih, h);
+            SkRect domain =
+                    compute_domain(info.domain(), this->filter(), origin, info.fSrcRect, iw, ih, h);
+            v = GrQuadPerEdgeAA::Tessellate(v, spec, device, info.fColor, srcQuad, domain,
+                                            info.aaFlags());
         }
     }
 
@@ -328,8 +344,8 @@
         auto config = fProxies[0].fProxy->config();
         GrAAType aaType = this->aaType();
         for (const auto& op : ChainRange<TextureOp>(this)) {
-            if (op.quadType() > quadType) {
-                quadType = op.quadType();
+            if (op.fQuads.quadType() > quadType) {
+                quadType = op.fQuads.quadType();
             }
             if (op.fDomain) {
                 domain = Domain::kYes;
@@ -352,7 +368,8 @@
         }
 
         VertexSpec vertexSpec(quadType, wideColor ? ColorType::kHalf : ColorType::kByte,
-                              GrQuadType::kRect, /* hasLocal */ true, domain, aaType);
+                              GrQuadType::kRect, /* hasLocal */ true, domain, aaType,
+                              /* alpha as coverage */ true);
 
         GrSamplerState samplerState = GrSamplerState(GrSamplerState::WrapMode::kClamp,
                                                      this->filter());
@@ -394,7 +411,7 @@
         GrMesh* meshes = target->allocMeshes(numProxies);
         const GrBuffer* vbuffer;
         int vertexOffsetInBuffer = 0;
-        int numQuadVerticesLeft = numTotalQuads * 4;
+        int numQuadVerticesLeft = numTotalQuads * vertexSpec.verticesPerQuad();
         int numAllocatedVertices = 0;
         void* vdata = nullptr;
 
@@ -404,7 +421,7 @@
             for (unsigned p = 0; p < op.fProxyCnt; ++p) {
                 int quadCnt = op.fProxies[p].fQuadCnt;
                 auto* proxy = op.fProxies[p].fProxy;
-                int meshVertexCnt = quadCnt * 4;
+                int meshVertexCnt = quadCnt * vertexSpec.verticesPerQuad();
                 if (numAllocatedVertices < meshVertexCnt) {
                     vdata = target->makeVertexSpaceAtLeast(
                             vertexSize, meshVertexCnt, numQuadVerticesLeft, &vbuffer,
@@ -419,19 +436,10 @@
 
                 op.tess(vdata, vertexSpec, proxy, q, quadCnt);
 
-                if (quadCnt > 1) {
-                    meshes[m].setPrimitiveType(GrPrimitiveType::kTriangles);
-                    sk_sp<const GrBuffer> ibuffer =
-                            target->resourceProvider()->refQuadIndexBuffer();
-                    if (!ibuffer) {
-                        SkDebugf("Could not allocate quad indices\n");
-                        return;
-                    }
-                    meshes[m].setIndexedPatterned(ibuffer.get(), 6, 4, quadCnt,
-                                                  GrResourceProvider::QuadCountOfQuadBuffer());
-                } else {
-                    meshes[m].setPrimitiveType(GrPrimitiveType::kTriangleStrip);
-                    meshes[m].setNonIndexedNonInstanced(4);
+                if (!GrQuadPerEdgeAA::ConfigureMeshIndices(target, &(meshes[m]), vertexSpec,
+                                                           quadCnt)) {
+                    SkDebugf("Could not allocate indices");
+                    return;
                 }
                 meshes[m].setVertexData(vbuffer, vertexOffsetInBuffer);
                 if (dynamicStateArrays) {
@@ -481,10 +489,7 @@
             return CombineResult::kCannotCombine;
         }
         fProxies[0].fQuadCnt += that->fQuads.count();
-        fQuads.push_back_n(that->fQuads.count(), that->fQuads.begin());
-        if (that->fQuadType > fQuadType) {
-            fQuadType = that->fQuadType;
-        }
+        fQuads.concat(that->fQuads);
         fDomain |= that->fDomain;
         fWideColor |= that->fWideColor;
         if (upgradeToCoverageAAOnMerge) {
@@ -495,47 +500,44 @@
 
     GrAAType aaType() const { return static_cast<GrAAType>(fAAType); }
     GrSamplerState::Filter filter() const { return static_cast<GrSamplerState::Filter>(fFilter); }
-    GrQuadType quadType() const { return static_cast<GrQuadType>(fQuadType); }
 
-    class Quad {
-    public:
-        Quad(const SkRect& srcRect, const GrPerspQuad& quad, GrQuadAAFlags aaFlags,
-             SkCanvas::SrcRectConstraint constraint, const SkPMColor4f& color)
-                : fSrcRect(srcRect)
-                , fQuad(quad)
-                , fColor(color)
-                , fHasDomain(constraint == SkCanvas::kStrict_SrcRectConstraint)
+    struct ColorDomainAndAA {
+        // Special constructor to convert enums into the packed bits, which should not delete
+        // the implicit move constructor (but it does require us to declare an empty ctor for
+        // use with the GrTQuadList).
+        ColorDomainAndAA(const SkPMColor4f& color, const SkRect& srcRect,
+                         Domain hasDomain, GrQuadAAFlags aaFlags)
+                : fColor(color)
+                , fSrcRect(srcRect)
+                , fHasDomain(static_cast<unsigned>(hasDomain))
                 , fAAFlags(static_cast<unsigned>(aaFlags)) {
+            SkASSERT(fHasDomain == static_cast<unsigned>(hasDomain));
             SkASSERT(fAAFlags == static_cast<unsigned>(aaFlags));
         }
-        const GrPerspQuad& quad() const { return fQuad; }
-        const SkRect& srcRect() const { return fSrcRect; }
-        SkPMColor4f color() const { return fColor; }
-        Domain domain() const { return Domain(fHasDomain); }
-        GrQuadAAFlags aaFlags() const { return static_cast<GrQuadAAFlags>(fAAFlags); }
+        ColorDomainAndAA() = default;
 
-    private:
-        SkRect fSrcRect;
-        GrPerspQuad fQuad;
         SkPMColor4f fColor;
+        SkRect fSrcRect;
         unsigned fHasDomain : 1;
         unsigned fAAFlags : 4;
+
+        Domain domain() const { return Domain(fHasDomain); }
+        GrQuadAAFlags aaFlags() const { return static_cast<GrQuadAAFlags>(fAAFlags); }
     };
     struct Proxy {
         GrTextureProxy* fProxy;
         int fQuadCnt;
     };
-    SkSTArray<1, Quad, true> fQuads;
+    GrTQuadList<ColorDomainAndAA> fQuads;
     sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
     unsigned fFilter : 2;
     unsigned fAAType : 2;
-    unsigned fQuadType : 2; // Device quad, src quad is always trivial
     unsigned fDomain : 1;
     unsigned fWideColor : 1;
     // Used to track whether fProxy is ref'ed or has a pending IO after finalize() is called.
     unsigned fFinalized : 1;
     unsigned fCanSkipAllocatorGather : 1;
-    unsigned fProxyCnt : 32 - 10;
+    unsigned fProxyCnt : 32 - 8;
     Proxy fProxies[1];
 
     static_assert(kGrQuadTypeCount <= 4, "GrQuadType does not fit in 2 bits");
diff --git a/src/gpu/text/GrAtlasManager.cpp b/src/gpu/text/GrAtlasManager.cpp
index 486127d..33f906c 100644
--- a/src/gpu/text/GrAtlasManager.cpp
+++ b/src/gpu/text/GrAtlasManager.cpp
@@ -17,7 +17,7 @@
             , fProxyProvider{proxyProvider}
             , fCaps{fProxyProvider->refCaps()}
             , fGlyphCache{glyphCache}
-            , fAtlasConfigs{fCaps->maxTextureSize(), maxTextureBytes} { }
+            , fAtlasConfig{fCaps->maxTextureSize(), maxTextureBytes} { }
 
 GrAtlasManager::~GrAtlasManager() = default;
 
@@ -77,8 +77,9 @@
                                                   GrGlyph* glyph,
                                                   GrDeferredUploadToken token) {
     SkASSERT(glyph);
-    updater->add(glyph->fID);
-    this->getAtlas(glyph->fMaskFormat)->setLastUseToken(glyph->fID, token);
+    if (updater->add(glyph->fID)) {
+        this->getAtlas(glyph->fMaskFormat)->setLastUseToken(glyph->fID, token);
+    }
 }
 
 #ifdef SK_DEBUG
@@ -169,7 +170,7 @@
     }
 
     // Set all the atlas sizes to 1x1 plot each.
-    new (&fAtlasConfigs) GrDrawOpAtlasConfig{};
+    new (&fAtlasConfig) GrDrawOpAtlasConfig{};
 }
 
 bool GrAtlasManager::initAtlas(GrMaskFormat format) {
@@ -177,14 +178,14 @@
     if (fAtlases[index] == nullptr) {
         GrPixelConfig config = mask_format_to_pixel_config(format);
         SkColorType colorType = mask_format_to_color_type(format);
-        SkISize atlasDimensions = fAtlasConfigs.atlasDimensions(format);
-        SkISize numPlots = fAtlasConfigs.numPlots(format);
+        SkISize atlasDimensions = fAtlasConfig.atlasDimensions(format);
+        SkISize plotDimensions = fAtlasConfig.plotDimensions(format);
 
         const GrBackendFormat format = fCaps->getBackendFormatFromColorType(colorType);
 
         fAtlases[index] = GrDrawOpAtlas::Make(
                 fProxyProvider, format, config, atlasDimensions.width(), atlasDimensions.height(),
-                numPlots.width(), numPlots.height(), fAllowMultitexturing,
+                plotDimensions.width(), plotDimensions.height(), fAllowMultitexturing,
                 &GrGlyphCache::HandleEviction, fGlyphCache);
         if (!fAtlases[index]) {
             return false;
diff --git a/src/gpu/text/GrAtlasManager.h b/src/gpu/text/GrAtlasManager.h
index e709c24..f8d4f3e 100644
--- a/src/gpu/text/GrAtlasManager.h
+++ b/src/gpu/text/GrAtlasManager.h
@@ -147,7 +147,7 @@
     GrProxyProvider* fProxyProvider;
     sk_sp<const GrCaps> fCaps;
     GrGlyphCache* fGlyphCache;
-    GrDrawOpAtlasConfig fAtlasConfigs;
+    GrDrawOpAtlasConfig fAtlasConfig;
 
     typedef GrOnFlushCallbackObject INHERITED;
 };
diff --git a/src/gpu/text/GrGlyphCache.cpp b/src/gpu/text/GrGlyphCache.cpp
index 7a298a4..7af769f 100644
--- a/src/gpu/text/GrGlyphCache.cpp
+++ b/src/gpu/text/GrGlyphCache.cpp
@@ -56,26 +56,6 @@
     }
 }
 
-static GrMaskFormat get_packed_glyph_mask_format(const SkGlyph& glyph) {
-    SkMask::Format format = static_cast<SkMask::Format>(glyph.fMaskFormat);
-    switch (format) {
-        case SkMask::kBW_Format:
-        case SkMask::kSDF_Format:
-            // fall through to kA8 -- we store BW and SDF glyphs in our 8-bit cache
-        case SkMask::kA8_Format:
-            return kA8_GrMaskFormat;
-        case SkMask::k3D_Format:
-            return kA8_GrMaskFormat; // ignore the mul and add planes, just use the mask
-        case SkMask::kLCD16_Format:
-            return kA565_GrMaskFormat;
-        case SkMask::kARGB32_Format:
-            return kARGB_GrMaskFormat;
-        default:
-            SkDEBUGFAIL("unsupported SkMask::Format");
-            return kA8_GrMaskFormat;
-    }
-}
-
 // expands each bit in a bitmask to 0 or ~0 of type INT_TYPE. Used to expand a BW glyph mask to
 // A8, RGB565, or RGBA8888.
 template <typename INT_TYPE>
@@ -113,7 +93,7 @@
     // Convert if the glyph uses a 565 mask format since it is using LCD text rendering but the
     // expected format is 8888 (will happen on macOS with Metal since that combination does not
     // support 565).
-    if (kA565_GrMaskFormat == get_packed_glyph_mask_format(glyph) &&
+    if (kA565_GrMaskFormat == GrGlyph::FormatFromSkGlyph(glyph) &&
         kARGB_GrMaskFormat == expectedMaskFormat) {
         const int a565Bpp = GrMaskFormatBytesPerPixel(kA565_GrMaskFormat);
         const int argbBpp = GrMaskFormatBytesPerPixel(kARGB_GrMaskFormat);
@@ -136,7 +116,7 @@
     // crbug:510931
     // Retrieving the image from the cache can actually change the mask format.  This case is very
     // uncommon so for now we just draw a clear box for these glyphs.
-    if (get_packed_glyph_mask_format(glyph) != expectedMaskFormat) {
+    if (GrGlyph::FormatFromSkGlyph(glyph) != expectedMaskFormat) {
         const int bpp = GrMaskFormatBytesPerPixel(expectedMaskFormat);
         for (int y = 0; y < height; y++) {
             sk_bzero(dst, width * bpp);
@@ -192,33 +172,14 @@
 GrTextStrike::GrTextStrike(const SkDescriptor& key)
     : fFontScalerKey(key) {}
 
-GrTextStrike::~GrTextStrike() {
-    SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
-    while (!iter.done()) {
-        (*iter).reset();
-        ++iter;
-    }
-}
-
-GrGlyph* GrTextStrike::generateGlyph(const SkGlyph& skGlyph, GrGlyph::PackedID packed,
-                                     SkGlyphCache* cache) {
-    SkIRect bounds;
-
-    // crbug:510931
-    // Retrieving the image from the cache can actually change the mask format.
-    cache->findImage(skGlyph);
-    bounds.setXYWH(skGlyph.fLeft, skGlyph.fTop, skGlyph.fWidth, skGlyph.fHeight);
-
-    GrMaskFormat format = get_packed_glyph_mask_format(skGlyph);
-
-    GrGlyph* glyph = fPool.make<GrGlyph>();
-    glyph->init(packed, bounds, format);
-    fCache.add(glyph);
-    return glyph;
+GrGlyph* GrTextStrike::generateGlyph(const SkGlyph& skGlyph) {
+    GrGlyph* grGlyph = fAlloc.make<GrGlyph>(skGlyph);
+    fCache.add(grGlyph);
+    return grGlyph;
 }
 
 void GrTextStrike::removeID(GrDrawOpAtlas::AtlasID id) {
-    SkTDynamicHash<GrGlyph, GrGlyph::PackedID>::Iter iter(&fCache);
+    SkTDynamicHash<GrGlyph, SkPackedGlyphID>::Iter iter(&fCache);
     while (!iter.done()) {
         if (id == (*iter).fID) {
             (*iter).fID = GrDrawOpAtlas::kInvalidAtlasID;
@@ -249,7 +210,7 @@
     int rowBytes = width * bytesPerPixel;
 
     size_t size = glyph->fBounds.area() * bytesPerPixel;
-    bool isSDFGlyph = GrGlyph::kDistance_MaskStyle == GrGlyph::UnpackMaskStyle(glyph->fPackedID);
+    bool isSDFGlyph = GrGlyph::kDistance_MaskStyle == glyph->maskStyle();
     bool addPad = isScaledGlyph && !isSDFGlyph;
     if (addPad) {
         width += 2;
diff --git a/src/gpu/text/GrGlyphCache.h b/src/gpu/text/GrGlyphCache.h
index 11c8120..585570c 100644
--- a/src/gpu/text/GrGlyphCache.h
+++ b/src/gpu/text/GrGlyphCache.h
@@ -29,13 +29,11 @@
 class GrTextStrike : public SkNVRefCnt<GrTextStrike> {
 public:
     GrTextStrike(const SkDescriptor& fontScalerKey);
-    ~GrTextStrike();
 
-    GrGlyph* getGlyph(const SkGlyph& skGlyph, GrGlyph::PackedID packed,
-                      SkGlyphCache* cache) {
-        GrGlyph* glyph = fCache.find(packed);
+    GrGlyph* getGlyph(const SkGlyph& skGlyph) {
+        GrGlyph* glyph = fCache.find(skGlyph.getPackedID());
         if (!glyph) {
-            glyph = this->generateGlyph(skGlyph, packed, cache);
+            glyph = this->generateGlyph(skGlyph);
         }
         return glyph;
     }
@@ -44,8 +42,7 @@
     // that the maskformat of the glyph differs from what we expect.  In these cases we will just
     // draw a clear square.
     // skbug:4143 crbug:510931
-    GrGlyph* getGlyph(GrGlyph::PackedID packed,
-                      GrMaskFormat expectedMaskFormat,
+    GrGlyph* getGlyph(SkPackedGlyphID packed,
                       SkGlyphCache* cache) {
         GrGlyph* glyph = fCache.find(packed);
         if (!glyph) {
@@ -53,8 +50,7 @@
             // potentially little benefit(ie, if the glyph is not in our font cache, then its not
             // in the atlas and we're going to be doing a texture upload anyways).
             const SkGlyph& skGlyph = GrToSkGlyph(cache, packed);
-            glyph = this->generateGlyph(skGlyph, packed, cache);
-            glyph->fMaskFormat = expectedMaskFormat;
+            glyph = this->generateGlyph(skGlyph);
         }
         return glyph;
     }
@@ -85,20 +81,18 @@
     static uint32_t Hash(const SkDescriptor& desc) { return desc.getChecksum(); }
 
 private:
-    SkTDynamicHash<GrGlyph, GrGlyph::PackedID> fCache;
+    SkTDynamicHash<GrGlyph, SkPackedGlyphID> fCache;
     SkAutoDescriptor fFontScalerKey;
-    SkArenaAlloc fPool{512};
+    SkArenaAlloc fAlloc{512};
 
     int fAtlasedGlyphs{0};
     bool fIsAbandoned{false};
 
-    static const SkGlyph& GrToSkGlyph(SkGlyphCache* cache, GrGlyph::PackedID id) {
-        return cache->getGlyphIDMetrics(GrGlyph::UnpackID(id),
-                                        GrGlyph::UnpackFixedX(id),
-                                        GrGlyph::UnpackFixedY(id));
+    static const SkGlyph& GrToSkGlyph(SkGlyphCache* cache, SkPackedGlyphID id) {
+        return cache->getGlyphIDMetrics(id.code(), id.getSubXFixed(), id.getSubYFixed());
     }
 
-    GrGlyph* generateGlyph(const SkGlyph&, GrGlyph::PackedID, SkGlyphCache*);
+    GrGlyph* generateGlyph(const SkGlyph&);
 
     friend class GrGlyphCache;
 };
diff --git a/src/gpu/text/GrTextBlob.cpp b/src/gpu/text/GrTextBlob.cpp
index 48aed7a..8db20b0 100644
--- a/src/gpu/text/GrTextBlob.cpp
+++ b/src/gpu/text/GrTextBlob.cpp
@@ -25,7 +25,7 @@
     return ((s + (N-1)) / N) * N;
 }
 
-sk_sp<GrTextBlob> GrTextBlob::Make(int glyphCount, int runCount) {
+sk_sp<GrTextBlob> GrTextBlob::Make(int glyphCount, int runCount, GrColor color) {
     // We allocate size for the GrTextBlob itself, plus size for the vertices array,
     // and size for the glyphIds array.
     size_t verticesCount = glyphCount * kVerticesPerGlyph * kMaxVASize;
@@ -52,26 +52,24 @@
 
     // Initialize runs
     for (int i = 0; i < runCount; i++) {
-        new (&blob->fRuns[i]) GrTextBlob::Run{blob.get()};
+        new (&blob->fRuns[i]) GrTextBlob::Run{blob.get(), color};
     }
     blob->fRunCountLimit = runCount;
     return blob;
 }
 
-SkExclusiveStrikePtr GrTextBlob::Run::setupCache(const SkPaint& skPaint,
-                                                 const SkSurfaceProps& props,
-                                                 SkScalerContextFlags scalerContextFlags,
-                                                 const SkMatrix& viewMatrix) {
-
-    // if we have an override descriptor for the run, then we should use that
-    SkAutoDescriptor* desc = fARGBFallbackDescriptor.get() ? fARGBFallbackDescriptor.get() : &fDescriptor;
-    SkScalerContextEffects effects;
-    SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
-        skPaint, props, scalerContextFlags, viewMatrix, desc, &effects);
-    fTypeface = SkPaintPriv::RefTypefaceOrDefault(skPaint);
+void GrTextBlob::Run::setupFont(const SkPaint& skPaint,
+                                const SkFont& skFont,
+                                const SkDescriptor& cacheDescriptor) {
+    fTypeface = SkFontPriv::RefTypefaceOrDefault(skFont);
+    SkScalerContextEffects effects{skPaint};
     fPathEffect = sk_ref_sp(effects.fPathEffect);
     fMaskFilter = sk_ref_sp(effects.fMaskFilter);
-    return SkStrikeCache::FindOrCreateStrikeExclusive(*desc->getDesc(), effects, *fTypeface);
+    // if we have an override descriptor for the run, then we should use that
+    SkAutoDescriptor* desc =
+            fARGBFallbackDescriptor.get() ? fARGBFallbackDescriptor.get() : &fDescriptor;
+    // Set up the descriptor for possible cache lookups during regen.
+    desc->reset(cacheDescriptor);
 }
 
 void GrTextBlob::Run::appendPathGlyph(const SkPath& path, SkPoint position,
@@ -468,8 +466,9 @@
 void GrTextBlob::SubRun::computeTranslation(const SkMatrix& viewMatrix,
                                                 SkScalar x, SkScalar y, SkScalar* transX,
                                                 SkScalar* transY) {
-    calculate_translation(!this->drawAsDistanceFields(), viewMatrix, x, y,
-                          fCurrentViewMatrix, fX, fY, transX, transY);
+    // Don't use the matrix to translate on distance field for fallback subruns.
+    calculate_translation(!this->drawAsDistanceFields() && !this->isFallback(), viewMatrix,
+            x, y, fCurrentViewMatrix, fX, fY, transX, transY);
     fCurrentViewMatrix = viewMatrix;
     fX = x;
     fY = y;
diff --git a/src/gpu/text/GrTextBlob.h b/src/gpu/text/GrTextBlob.h
index 9251fc8..6b85c5d 100644
--- a/src/gpu/text/GrTextBlob.h
+++ b/src/gpu/text/GrTextBlob.h
@@ -59,14 +59,13 @@
                                   const GrShaderCaps& shaderCaps,
                                   const GrTextContext::Options& options,
                                   const SkPaint& paint,
-                                  const SkPMColor4f& filteredColor,
                                   SkScalerContextFlags scalerContextFlags,
                                   const SkMatrix& viewMatrix,
                                   const SkSurfaceProps& props,
                                   const SkGlyphRunList& glyphRunList,
                                   SkGlyphRunListPainter* glyphPainter);
 
-    static sk_sp<GrTextBlob> Make(int glyphCount, int runCount);
+    static sk_sp<GrTextBlob> Make(int glyphCount, int runCount, GrColor color);
 
     /**
      * We currently force regeneration of a blob if old or new matrix differ in having perspective.
@@ -276,11 +275,16 @@
 
     class SubRun {
     public:
-        SubRun(Run* run, const SkAutoDescriptor& desc)
-            : fRun{run}
+        SubRun(Run* run, const SkAutoDescriptor& desc, GrColor color)
+            : fColor{color}
+            , fRun{run}
             , fDesc{desc} {}
 
-        void appendGlyph(GrTextBlob* blob, GrGlyph* glyph, SkRect dstRect);
+        // When used with emplace_back, this constructs a SubRun from the last SubRun in an array.
+        //SubRun(SkSTArray<1, SubRun>* subRunList)
+        //    : fColor{subRunList->fromBack(1).fColor} { }
+
+        void appendGlyph(GrGlyph* glyph, SkRect dstRect);
 
         // TODO when this object is more internal, drop the privacy
         void resetBulkUseToken() { fBulkUseToken.reset(); }
@@ -341,7 +345,8 @@
         bool hasWCoord() const { return fFlags.hasWCoord; }
         void setNeedsTransform(bool needsTransform) { fFlags.needsTransform = needsTransform; }
         bool needsTransform() const { return fFlags.needsTransform; }
-        void setFallback() {fFlags.argbFallback = true;}
+        void setFallback() { fFlags.argbFallback = true; }
+        bool isFallback() { return fFlags.argbFallback; }
 
         const SkDescriptor* desc() const { return fDesc.getDesc(); }
 
@@ -371,7 +376,6 @@
         const SkAutoDescriptor& fDesc;
     };  // SubRunInfo
 
-
     /*
      * Each Run inside of the blob can have its texture coordinates regenerated if required.
      * To determine if regeneration is necessary, fAtlasGeneration is used.  If there have been
@@ -396,10 +400,10 @@
      * would greatly increase the memory of these cached items.
      */
     struct Run {
-        explicit Run(GrTextBlob* blob)
-        : fBlob{blob} {
+        explicit Run(GrTextBlob* blob, GrColor color)
+        : fBlob{blob}, fColor{color} {
             // To ensure we always have one subrun, we push back a fresh run here
-            fSubRunInfo.emplace_back(this, fDescriptor);
+            fSubRunInfo.emplace_back(this, fDescriptor, color);
         }
 
         // sets the last subrun of runIndex to use w values
@@ -413,7 +417,7 @@
         SubRun* initARGBFallback() {
             fARGBFallbackDescriptor.reset(new SkAutoDescriptor{});
             // Push back a new subrun to fill and set the override descriptor
-            SubRun* subRun = this->pushBackSubRun(*fARGBFallbackDescriptor);
+            SubRun* subRun = this->pushBackSubRun(*fARGBFallbackDescriptor, fColor);
             subRun->setMaskFormat(kARGB_GrMaskFormat);
             subRun->setFallback();
             return subRun;
@@ -423,19 +427,27 @@
         void appendPathGlyph(
                 const SkPath& path, SkPoint position, SkScalar scale, bool preTransformed);
 
-        // Appends a glyph to the blob.  If the glyph is too large, the glyph will be appended
-        // as a path.
-        void appendGlyph(GrTextBlob* blob,
-                         const sk_sp<GrTextStrike>& strike,
-                         const SkGlyph& skGlyph, GrGlyph::MaskStyle maskStyle,
-                         SkPoint origin,
-                         const SkPMColor4f& color, SkGlyphCache* skGlyphCache,
-                         SkScalar textRatio, bool needsTransform);
+        // Append a glyph to the sub run taking care to switch the glyph if needed.
+        void switchSubRunIfNeededAndAppendGlyph(GrGlyph* glyph,
+                                                const sk_sp<GrTextStrike>& strike,
+                                                const SkRect& destRect,
+                                                bool needsTransform);
 
-        SkExclusiveStrikePtr setupCache(const SkPaint& skPaint,
-                                        const SkSurfaceProps& props,
-                                        SkScalerContextFlags scalerContextFlags,
-                                        const SkMatrix& viewMatrix);
+        // Used when the glyph in the cache has the CTM already applied, therefore no transform
+        // is needed during rendering.
+        void appendDeviceSpaceGlyph(const sk_sp<GrTextStrike>& strike,
+                                    const SkGlyph& skGlyph,
+                                    SkPoint origin);
+
+        // The glyph is oriented upright in the cache and needs to be transformed onto the screen.
+        void appendSourceSpaceGlyph(const sk_sp<GrTextStrike>& strike,
+                                    const SkGlyph& skGlyph,
+                                    SkPoint origin,
+                                    SkScalar textScale);
+
+        void setupFont(const SkPaint& skPaint,
+                       const SkFont& skFont,
+                       const SkDescriptor& skCache);
 
         void setRunFontAntiAlias(bool aa) {
             fAntiAlias = aa;
@@ -450,11 +462,13 @@
             subRun.setHasWCoord(hasWCoord);
         }
 
-        SubRun* pushBackSubRun(const SkAutoDescriptor& desc) {
+        SubRun* pushBackSubRun(const SkAutoDescriptor& desc, GrColor color) {
             // Forward glyph / vertex information to seed the new sub run
-            SubRun& newSubRun = fSubRunInfo.emplace_back(this, desc);
+            SubRun& newSubRun = fSubRunInfo.emplace_back(this, desc, color);
+
             const SubRun& prevSubRun = fSubRunInfo.fromBack(1);
 
+            // Forward glyph / vertex information to seed the new sub run
             newSubRun.setAsSuccessor(prevSubRun);
             return &newSubRun;
         }
@@ -496,6 +510,7 @@
         bool fInitialized{false};
 
         GrTextBlob* const fBlob;
+        GrColor fColor;
     };  // Run
 
     inline std::unique_ptr<GrAtlasTextOp> makeOp(
@@ -581,8 +596,7 @@
     bool regenerate(Result*);
 
 private:
-    template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
-    bool doRegen(Result*);
+    bool doRegen(Result*, bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs);
 
     GrResourceProvider* fResourceProvider;
     const SkMatrix& fViewMatrix;
diff --git a/src/gpu/text/GrTextBlobCache.h b/src/gpu/text/GrTextBlobCache.h
index fb991db..4e9b5ce 100644
--- a/src/gpu/text/GrTextBlobCache.h
+++ b/src/gpu/text/GrTextBlobCache.h
@@ -33,38 +33,16 @@
     }
     ~GrTextBlobCache();
 
-    // creates an uncached blob
-    sk_sp<GrTextBlob> makeBlob(int glyphCount, int runCount) {
-        return GrTextBlob::Make(glyphCount, runCount);
-    }
-
-    sk_sp<GrTextBlob> makeBlob(const SkTextBlob* blob) {
-        int glyphCount = 0;
-        int runCount = 0;
-        BlobGlyphCount(&glyphCount, &runCount, blob);
-        return GrTextBlob::Make(glyphCount, runCount);
-    }
-
-    sk_sp<GrTextBlob> makeCachedBlob(const SkTextBlob* blob,
-                                          const GrTextBlob::Key& key,
-                                          const SkMaskFilterBase::BlurRec& blurRec,
-                                          const SkPaint& paint) {
-        sk_sp<GrTextBlob> cacheBlob(this->makeBlob(blob));
-        cacheBlob->setupKey(key, blurRec, paint);
-        this->add(cacheBlob);
-        blob->notifyAddedToCache(fUniqueID);
-        return cacheBlob;
-    }
-
-    sk_sp<GrTextBlob> makeBlob(const SkGlyphRunList& glyphRunList) {
-        return GrTextBlob::Make(glyphRunList.totalGlyphCount(), glyphRunList.size());
+    sk_sp<GrTextBlob> makeBlob(const SkGlyphRunList& glyphRunList, GrColor color) {
+        return GrTextBlob::Make(glyphRunList.totalGlyphCount(), glyphRunList.size(), color);
     }
 
     sk_sp<GrTextBlob> makeCachedBlob(const SkGlyphRunList& glyphRunList,
                                      const GrTextBlob::Key& key,
                                      const SkMaskFilterBase::BlurRec& blurRec,
-                                     const SkPaint& paint) {
-        sk_sp<GrTextBlob> cacheBlob(makeBlob(glyphRunList));
+                                     const SkPaint& paint,
+                                     GrColor color) {
+        sk_sp<GrTextBlob> cacheBlob(makeBlob(glyphRunList, color));
         cacheBlob->setupKey(key, blurRec, paint);
         this->add(cacheBlob);
         glyphRunList.temporaryShuntBlobNotifyAddedToCache(fUniqueID);
diff --git a/src/gpu/text/GrTextBlobVertexRegenerator.cpp b/src/gpu/text/GrTextBlobVertexRegenerator.cpp
index 0cfddf6..2dcda1e 100644
--- a/src/gpu/text/GrTextBlobVertexRegenerator.cpp
+++ b/src/gpu/text/GrTextBlobVertexRegenerator.cpp
@@ -17,176 +17,103 @@
     kRegenPos   = 0x1,
     kRegenCol   = 0x2,
     kRegenTex   = 0x4,
-    kRegenGlyph = 0x8 | kRegenTex,  // we have to regenerate the texture coords when we regen glyphs
-
-    // combinations
-    kRegenPosCol = kRegenPos | kRegenCol,
-    kRegenPosTex = kRegenPos | kRegenTex,
-    kRegenPosTexGlyph = kRegenPos | kRegenGlyph,
-    kRegenPosColTex = kRegenPos | kRegenCol | kRegenTex,
-    kRegenPosColTexGlyph = kRegenPos | kRegenCol | kRegenGlyph,
-    kRegenColTex = kRegenCol | kRegenTex,
-    kRegenColTexGlyph = kRegenCol | kRegenGlyph,
+    kRegenGlyph = 0x8,
 };
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
-// A large template to handle regenerating the vertices of a textblob with as few branches as
-// possible
-template <bool regenPos, bool regenCol, bool regenTexCoords>
-inline void regen_vertices(char* vertex, const GrGlyph* glyph, size_t vertexStride,
-                           bool useDistanceFields, SkScalar transX, SkScalar transY,
-                           GrColor color) {
+
+static void regen_positions(char* vertex, size_t vertexStride, SkScalar transX, SkScalar transY) {
+    SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
+    for (int i = 0; i < 4; ++i) {
+        point->fX += transX;
+        point->fY += transY;
+        point = SkTAddOffset<SkPoint>(point, vertexStride);
+    }
+}
+
+static void regen_colors(char* vertex, size_t vertexStride, GrColor color) {
+    // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color
+    // vertices, hence vertexStride - sizeof(SkIPoint16)
+    size_t colorOffset = vertexStride - sizeof(SkIPoint16) - sizeof(GrColor);
+    GrColor* vcolor = reinterpret_cast<GrColor*>(vertex + colorOffset);
+    for (int i = 0; i < 4; ++i) {
+        *vcolor = color;
+        vcolor = SkTAddOffset<GrColor>(vcolor, vertexStride);
+    }
+}
+
+static void regen_texcoords(char* vertex, size_t vertexStride, const GrGlyph* glyph,
+                            bool useDistanceFields) {
+    // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color
+    // vertices, hence vertexStride - sizeof(SkIPoint16)
+    size_t texCoordOffset = vertexStride - sizeof(SkIPoint16);
+
     uint16_t u0, v0, u1, v1;
+    SkASSERT(glyph);
+    int width = glyph->fBounds.width();
+    int height = glyph->fBounds.height();
+
+    if (useDistanceFields) {
+        u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset;
+        v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset;
+        u1 = u0 + width - 2 * SK_DistanceFieldInset;
+        v1 = v0 + height - 2 * SK_DistanceFieldInset;
+    } else {
+        u0 = glyph->fAtlasLocation.fX;
+        v0 = glyph->fAtlasLocation.fY;
+        u1 = u0 + width;
+        v1 = v0 + height;
+    }
+    // We pack the 2bit page index in the low bit of the u and v texture coords
+    uint32_t pageIndex = glyph->pageIndex();
+    SkASSERT(pageIndex < 4);
+    uint16_t uBit = (pageIndex >> 1) & 0x1;
+    uint16_t vBit = pageIndex & 0x1;
+    u0 <<= 1;
+    u0 |= uBit;
+    v0 <<= 1;
+    v0 |= vBit;
+    u1 <<= 1;
+    u1 |= uBit;
+    v1 <<= 1;
+    v1 |= vBit;
+
+    uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
+    textureCoords[0] = u0;
+    textureCoords[1] = v0;
+    textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
+    textureCoords[0] = u0;
+    textureCoords[1] = v1;
+    textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
+    textureCoords[0] = u1;
+    textureCoords[1] = v0;
+    textureCoords = SkTAddOffset<uint16_t>(textureCoords, vertexStride);
+    textureCoords[0] = u1;
+    textureCoords[1] = v1;
+
 #ifdef DISPLAY_PAGE_INDEX
     // Enable this to visualize the page from which each glyph is being drawn.
     // Green Red Magenta Cyan -> 0 1 2 3; Black -> error
-    SkColor hackColor;
+    GrColor hackColor;
+    switch (pageIndex) {
+        case 0:
+            hackColor = GrColorPackRGBA(0, 255, 0, 255);
+            break;
+        case 1:
+            hackColor = GrColorPackRGBA(255, 0, 0, 255);;
+            break;
+        case 2:
+            hackColor = GrColorPackRGBA(255, 0, 255, 255);
+            break;
+        case 3:
+            hackColor = GrColorPackRGBA(0, 255, 255, 255);
+            break;
+        default:
+            hackColor = GrColorPackRGBA(0, 0, 0, 255);
+            break;
+    }
+    regen_colors(vertex, vertexStride, hackColor);
 #endif
-    if (regenTexCoords) {
-        SkASSERT(glyph);
-        int width = glyph->fBounds.width();
-        int height = glyph->fBounds.height();
-
-        if (useDistanceFields) {
-            u0 = glyph->fAtlasLocation.fX + SK_DistanceFieldInset;
-            v0 = glyph->fAtlasLocation.fY + SK_DistanceFieldInset;
-            u1 = u0 + width - 2 * SK_DistanceFieldInset;
-            v1 = v0 + height - 2 * SK_DistanceFieldInset;
-        } else {
-            u0 = glyph->fAtlasLocation.fX;
-            v0 = glyph->fAtlasLocation.fY;
-            u1 = u0 + width;
-            v1 = v0 + height;
-        }
-        // We pack the 2bit page index in the low bit of the u and v texture coords
-        uint32_t pageIndex = glyph->pageIndex();
-        SkASSERT(pageIndex < 4);
-        uint16_t uBit = (pageIndex >> 1) & 0x1;
-        uint16_t vBit = pageIndex & 0x1;
-        u0 <<= 1;
-        u0 |= uBit;
-        v0 <<= 1;
-        v0 |= vBit;
-        u1 <<= 1;
-        u1 |= uBit;
-        v1 <<= 1;
-        v1 |= vBit;
-#ifdef DISPLAY_PAGE_INDEX
-        switch (pageIndex) {
-            case 0:
-                hackColor = SK_ColorGREEN;
-                break;
-            case 1:
-                hackColor = SK_ColorRED;
-                break;
-            case 2:
-                hackColor = SK_ColorMAGENTA;
-                break;
-            case 3:
-                hackColor = SK_ColorCYAN;
-                break;
-            default:
-                hackColor = SK_ColorBLACK;
-                break;
-        }
-#endif
-    }
-
-    // This is a bit wonky, but sometimes we have LCD text, in which case we won't have color
-    // vertices, hence vertexStride - sizeof(SkIPoint16)
-    intptr_t texCoordOffset = vertexStride - sizeof(SkIPoint16);
-    intptr_t colorOffset = texCoordOffset - sizeof(GrColor);
-
-    // V0
-    if (regenPos) {
-        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
-        point->fX += transX;
-        point->fY += transY;
-    }
-
-    if (regenCol) {
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = color;
-    }
-
-    if (regenTexCoords) {
-        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
-        textureCoords[0] = u0;
-        textureCoords[1] = v0;
-#ifdef DISPLAY_PAGE_INDEX
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = hackColor;
-#endif
-    }
-    vertex += vertexStride;
-
-    // V1
-    if (regenPos) {
-        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
-        point->fX += transX;
-        point->fY += transY;
-    }
-
-    if (regenCol) {
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = color;
-    }
-
-    if (regenTexCoords) {
-        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
-        textureCoords[0] = u0;
-        textureCoords[1] = v1;
-#ifdef DISPLAY_PAGE_INDEX
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = hackColor;
-#endif
-    }
-    vertex += vertexStride;
-
-    // V2
-    if (regenPos) {
-        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
-        point->fX += transX;
-        point->fY += transY;
-    }
-
-    if (regenCol) {
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = color;
-    }
-
-    if (regenTexCoords) {
-        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
-        textureCoords[0] = u1;
-        textureCoords[1] = v0;
-#ifdef DISPLAY_PAGE_INDEX
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = hackColor;
-#endif
-    }
-    vertex += vertexStride;
-
-    // V3
-    if (regenPos) {
-        SkPoint* point = reinterpret_cast<SkPoint*>(vertex);
-        point->fX += transX;
-        point->fY += transY;
-    }
-
-    if (regenCol) {
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = color;
-    }
-
-    if (regenTexCoords) {
-        uint16_t* textureCoords = reinterpret_cast<uint16_t*>(vertex + texCoordOffset);
-        textureCoords[0] = u1;
-        textureCoords[1] = v1;
-#ifdef DISPLAY_PAGE_INDEX
-        SkColor* vcolor = reinterpret_cast<SkColor*>(vertex + colorOffset);
-        *vcolor = hackColor;
-#endif
-    }
 }
 
 GrTextBlob::VertexRegenerator::VertexRegenerator(GrResourceProvider* resourceProvider,
@@ -231,9 +158,10 @@
     }
 }
 
-template <bool regenPos, bool regenCol, bool regenTexCoords, bool regenGlyphs>
-bool GrTextBlob::VertexRegenerator::doRegen(GrTextBlob::VertexRegenerator::Result* result) {
-    static_assert(!regenGlyphs || regenTexCoords, "must regenTexCoords along regenGlyphs");
+bool GrTextBlob::VertexRegenerator::doRegen(GrTextBlob::VertexRegenerator::Result* result,
+                                            bool regenPos, bool regenCol, bool regenTexCoords,
+                                            bool regenGlyphs) {
+    SkASSERT(!regenGlyphs || regenTexCoords);
     sk_sp<GrTextStrike> strike;
     if (regenTexCoords) {
         fSubRun->resetBulkUseToken();
@@ -269,9 +197,8 @@
             if (regenGlyphs) {
                 // Get the id from the old glyph, and use the new strike to lookup
                 // the glyph.
-                GrGlyph::PackedID id = fBlob->fGlyphs[glyphOffset]->fPackedID;
-                fBlob->fGlyphs[glyphOffset] =
-                        strike->getGlyph(id, fSubRun->maskFormat(), fLazyCache->get());
+                SkPackedGlyphID id = fBlob->fGlyphs[glyphOffset]->fPackedID;
+                fBlob->fGlyphs[glyphOffset] = strike->getGlyph(id, fLazyCache->get());
                 SkASSERT(id == fBlob->fGlyphs[glyphOffset]->fPackedID);
             }
             glyph = fBlob->fGlyphs[glyphOffset];
@@ -298,9 +225,16 @@
                                                             tokenTracker->nextDrawToken());
         }
 
-        regen_vertices<regenPos, regenCol, regenTexCoords>(currVertex, glyph, vertexStride,
-                                                           fSubRun->drawAsDistanceFields(), fTransX,
-                                                           fTransY, fColor);
+        if (regenPos) {
+            regen_positions(currVertex, vertexStride, fTransX, fTransY);
+        }
+        if (regenCol) {
+            regen_colors(currVertex, vertexStride, fColor);
+        }
+        if (regenTexCoords) {
+            regen_texcoords(currVertex, vertexStride, glyph, fSubRun->drawAsDistanceFields());
+        }
+
         currVertex += vertexStride * GrAtlasTextOp::kVerticesPerGlyph;
         ++result->fGlyphsRegenerated;
         ++fCurrGlyph;
@@ -315,6 +249,11 @@
         fSubRun->setAtlasGeneration(fBrokenRun
                                     ? GrDrawOpAtlas::kInvalidAtlasGeneration
                                     : fFullAtlasManager->atlasGeneration(fSubRun->maskFormat()));
+    } else {
+        // For the non-texCoords case we need to ensure that we update the associated use tokens
+        fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(),
+                                           fUploadTarget->tokenTracker()->nextDrawToken(),
+                                           fSubRun->maskFormat());
     }
     return true;
 }
@@ -327,47 +266,27 @@
         fRegenFlags |= kRegenTex;
     }
 
-    switch (static_cast<RegenMask>(fRegenFlags)) {
-        case kRegenPos:
-            return this->doRegen<true, false, false, false>(result);
-        case kRegenCol:
-            return this->doRegen<false, true, false, false>(result);
-        case kRegenTex:
-            return this->doRegen<false, false, true, false>(result);
-        case kRegenGlyph:
-            return this->doRegen<false, false, true, true>(result);
+    if (fRegenFlags) {
+        return this->doRegen(result,
+                             fRegenFlags & kRegenPos,
+                             fRegenFlags & kRegenCol,
+                             fRegenFlags & kRegenTex,
+                             fRegenFlags & kRegenGlyph);
+    } else {
+        bool hasW = fSubRun->hasWCoord();
+        auto vertexStride = GetVertexStride(fSubRun->maskFormat(), hasW);
+        result->fFinished = true;
+        result->fGlyphsRegenerated = fSubRun->glyphCount() - fCurrGlyph;
+        result->fFirstVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
+                               fCurrGlyph * kVerticesPerGlyph * vertexStride;
+        fCurrGlyph = fSubRun->glyphCount();
 
-        // combinations
-        case kRegenPosCol:
-            return this->doRegen<true, true, false, false>(result);
-        case kRegenPosTex:
-            return this->doRegen<true, false, true, false>(result);
-        case kRegenPosTexGlyph:
-            return this->doRegen<true, false, true, true>(result);
-        case kRegenPosColTex:
-            return this->doRegen<true, true, true, false>(result);
-        case kRegenPosColTexGlyph:
-            return this->doRegen<true, true, true, true>(result);
-        case kRegenColTex:
-            return this->doRegen<false, true, true, false>(result);
-        case kRegenColTexGlyph:
-            return this->doRegen<false, true, true, true>(result);
-        case kNoRegen: {
-            bool hasW = fSubRun->hasWCoord();
-            auto vertexStride = GetVertexStride(fSubRun->maskFormat(), hasW);
-            result->fFinished = true;
-            result->fGlyphsRegenerated = fSubRun->glyphCount() - fCurrGlyph;
-            result->fFirstVertex = fBlob->fVertices + fSubRun->vertexStartIndex() +
-                                    fCurrGlyph * kVerticesPerGlyph * vertexStride;
-            fCurrGlyph = fSubRun->glyphCount();
-
-            // set use tokens for all of the glyphs in our subrun.  This is only valid if we
-            // have a valid atlas generation
-            fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(),
-                                               fUploadTarget->tokenTracker()->nextDrawToken(),
-                                               fSubRun->maskFormat());
-            return true;
-        }
+        // set use tokens for all of the glyphs in our subrun.  This is only valid if we
+        // have a valid atlas generation
+        fFullAtlasManager->setUseTokenBulk(*fSubRun->bulkUseToken(),
+                                           fUploadTarget->tokenTracker()->nextDrawToken(),
+                                           fSubRun->maskFormat());
+        return true;
     }
     SK_ABORT("Should not get here");
     return false;
diff --git a/src/gpu/text/GrTextContext.cpp b/src/gpu/text/GrTextContext.cpp
index 4e9b42b..01a4a4f 100644
--- a/src/gpu/text/GrTextContext.cpp
+++ b/src/gpu/text/GrTextContext.cpp
@@ -129,13 +129,14 @@
     return true;
 }
 
-void GrTextContext::InitDistanceFieldPaint(GrTextBlob* blob,
-                                           SkPaint* skPaint,
+void GrTextContext::InitDistanceFieldPaint(const SkScalar textSize,
                                            const SkMatrix& viewMatrix,
                                            const Options& options,
+                                           GrTextBlob* blob,
+                                           SkPaint* skPaint,
+                                           SkFont* skFont,
                                            SkScalar* textRatio,
                                            SkScalerContextFlags* flags) {
-    SkScalar textSize = skPaint->getTextSize();
     SkScalar scaledTextSize = textSize;
 
     if (viewMatrix.hasPerspective()) {
@@ -160,17 +161,17 @@
         dfMaskScaleFloor = options.fMinDistanceFieldFontSize;
         dfMaskScaleCeil = kSmallDFFontLimit;
         *textRatio = textSize / kSmallDFFontSize;
-        skPaint->setTextSize(SkIntToScalar(kSmallDFFontSize));
+        skFont->setSize(SkIntToScalar(kSmallDFFontSize));
     } else if (scaledTextSize <= kMediumDFFontLimit) {
         dfMaskScaleFloor = kSmallDFFontLimit;
         dfMaskScaleCeil = kMediumDFFontLimit;
         *textRatio = textSize / kMediumDFFontSize;
-        skPaint->setTextSize(SkIntToScalar(kMediumDFFontSize));
+        skFont->setSize(SkIntToScalar(kMediumDFFontSize));
     } else {
         dfMaskScaleFloor = kMediumDFFontLimit;
         dfMaskScaleCeil = options.fMaxDistanceFieldFontSize;
         *textRatio = textSize / kLargeDFFontSize;
-        skPaint->setTextSize(SkIntToScalar(kLargeDFFontSize));
+        skFont->setSize(SkIntToScalar(kLargeDFFontSize));
     }
 
     // Because there can be multiple runs in the blob, we want the overall maxMinScale, and
@@ -186,11 +187,10 @@
                                 dfMaskScaleCeil / scaledTextSize);
     }
 
-    skPaint->setAntiAlias(true);
-    skPaint->setLCDRenderText(false);
-    skPaint->setAutohinted(false);
-    skPaint->setHinting(kNormal_SkFontHinting);
-    skPaint->setSubpixelText(true);
+    skFont->setEdging(SkFont::Edging::kAntiAlias);
+    skFont->setForceAutoHinting(false);
+    skFont->setHinting(kNormal_SkFontHinting);
+    skFont->setSubpixel(true);
 
     skPaint->setMaskFilter(GrSDFMaskFilter::Make());
 
@@ -226,9 +226,14 @@
 
     SkPaint skPaint;
     skPaint.setColor(random->nextU());
-    skPaint.setLCDRenderText(random->nextBool());
-    skPaint.setAntiAlias(skPaint.isLCDRenderText() ? true : random->nextBool());
-    skPaint.setSubpixelText(random->nextBool());
+
+    SkFont font;
+    if (random->nextBool()) {
+        font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
+    } else {
+        font.setEdging(random->nextBool() ? SkFont::Edging::kAntiAlias : SkFont::Edging::kAlias);
+    }
+    font.setSubpixel(random->nextBool());
 
     const char* text = "The quick brown fox jumps over the lazy dog.";
 
@@ -240,7 +245,7 @@
     int yInt = (random->nextU() % kMaxTrans) * yPos;
 
     return gTextContext->createOp_TestingOnly(context, gTextContext.get(), rtc.get(),
-                                              skPaint, viewMatrix, text, xInt, yInt);
+                                              skPaint, font, viewMatrix, text, xInt, yInt);
 }
 
 #endif
diff --git a/src/gpu/text/GrTextContext.h b/src/gpu/text/GrTextContext.h
index 604f168..ff9043c 100644
--- a/src/gpu/text/GrTextContext.h
+++ b/src/gpu/text/GrTextContext.h
@@ -50,7 +50,7 @@
     std::unique_ptr<GrDrawOp> createOp_TestingOnly(GrContext*,
                                                    GrTextContext*,
                                                    GrRenderTargetContext*,
-                                                   const SkPaint&,
+                                                   const SkPaint&, const SkFont&,
                                                    const SkMatrix& viewMatrix,
                                                    const char* text,
                                                    int x,
@@ -61,10 +61,12 @@
                                         const SkSurfaceProps& props,
                                         bool contextSupportsDistanceFieldText,
                                         const Options& options);
-    static void InitDistanceFieldPaint(GrTextBlob* blob,
-                                       SkPaint* skPaint,
+    static void InitDistanceFieldPaint(SkScalar textSize,
                                        const SkMatrix& viewMatrix,
                                        const Options& options,
+                                       GrTextBlob* blob,
+                                       SkPaint* skPaint,
+                                       SkFont* skFont,
                                        SkScalar* textRatio,
                                        SkScalerContextFlags* flags);
 
diff --git a/src/gpu/vk/GrVkAMDMemoryAllocator.h b/src/gpu/vk/GrVkAMDMemoryAllocator.h
index ac15fa7..57d6076 100644
--- a/src/gpu/vk/GrVkAMDMemoryAllocator.h
+++ b/src/gpu/vk/GrVkAMDMemoryAllocator.h
@@ -8,7 +8,6 @@
 #ifndef GrVkAMDMemoryAllocator_DEFINED
 #define GrVkAMDMemoryAllocator_DEFINED
 
-#include "GrVkVulkan.h"
 
 #include "vk/GrVkMemoryAllocator.h"
 
diff --git a/src/gpu/vk/GrVkBuffer.cpp b/src/gpu/vk/GrVkBuffer.cpp
index b3c1d82..f8c120d 100644
--- a/src/gpu/vk/GrVkBuffer.cpp
+++ b/src/gpu/vk/GrVkBuffer.cpp
@@ -102,7 +102,7 @@
     gpu->addBufferMemoryBarrier(srcStageMask, dstStageMask, byRegion, &bufferMemoryBarrier);
 }
 
-void GrVkBuffer::Resource::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkBuffer::Resource::freeGPUData(GrVkGpu* gpu) const {
     SkASSERT(fBuffer);
     SkASSERT(fAlloc.fMemory);
     VK_CALL(gpu, DestroyBuffer(gpu->device(), fBuffer, nullptr));
diff --git a/src/gpu/vk/GrVkBuffer.h b/src/gpu/vk/GrVkBuffer.h
index 987c50b..f096921 100644
--- a/src/gpu/vk/GrVkBuffer.h
+++ b/src/gpu/vk/GrVkBuffer.h
@@ -8,8 +8,6 @@
 #ifndef GrVkBuffer_DEFINED
 #define GrVkBuffer_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrVkResource.h"
 #include "vk/GrVkTypes.h"
 
@@ -71,7 +69,7 @@
         Type               fType;
 
     private:
-        void freeGPUData(const GrVkGpu* gpu) const override;
+        void freeGPUData(GrVkGpu* gpu) const override;
 
         void onRecycle(GrVkGpu* gpu) const override { this->unref(gpu); }
 
diff --git a/src/gpu/vk/GrVkBufferView.cpp b/src/gpu/vk/GrVkBufferView.cpp
index b7e35c7..860cd68 100644
--- a/src/gpu/vk/GrVkBufferView.cpp
+++ b/src/gpu/vk/GrVkBufferView.cpp
@@ -33,6 +33,6 @@
     return new GrVkBufferView(bufferView);
 }
 
-void GrVkBufferView::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkBufferView::freeGPUData(GrVkGpu* gpu) const {
     GR_VK_CALL(gpu->vkInterface(), DestroyBufferView(gpu->device(), fBufferView, nullptr));
 }
diff --git a/src/gpu/vk/GrVkBufferView.h b/src/gpu/vk/GrVkBufferView.h
index 24f128f..ce3873a 100644
--- a/src/gpu/vk/GrVkBufferView.h
+++ b/src/gpu/vk/GrVkBufferView.h
@@ -8,10 +8,9 @@
 #ifndef GrVkBufferView_DEFINED
 #define GrVkBufferView_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrTypes.h"
 #include "GrVkResource.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkBufferView : public GrVkResource {
 public:
@@ -29,7 +28,7 @@
 private:
     GrVkBufferView(VkBufferView bufferView) : INHERITED(), fBufferView(bufferView) {}
 
-    void freeGPUData(const GrVkGpu* gpu) const override;
+    void freeGPUData(GrVkGpu* gpu) const override;
 
     VkBufferView  fBufferView;
 
diff --git a/src/gpu/vk/GrVkCaps.cpp b/src/gpu/vk/GrVkCaps.cpp
index 4797d39..ba4d1a6 100644
--- a/src/gpu/vk/GrVkCaps.cpp
+++ b/src/gpu/vk/GrVkCaps.cpp
@@ -158,8 +158,8 @@
     return true;
 }
 
-bool GrVkCaps::canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
-                              const SkIRect& srcRect, const SkIPoint& dstPoint) const {
+bool GrVkCaps::onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                                const SkIRect& srcRect, const SkIPoint& dstPoint) const {
     GrSurfaceOrigin dstOrigin = dst->origin();
     GrSurfaceOrigin srcOrigin = src->origin();
 
@@ -176,9 +176,21 @@
     int dstSampleCnt = 0;
     int srcSampleCnt = 0;
     if (const GrRenderTargetProxy* rtProxy = dst->asRenderTargetProxy()) {
+        // Copying to or from render targets that wrap a secondary command buffer is not allowed
+        // since they would require us to know the VkImage, which we don't have, as well as need us
+        // to stop and start the VkRenderPass which we don't have access to.
+        if (rtProxy->wrapsVkSecondaryCB()) {
+            return false;
+        }
         dstSampleCnt = rtProxy->numColorSamples();
     }
     if (const GrRenderTargetProxy* rtProxy = src->asRenderTargetProxy()) {
+        // Copying to or from render targets that wrap a secondary command buffer is not allowed
+        // since they would require us to know the VkImage, which we don't have, as well as need us
+        // to stop and start the VkRenderPass which we don't have access to.
+        if (rtProxy->wrapsVkSecondaryCB()) {
+            return false;
+        }
         srcSampleCnt = rtProxy->numColorSamples();
     }
     SkASSERT((dstSampleCnt > 0) == SkToBool(dst->asRenderTargetProxy()));
@@ -344,10 +356,6 @@
         fMustDoCopiesFromOrigin = true;
     }
 
-    if (kNvidia_VkVendor == properties.vendorID) {
-        fMustSubmitCommandsBeforeCopyOp = true;
-    }
-
 #if defined(SK_BUILD_FOR_WIN)
     if (kNvidia_VkVendor == properties.vendorID || kIntel_VkVendor == properties.vendorID) {
         fMustSleepOnTearDown = true;
@@ -449,7 +457,6 @@
     fMapBufferFlags = kCanMap_MapFlag | kSubset_MapFlag;
 
     fOversizedStencilSupport = true;
-    fSampleShadingSupport = features.features.sampleRateShading;
 
     if (extensions.hasExtension(VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME, 2) &&
         this->supportsPhysicalDeviceProperties2()) {
@@ -698,159 +705,138 @@
     return table[table.count() - 1];
 }
 
-bool GrVkCaps::surfaceSupportsWritePixels(const GrSurface* surface) const {
+bool GrVkCaps::onSurfaceSupportsWritePixels(const GrSurface* surface) const {
     if (auto rt = surface->asRenderTarget()) {
         return rt->numColorSamples() <= 1 && SkToBool(surface->asTexture());
     }
     return true;
 }
 
-bool validate_image_info(VkFormat format, SkColorType ct, GrPixelConfig* config) {
-    *config = kUnknown_GrPixelConfig;
+GrPixelConfig validate_image_info(VkFormat format, SkColorType ct, bool hasYcbcrConversion) {
+    if (format == VK_FORMAT_UNDEFINED) {
+        // If the format is undefined then it is only valid as an external image which requires that
+        // we have a valid VkYcbcrConversion.
+        if (hasYcbcrConversion) {
+            // We don't actually care what the color type or config are since we won't use those
+            // values for external textures, but since our code requires setting a config here
+            // just default it to RGBA.
+            return kRGBA_8888_GrPixelConfig;
+        } else {
+            return kUnknown_GrPixelConfig;
+        }
+    }
+
+    if (hasYcbcrConversion) {
+        // We only support having a ycbcr conversion for external images.
+        return kUnknown_GrPixelConfig;
+    }
 
     switch (ct) {
         case kUnknown_SkColorType:
-            return false;
+            break;
         case kAlpha_8_SkColorType:
             if (VK_FORMAT_R8_UNORM == format) {
-                *config = kAlpha_8_as_Red_GrPixelConfig;
+                return kAlpha_8_as_Red_GrPixelConfig;
             }
             break;
         case kRGB_565_SkColorType:
             if (VK_FORMAT_R5G6B5_UNORM_PACK16 == format) {
-                *config = kRGB_565_GrPixelConfig;
+                return kRGB_565_GrPixelConfig;
             }
             break;
         case kARGB_4444_SkColorType:
             if (VK_FORMAT_B4G4R4A4_UNORM_PACK16 == format) {
-                *config = kRGBA_4444_GrPixelConfig;
+                return kRGBA_4444_GrPixelConfig;
             }
             break;
         case kRGBA_8888_SkColorType:
             if (VK_FORMAT_R8G8B8A8_UNORM == format) {
-                *config = kRGBA_8888_GrPixelConfig;
+                return kRGBA_8888_GrPixelConfig;
             } else if (VK_FORMAT_R8G8B8A8_SRGB == format) {
-                *config = kSRGBA_8888_GrPixelConfig;
+                return kSRGBA_8888_GrPixelConfig;
             }
             break;
         case kRGB_888x_SkColorType:
             if (VK_FORMAT_R8G8B8_UNORM == format) {
-                *config = kRGB_888_GrPixelConfig;
+                return kRGB_888_GrPixelConfig;
             }
             break;
         case kBGRA_8888_SkColorType:
             if (VK_FORMAT_B8G8R8A8_UNORM == format) {
-                *config = kBGRA_8888_GrPixelConfig;
+                return kBGRA_8888_GrPixelConfig;
             } else if (VK_FORMAT_B8G8R8A8_SRGB == format) {
-                *config = kSBGRA_8888_GrPixelConfig;
+                return kSBGRA_8888_GrPixelConfig;
             }
             break;
         case kRGBA_1010102_SkColorType:
             if (VK_FORMAT_A2B10G10R10_UNORM_PACK32 == format) {
-                *config = kRGBA_1010102_GrPixelConfig;
+                return kRGBA_1010102_GrPixelConfig;
             }
             break;
         case kRGB_101010x_SkColorType:
-            return false;
+            return kUnknown_GrPixelConfig;
         case kGray_8_SkColorType:
             if (VK_FORMAT_R8_UNORM == format) {
-                *config = kGray_8_as_Red_GrPixelConfig;
+                return kGray_8_as_Red_GrPixelConfig;
             }
             break;
         case kRGBA_F16_SkColorType:
             if (VK_FORMAT_R16G16B16A16_SFLOAT == format) {
-                *config = kRGBA_half_GrPixelConfig;
+                return kRGBA_half_GrPixelConfig;
             }
             break;
         case kRGBA_F32_SkColorType:
             if (VK_FORMAT_R32G32B32A32_SFLOAT == format) {
-                *config = kRGBA_float_GrPixelConfig;
+                return kRGBA_float_GrPixelConfig;
             }
             break;
     }
 
-    return kUnknown_GrPixelConfig != *config;
+    return kUnknown_GrPixelConfig;
 }
 
-bool GrVkCaps::validateBackendTexture(const GrBackendTexture& tex, SkColorType ct,
-                                      GrPixelConfig* config) const {
-    GrVkImageInfo imageInfo;
-    if (!tex.getVkImageInfo(&imageInfo)) {
-        return false;
-    }
-
-    return validate_image_info(imageInfo.fFormat, ct, config);
-}
-
-bool GrVkCaps::validateBackendRenderTarget(const GrBackendRenderTarget& rt, SkColorType ct,
-                                           GrPixelConfig* config) const {
+GrPixelConfig GrVkCaps::validateBackendRenderTarget(const GrBackendRenderTarget& rt,
+                                                    SkColorType ct) const {
     GrVkImageInfo imageInfo;
     if (!rt.getVkImageInfo(&imageInfo)) {
-        return false;
+        return kUnknown_GrPixelConfig;
     }
-
-    return validate_image_info(imageInfo.fFormat, ct, config);
+    return validate_image_info(imageInfo.fFormat, ct, imageInfo.fYcbcrConversionInfo.isValid());
 }
 
-bool GrVkCaps::getConfigFromBackendFormat(const GrBackendFormat& format, SkColorType ct,
-                                          GrPixelConfig* config) const {
+GrPixelConfig GrVkCaps::getConfigFromBackendFormat(const GrBackendFormat& format,
+                                                   SkColorType ct) const {
     const VkFormat* vkFormat = format.getVkFormat();
-    if (!vkFormat) {
-        return false;
+    const GrVkYcbcrConversionInfo* ycbcrInfo = format.getVkYcbcrConversionInfo();
+    if (!vkFormat || !ycbcrInfo) {
+        return kUnknown_GrPixelConfig;
     }
-    return validate_image_info(*vkFormat, ct, config);
+    return validate_image_info(*vkFormat, ct, ycbcrInfo->isValid());
 }
 
-static bool get_yuva_config(VkFormat vkFormat, GrPixelConfig* config) {
-    *config = kUnknown_GrPixelConfig;
-
+static GrPixelConfig get_yuva_config(VkFormat vkFormat) {
     switch (vkFormat) {
-    case VK_FORMAT_R8_UNORM:
-        *config = kAlpha_8_as_Red_GrPixelConfig;
-        break;
-    case VK_FORMAT_R8G8B8A8_UNORM:
-        *config = kRGBA_8888_GrPixelConfig;
-        break;
-    case VK_FORMAT_R8G8B8_UNORM:
-        *config = kRGB_888_GrPixelConfig;
-        break;
-    case VK_FORMAT_B8G8R8A8_UNORM:
-        *config = kBGRA_8888_GrPixelConfig;
-        break;
-    default:
-        return false;
+        case VK_FORMAT_R8_UNORM:
+            return kAlpha_8_as_Red_GrPixelConfig;
+        case VK_FORMAT_R8G8B8A8_UNORM:
+            return kRGBA_8888_GrPixelConfig;
+        case VK_FORMAT_R8G8B8_UNORM:
+            return kRGB_888_GrPixelConfig;
+        case VK_FORMAT_R8G8_UNORM:
+            return kRG_88_GrPixelConfig;
+        case VK_FORMAT_B8G8R8A8_UNORM:
+            return kBGRA_8888_GrPixelConfig;
+        default:
+            return kUnknown_GrPixelConfig;
     }
-
-    return true;
 }
 
-bool GrVkCaps::getYUVAConfigFromBackendTexture(const GrBackendTexture& tex,
-                                               GrPixelConfig* config) const {
-    GrVkImageInfo imageInfo;
-    if (!tex.getVkImageInfo(&imageInfo)) {
-        return false;
-    }
-    return get_yuva_config(imageInfo.fFormat, config);
-}
-
-bool GrVkCaps::getYUVAConfigFromBackendFormat(const GrBackendFormat& format,
-                                              GrPixelConfig* config) const {
+GrPixelConfig GrVkCaps::getYUVAConfigFromBackendFormat(const GrBackendFormat& format) const {
     const VkFormat* vkFormat = format.getVkFormat();
     if (!vkFormat) {
-        return false;
+        return kUnknown_GrPixelConfig;
     }
-    return get_yuva_config(*vkFormat, config);
-}
-
-GrBackendFormat GrVkCaps::onCreateFormatFromBackendTexture(
-        const GrBackendTexture& backendTex) const {
-    GrVkImageInfo vkInfo;
-    SkAssertResult(backendTex.getVkImageInfo(&vkInfo));
-    if (vkInfo.fYcbcrConversionInfo.isValid()) {
-        SkASSERT(vkInfo.fFormat == VK_FORMAT_UNDEFINED);
-        return GrBackendFormat::MakeVk(vkInfo.fYcbcrConversionInfo);
-    }
-    return GrBackendFormat::MakeVk(vkInfo.fFormat);
+    return get_yuva_config(*vkFormat);
 }
 
 GrBackendFormat GrVkCaps::getBackendFormatFromGrColorType(GrColorType ct,
diff --git a/src/gpu/vk/GrVkCaps.h b/src/gpu/vk/GrVkCaps.h
index a20a104..79bd7d4 100644
--- a/src/gpu/vk/GrVkCaps.h
+++ b/src/gpu/vk/GrVkCaps.h
@@ -8,10 +8,9 @@
 #ifndef GrVkCaps_DEFINED
 #define GrVkCaps_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrCaps.h"
 #include "GrVkStencilAttachment.h"
+#include "vk/GrVkTypes.h"
 
 class GrShaderCaps;
 class GrVkExtensions;
@@ -43,7 +42,6 @@
     int getRenderTargetSampleCount(int requestedCount, GrPixelConfig config) const override;
     int maxRenderTargetSampleCount(GrPixelConfig config) const override;
 
-    bool surfaceSupportsWritePixels(const GrSurface*) const override;
     bool surfaceSupportsReadPixels(const GrSurface*) const override { return true; }
 
     bool isConfigTexturableLinearly(GrPixelConfig config) const {
@@ -73,13 +71,6 @@
         return fMustDoCopiesFromOrigin;
     }
 
-    // On Nvidia there is a current bug where we must the current command buffer before copy
-    // operations or else the copy will not happen. This includes copies, blits, resolves, and copy
-    // as draws.
-    bool mustSubmitCommandsBeforeCopyOp() const {
-        return fMustSubmitCommandsBeforeCopyOp;
-    }
-
     // Sometimes calls to QueueWaitIdle return before actually signalling the fences
     // on the command buffers even though they have completed. This causes an assert to fire when
     // destroying the command buffers. Therefore we add a sleep to make sure the fence signals.
@@ -151,23 +142,14 @@
     bool canCopyAsDraw(GrPixelConfig dstConfig, bool dstIsRenderable,
                        GrPixelConfig srcConfig, bool srcIsTextureable) const;
 
-    bool canCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
-                        const SkIRect& srcRect, const SkIPoint& dstPoint) const override;
-
     bool initDescForDstCopy(const GrRenderTargetProxy* src, GrSurfaceDesc* desc, GrSurfaceOrigin*,
                             bool* rectsMustMatch, bool* disallowSubrect) const override;
 
-    bool validateBackendTexture(const GrBackendTexture&, SkColorType,
-                                GrPixelConfig*) const override;
-    bool validateBackendRenderTarget(const GrBackendRenderTarget&, SkColorType,
-                                     GrPixelConfig*) const override;
+    GrPixelConfig validateBackendRenderTarget(const GrBackendRenderTarget&,
+                                              SkColorType) const override;
 
-    bool getConfigFromBackendFormat(const GrBackendFormat&, SkColorType,
-                                    GrPixelConfig*) const override;
-    bool getYUVAConfigFromBackendTexture(const GrBackendTexture&,
-                                         GrPixelConfig*) const override;
-    bool getYUVAConfigFromBackendFormat(const GrBackendFormat&,
-                                        GrPixelConfig*) const override;
+    GrPixelConfig getConfigFromBackendFormat(const GrBackendFormat&, SkColorType) const override;
+    GrPixelConfig getYUVAConfigFromBackendFormat(const GrBackendFormat&) const override;
 
     GrBackendFormat getBackendFormatFromGrColorType(GrColorType ct,
                                                     GrSRGBEncoded srgbEncoded) const override;
@@ -192,8 +174,6 @@
                     const GrVkExtensions&);
     void initShaderCaps(const VkPhysicalDeviceProperties&, const VkPhysicalDeviceFeatures2&);
 
-    GrBackendFormat onCreateFormatFromBackendTexture(const GrBackendTexture&) const override;
-
     void initConfigTable(const GrVkInterface*, VkPhysicalDevice, const VkPhysicalDeviceProperties&);
     void initStencilFormat(const GrVkInterface* iface, VkPhysicalDevice physDev);
 
@@ -201,6 +181,10 @@
 
     void applyDriverCorrectnessWorkarounds(const VkPhysicalDeviceProperties&);
 
+    bool onSurfaceSupportsWritePixels(const GrSurface*) const override;
+    bool onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
+                          const SkIRect& srcRect, const SkIPoint& dstPoint) const override;
+
     struct ConfigInfo {
         ConfigInfo() : fOptimalFlags(0), fLinearFlags(0) {}
 
@@ -229,7 +213,6 @@
     SkSTArray<1, GrVkYcbcrConversionInfo> fYcbcrInfos;
 
     bool fMustDoCopiesFromOrigin = false;
-    bool fMustSubmitCommandsBeforeCopyOp = false;
     bool fMustSleepOnTearDown = false;
     bool fNewCBOnPipelineChange = false;
     bool fShouldAlwaysUseDedicatedImageMemory = false;
diff --git a/src/gpu/vk/GrVkCommandBuffer.cpp b/src/gpu/vk/GrVkCommandBuffer.cpp
index 7b4a92d..09e8aba 100644
--- a/src/gpu/vk/GrVkCommandBuffer.cpp
+++ b/src/gpu/vk/GrVkCommandBuffer.cpp
@@ -7,6 +7,7 @@
 
 #include "GrVkCommandBuffer.h"
 
+#include "GrVkCommandPool.h"
 #include "GrVkGpu.h"
 #include "GrVkFramebuffer.h"
 #include "GrVkImage.h"
@@ -40,51 +41,66 @@
     }
 }
 
-void GrVkCommandBuffer::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkCommandBuffer::freeGPUData(GrVkGpu* gpu) const {
     SkASSERT(!fIsActive);
     for (int i = 0; i < fTrackedResources.count(); ++i) {
+        fTrackedResources[i]->notifyRemovedFromCommandBuffer();
         fTrackedResources[i]->unref(gpu);
     }
 
     for (int i = 0; i < fTrackedRecycledResources.count(); ++i) {
+        fTrackedRecycledResources[i]->notifyRemovedFromCommandBuffer();
         fTrackedRecycledResources[i]->recycle(const_cast<GrVkGpu*>(gpu));
     }
 
     for (int i = 0; i < fTrackedRecordingResources.count(); ++i) {
+        fTrackedRecordingResources[i]->notifyRemovedFromCommandBuffer();
         fTrackedRecordingResources[i]->unref(gpu);
     }
 
-    GR_VK_CALL(gpu->vkInterface(), FreeCommandBuffers(gpu->device(), gpu->cmdPool(),
-                                                      1, &fCmdBuffer));
+    if (!this->isWrapped()) {
+        GR_VK_CALL(gpu->vkInterface(), FreeCommandBuffers(gpu->device(), fCmdPool->vkCommandPool(),
+                                                          1, &fCmdBuffer));
+    }
 
     this->onFreeGPUData(gpu);
 }
 
 void GrVkCommandBuffer::abandonGPUData() const {
+    SkDEBUGCODE(fResourcesReleased = true;)
     for (int i = 0; i < fTrackedResources.count(); ++i) {
+        fTrackedResources[i]->notifyRemovedFromCommandBuffer();
         fTrackedResources[i]->unrefAndAbandon();
     }
 
     for (int i = 0; i < fTrackedRecycledResources.count(); ++i) {
+        fTrackedRecycledResources[i]->notifyRemovedFromCommandBuffer();
         // We don't recycle resources when abandoning them.
         fTrackedRecycledResources[i]->unrefAndAbandon();
     }
 
     for (int i = 0; i < fTrackedRecordingResources.count(); ++i) {
+        fTrackedRecordingResources[i]->notifyRemovedFromCommandBuffer();
         fTrackedRecordingResources[i]->unrefAndAbandon();
     }
+
+    this->onAbandonGPUData();
 }
 
-void GrVkCommandBuffer::reset(GrVkGpu* gpu) {
+void GrVkCommandBuffer::releaseResources(GrVkGpu* gpu) {
+    SkDEBUGCODE(fResourcesReleased = true;)
     SkASSERT(!fIsActive);
     for (int i = 0; i < fTrackedResources.count(); ++i) {
+        fTrackedResources[i]->notifyRemovedFromCommandBuffer();
         fTrackedResources[i]->unref(gpu);
     }
     for (int i = 0; i < fTrackedRecycledResources.count(); ++i) {
+        fTrackedRecycledResources[i]->notifyRemovedFromCommandBuffer();
         fTrackedRecycledResources[i]->recycle(const_cast<GrVkGpu*>(gpu));
     }
 
     for (int i = 0; i < fTrackedRecordingResources.count(); ++i) {
+        fTrackedRecordingResources[i]->notifyRemovedFromCommandBuffer();
         fTrackedRecordingResources[i]->unref(gpu);
     }
 
@@ -102,14 +118,9 @@
         fTrackedRecordingResources.rewind();
     }
 
-
     this->invalidateState();
 
-    // we will retain resources for later use
-    VkCommandBufferResetFlags flags = 0;
-    GR_VK_CALL(gpu->vkInterface(), ResetCommandBuffer(fCmdBuffer, flags));
-
-    this->onReset(gpu);
+    this->onReleaseResources(gpu);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -122,6 +133,7 @@
                                         bool byRegion,
                                         BarrierType barrierType,
                                         void* barrier) const {
+    SkASSERT(!this->isWrapped());
     SkASSERT(fIsActive);
     // For images we can have barriers inside of render passes but they require us to add more
     // support in subpasses which need self dependencies to have barriers inside them. Also, we can
@@ -358,11 +370,11 @@
 }
 
 GrVkPrimaryCommandBuffer* GrVkPrimaryCommandBuffer::Create(const GrVkGpu* gpu,
-                                                           VkCommandPool cmdPool) {
+                                                           GrVkCommandPool* cmdPool) {
     const VkCommandBufferAllocateInfo cmdInfo = {
         VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,   // sType
         nullptr,                                          // pNext
-        cmdPool,                                          // commandPool
+        cmdPool->vkCommandPool(),                         // commandPool
         VK_COMMAND_BUFFER_LEVEL_PRIMARY,                  // level
         1                                                 // bufferCount
     };
@@ -374,7 +386,7 @@
     if (err) {
         return nullptr;
     }
-    return new GrVkPrimaryCommandBuffer(cmdBuffer);
+    return new GrVkPrimaryCommandBuffer(cmdBuffer, cmdPool);
 }
 
 void GrVkPrimaryCommandBuffer::begin(const GrVkGpu* gpu) {
@@ -391,7 +403,7 @@
     fIsActive = true;
 }
 
-void GrVkPrimaryCommandBuffer::end(const GrVkGpu* gpu) {
+void GrVkPrimaryCommandBuffer::end(GrVkGpu* gpu) {
     SkASSERT(fIsActive);
     SkASSERT(!fActiveRenderPass);
     GR_VK_CALL_ERRCHECK(gpu->vkInterface(), EndCommandBuffer(fCmdBuffer));
@@ -445,6 +457,10 @@
 
 void GrVkPrimaryCommandBuffer::executeCommands(const GrVkGpu* gpu,
                                                GrVkSecondaryCommandBuffer* buffer) {
+    // The Vulkan spec allows secondary command buffers to be executed on a primary command buffer
+    // if the command pools both were created from were created with the same queue family. However,
+    // we currently always create them from the same pool.
+    SkASSERT(buffer->commandPool() == fCmdPool);
     SkASSERT(fIsActive);
     SkASSERT(!buffer->fIsActive);
     SkASSERT(fActiveRenderPass);
@@ -565,6 +581,7 @@
 }
 
 bool GrVkPrimaryCommandBuffer::finished(const GrVkGpu* gpu) const {
+    SkASSERT(!fIsActive);
     if (VK_NULL_HANDLE == fSubmitFence) {
         return true;
     }
@@ -586,9 +603,16 @@
     return false;
 }
 
-void GrVkPrimaryCommandBuffer::onReset(GrVkGpu* gpu) {
+void GrVkPrimaryCommandBuffer::onReleaseResources(GrVkGpu* gpu) {
     for (int i = 0; i < fSecondaryCommandBuffers.count(); ++i) {
-        gpu->resourceProvider().recycleSecondaryCommandBuffer(fSecondaryCommandBuffers[i]);
+        fSecondaryCommandBuffers[i]->releaseResources(gpu);
+    }
+}
+
+void GrVkPrimaryCommandBuffer::recycleSecondaryCommandBuffers() {
+    for (int i = 0; i < fSecondaryCommandBuffers.count(); ++i) {
+        SkASSERT(fSecondaryCommandBuffers[i]->commandPool() == fCmdPool);
+        fCmdPool->recycleSecondaryCommandBuffer(fSecondaryCommandBuffers[i]);
     }
     fSecondaryCommandBuffers.reset();
 }
@@ -790,12 +814,22 @@
                                                    regions));
 }
 
-void GrVkPrimaryCommandBuffer::onFreeGPUData(const GrVkGpu* gpu) const {
+void GrVkPrimaryCommandBuffer::onFreeGPUData(GrVkGpu* gpu) const {
     SkASSERT(!fActiveRenderPass);
     // Destroy the fence, if any
     if (VK_NULL_HANDLE != fSubmitFence) {
         GR_VK_CALL(gpu->vkInterface(), DestroyFence(gpu->device(), fSubmitFence, nullptr));
     }
+    for (GrVkSecondaryCommandBuffer* buffer : fSecondaryCommandBuffers) {
+        buffer->unref(gpu);
+    }
+}
+
+void GrVkPrimaryCommandBuffer::onAbandonGPUData() const {
+    SkASSERT(!fActiveRenderPass);
+    for (GrVkSecondaryCommandBuffer* buffer : fSecondaryCommandBuffers) {
+        buffer->unrefAndAbandon();
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -803,11 +837,12 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 GrVkSecondaryCommandBuffer* GrVkSecondaryCommandBuffer::Create(const GrVkGpu* gpu,
-                                                               VkCommandPool cmdPool) {
+                                                               GrVkCommandPool* cmdPool) {
+    SkASSERT(cmdPool);
     const VkCommandBufferAllocateInfo cmdInfo = {
         VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,   // sType
         nullptr,                                          // pNext
-        cmdPool,                                          // commandPool
+        cmdPool->vkCommandPool(),                         // commandPool
         VK_COMMAND_BUFFER_LEVEL_SECONDARY,                // level
         1                                                 // bufferCount
     };
@@ -819,9 +854,12 @@
     if (err) {
         return nullptr;
     }
-    return new GrVkSecondaryCommandBuffer(cmdBuffer);
+    return new GrVkSecondaryCommandBuffer(cmdBuffer, cmdPool);
 }
 
+GrVkSecondaryCommandBuffer* GrVkSecondaryCommandBuffer::Create(VkCommandBuffer cmdBuffer) {
+    return new GrVkSecondaryCommandBuffer(cmdBuffer, nullptr);
+}
 
 void GrVkSecondaryCommandBuffer::begin(const GrVkGpu* gpu, const GrVkFramebuffer* framebuffer,
                                        const GrVkRenderPass* compatibleRenderPass) {
@@ -829,34 +867,37 @@
     SkASSERT(compatibleRenderPass);
     fActiveRenderPass = compatibleRenderPass;
 
-    VkCommandBufferInheritanceInfo inheritanceInfo;
-    memset(&inheritanceInfo, 0, sizeof(VkCommandBufferInheritanceInfo));
-    inheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
-    inheritanceInfo.pNext = nullptr;
-    inheritanceInfo.renderPass = fActiveRenderPass->vkRenderPass();
-    inheritanceInfo.subpass = 0; // Currently only using 1 subpass for each render pass
-    inheritanceInfo.framebuffer = framebuffer ? framebuffer->framebuffer() : VK_NULL_HANDLE;
-    inheritanceInfo.occlusionQueryEnable = false;
-    inheritanceInfo.queryFlags = 0;
-    inheritanceInfo.pipelineStatistics = 0;
+    if (!this->isWrapped()) {
+        VkCommandBufferInheritanceInfo inheritanceInfo;
+        memset(&inheritanceInfo, 0, sizeof(VkCommandBufferInheritanceInfo));
+        inheritanceInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
+        inheritanceInfo.pNext = nullptr;
+        inheritanceInfo.renderPass = fActiveRenderPass->vkRenderPass();
+        inheritanceInfo.subpass = 0; // Currently only using 1 subpass for each render pass
+        inheritanceInfo.framebuffer = framebuffer ? framebuffer->framebuffer() : VK_NULL_HANDLE;
+        inheritanceInfo.occlusionQueryEnable = false;
+        inheritanceInfo.queryFlags = 0;
+        inheritanceInfo.pipelineStatistics = 0;
 
-    VkCommandBufferBeginInfo cmdBufferBeginInfo;
-    memset(&cmdBufferBeginInfo, 0, sizeof(VkCommandBufferBeginInfo));
-    cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
-    cmdBufferBeginInfo.pNext = nullptr;
-    cmdBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT |
-                               VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
-    cmdBufferBeginInfo.pInheritanceInfo = &inheritanceInfo;
+        VkCommandBufferBeginInfo cmdBufferBeginInfo;
+        memset(&cmdBufferBeginInfo, 0, sizeof(VkCommandBufferBeginInfo));
+        cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+        cmdBufferBeginInfo.pNext = nullptr;
+        cmdBufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT |
+                VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+        cmdBufferBeginInfo.pInheritanceInfo = &inheritanceInfo;
 
-    GR_VK_CALL_ERRCHECK(gpu->vkInterface(), BeginCommandBuffer(fCmdBuffer,
-                                                               &cmdBufferBeginInfo));
+        GR_VK_CALL_ERRCHECK(gpu->vkInterface(), BeginCommandBuffer(fCmdBuffer,
+                                                                   &cmdBufferBeginInfo));
+    }
     fIsActive = true;
 }
 
-void GrVkSecondaryCommandBuffer::end(const GrVkGpu* gpu) {
+void GrVkSecondaryCommandBuffer::end(GrVkGpu* gpu) {
     SkASSERT(fIsActive);
-    GR_VK_CALL_ERRCHECK(gpu->vkInterface(), EndCommandBuffer(fCmdBuffer));
+    if (!this->isWrapped()) {
+        GR_VK_CALL_ERRCHECK(gpu->vkInterface(), EndCommandBuffer(fCmdBuffer));
+    }
     this->invalidateState();
     fIsActive = false;
 }
-
diff --git a/src/gpu/vk/GrVkCommandBuffer.h b/src/gpu/vk/GrVkCommandBuffer.h
index 6f5da0e..ab11565 100644
--- a/src/gpu/vk/GrVkCommandBuffer.h
+++ b/src/gpu/vk/GrVkCommandBuffer.h
@@ -8,12 +8,11 @@
 #ifndef GrVkCommandBuffer_DEFINED
 #define GrVkCommandBuffer_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrVkGpu.h"
 #include "GrVkResource.h"
 #include "GrVkSemaphore.h"
 #include "GrVkUtil.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkBuffer;
 class GrVkFramebuffer;
@@ -71,6 +70,8 @@
                             uint32_t dynamicOffsetCount,
                             const uint32_t* dynamicOffsets);
 
+    GrVkCommandPool* commandPool() { return fCmdPool; }
+
     void setViewport(const GrVkGpu* gpu,
                      uint32_t firstViewport,
                      uint32_t viewportCount,
@@ -107,6 +108,7 @@
     // execution
     void addResource(const GrVkResource* resource) {
         resource->ref();
+        resource->notifyAddedToCommandBuffer();
         fTrackedResources.append(1, &resource);
     }
 
@@ -114,6 +116,7 @@
     // execution. When it is released, it will signal that the resource can be recycled for reuse.
     void addRecycledResource(const GrVkRecycledResource* resource) {
         resource->ref();
+        resource->notifyAddedToCommandBuffer();
         fTrackedRecycledResources.append(1, &resource);
     }
 
@@ -121,16 +124,19 @@
     // recording.
     void addRecordingResource(const GrVkResource* resource) {
         resource->ref();
+        resource->notifyAddedToCommandBuffer();
         fTrackedRecordingResources.append(1, &resource);
     }
 
-    void reset(GrVkGpu* gpu);
+    void releaseResources(GrVkGpu* gpu);
 
 protected:
-        GrVkCommandBuffer(VkCommandBuffer cmdBuffer, const GrVkRenderPass* rp = VK_NULL_HANDLE)
+        GrVkCommandBuffer(VkCommandBuffer cmdBuffer, GrVkCommandPool* cmdPool,
+                          const GrVkRenderPass* rp = nullptr)
             : fIsActive(false)
             , fActiveRenderPass(rp)
             , fCmdBuffer(cmdBuffer)
+            , fCmdPool(cmdPool)
             , fNumResets(0) {
             fTrackedResources.setReserve(kInitialTrackedResourcesCount);
             fTrackedRecycledResources.setReserve(kInitialTrackedResourcesCount);
@@ -138,6 +144,10 @@
             this->invalidateState();
         }
 
+        bool isWrapped() const {
+            return fCmdPool == nullptr;
+        }
+
         SkTDArray<const GrVkResource*>          fTrackedResources;
         SkTDArray<const GrVkRecycledResource*>  fTrackedRecycledResources;
         SkTDArray<const GrVkResource*>          fTrackedRecordingResources;
@@ -153,14 +163,19 @@
 
         VkCommandBuffer           fCmdBuffer;
 
+        // Raw pointer, not refcounted. The command pool controls the command buffer's lifespan, so
+        // it's guaranteed to outlive us.
+        GrVkCommandPool*          fCmdPool;
+
 private:
     static const int kInitialTrackedResourcesCount = 32;
 
-    void freeGPUData(const GrVkGpu* gpu) const override;
-    virtual void onFreeGPUData(const GrVkGpu* gpu) const = 0;
-    void abandonGPUData() const override;
+    void freeGPUData(GrVkGpu* gpu) const final override;
+    virtual void onFreeGPUData(GrVkGpu* gpu) const = 0;
+    void abandonGPUData() const final override;
+    virtual void onAbandonGPUData() const = 0;
 
-    virtual void onReset(GrVkGpu* gpu) {}
+    virtual void onReleaseResources(GrVkGpu* gpu) {}
 
     static constexpr uint32_t kMaxInputBuffers = 2;
 
@@ -178,6 +193,10 @@
     VkViewport fCachedViewport;
     VkRect2D   fCachedScissor;
     float      fCachedBlendConstant[4];
+
+#ifdef SK_DEBUG
+    mutable bool fResourcesReleased = false;
+#endif
 };
 
 class GrVkSecondaryCommandBuffer;
@@ -186,10 +205,10 @@
 public:
     ~GrVkPrimaryCommandBuffer() override;
 
-    static GrVkPrimaryCommandBuffer* Create(const GrVkGpu* gpu, VkCommandPool cmdPool);
+    static GrVkPrimaryCommandBuffer* Create(const GrVkGpu* gpu, GrVkCommandPool* cmdPool);
 
     void begin(const GrVkGpu* gpu);
-    void end(const GrVkGpu* gpu);
+    void end(GrVkGpu* gpu);
 
     // Begins render pass on this command buffer. The framebuffer from GrVkRenderTarget will be used
     // in the render pass.
@@ -283,6 +302,8 @@
                        SkTArray<GrVkSemaphore::Resource*>& waitSemaphores);
     bool finished(const GrVkGpu* gpu) const;
 
+    void recycleSecondaryCommandBuffers();
+
 #ifdef SK_TRACE_VK_RESOURCES
     void dumpInfo() const override {
         SkDebugf("GrVkPrimaryCommandBuffer: %d (%d refs)\n", fCmdBuffer, this->getRefCnt());
@@ -290,13 +311,15 @@
 #endif
 
 private:
-    explicit GrVkPrimaryCommandBuffer(VkCommandBuffer cmdBuffer)
-        : INHERITED(cmdBuffer)
+    explicit GrVkPrimaryCommandBuffer(VkCommandBuffer cmdBuffer, GrVkCommandPool* cmdPool)
+        : INHERITED(cmdBuffer, cmdPool)
         , fSubmitFence(VK_NULL_HANDLE) {}
 
-    void onFreeGPUData(const GrVkGpu* gpu) const override;
+    void onFreeGPUData(GrVkGpu* gpu) const override;
 
-    void onReset(GrVkGpu* gpu) override;
+    void onAbandonGPUData() const override;
+
+    void onReleaseResources(GrVkGpu* gpu) override;
 
     SkTArray<GrVkSecondaryCommandBuffer*, true> fSecondaryCommandBuffers;
     VkFence                                     fSubmitFence;
@@ -306,11 +329,13 @@
 
 class GrVkSecondaryCommandBuffer : public GrVkCommandBuffer {
 public:
-    static GrVkSecondaryCommandBuffer* Create(const GrVkGpu* gpu, VkCommandPool cmdPool);
+    static GrVkSecondaryCommandBuffer* Create(const GrVkGpu* gpu, GrVkCommandPool* cmdPool);
+    // Used for wrapping an external secondary command buffer.
+    static GrVkSecondaryCommandBuffer* Create(VkCommandBuffer externalSecondaryCB);
 
     void begin(const GrVkGpu* gpu, const GrVkFramebuffer* framebuffer,
                const GrVkRenderPass* compatibleRenderPass);
-    void end(const GrVkGpu* gpu);
+    void end(GrVkGpu* gpu);
 
     VkCommandBuffer vkCommandBuffer() { return fCmdBuffer; }
 
@@ -321,11 +346,12 @@
 #endif
 
 private:
-    explicit GrVkSecondaryCommandBuffer(VkCommandBuffer cmdBuffer)
-        : INHERITED(cmdBuffer) {
-    }
+    explicit GrVkSecondaryCommandBuffer(VkCommandBuffer cmdBuffer, GrVkCommandPool* cmdPool)
+        : INHERITED(cmdBuffer, cmdPool) {}
 
-    void onFreeGPUData(const GrVkGpu* gpu) const override {}
+    void onFreeGPUData(GrVkGpu* gpu) const override {}
+
+    void onAbandonGPUData() const override {}
 
     friend class GrVkPrimaryCommandBuffer;
 
diff --git a/src/gpu/vk/GrVkCommandPool.cpp b/src/gpu/vk/GrVkCommandPool.cpp
new file mode 100644
index 0000000..9d7c17e
--- /dev/null
+++ b/src/gpu/vk/GrVkCommandPool.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrVkCommandPool.h"
+
+#include "GrContextPriv.h"
+#include "GrVkCommandBuffer.h"
+#include "GrVkGpu.h"
+
+GrVkCommandPool* GrVkCommandPool::Create(const GrVkGpu* gpu) {
+    const VkCommandPoolCreateInfo cmdPoolInfo = {
+        VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,      // sType
+        nullptr,                                         // pNext
+        VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
+        VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, // CmdPoolCreateFlags
+        gpu->queueIndex(),                              // queueFamilyIndex
+    };
+    VkCommandPool pool;
+    GR_VK_CALL_ERRCHECK(gpu->vkInterface(), CreateCommandPool(gpu->device(), &cmdPoolInfo,
+                                                               nullptr, &pool));
+    return new GrVkCommandPool(gpu, pool);
+}
+
+GrVkCommandPool::GrVkCommandPool(const GrVkGpu* gpu, VkCommandPool commandPool)
+        : fCommandPool(commandPool) {
+    fPrimaryCommandBuffer = GrVkPrimaryCommandBuffer::Create(gpu, this);
+}
+
+GrVkSecondaryCommandBuffer* GrVkCommandPool::findOrCreateSecondaryCommandBuffer(GrVkGpu* gpu) {
+    if (fAvailableSecondaryBuffers.count()) {
+        GrVkSecondaryCommandBuffer* result = fAvailableSecondaryBuffers.back();
+        fAvailableSecondaryBuffers.pop_back();
+        return result;
+    }
+    return GrVkSecondaryCommandBuffer::Create(gpu, this);
+}
+
+void GrVkCommandPool::recycleSecondaryCommandBuffer(GrVkSecondaryCommandBuffer* buffer) {
+    SkASSERT(buffer->commandPool() == this);
+    fAvailableSecondaryBuffers.push_back(buffer);
+}
+
+void GrVkCommandPool::close() {
+    fOpen = false;
+}
+
+void GrVkCommandPool::reset(GrVkGpu* gpu) {
+    SkASSERT(!fOpen);
+    fOpen = true;
+    fPrimaryCommandBuffer->recycleSecondaryCommandBuffers();
+    GR_VK_CALL_ERRCHECK(gpu->vkInterface(), ResetCommandPool(gpu->device(), fCommandPool, 0));
+}
+
+void GrVkCommandPool::releaseResources(GrVkGpu* gpu) {
+    SkASSERT(!fOpen);
+    fPrimaryCommandBuffer->releaseResources(gpu);
+    for (GrVkSecondaryCommandBuffer* buffer : fAvailableSecondaryBuffers) {
+        buffer->releaseResources(gpu);
+    }
+}
+
+void GrVkCommandPool::abandonGPUData() const {
+    fPrimaryCommandBuffer->unrefAndAbandon();
+    for (GrVkSecondaryCommandBuffer* buffer : fAvailableSecondaryBuffers) {
+        SkASSERT(buffer->unique());
+        buffer->unrefAndAbandon();
+    }
+}
+
+void GrVkCommandPool::freeGPUData(GrVkGpu* gpu) const {
+    fPrimaryCommandBuffer->unref(gpu);
+    for (GrVkSecondaryCommandBuffer* buffer : fAvailableSecondaryBuffers) {
+        SkASSERT(buffer->unique());
+        buffer->unref(gpu);
+    }
+    if (fCommandPool != VK_NULL_HANDLE) {
+        GR_VK_CALL(gpu->vkInterface(),
+                   DestroyCommandPool(gpu->device(), fCommandPool, nullptr));
+    }
+}
diff --git a/src/gpu/vk/GrVkCommandPool.h b/src/gpu/vk/GrVkCommandPool.h
new file mode 100644
index 0000000..f65acfc
--- /dev/null
+++ b/src/gpu/vk/GrVkCommandPool.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrVkCommandPool_DEFINED
+#define GrVkCommandPool_DEFINED
+
+#include "GrVkGpuCommandBuffer.h"
+#include "GrVkInterface.h"
+#include "GrVkResource.h"
+#include "GrVkResourceProvider.h"
+
+class GrVkPrimaryCommandBuffer;
+class GrVkSecondaryCommandBuffer;
+class GrVkGpu;
+
+class GrVkCommandPool : public GrVkResource {
+public:
+    static GrVkCommandPool* Create(const GrVkGpu* gpu);
+
+    VkCommandPool vkCommandPool() const {
+        return fCommandPool;
+    }
+
+    void reset(GrVkGpu* gpu);
+
+    void releaseResources(GrVkGpu* gpu);
+
+    GrVkPrimaryCommandBuffer* getPrimaryCommandBuffer() { return fPrimaryCommandBuffer; }
+
+    GrVkSecondaryCommandBuffer* findOrCreateSecondaryCommandBuffer(GrVkGpu* gpu);
+
+    void recycleSecondaryCommandBuffer(GrVkSecondaryCommandBuffer* buffer);
+
+    // marks that we are finished with this command pool; it is not legal to continue creating or
+    // writing to command buffers in a closed pool
+    void close();
+
+    // returns true if close() has not been called
+    bool isOpen() const { return fOpen; }
+
+#ifdef SK_DEBUG
+    void dumpInfo() const override {
+        SkDebugf("GrVkCommandPool: %p (%d refs)\n", fCommandPool, this->getRefCnt());
+    }
+#endif
+
+private:
+    GrVkCommandPool() = delete;
+
+    GrVkCommandPool(const GrVkGpu* gpu, VkCommandPool commandPool);
+
+    void abandonGPUData() const override;
+
+    void freeGPUData(GrVkGpu* gpu) const override;
+
+    bool fOpen = true;
+
+    VkCommandPool fCommandPool;
+
+    GrVkPrimaryCommandBuffer* fPrimaryCommandBuffer;
+
+    // Array of available secondary command buffers that are not in flight
+    SkSTArray<4, GrVkSecondaryCommandBuffer*, true> fAvailableSecondaryBuffers;
+};
+
+#endif
diff --git a/src/gpu/vk/GrVkCopyManager.cpp b/src/gpu/vk/GrVkCopyManager.cpp
index fd91507..09fe216 100644
--- a/src/gpu/vk/GrVkCopyManager.cpp
+++ b/src/gpu/vk/GrVkCopyManager.cpp
@@ -13,6 +13,7 @@
 #include "GrSurface.h"
 #include "GrTexturePriv.h"
 #include "GrVkCommandBuffer.h"
+#include "GrVkCommandPool.h"
 #include "GrVkCopyPipeline.h"
 #include "GrVkDescriptorSet.h"
 #include "GrVkGpu.h"
@@ -75,16 +76,19 @@
     );
 
     SkSL::Program::Settings settings;
+    SkSL::String spirv;
     SkSL::Program::Inputs inputs;
     if (!GrCompileVkShaderModule(gpu, vertShaderText.c_str(), VK_SHADER_STAGE_VERTEX_BIT,
-                                 &fVertShaderModule, &fShaderStageInfo[0], settings, &inputs)) {
+                                 &fVertShaderModule, &fShaderStageInfo[0], settings, &spirv,
+                                 &inputs)) {
         this->destroyResources(gpu);
         return false;
     }
     SkASSERT(inputs.isEmpty());
 
     if (!GrCompileVkShaderModule(gpu, fragShaderText.c_str(), VK_SHADER_STAGE_FRAGMENT_BIT,
-                                 &fFragShaderModule, &fShaderStageInfo[1], settings, &inputs)) {
+                                 &fFragShaderModule, &fShaderStageInfo[1], settings, &spirv,
+                                 &inputs)) {
         this->destroyResources(gpu);
         return false;
     }
@@ -156,11 +160,6 @@
         return false;
     }
 
-    if (gpu->vkCaps().newCBOnPipelineChange()) {
-        // We bind a new pipeline here for the copy so we must start a new command buffer.
-        gpu->finishFlush(0, nullptr);
-    }
-
     GrVkRenderTarget* rt = static_cast<GrVkRenderTarget*>(dst->asRenderTarget());
     if (!rt) {
         return false;
@@ -292,11 +291,9 @@
 
     GrVkRenderTarget* texRT = static_cast<GrVkRenderTarget*>(srcTex->asRenderTarget());
     if (texRT) {
-        gpu->onResolveRenderTarget(texRT);
+        gpu->resolveRenderTargetNoFlush(texRT);
     }
 
-    GrVkPrimaryCommandBuffer* cmdBuffer = gpu->currentCommandBuffer();
-
     // TODO: Make tighter bounds and then adjust bounds for origin and granularity if we see
     //       any perf issues with using the whole bounds
     SkIRect bounds = SkIRect::MakeWH(rt->width(), rt->height());
@@ -355,9 +352,16 @@
 
     SkASSERT(renderPass->isCompatible(*rt->simpleRenderPass()));
 
+    GrVkPrimaryCommandBuffer* cmdBuffer = gpu->currentCommandBuffer();
+    cmdBuffer->beginRenderPass(gpu, renderPass, nullptr, *rt, bounds, true);
 
-    cmdBuffer->beginRenderPass(gpu, renderPass, nullptr, *rt, bounds, false);
-    cmdBuffer->bindPipeline(gpu, pipeline);
+    GrVkSecondaryCommandBuffer* secondary = gpu->cmdPool()->findOrCreateSecondaryCommandBuffer(gpu);
+    if (!secondary) {
+        return false;
+    }
+    secondary->begin(gpu, rt->framebuffer(), renderPass);
+
+    secondary->bindPipeline(gpu, pipeline);
 
     // Uniform DescriptorSet, Sampler DescriptorSet, and vertex shader uniformBuffer
     SkSTArray<3, const GrVkRecycledResource*> descriptorRecycledResources;
@@ -371,7 +375,7 @@
     descriptorResources.push_back(srcTex->textureView());
     descriptorResources.push_back(srcTex->resource());
 
-    cmdBuffer->bindDescriptorSets(gpu,
+    secondary->bindDescriptorSets(gpu,
                                   descriptorRecycledResources,
                                   descriptorResources,
                                   fPipelineLayout,
@@ -390,7 +394,7 @@
     viewport.height = SkIntToScalar(rt->height());
     viewport.minDepth = 0.0f;
     viewport.maxDepth = 1.0f;
-    cmdBuffer->setViewport(gpu, 0, 1, &viewport);
+    secondary->setViewport(gpu, 0, 1, &viewport);
 
     // We assume the scissor is not enabled so just set it to the whole RT
     VkRect2D scissor;
@@ -398,11 +402,14 @@
     scissor.extent.height = rt->height();
     scissor.offset.x = 0;
     scissor.offset.y = 0;
-    cmdBuffer->setScissor(gpu, 0, 1, &scissor);
+    secondary->setScissor(gpu, 0, 1, &scissor);
 
-    cmdBuffer->bindInputBuffer(gpu, 0, fVertexBuffer.get());
-    cmdBuffer->draw(gpu, 4, 1, 0, 0);
+    secondary->bindInputBuffer(gpu, 0, fVertexBuffer.get());
+    secondary->draw(gpu, 4, 1, 0, 0);
+    secondary->end(gpu);
+    cmdBuffer->executeCommands(gpu, secondary);
     cmdBuffer->endRenderPass(gpu);
+    secondary->unref(gpu);
 
     // Release all temp resources which should now be reffed by the cmd buffer
     pipeline->unref(gpu);
diff --git a/src/gpu/vk/GrVkCopyManager.h b/src/gpu/vk/GrVkCopyManager.h
index 426749a..716c87c 100644
--- a/src/gpu/vk/GrVkCopyManager.h
+++ b/src/gpu/vk/GrVkCopyManager.h
@@ -8,10 +8,9 @@
 #ifndef GrVkCopyManager_DEFINED
 #define GrVkCopyManager_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrTypes.h"
 #include "GrVkDescriptorSetManager.h"
+#include "vk/GrVkTypes.h"
 
 class GrSurface;
 class GrVkCopyPipeline;
diff --git a/src/gpu/vk/GrVkDescriptorPool.cpp b/src/gpu/vk/GrVkDescriptorPool.cpp
index b89145a..ffb6626 100644
--- a/src/gpu/vk/GrVkDescriptorPool.cpp
+++ b/src/gpu/vk/GrVkDescriptorPool.cpp
@@ -44,7 +44,7 @@
     GR_VK_CALL_ERRCHECK(gpu->vkInterface(), ResetDescriptorPool(gpu->device(), fDescPool, 0));
 }
 
-void GrVkDescriptorPool::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkDescriptorPool::freeGPUData(GrVkGpu* gpu) const {
     // Destroying the VkDescriptorPool will automatically free and delete any VkDescriptorSets
     // allocated from the pool.
     GR_VK_CALL(gpu->vkInterface(), DestroyDescriptorPool(gpu->device(), fDescPool, nullptr));
diff --git a/src/gpu/vk/GrVkDescriptorPool.h b/src/gpu/vk/GrVkDescriptorPool.h
index 8d77c20..08f7431 100644
--- a/src/gpu/vk/GrVkDescriptorPool.h
+++ b/src/gpu/vk/GrVkDescriptorPool.h
@@ -8,9 +8,8 @@
 #ifndef GrVkDescriptorPool_DEFINED
 #define GrVkDescriptorPool_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrVkResource.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkGpu;
 
@@ -39,7 +38,7 @@
 #endif
 
 private:
-    void freeGPUData(const GrVkGpu* gpu) const override;
+    void freeGPUData(GrVkGpu* gpu) const override;
 
     VkDescriptorType     fType;
     uint32_t             fCount;
diff --git a/src/gpu/vk/GrVkDescriptorSet.cpp b/src/gpu/vk/GrVkDescriptorSet.cpp
index 3cb1035..4a662aa 100644
--- a/src/gpu/vk/GrVkDescriptorSet.cpp
+++ b/src/gpu/vk/GrVkDescriptorSet.cpp
@@ -20,7 +20,7 @@
     fPool->ref();
 }
 
-void GrVkDescriptorSet::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkDescriptorSet::freeGPUData(GrVkGpu* gpu) const {
     fPool->unref(gpu);
 }
 
diff --git a/src/gpu/vk/GrVkDescriptorSet.h b/src/gpu/vk/GrVkDescriptorSet.h
index 56cfb45..2931d85 100644
--- a/src/gpu/vk/GrVkDescriptorSet.h
+++ b/src/gpu/vk/GrVkDescriptorSet.h
@@ -8,10 +8,9 @@
 #ifndef GrVkDescriptorSet_DEFINED
 #define GrVkDescriptorSet_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrVkDescriptorSetManager.h"
 #include "GrVkResource.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkDescriptorPool;
 class GrVkGpu;
@@ -33,7 +32,7 @@
 #endif
 
 private:
-    void freeGPUData(const GrVkGpu* gpu) const override;
+    void freeGPUData(GrVkGpu* gpu) const override;
     void abandonGPUData() const override;
     void onRecycle(GrVkGpu* gpu) const override;
 
diff --git a/src/gpu/vk/GrVkDescriptorSetManager.cpp b/src/gpu/vk/GrVkDescriptorSetManager.cpp
index ba0a060..84a2055 100644
--- a/src/gpu/vk/GrVkDescriptorSetManager.cpp
+++ b/src/gpu/vk/GrVkDescriptorSetManager.cpp
@@ -97,7 +97,7 @@
     fFreeSets.push_back(descSet);
 }
 
-void GrVkDescriptorSetManager::release(const GrVkGpu* gpu) {
+void GrVkDescriptorSetManager::release(GrVkGpu* gpu) {
     fPoolManager.freeGPUResources(gpu);
 
     for (int i = 0; i < fFreeSets.count(); ++i) {
@@ -310,7 +310,7 @@
                                                                    ds));
 }
 
-void GrVkDescriptorSetManager::DescriptorPoolManager::freeGPUResources(const GrVkGpu* gpu) {
+void GrVkDescriptorSetManager::DescriptorPoolManager::freeGPUResources(GrVkGpu* gpu) {
     if (fDescLayout) {
         GR_VK_CALL(gpu->vkInterface(), DestroyDescriptorSetLayout(gpu->device(), fDescLayout,
                                                                   nullptr));
diff --git a/src/gpu/vk/GrVkDescriptorSetManager.h b/src/gpu/vk/GrVkDescriptorSetManager.h
index f9ee7b2..b69fe36 100644
--- a/src/gpu/vk/GrVkDescriptorSetManager.h
+++ b/src/gpu/vk/GrVkDescriptorSetManager.h
@@ -8,13 +8,12 @@
 #ifndef GrVkDescriptorSetManager_DEFINED
 #define GrVkDescriptorSetManager_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrResourceHandle.h"
 #include "GrVkDescriptorPool.h"
 #include "GrVkSampler.h"
 #include "SkRefCnt.h"
 #include "SkTArray.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkDescriptorSet;
 class GrVkGpu;
@@ -37,7 +36,7 @@
     ~GrVkDescriptorSetManager() {}
 
     void abandon();
-    void release(const GrVkGpu* gpu);
+    void release(GrVkGpu* gpu);
 
     VkDescriptorSetLayout layout() const { return fPoolManager.fDescLayout; }
 
@@ -63,7 +62,7 @@
 
         void getNewDescriptorSet(GrVkGpu* gpu, VkDescriptorSet* ds);
 
-        void freeGPUResources(const GrVkGpu* gpu);
+        void freeGPUResources(GrVkGpu* gpu);
         void abandonGPUResources();
 
         VkDescriptorSetLayout  fDescLayout;
diff --git a/src/gpu/vk/GrVkFramebuffer.cpp b/src/gpu/vk/GrVkFramebuffer.cpp
index f9add63..11d10f95 100644
--- a/src/gpu/vk/GrVkFramebuffer.cpp
+++ b/src/gpu/vk/GrVkFramebuffer.cpp
@@ -51,7 +51,7 @@
     return new GrVkFramebuffer(framebuffer);
 }
 
-void GrVkFramebuffer::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkFramebuffer::freeGPUData(GrVkGpu* gpu) const {
     SkASSERT(fFramebuffer);
     GR_VK_CALL(gpu->vkInterface(), DestroyFramebuffer(gpu->device(), fFramebuffer, nullptr));
 }
diff --git a/src/gpu/vk/GrVkFramebuffer.h b/src/gpu/vk/GrVkFramebuffer.h
index b817c7a..0cb0e63 100644
--- a/src/gpu/vk/GrVkFramebuffer.h
+++ b/src/gpu/vk/GrVkFramebuffer.h
@@ -8,10 +8,9 @@
 #ifndef GrVkFramebuffer_DEFINED
 #define GrVkFramebuffer_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrTypes.h"
 #include "GrVkResource.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkGpu;
 class GrVkImageView;
@@ -39,7 +38,7 @@
     GrVkFramebuffer(const GrVkFramebuffer&);
     GrVkFramebuffer& operator=(const GrVkFramebuffer&);
 
-    void freeGPUData(const GrVkGpu* gpu) const override;
+    void freeGPUData(GrVkGpu* gpu) const override;
 
     VkFramebuffer  fFramebuffer;
 
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index ca48271..1bb1062 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -7,6 +7,7 @@
 
 #include "GrVkGpu.h"
 
+#include "GrContextPriv.h"
 #include "GrBackendSemaphore.h"
 #include "GrBackendSurface.h"
 #include "GrContextOptions.h"
@@ -18,6 +19,7 @@
 #include "GrTexturePriv.h"
 #include "GrVkAMDMemoryAllocator.h"
 #include "GrVkCommandBuffer.h"
+#include "GrVkCommandPool.h"
 #include "GrVkGpuCommandBuffer.h"
 #include "GrVkImage.h"
 #include "GrVkIndexBuffer.h"
@@ -168,31 +170,21 @@
     VK_CALL(GetPhysicalDeviceProperties(backendContext.fPhysicalDevice, &fPhysDevProps));
     VK_CALL(GetPhysicalDeviceMemoryProperties(backendContext.fPhysicalDevice, &fPhysDevMemProps));
 
-    const VkCommandPoolCreateInfo cmdPoolInfo = {
-        VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,      // sType
-        nullptr,                                         // pNext
-        VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
-        VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, // CmdPoolCreateFlags
-        backendContext.fGraphicsQueueIndex,              // queueFamilyIndex
-    };
-    GR_VK_CALL_ERRCHECK(this->vkInterface(), CreateCommandPool(fDevice, &cmdPoolInfo, nullptr,
-                                                               &fCmdPool));
-
-    // must call this after creating the CommandPool
     fResourceProvider.init();
-    fCurrentCmdBuffer = fResourceProvider.findOrCreatePrimaryCommandBuffer();
+
+    fCmdPool = fResourceProvider.findOrCreateCommandPool();
+    fCurrentCmdBuffer = fCmdPool->getPrimaryCommandBuffer();
     SkASSERT(fCurrentCmdBuffer);
     fCurrentCmdBuffer->begin(this);
 }
 
 void GrVkGpu::destroyResources() {
-    if (fCurrentCmdBuffer) {
-        fCurrentCmdBuffer->end(this);
-        fCurrentCmdBuffer->unref(this);
+    if (fCmdPool) {
+        fCmdPool->getPrimaryCommandBuffer()->end(this);
+        fCmdPool->close();
     }
 
     // wait for all commands to finish
-    fResourceProvider.checkCommandBuffers();
     VkResult res = VK_CALL(QueueWaitIdle(fQueue));
 
     // On windows, sometimes calls to QueueWaitIdle return before actually signalling the fences
@@ -213,6 +205,11 @@
     SkASSERT(VK_SUCCESS == res || VK_ERROR_DEVICE_LOST == res);
 #endif
 
+    if (fCmdPool) {
+        fCmdPool->unref(this);
+        fCmdPool = nullptr;
+    }
+
     for (int i = 0; i < fSemaphoresToWaitOn.count(); ++i) {
         fSemaphoresToWaitOn[i]->unref(this);
     }
@@ -229,10 +226,6 @@
     // must call this just before we destroy the command pool and VkDevice
     fResourceProvider.destroyResources(VK_ERROR_DEVICE_LOST == res);
 
-    if (fCmdPool != VK_NULL_HANDLE) {
-        VK_CALL(DestroyCommandPool(fDevice, fCmdPool, nullptr));
-    }
-
     fMemoryAllocator.reset();
 
     fQueue = VK_NULL_HANDLE;
@@ -254,8 +247,9 @@
         if (DisconnectType::kCleanup == type) {
             this->destroyResources();
         } else {
-            if (fCurrentCmdBuffer) {
-                fCurrentCmdBuffer->unrefAndAbandon();
+            if (fCmdPool) {
+                fCmdPool->unrefAndAbandon();
+                fCmdPool = nullptr;
             }
             for (int i = 0; i < fSemaphoresToWaitOn.count(); ++i) {
                 fSemaphoresToWaitOn[i]->unrefAndAbandon();
@@ -273,7 +267,6 @@
         fSemaphoresToWaitOn.reset();
         fSemaphoresToSignal.reset();
         fCurrentCmdBuffer = nullptr;
-        fCmdPool = VK_NULL_HANDLE;
         fDisconnected = true;
     }
 }
@@ -304,7 +297,7 @@
 void GrVkGpu::submitCommandBuffer(SyncQueue sync) {
     SkASSERT(fCurrentCmdBuffer);
     fCurrentCmdBuffer->end(this);
-
+    fCmdPool->close();
     fCurrentCmdBuffer->submitToQueue(this, fQueue, sync, fSemaphoresToSignal, fSemaphoresToWaitOn);
 
     // We must delete and drawables that have been waitint till submit for us to destroy.
@@ -319,13 +312,11 @@
     }
     fSemaphoresToSignal.reset();
 
+    // Release old command pool and create a new one
+    fCmdPool->unref(this);
     fResourceProvider.checkCommandBuffers();
-
-    // Release old command buffer and create a new one
-    fCurrentCmdBuffer->unref(this);
-    fCurrentCmdBuffer = fResourceProvider.findOrCreatePrimaryCommandBuffer();
-    SkASSERT(fCurrentCmdBuffer);
-
+    fCmdPool = fResourceProvider.findOrCreateCommandPool();
+    fCurrentCmdBuffer = fCmdPool->getPrimaryCommandBuffer();
     fCurrentCmdBuffer->begin(this);
 }
 
@@ -467,10 +458,6 @@
     SkASSERT(dst);
     SkASSERT(src && src->numColorSamples() > 1 && src->msaaImage());
 
-    if (this->vkCaps().mustSubmitCommandsBeforeCopyOp()) {
-        this->submitCommandBuffer(GrVkGpu::kSkip_SyncQueue);
-    }
-
     VkImageResolve resolveInfo;
     resolveInfo.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
     resolveInfo.srcOffset = {srcRect.fLeft, srcRect.fTop, 0};
@@ -888,7 +875,8 @@
 }
 
 sk_sp<GrTexture> GrVkGpu::onWrapBackendTexture(const GrBackendTexture& backendTex,
-                                               GrWrapOwnership ownership, bool purgeImmediately) {
+                                               GrWrapOwnership ownership, GrIOType ioType,
+                                               bool purgeImmediately) {
     GrVkImageInfo imageInfo;
     if (!backendTex.getVkImageInfo(&imageInfo)) {
         return nullptr;
@@ -907,7 +895,7 @@
 
     sk_sp<GrVkImageLayout> layout = backendTex.getGrVkImageLayout();
     SkASSERT(layout);
-    return GrVkTexture::MakeWrappedTexture(this, surfDesc, ownership, purgeImmediately,
+    return GrVkTexture::MakeWrappedTexture(this, surfDesc, ownership, ioType, purgeImmediately,
                                            imageInfo, std::move(layout));
 }
 
@@ -1003,6 +991,36 @@
     return GrVkRenderTarget::MakeWrappedRenderTarget(this, desc, imageInfo, std::move(layout));
 }
 
+sk_sp<GrRenderTarget> GrVkGpu::onWrapVulkanSecondaryCBAsRenderTarget(
+        const SkImageInfo& imageInfo, const GrVkDrawableInfo& vkInfo) {
+    int maxSize = this->caps()->maxTextureSize();
+    if (imageInfo.width() > maxSize || imageInfo.height() > maxSize) {
+        return nullptr;
+    }
+
+    GrBackendFormat backendFormat = GrBackendFormat::MakeVk(vkInfo.fFormat);
+    if (!backendFormat.isValid()) {
+        return nullptr;
+    }
+    GrPixelConfig config = this->caps()->getConfigFromBackendFormat(backendFormat,
+                                                                    imageInfo.colorType());
+    if (config == kUnknown_GrPixelConfig) {
+        return nullptr;
+    }
+
+    GrSurfaceDesc desc;
+    desc.fFlags = kRenderTarget_GrSurfaceFlag;
+    desc.fWidth = imageInfo.width();
+    desc.fHeight = imageInfo.height();
+    desc.fConfig = config;
+    desc.fSampleCnt = this->caps()->getRenderTargetSampleCount(1, config);
+    if (!desc.fSampleCnt) {
+        return nullptr;
+    }
+
+    return GrVkRenderTarget::MakeSecondaryCBRenderTarget(this, desc, vkInfo);
+}
+
 bool GrVkGpu::onRegenerateMipMapLevels(GrTexture* tex) {
     auto* vkTex = static_cast<GrVkTexture*>(tex);
     // don't do anything for linearly tiled textures (can't have mipmaps)
@@ -1019,10 +1037,6 @@
         return false;
     }
 
-    if (this->vkCaps().mustSubmitCommandsBeforeCopyOp()) {
-        this->submitCommandBuffer(kSkip_SyncQueue);
-    }
-
     int width = tex->width();
     int height = tex->height();
     VkImageBlit blitRegion;
@@ -1228,7 +1242,7 @@
     const VkCommandBufferAllocateInfo cmdInfo = {
         VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,   // sType
         nullptr,                                          // pNext
-        fCmdPool,                                         // commandPool
+        fCmdPool->vkCommandPool(),                        // commandPool
         VK_COMMAND_BUFFER_LEVEL_PRIMARY,                  // level
         1                                                 // bufferCount
     };
@@ -1297,7 +1311,7 @@
         GrVkMemory::FreeImageMemory(this, false, alloc);
         VK_CALL(DestroyImage(fDevice, image, nullptr));
         VK_CALL(EndCommandBuffer(cmdBuffer));
-        VK_CALL(FreeCommandBuffers(fDevice, fCmdPool, 1, &cmdBuffer));
+        VK_CALL(FreeCommandBuffers(fDevice, fCmdPool->vkCommandPool(), 1, &cmdBuffer));
         return false;
     }
 
@@ -1307,7 +1321,7 @@
         VK_CALL(DestroyImage(fDevice, image, nullptr));
         VK_CALL(DestroyBuffer(fDevice, buffer, nullptr));
         VK_CALL(EndCommandBuffer(cmdBuffer));
-        VK_CALL(FreeCommandBuffers(fDevice, fCmdPool, 1, &cmdBuffer));
+        VK_CALL(FreeCommandBuffers(fDevice, fCmdPool->vkCommandPool(), 1, &cmdBuffer));
         return false;
     }
 
@@ -1324,7 +1338,7 @@
             GrVkMemory::FreeBufferMemory(this, GrVkBuffer::kCopyRead_Type, bufferAlloc);
             VK_CALL(DestroyBuffer(fDevice, buffer, nullptr));
             VK_CALL(EndCommandBuffer(cmdBuffer));
-            VK_CALL(FreeCommandBuffers(fDevice, fCmdPool, 1, &cmdBuffer));
+            VK_CALL(FreeCommandBuffers(fDevice, fCmdPool->vkCommandPool(), 1, &cmdBuffer));
             return false;
         }
         currentWidth = SkTMax(1, currentWidth / 2);
@@ -1428,7 +1442,7 @@
         VK_CALL(DestroyImage(fDevice, image, nullptr));
         GrVkMemory::FreeBufferMemory(this, GrVkBuffer::kCopyRead_Type, bufferAlloc);
         VK_CALL(DestroyBuffer(fDevice, buffer, nullptr));
-        VK_CALL(FreeCommandBuffers(fDevice, fCmdPool, 1, &cmdBuffer));
+        VK_CALL(FreeCommandBuffers(fDevice, fCmdPool->vkCommandPool(), 1, &cmdBuffer));
         VK_CALL(DestroyFence(fDevice, fence, nullptr));
         SkDebugf("Fence failed to signal: %d\n", err);
         SK_ABORT("failing");
@@ -1440,7 +1454,7 @@
         GrVkMemory::FreeBufferMemory(this, GrVkBuffer::kCopyRead_Type, bufferAlloc);
         VK_CALL(DestroyBuffer(fDevice, buffer, nullptr));
     }
-    VK_CALL(FreeCommandBuffers(fDevice, fCmdPool, 1, &cmdBuffer));
+    VK_CALL(FreeCommandBuffers(fDevice, fCmdPool->vkCommandPool(), 1, &cmdBuffer));
     VK_CALL(DestroyFence(fDevice, fence, nullptr));
 
     info->fImage = image;
@@ -1760,6 +1774,15 @@
                             GrSurface* src, GrSurfaceOrigin srcOrigin,
                             const SkIRect& srcRect, const SkIPoint& dstPoint,
                             bool canDiscardOutsideDstRect) {
+#ifdef SK_DEBUG
+    if (GrVkRenderTarget* srcRT = static_cast<GrVkRenderTarget*>(src->asRenderTarget())) {
+        SkASSERT(!srcRT->wrapsSecondaryCommandBuffer());
+    }
+    if (GrVkRenderTarget* dstRT = static_cast<GrVkRenderTarget*>(dst->asRenderTarget())) {
+        SkASSERT(!dstRT->wrapsSecondaryCommandBuffer());
+    }
+#endif
+
     GrPixelConfig dstConfig = dst->config();
     GrPixelConfig srcConfig = src->config();
 
@@ -1772,10 +1795,6 @@
         return true;
     }
 
-    if (this->vkCaps().mustSubmitCommandsBeforeCopyOp()) {
-        this->submitCommandBuffer(GrVkGpu::kSkip_SyncQueue);
-    }
-
     if (this->vkCaps().canCopyAsDraw(dstConfig, SkToBool(dst->asRenderTarget()),
                                      srcConfig, SkToBool(src->asTexture()))) {
         SkAssertResult(fCopyManager.copySurfaceAsDraw(this, dst, dstOrigin, src, srcOrigin, srcRect,
@@ -1790,6 +1809,9 @@
     GrRenderTarget* dstRT = dst->asRenderTarget();
     if (dstRT) {
         GrVkRenderTarget* vkRT = static_cast<GrVkRenderTarget*>(dstRT);
+        if (vkRT->wrapsSecondaryCommandBuffer()) {
+            return false;
+        }
         dstImage = vkRT->numColorSamples() > 1 ? vkRT->msaaImage() : vkRT;
     } else {
         SkASSERT(dst->asTexture());
@@ -1830,6 +1852,12 @@
     GrVkImage* image = nullptr;
     GrVkRenderTarget* rt = static_cast<GrVkRenderTarget*>(surface->asRenderTarget());
     if (rt) {
+        // Reading from render targets that wrap a secondary command buffer is not allowed since
+        // it would require us to know the VkImage, which we don't have, as well as need us to
+        // stop and start the VkRenderPass which we don't have access to.
+        if (rt->wrapsSecondaryCommandBuffer()) {
+            return false;
+        }
         // resolve the render target if necessary
         switch (rt->getResolveType()) {
             case GrVkRenderTarget::kCantResolve_ResolveType:
@@ -1837,7 +1865,7 @@
             case GrVkRenderTarget::kAutoResolves_ResolveType:
                 break;
             case GrVkRenderTarget::kCanResolve_ResolveType:
-                this->internalResolveRenderTarget(rt, false);
+                this->resolveRenderTargetNoFlush(rt);
                 break;
             default:
                 SK_ABORT("Unknown resolve type");
@@ -2030,6 +2058,7 @@
                                            const VkClearValue* colorClear,
                                            GrVkRenderTarget* target, GrSurfaceOrigin origin,
                                            const SkIRect& bounds) {
+    SkASSERT (!target->wrapsSecondaryCommandBuffer());
     const SkIRect* pBounds = &bounds;
     SkIRect flippedBounds;
     if (kBottomLeft_GrSurfaceOrigin == origin) {
@@ -2123,7 +2152,7 @@
     return GrVkSemaphore::MakeWrapped(this, semaphore.vkSemaphore(), wrapType, ownership);
 }
 
-void GrVkGpu::insertSemaphore(sk_sp<GrSemaphore> semaphore, bool flush) {
+void GrVkGpu::insertSemaphore(sk_sp<GrSemaphore> semaphore) {
     GrVkSemaphore* vkSem = static_cast<GrVkSemaphore*>(semaphore.get());
 
     GrVkSemaphore::Resource* resource = vkSem->getResource();
@@ -2131,10 +2160,6 @@
         resource->ref();
         fSemaphoresToSignal.push_back(resource);
     }
-
-    if (flush) {
-        this->submitCommandBuffer(kSkip_SyncQueue);
-    }
 }
 
 void GrVkGpu::waitSemaphore(sk_sp<GrSemaphore> semaphore) {
@@ -2179,3 +2204,9 @@
     return sampler->uniqueID();
 }
 
+void GrVkGpu::storeVkPipelineCacheData() {
+    if (this->getContext()->contextPriv().getPersistentCache()) {
+        this->resourceProvider().storePipelineCacheData();
+    }
+}
+
diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h
index c195d3b..acd814c 100644
--- a/src/gpu/vk/GrVkGpu.h
+++ b/src/gpu/vk/GrVkGpu.h
@@ -8,10 +8,7 @@
 #ifndef GrVkGpu_DEFINED
 #define GrVkGpu_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrGpu.h"
-#include "vk/GrVkBackendContext.h"
 #include "GrVkCaps.h"
 #include "GrVkCopyManager.h"
 #include "GrVkIndexBuffer.h"
@@ -20,10 +17,13 @@
 #include "GrVkSemaphore.h"
 #include "GrVkVertexBuffer.h"
 #include "GrVkUtil.h"
+#include "vk/GrVkBackendContext.h"
+#include "vk/GrVkTypes.h"
 
 class GrPipeline;
 
 class GrVkBufferImpl;
+class GrVkCommandPool;
 class GrVkGpuRTCommandBuffer;
 class GrVkGpuTextureCommandBuffer;
 class GrVkMemoryAllocator;
@@ -56,11 +56,11 @@
     VkDevice device() const { return fDevice; }
     VkQueue  queue() const { return fQueue; }
     uint32_t  queueIndex() const { return fQueueIndex; }
-    VkCommandPool cmdPool() const { return fCmdPool; }
-    VkPhysicalDeviceProperties physicalDeviceProperties() const {
+    GrVkCommandPool* cmdPool() const { return fCmdPool; }
+    const VkPhysicalDeviceProperties& physicalDeviceProperties() const {
         return fPhysDevProps;
     }
-    VkPhysicalDeviceMemoryProperties physicalDeviceMemoryProperties() const {
+    const VkPhysicalDeviceMemoryProperties& physicalDeviceMemoryProperties() const {
         return fPhysDevMemProps;
     }
 
@@ -119,7 +119,14 @@
 
     bool onRegenerateMipMapLevels(GrTexture* tex) override;
 
+    void resolveRenderTargetNoFlush(GrRenderTarget* target) {
+        this->internalResolveRenderTarget(target, false);
+    }
+
     void onResolveRenderTarget(GrRenderTarget* target) override {
+        // This resolve is called when we are preparing an msaa surface for external I/O. It is
+        // called after flushing, so we need to make sure we submit the command buffer after doing
+        // the resolve so that the resolve actually happens.
         this->internalResolveRenderTarget(target, true);
     }
 
@@ -139,7 +146,7 @@
     sk_sp<GrSemaphore> wrapBackendSemaphore(const GrBackendSemaphore& semaphore,
                                             GrResourceProvider::SemaphoreWrapType wrapType,
                                             GrWrapOwnership ownership) override;
-    void insertSemaphore(sk_sp<GrSemaphore> semaphore, bool flush) override;
+    void insertSemaphore(sk_sp<GrSemaphore> semaphore) override;
     void waitSemaphore(sk_sp<GrSemaphore> semaphore) override;
 
     // These match the definitions in SkDrawable, from whence they came
@@ -159,6 +166,13 @@
     uint32_t getExtraSamplerKeyForProgram(const GrSamplerState&,
                                           const GrBackendFormat& format) override;
 
+    enum PersistentCacheKeyType : uint32_t {
+        kShader_PersistentCacheKeyType = 0,
+        kPipelineCache_PersistentCacheKeyType = 1,
+    };
+
+    void storeVkPipelineCacheData() override;
+
 private:
     GrVkGpu(GrContext*, const GrContextOptions&, const GrVkBackendContext&,
             sk_sp<const GrVkInterface>);
@@ -170,7 +184,7 @@
     sk_sp<GrTexture> onCreateTexture(const GrSurfaceDesc&, SkBudgeted, const GrMipLevel[],
                                      int mipLevelCount) override;
 
-    sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership,
+    sk_sp<GrTexture> onWrapBackendTexture(const GrBackendTexture&, GrWrapOwnership, GrIOType,
                                           bool purgeImmediately) override;
     sk_sp<GrTexture> onWrapRenderableBackendTexture(const GrBackendTexture&,
                                                     int sampleCnt,
@@ -180,6 +194,9 @@
     sk_sp<GrRenderTarget> onWrapBackendTextureAsRenderTarget(const GrBackendTexture&,
                                                              int sampleCnt) override;
 
+    sk_sp<GrRenderTarget> onWrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo&,
+                                                                const GrVkDrawableInfo&) override;
+
     GrBuffer* onCreateBuffer(size_t size, GrBufferType type, GrAccessPattern,
                              const void* data) override;
 
@@ -252,8 +269,10 @@
 
     // Created by GrVkGpu
     GrVkResourceProvider                                  fResourceProvider;
-    VkCommandPool                                         fCmdPool;
 
+    GrVkCommandPool*                                      fCmdPool;
+
+    // just a raw pointer; object's lifespan is managed by fCmdPool
     GrVkPrimaryCommandBuffer*                             fCurrentCmdBuffer;
 
     SkSTArray<1, GrVkSemaphore::Resource*>                fSemaphoresToWaitOn;
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.cpp b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
index 1d6d585..ef6ec6d 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.cpp
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.cpp
@@ -15,6 +15,7 @@
 #include "GrRenderTargetPriv.h"
 #include "GrTexturePriv.h"
 #include "GrVkCommandBuffer.h"
+#include "GrVkCommandPool.h"
 #include "GrVkGpu.h"
 #include "GrVkPipeline.h"
 #include "GrVkRenderPass.h"
@@ -122,10 +123,25 @@
         cbInfo.fLoadStoreState = LoadStoreState::kStartsWithDiscard;
     }
 
-    cbInfo.fCommandBuffers.push_back(fGpu->resourceProvider().findOrCreateSecondaryCommandBuffer());
+    cbInfo.fCommandBuffers.push_back(fGpu->cmdPool()->findOrCreateSecondaryCommandBuffer(fGpu));
     cbInfo.currentCmdBuf()->begin(fGpu, vkRT->framebuffer(), cbInfo.fRenderPass);
 }
 
+void GrVkGpuRTCommandBuffer::initWrapped() {
+    CommandBufferInfo& cbInfo = fCommandBufferInfos.push_back();
+    SkASSERT(fCommandBufferInfos.count() == 1);
+    fCurrentCmdInfo = 0;
+
+    GrVkRenderTarget* vkRT = static_cast<GrVkRenderTarget*>(fRenderTarget);
+    SkASSERT(vkRT->wrapsSecondaryCommandBuffer());
+    cbInfo.fRenderPass = vkRT->externalRenderPass();
+    cbInfo.fRenderPass->ref();
+
+    cbInfo.fBounds.setEmpty();
+    cbInfo.fCommandBuffers.push_back(vkRT->getExternalSecondaryCommandBuffer());
+    cbInfo.fCommandBuffers[0]->ref();
+    cbInfo.currentCmdBuf()->begin(fGpu, nullptr, cbInfo.fRenderPass);
+}
 
 GrVkGpuRTCommandBuffer::~GrVkGpuRTCommandBuffer() {
     this->reset();
@@ -175,6 +191,23 @@
             continue;
         }
 
+        // We don't want to actually submit the secondary command buffer if it is wrapped.
+        if (this->wrapsSecondaryCommandBuffer()) {
+            // If we have any sampled images set their layout now.
+            for (int j = 0; j < cbInfo.fSampledImages.count(); ++j) {
+                cbInfo.fSampledImages[j]->setImageLayout(fGpu,
+                                                         VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                                                         VK_ACCESS_SHADER_READ_BIT,
+                                                         VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
+                                                         false);
+            }
+
+            // There should have only been one secondary command buffer in the wrapped case so it is
+            // safe to just return here.
+            SkASSERT(fCommandBufferInfos.count() == 1);
+            return;
+        }
+
         // Make sure if we only have a discard load that we execute the discard on the whole image.
         // TODO: Once we improve our tracking of discards so that we never end up flushing a discard
         // call with no actually ops, remove this.
@@ -243,6 +276,11 @@
 
     this->INHERITED::set(rt, origin);
 
+    if (this->wrapsSecondaryCommandBuffer()) {
+        this->initWrapped();
+        return;
+    }
+
     fClearColor = colorInfo.fClearColor;
 
     get_vk_load_store_ops(colorInfo.fLoadOp, colorInfo.fStoreOp,
@@ -270,6 +308,11 @@
     fRenderTarget = nullptr;
 }
 
+bool GrVkGpuRTCommandBuffer::wrapsSecondaryCommandBuffer() const {
+    GrVkRenderTarget* vkRT = static_cast<GrVkRenderTarget*>(fRenderTarget);
+    return vkRT->wrapsSecondaryCommandBuffer();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 void GrVkGpuRTCommandBuffer::discard() {
@@ -461,7 +504,7 @@
 
     CommandBufferInfo& cbInfo = fCommandBufferInfos[fCurrentCmdInfo];
     cbInfo.currentCmdBuf()->end(fGpu);
-    cbInfo.fCommandBuffers.push_back(fGpu->resourceProvider().findOrCreateSecondaryCommandBuffer());
+    cbInfo.fCommandBuffers.push_back(fGpu->cmdPool()->findOrCreateSecondaryCommandBuffer(fGpu));
     cbInfo.currentCmdBuf()->begin(fGpu, vkRT->framebuffer(), cbInfo.fRenderPass);
 }
 
@@ -491,7 +534,7 @@
     }
     cbInfo.fLoadStoreState = LoadStoreState::kLoadAndStore;
 
-    cbInfo.fCommandBuffers.push_back(fGpu->resourceProvider().findOrCreateSecondaryCommandBuffer());
+    cbInfo.fCommandBuffers.push_back(fGpu->cmdPool()->findOrCreateSecondaryCommandBuffer(fGpu));
     // It shouldn't matter what we set the clear color to here since we will assume loading of the
     // attachment.
     memset(&cbInfo.fColorClearValue, 0, sizeof(VkClearValue));
@@ -676,7 +719,7 @@
         // We may need to resolve the texture first if it is also a render target
         GrVkRenderTarget* texRT = static_cast<GrVkRenderTarget*>(vkTexture->asRenderTarget());
         if (texRT) {
-            fGpu->onResolveRenderTarget(texRT);
+            fGpu->resolveRenderTargetNoFlush(texRT);
         }
 
         // Check if we need to regenerate any mip maps
@@ -802,7 +845,7 @@
     GrVkDrawableInfo vkInfo;
     vkInfo.fSecondaryCommandBuffer = cbInfo.currentCmdBuf()->vkCommandBuffer();
     vkInfo.fCompatibleRenderPass = cbInfo.fRenderPass->vkRenderPass();
-    SkAssertResult(cbInfo.fRenderPass->colorAttachmentIndex(&vkInfo.fImageAttachmentIndex));
+    SkAssertResult(cbInfo.fRenderPass->colorAttachmentIndex(&vkInfo.fColorAttachmentIndex));
     vkInfo.fFormat = targetImage->imageFormat();
     vkInfo.fDrawBounds = &bounds;
 
diff --git a/src/gpu/vk/GrVkGpuCommandBuffer.h b/src/gpu/vk/GrVkGpuCommandBuffer.h
index d9900f8..9dac586 100644
--- a/src/gpu/vk/GrVkGpuCommandBuffer.h
+++ b/src/gpu/vk/GrVkGpuCommandBuffer.h
@@ -8,14 +8,13 @@
 #ifndef GrVkGpuCommandBuffer_DEFINED
 #define GrVkGpuCommandBuffer_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrGpuCommandBuffer.h"
 
 #include "GrColor.h"
 #include "GrMesh.h"
 #include "GrTypes.h"
 #include "GrVkPipelineState.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkGpu;
 class GrVkImage;
@@ -88,6 +87,12 @@
 private:
     void init();
 
+    // Called instead of init when we are drawing to a render target that already wraps a secondary
+    // command buffer.
+    void initWrapped();
+
+    bool wrapsSecondaryCommandBuffer() const;
+
     GrGpu* gpu() override;
 
     // Bind vertex and index buffers
diff --git a/src/gpu/vk/GrVkImage.cpp b/src/gpu/vk/GrVkImage.cpp
index 67c19d0..ead8d8d 100644
--- a/src/gpu/vk/GrVkImage.cpp
+++ b/src/gpu/vk/GrVkImage.cpp
@@ -5,9 +5,11 @@
  * found in the LICENSE file.
  */
 
-#include "GrVkGpu.h"
 #include "GrVkImage.h"
+#include "GrGpuResourcePriv.h"
+#include "GrVkGpu.h"
 #include "GrVkMemory.h"
+#include "GrVkTexture.h"
 #include "GrVkUtil.h"
 
 #define VK_CALL(GPU, X) GR_VK_CALL(GPU->vkInterface(), X)
@@ -207,20 +209,17 @@
     GrVkMemory::FreeImageMemory(gpu, isLinear, info->fAlloc);
 }
 
-void GrVkImage::setNewResource(VkImage image, const GrVkAlloc& alloc, VkImageTiling tiling) {
-    fResource = new Resource(image, alloc, tiling);
-}
-
 GrVkImage::~GrVkImage() {
     // should have been released or abandoned first
     SkASSERT(!fResource);
 }
 
-void GrVkImage::releaseImage(const GrVkGpu* gpu) {
+void GrVkImage::releaseImage(GrVkGpu* gpu) {
     if (fInfo.fCurrentQueueFamily != fInitialQueueFamily) {
         this->setImageLayout(gpu, this->currentLayout(), 0, 0, false, true);
     }
     if (fResource) {
+        fResource->removeOwningTexture();
         fResource->unref(gpu);
         fResource = nullptr;
     }
@@ -228,24 +227,57 @@
 
 void GrVkImage::abandonImage() {
     if (fResource) {
+        fResource->removeOwningTexture();
         fResource->unrefAndAbandon();
         fResource = nullptr;
     }
 }
 
 void GrVkImage::setResourceRelease(sk_sp<GrReleaseProcHelper> releaseHelper) {
+    SkASSERT(fResource);
     // Forward the release proc on to GrVkImage::Resource
     fResource->setRelease(std::move(releaseHelper));
 }
 
-void GrVkImage::Resource::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkImage::Resource::freeGPUData(GrVkGpu* gpu) const {
     SkASSERT(!fReleaseHelper);
     VK_CALL(gpu, DestroyImage(gpu->device(), fImage, nullptr));
     bool isLinear = (VK_IMAGE_TILING_LINEAR == fImageTiling);
     GrVkMemory::FreeImageMemory(gpu, isLinear, fAlloc);
 }
 
-void GrVkImage::BorrowedResource::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkImage::Resource::setIdleProc(GrVkTexture* owner, GrTexture::IdleProc proc,
+                                      void* context) const {
+    fOwningTexture = owner;
+    fIdleProc = proc;
+    fIdleProcContext = context;
+}
+
+void GrVkImage::Resource::removeOwningTexture() const { fOwningTexture = nullptr; }
+
+void GrVkImage::Resource::notifyAddedToCommandBuffer() const { ++fNumCommandBufferOwners; }
+
+void GrVkImage::Resource::notifyRemovedFromCommandBuffer() const {
+    SkASSERT(fNumCommandBufferOwners);
+    if (--fNumCommandBufferOwners || !fIdleProc) {
+        return;
+    }
+    if (fOwningTexture && !fOwningTexture->resourcePriv().isPurgeable()) {
+        return;
+    }
+    fIdleProc(fIdleProcContext);
+    if (fOwningTexture) {
+        fOwningTexture->setIdleProc(nullptr, nullptr);
+        // Changing the texture's proc should change ours.
+        SkASSERT(!fIdleProc);
+        SkASSERT(!fIdleProc);
+    } else {
+        fIdleProc = nullptr;
+        fIdleProcContext = nullptr;
+    }
+}
+
+void GrVkImage::BorrowedResource::freeGPUData(GrVkGpu* gpu) const {
     this->invokeReleaseProc();
 }
 
diff --git a/src/gpu/vk/GrVkImage.h b/src/gpu/vk/GrVkImage.h
index 44c8ce2..0ce223c 100644
--- a/src/gpu/vk/GrVkImage.h
+++ b/src/gpu/vk/GrVkImage.h
@@ -8,18 +8,16 @@
 #ifndef GrVkImage_DEFINED
 #define GrVkImage_DEFINED
 
-#include "GrVkVulkan.h"
-
-#include "GrVkResource.h"
-
 #include "GrBackendSurface.h"
+#include "GrTexture.h"
 #include "GrTypesPriv.h"
 #include "GrVkImageLayout.h"
+#include "GrVkResource.h"
 #include "SkTypes.h"
-
 #include "vk/GrVkTypes.h"
 
 class GrVkGpu;
+class GrVkTexture;
 
 class GrVkImage : SkNoncopyable {
 private:
@@ -27,13 +25,15 @@
 
 public:
     GrVkImage(const GrVkImageInfo& info, sk_sp<GrVkImageLayout> layout,
-              GrBackendObjectOwnership ownership)
+              GrBackendObjectOwnership ownership, bool forSecondaryCB = false)
             : fInfo(info)
             , fInitialQueueFamily(info.fCurrentQueueFamily)
             , fLayout(std::move(layout))
             , fIsBorrowed(GrBackendObjectOwnership::kBorrowed == ownership) {
         SkASSERT(fLayout->getImageLayout() == fInfo.fImageLayout);
-        if (fIsBorrowed) {
+        if (forSecondaryCB) {
+            fResource = nullptr;
+        } else if (fIsBorrowed) {
             fResource = new BorrowedResource(info.fImage, info.fAlloc, info.fImageTiling);
         } else {
             fResource = new Resource(info.fImage, info.fAlloc, info.fImageTiling);
@@ -41,18 +41,37 @@
     }
     virtual ~GrVkImage();
 
-    VkImage image() const { return fInfo.fImage; }
-    const GrVkAlloc& alloc() const { return fInfo.fAlloc; }
+    VkImage image() const {
+        // Should only be called when we have a real fResource object, i.e. never when being used as
+        // a RT in an external secondary command buffer.
+        SkASSERT(fResource);
+        return fInfo.fImage;
+    }
+    const GrVkAlloc& alloc() const {
+        // Should only be called when we have a real fResource object, i.e. never when being used as
+        // a RT in an external secondary command buffer.
+        SkASSERT(fResource);
+        return fInfo.fAlloc;
+    }
     VkFormat imageFormat() const { return fInfo.fFormat; }
     GrBackendFormat getBackendFormat() const {
         return GrBackendFormat::MakeVk(this->imageFormat());
     }
     uint32_t mipLevels() const { return fInfo.fLevelCount; }
     const GrVkYcbcrConversionInfo& ycbcrConversionInfo() const {
+        // Should only be called when we have a real fResource object, i.e. never when being used as
+        // a RT in an external secondary command buffer.
+        SkASSERT(fResource);
         return fInfo.fYcbcrConversionInfo;
     }
-    const Resource* resource() const { return fResource; }
+    const Resource* resource() const {
+        SkASSERT(fResource);
+        return fResource;
+    }
     bool isLinearTiled() const {
+        // Should only be called when we have a real fResource object, i.e. never when being used as
+        // a RT in an external secondary command buffer.
+        SkASSERT(fResource);
         return SkToBool(VK_IMAGE_TILING_LINEAR == fInfo.fImageTiling);
     }
     bool isBorrowed() const { return fIsBorrowed; }
@@ -74,6 +93,9 @@
     // This is only used for mip map generation where we are manually changing the layouts as we
     // blit each layer, and then at the end need to update our tracking.
     void updateImageLayout(VkImageLayout newLayout) {
+        // Should only be called when we have a real fResource object, i.e. never when being used as
+        // a RT in an external secondary command buffer.
+        SkASSERT(fResource);
         fLayout->setImageLayout(newLayout);
     }
 
@@ -115,11 +137,9 @@
     static VkAccessFlags LayoutToSrcAccessMask(const VkImageLayout layout);
 
 protected:
-    void releaseImage(const GrVkGpu* gpu);
+    void releaseImage(GrVkGpu* gpu);
     void abandonImage();
 
-    void setNewResource(VkImage image, const GrVkAlloc& alloc, VkImageTiling tiling);
-
     GrVkImageInfo          fInfo;
     uint32_t               fInitialQueueFamily;
     sk_sp<GrVkImageLayout> fLayout;
@@ -151,11 +171,29 @@
         void setRelease(sk_sp<GrReleaseProcHelper> releaseHelper) {
             fReleaseHelper = std::move(releaseHelper);
         }
+
+        /**
+         * These are used to coordinate calling the idle proc between the GrVkTexture and the
+         * Resource. If the GrVkTexture becomes purgeable and if there are no command buffers
+         * referring to the Resource then it calls the proc. Otherwise, the Resource calls it
+         * when the last command buffer reference goes away and the GrVkTexture is purgeable.
+         */
+        void setIdleProc(GrVkTexture* owner, GrTexture::IdleProc, void* context) const;
+        void removeOwningTexture() const;
+
+        /**
+         * We track how many outstanding references this Resource has in command buffers and
+         * when the count reaches zero we call the idle proc.
+         */
+        void notifyAddedToCommandBuffer() const override;
+        void notifyRemovedFromCommandBuffer() const override;
+        bool isOwnedByCommandBuffer() const { return fNumCommandBufferOwners > 0; }
+
     protected:
         mutable sk_sp<GrReleaseProcHelper> fReleaseHelper;
 
     private:
-        void freeGPUData(const GrVkGpu* gpu) const override;
+        void freeGPUData(GrVkGpu* gpu) const override;
         void abandonGPUData() const override {
             SkASSERT(!fReleaseHelper);
         }
@@ -163,6 +201,10 @@
         VkImage        fImage;
         GrVkAlloc      fAlloc;
         VkImageTiling  fImageTiling;
+        mutable int fNumCommandBufferOwners = 0;
+        mutable GrTexture::IdleProc* fIdleProc = nullptr;
+        mutable void* fIdleProcContext = nullptr;
+        mutable GrVkTexture* fOwningTexture = nullptr;
 
         typedef GrVkResource INHERITED;
     };
@@ -182,7 +224,7 @@
             }
         }
 
-        void freeGPUData(const GrVkGpu* gpu) const override;
+        void freeGPUData(GrVkGpu* gpu) const override;
         void abandonGPUData() const override;
     };
 
diff --git a/src/gpu/vk/GrVkImageLayout.h b/src/gpu/vk/GrVkImageLayout.h
index 79d8ce3..8f21fa8 100644
--- a/src/gpu/vk/GrVkImageLayout.h
+++ b/src/gpu/vk/GrVkImageLayout.h
@@ -8,8 +8,6 @@
 #ifndef GrVkImageLayout_DEFINED
 #define GrVkImageLayout_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "SkRefCnt.h"
 #include "vk/GrVkTypes.h"
 
diff --git a/src/gpu/vk/GrVkImageView.cpp b/src/gpu/vk/GrVkImageView.cpp
index 342ce46..82c9457 100644
--- a/src/gpu/vk/GrVkImageView.cpp
+++ b/src/gpu/vk/GrVkImageView.cpp
@@ -61,7 +61,7 @@
     return new GrVkImageView(imageView, ycbcrConversion);
 }
 
-void GrVkImageView::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkImageView::freeGPUData(GrVkGpu* gpu) const {
     GR_VK_CALL(gpu->vkInterface(), DestroyImageView(gpu->device(), fImageView, nullptr));
 
     if (fYcbcrConversion) {
diff --git a/src/gpu/vk/GrVkImageView.h b/src/gpu/vk/GrVkImageView.h
index d953249..a9a8b43 100644
--- a/src/gpu/vk/GrVkImageView.h
+++ b/src/gpu/vk/GrVkImageView.h
@@ -8,10 +8,9 @@
 #ifndef GrVkImageView_DEFINED
 #define GrVkImageView_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrTypes.h"
 #include "GrVkResource.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkSamplerYcbcrConversion;
 struct GrVkYcbcrConversionInfo;
@@ -42,7 +41,7 @@
     GrVkImageView(const GrVkImageView&);
     GrVkImageView& operator=(const GrVkImageView&);
 
-    void freeGPUData(const GrVkGpu* gpu) const override;
+    void freeGPUData(GrVkGpu* gpu) const override;
     void abandonGPUData() const override;
 
     VkImageView  fImageView;
diff --git a/src/gpu/vk/GrVkIndexBuffer.h b/src/gpu/vk/GrVkIndexBuffer.h
index c6290c4..cd945ac 100644
--- a/src/gpu/vk/GrVkIndexBuffer.h
+++ b/src/gpu/vk/GrVkIndexBuffer.h
@@ -8,8 +8,6 @@
 #ifndef GrVkIndexBuffer_DEFINED
 #define GrVkIndexBuffer_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrBuffer.h"
 #include "GrVkBuffer.h"
 
diff --git a/src/gpu/vk/GrVkInterface.h b/src/gpu/vk/GrVkInterface.h
index 67e8f28..5168ab2 100644
--- a/src/gpu/vk/GrVkInterface.h
+++ b/src/gpu/vk/GrVkInterface.h
@@ -8,8 +8,6 @@
 #ifndef GrVkInterface_DEFINED
 #define GrVkInterface_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "SkRefCnt.h"
 
 #include "vk/GrVkBackendContext.h"
diff --git a/src/gpu/vk/GrVkMemory.h b/src/gpu/vk/GrVkMemory.h
index d68f9a4..a1cdf1c 100644
--- a/src/gpu/vk/GrVkMemory.h
+++ b/src/gpu/vk/GrVkMemory.h
@@ -8,8 +8,6 @@
 #ifndef GrVkMemory_DEFINED
 #define GrVkMemory_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrVkBuffer.h"
 #include "SkTArray.h"
 #include "vk/GrVkTypes.h"
diff --git a/src/gpu/vk/GrVkPipeline.cpp b/src/gpu/vk/GrVkPipeline.cpp
index 1a47c85..90d5e44 100644
--- a/src/gpu/vk/GrVkPipeline.cpp
+++ b/src/gpu/vk/GrVkPipeline.cpp
@@ -287,10 +287,8 @@
     int numSamples = pipeline.proxy()->numColorSamples();
     SkAssertResult(GrSampleCountToVkSampleCount(numSamples,
                    &multisampleInfo->rasterizationSamples));
-    float sampleShading = primProc.getSampleShading();
-    SkASSERT(sampleShading == 0.0f || caps->sampleShadingSupport());
-    multisampleInfo->sampleShadingEnable = sampleShading > 0.0f;
-    multisampleInfo->minSampleShading = sampleShading;
+    multisampleInfo->sampleShadingEnable = VK_FALSE;
+    multisampleInfo->minSampleShading = 0.0f;
     multisampleInfo->pSampleMask = nullptr;
     multisampleInfo->alphaToCoverageEnable = VK_FALSE;
     multisampleInfo->alphaToOneEnable = VK_FALSE;
@@ -571,7 +569,7 @@
     return new GrVkPipeline(vkPipeline);
 }
 
-void GrVkPipeline::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkPipeline::freeGPUData(GrVkGpu* gpu) const {
     GR_VK_CALL(gpu->vkInterface(), DestroyPipeline(gpu->device(), fPipeline, nullptr));
 }
 
diff --git a/src/gpu/vk/GrVkPipeline.h b/src/gpu/vk/GrVkPipeline.h
index 579ad75..e02fd7c 100644
--- a/src/gpu/vk/GrVkPipeline.h
+++ b/src/gpu/vk/GrVkPipeline.h
@@ -8,10 +8,9 @@
 #ifndef GrVkPipeline_DEFINED
 #define GrVkPipeline_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrTypesPriv.h"
 #include "GrVkResource.h"
+#include "vk/GrVkTypes.h"
 
 class GrPipeline;
 class GrPrimitiveProcessor;
@@ -56,7 +55,7 @@
     VkPipeline  fPipeline;
 
 private:
-    void freeGPUData(const GrVkGpu* gpu) const override;
+    void freeGPUData(GrVkGpu* gpu) const override;
 
     typedef GrVkResource INHERITED;
 };
diff --git a/src/gpu/vk/GrVkPipelineLayout.cpp b/src/gpu/vk/GrVkPipelineLayout.cpp
index 7deb5ac..220e28b 100644
--- a/src/gpu/vk/GrVkPipelineLayout.cpp
+++ b/src/gpu/vk/GrVkPipelineLayout.cpp
@@ -9,6 +9,6 @@
 #include "GrVkGpu.h"
 #include "GrVkUtil.h"
 
-void GrVkPipelineLayout::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkPipelineLayout::freeGPUData(GrVkGpu* gpu) const {
     GR_VK_CALL(gpu->vkInterface(), DestroyPipelineLayout(gpu->device(), fPipelineLayout, nullptr));
 }
diff --git a/src/gpu/vk/GrVkPipelineLayout.h b/src/gpu/vk/GrVkPipelineLayout.h
index 50ebf85..f9722d2 100644
--- a/src/gpu/vk/GrVkPipelineLayout.h
+++ b/src/gpu/vk/GrVkPipelineLayout.h
@@ -8,10 +8,9 @@
 #ifndef GrVkPipelineLayout_DEFINED
 #define GrVkPipelineLayout_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrTypes.h"
 #include "GrVkResource.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkPipelineLayout : public GrVkResource {
 public:
@@ -29,7 +28,7 @@
     GrVkPipelineLayout(const GrVkPipelineLayout&);
     GrVkPipelineLayout& operator=(const GrVkPipelineLayout&);
 
-    void freeGPUData(const GrVkGpu* gpu) const override;
+    void freeGPUData(GrVkGpu* gpu) const override;
 
     VkPipelineLayout  fPipelineLayout;
 
diff --git a/src/gpu/vk/GrVkPipelineState.cpp b/src/gpu/vk/GrVkPipelineState.cpp
index 9f18b1a..c639377 100644
--- a/src/gpu/vk/GrVkPipelineState.cpp
+++ b/src/gpu/vk/GrVkPipelineState.cpp
@@ -75,7 +75,7 @@
     SkASSERT(!fPipelineLayout);
 }
 
-void GrVkPipelineState::freeGPUResources(const GrVkGpu* gpu) {
+void GrVkPipelineState::freeGPUResources(GrVkGpu* gpu) {
     if (fPipeline) {
         fPipeline->unref(gpu);
         fPipeline = nullptr;
diff --git a/src/gpu/vk/GrVkPipelineState.h b/src/gpu/vk/GrVkPipelineState.h
index e8a1b39..297209e 100644
--- a/src/gpu/vk/GrVkPipelineState.h
+++ b/src/gpu/vk/GrVkPipelineState.h
@@ -9,11 +9,10 @@
 #ifndef GrVkPipelineState_DEFINED
 #define GrVkPipelineState_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrVkDescriptorSetManager.h"
 #include "GrVkPipelineStateDataManager.h"
 #include "glsl/GrGLSLProgramBuilder.h"
+#include "vk/GrVkTypes.h"
 
 class GrPipeline;
 class GrStencilSettings;
@@ -71,7 +70,7 @@
 
     void addUniformResources(GrVkCommandBuffer&, GrVkSampler*[], GrVkTexture*[], int numTextures);
 
-    void freeGPUResources(const GrVkGpu* gpu);
+    void freeGPUResources(GrVkGpu* gpu);
 
     void abandonGPUResources();
 
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.cpp b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
index e2ccbda..6219277 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.cpp
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
@@ -15,6 +15,8 @@
 #include "vk/GrVkGpu.h"
 #include "vk/GrVkRenderPass.h"
 
+typedef size_t shader_size;
+
 GrVkPipelineState* GrVkPipelineStateBuilder::CreatePipelineState(
         GrVkGpu* gpu,
         const GrPrimitiveProcessor& primProc,
@@ -62,7 +64,9 @@
                                                     VkShaderModule* shaderModule,
                                                     VkPipelineShaderStageCreateInfo* stageInfo,
                                                     const SkSL::Program::Settings& settings,
-                                                    Desc* desc) {
+                                                    Desc* desc,
+                                                    SkSL::String* outSPIRV,
+                                                    SkSL::Program::Inputs* outInputs) {
     SkString shaderString;
     for (int i = 0; i < builder.fCompilerStrings.count(); ++i) {
         if (builder.fCompilerStrings[i]) {
@@ -71,21 +75,152 @@
         }
     }
 
-    SkSL::Program::Inputs inputs;
-    bool result = GrCompileVkShaderModule(fGpu, shaderString.c_str(), stage, shaderModule,
-                                          stageInfo, settings, &inputs);
-    if (!result) {
+    if (!GrCompileVkShaderModule(fGpu, shaderString.c_str(), stage, shaderModule,
+                                 stageInfo, settings, outSPIRV, outInputs)) {
+        return false;
+    }
+    if (outInputs->fRTHeight) {
+        this->addRTHeightUniform(SKSL_RTHEIGHT_NAME);
+    }
+    if (outInputs->fFlipY) {
+        desc->setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(
+                                                     this->pipeline().proxy()->origin()));
+    }
+    return true;
+}
+
+bool GrVkPipelineStateBuilder::installVkShaderModule(VkShaderStageFlagBits stage,
+                                                     const GrGLSLShaderBuilder& builder,
+                                                     VkShaderModule* shaderModule,
+                                                     VkPipelineShaderStageCreateInfo* stageInfo,
+                                                     SkSL::String spirv,
+                                                     SkSL::Program::Inputs inputs) {
+    if (!GrInstallVkShaderModule(fGpu, spirv, stage, shaderModule, stageInfo)) {
         return false;
     }
     if (inputs.fRTHeight) {
         this->addRTHeightUniform(SKSL_RTHEIGHT_NAME);
     }
-    if (inputs.fFlipY) {
-        desc->setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(
-                                                     this->pipeline().proxy()->origin()));
-        desc->finalize();
+    return true;
+}
+
+int GrVkPipelineStateBuilder::loadShadersFromCache(const SkData& cached,
+                                                   VkShaderModule* outVertShaderModule,
+                                                   VkShaderModule* outFragShaderModule,
+                                                   VkShaderModule* outGeomShaderModule,
+                                                   VkPipelineShaderStageCreateInfo* outStageInfo) {
+    // format for shader cache entries is:
+    //     shader_size vertSize;
+    //     char[vertSize] vert;
+    //     SkSL::Program::Inputs vertInputs;
+    //     shader_size fragSize;
+    //     char[fragSize] frag;
+    //     SkSL::Program::Inputs fragInputs;
+    //     shader_size geomSize;
+    //     char[geomSize] geom;
+    //     SkSL::Program::Inputs geomInputs;
+    size_t offset = 0;
+
+    // vertex shader
+    shader_size vertSize = *((shader_size*) ((char*) cached.data() + offset));
+    offset += sizeof(shader_size);
+    SkSL::String vert((char*) cached.data() + offset, vertSize);
+    offset += vertSize;
+    SkSL::Program::Inputs vertInputs;
+    memcpy(&vertInputs, (char*) cached.data() + offset, sizeof(vertInputs));
+    offset += sizeof(vertInputs);
+
+    // fragment shader
+    shader_size fragSize = *((shader_size*) ((char*) cached.data() + offset));
+    offset += sizeof(shader_size);
+    SkSL::String frag((char*) cached.data() + offset, fragSize);
+    offset += fragSize;
+    SkSL::Program::Inputs fragInputs;
+    memcpy(&fragInputs, (char*) cached.data() + offset, sizeof(fragInputs));
+    offset += sizeof(fragInputs);
+
+    // geometry shader
+    shader_size geomSize = *((shader_size*) ((char*) cached.data() + offset));
+    offset += sizeof(shader_size);
+    SkSL::String geom((char*) cached.data() + offset, geomSize);
+    offset += geomSize;
+    SkSL::Program::Inputs geomInputs;
+    memcpy(&geomInputs, (char*) cached.data() + offset, sizeof(geomInputs));
+    offset += sizeof(geomInputs);
+
+    SkASSERT(offset == cached.size());
+
+    SkAssertResult(this->installVkShaderModule(VK_SHADER_STAGE_VERTEX_BIT,
+                                               fVS,
+                                               outVertShaderModule,
+                                               &outStageInfo[0],
+                                               vert,
+                                               vertInputs));
+
+    SkAssertResult(this->installVkShaderModule(VK_SHADER_STAGE_FRAGMENT_BIT,
+                                               fFS,
+                                               outFragShaderModule,
+                                               &outStageInfo[1],
+                                               frag,
+                                               fragInputs));
+
+    if (geomSize) {
+        SkAssertResult(this->installVkShaderModule(VK_SHADER_STAGE_GEOMETRY_BIT,
+                                                   fGS,
+                                                   outGeomShaderModule,
+                                                   &outStageInfo[2],
+                                                   geom,
+                                                   geomInputs));
+        return 3;
+    } else {
+        return 2;
     }
-    return result;
+}
+
+void GrVkPipelineStateBuilder::storeShadersInCache(const SkSL::String& vert,
+                                                   const SkSL::Program::Inputs& vertInputs,
+                                                   const SkSL::String& frag,
+                                                   const SkSL::Program::Inputs& fragInputs,
+                                                   const SkSL::String& geom,
+                                                   const SkSL::Program::Inputs& geomInputs) {
+    Desc* desc = static_cast<Desc*>(this->desc());
+
+    // see loadShadersFromCache for the layout of cache entries
+    sk_sp<SkData> key = SkData::MakeWithoutCopy(desc->asKey(), desc->shaderKeyLength());
+    size_t dataLength = (sizeof(shader_size) + sizeof(SkSL::Program::Inputs)) * 3 + vert.length() +
+                        frag.length() + geom.length();
+    std::unique_ptr<uint8_t[]> data(new uint8_t[dataLength]);
+    size_t offset = 0;
+
+    // vertex shader
+    *((shader_size*) (data.get() + offset)) = (shader_size) vert.length();
+    offset += sizeof(shader_size);
+    memcpy(data.get() + offset, vert.data(), vert.length());
+    offset += vert.length();
+    memcpy(data.get() + offset, &vertInputs, sizeof(vertInputs));
+    offset += sizeof(vertInputs);
+
+    // fragment shader
+    *((shader_size*) (data.get() + offset)) = (shader_size) frag.length();
+    offset += sizeof(shader_size);
+    memcpy(data.get() + offset, frag.data(), frag.length());
+    offset += frag.length();
+    memcpy(data.get() + offset, &fragInputs, sizeof(fragInputs));
+    offset += sizeof(fragInputs);
+
+    // geometry shader
+    *((shader_size*) (data.get() + offset)) = (shader_size) geom.length();
+    offset += sizeof(shader_size);
+    memcpy(data.get() + offset, geom.data(), geom.length());
+    offset += geom.length();
+    memcpy(data.get() + offset, &geomInputs, sizeof(geomInputs));
+    offset += sizeof(geomInputs);
+
+    SkASSERT(offset == dataLength);
+
+    this->gpu()->getContext()->contextPriv().getPersistentCache()->store(
+                                                  *key,
+                                                  *SkData::MakeWithoutCopy(data.get(), dataLength));
 }
 
 GrVkPipelineState* GrVkPipelineStateBuilder::finalize(const GrStencilSettings& stencil,
@@ -139,31 +274,58 @@
     settings.fFlipY = this->pipeline().proxy()->origin() != kTopLeft_GrSurfaceOrigin;
     settings.fSharpenTextures = this->gpu()->getContext()->contextPriv().sharpenMipmappedTextures();
     SkASSERT(!this->fragColorIsInOut());
-    SkAssertResult(this->createVkShaderModule(VK_SHADER_STAGE_VERTEX_BIT,
-                                              fVS,
-                                              &vertShaderModule,
-                                              &shaderStageInfo[0],
-                                              settings,
-                                              desc));
 
-    SkAssertResult(this->createVkShaderModule(VK_SHADER_STAGE_FRAGMENT_BIT,
-                                              fFS,
-                                              &fragShaderModule,
-                                              &shaderStageInfo[1],
-                                              settings,
-                                              desc));
-
-    int numShaderStages = 2; // We always have at least vertex and fragment stages.
-    if (this->primitiveProcessor().willUseGeoShader()) {
-        SkAssertResult(this->createVkShaderModule(VK_SHADER_STAGE_GEOMETRY_BIT,
-                                                  fGS,
-                                                  &geomShaderModule,
-                                                  &shaderStageInfo[2],
-                                                  settings,
-                                                  desc));
-        ++numShaderStages;
+    sk_sp<SkData> cached;
+    auto persistentCache = fGpu->getContext()->contextPriv().getPersistentCache();
+    if (persistentCache) {
+        sk_sp<SkData> key = SkData::MakeWithoutCopy(desc->asKey(), desc->shaderKeyLength());
+        cached = persistentCache->load(*key);
     }
+    int numShaderStages;
+    if (cached) {
+        numShaderStages = this->loadShadersFromCache(*cached, &vertShaderModule, &fragShaderModule,
+                                                     &geomShaderModule, shaderStageInfo);
+    } else {
+        numShaderStages = 2; // We always have at least vertex and fragment stages.
+        SkSL::String vert;
+        SkSL::Program::Inputs vertInputs;
+        SkSL::String frag;
+        SkSL::Program::Inputs fragInputs;
+        SkSL::String geom;
+        SkSL::Program::Inputs geomInputs;
+        SkAssertResult(this->createVkShaderModule(VK_SHADER_STAGE_VERTEX_BIT,
+                                                  fVS,
+                                                  &vertShaderModule,
+                                                  &shaderStageInfo[0],
+                                                  settings,
+                                                  desc,
+                                                  &vert,
+                                                  &vertInputs));
 
+        SkAssertResult(this->createVkShaderModule(VK_SHADER_STAGE_FRAGMENT_BIT,
+                                                  fFS,
+                                                  &fragShaderModule,
+                                                  &shaderStageInfo[1],
+                                                  settings,
+                                                  desc,
+                                                  &frag,
+                                                  &fragInputs));
+
+        if (this->primitiveProcessor().willUseGeoShader()) {
+            SkAssertResult(this->createVkShaderModule(VK_SHADER_STAGE_GEOMETRY_BIT,
+                                                      fGS,
+                                                      &geomShaderModule,
+                                                      &shaderStageInfo[2],
+                                                      settings,
+                                                      desc,
+                                                      &geom,
+                                                      &geomInputs));
+            ++numShaderStages;
+        }
+        if (persistentCache) {
+            this->storeShadersInCache(vert, vertInputs, frag, fragInputs, geom, geomInputs);
+        }
+    }
     GrVkPipeline* pipeline = resourceProvider.createPipeline(fPrimProc,
                                                              fPipeline,
                                                              stencil,
@@ -235,6 +397,12 @@
     }
 
     GrProcessorKeyBuilder b(&desc->key());
+
+    b.add32(GrVkGpu::kShader_PersistentCacheKeyType);
+    int keyLength = desc->key().count();
+    SkASSERT(0 == (keyLength % 4));
+    desc->fShaderKeyLength = SkToU32(keyLength);
+
     GrVkRenderTarget* vkRT = (GrVkRenderTarget*)pipeline.renderTarget();
     vkRT->simpleRenderPass()->genKey(&b);
 
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.h b/src/gpu/vk/GrVkPipelineStateBuilder.h
index b70539e..7351789 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.h
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.h
@@ -8,8 +8,6 @@
 #ifndef GrVkPipelineStateBuilder_DEFINED
 #define GrVkPipelineStateBuilder_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrPipeline.h"
 #include "GrProgramDesc.h"
 #include "GrVkPipelineState.h"
@@ -17,6 +15,7 @@
 #include "GrVkVaryingHandler.h"
 #include "SkSLCompiler.h"
 #include "glsl/GrGLSLProgramBuilder.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkGpu;
 class GrVkRenderPass;
@@ -45,7 +44,11 @@
                           GrPrimitiveType primitiveType,
                           GrVkGpu* gpu);
 
+        size_t shaderKeyLength() const { return fShaderKeyLength; }
+
     private:
+        size_t fShaderKeyLength;
+
         typedef GrProgramDesc INHERITED;
     };
 
@@ -85,12 +88,35 @@
                                 VkRenderPass compatibleRenderPass,
                                 Desc*);
 
+    // returns number of shader stages
+    int loadShadersFromCache(const SkData& cached,
+                             VkShaderModule* outVertShaderModule,
+                             VkShaderModule* outFragShaderModule,
+                             VkShaderModule* outGeomShaderModule,
+                             VkPipelineShaderStageCreateInfo* outStageInfo);
+
+    void storeShadersInCache(const SkSL::String& vert,
+                             const SkSL::Program::Inputs& vertInputs,
+                             const SkSL::String& frag,
+                             const SkSL::Program::Inputs& fragInputs,
+                             const SkSL::String& geom,
+                             const SkSL::Program::Inputs& geomInputs);
+
     bool createVkShaderModule(VkShaderStageFlagBits stage,
                               const GrGLSLShaderBuilder& builder,
                               VkShaderModule* shaderModule,
                               VkPipelineShaderStageCreateInfo* stageInfo,
                               const SkSL::Program::Settings& settings,
-                              Desc* desc);
+                              Desc* desc,
+                              SkSL::String* outSPIRV,
+                              SkSL::Program::Inputs* outInputs);
+
+    bool installVkShaderModule(VkShaderStageFlagBits stage,
+                               const GrGLSLShaderBuilder& builder,
+                               VkShaderModule* shaderModule,
+                               VkPipelineShaderStageCreateInfo* stageInfo,
+                               SkSL::String spirv,
+                               SkSL::Program::Inputs inputs);
 
     GrGLSLUniformHandler* uniformHandler() override { return &fUniformHandler; }
     const GrGLSLUniformHandler* uniformHandler() const override { return &fUniformHandler; }
diff --git a/src/gpu/vk/GrVkPipelineStateCache.cpp b/src/gpu/vk/GrVkPipelineStateCache.cpp
index 7b0f9f8..d2fd352 100644
--- a/src/gpu/vk/GrVkPipelineStateCache.cpp
+++ b/src/gpu/vk/GrVkPipelineStateCache.cpp
@@ -5,7 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "GrVkVulkan.h"
 
 #include "GrProcessor.h"
 #include "GrRenderTargetPriv.h"  // TODO: remove once refPipelineState gets passed stencil settings.
@@ -100,14 +99,12 @@
         GrCapsDebugf(fGpu->caps(), "Failed to build vk program descriptor!\n");
         return nullptr;
     }
-    desc.finalize();
 
     std::unique_ptr<Entry>* entry = fMap.find(desc);
     if (!entry) {
         // Didn't find an origin-independent version, check with the specific origin
         GrSurfaceOrigin origin = pipeline.proxy()->origin();
         desc.setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(origin));
-        desc.finalize();
         entry = fMap.find(desc);
     }
     if (!entry) {
diff --git a/src/gpu/vk/GrVkPipelineStateDataManager.h b/src/gpu/vk/GrVkPipelineStateDataManager.h
index 95e04a9..f112d9e 100644
--- a/src/gpu/vk/GrVkPipelineStateDataManager.h
+++ b/src/gpu/vk/GrVkPipelineStateDataManager.h
@@ -8,11 +8,10 @@
 #ifndef GrVkPipelineStateDataManager_DEFINED
 #define GrVkPipelineStateDataManager_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "glsl/GrGLSLProgramDataManager.h"
 
 #include "SkAutoMalloc.h"
+#include "vk/GrVkTypes.h"
 #include "vk/GrVkUniformHandler.h"
 
 class GrVkGpu;
diff --git a/src/gpu/vk/GrVkRenderPass.cpp b/src/gpu/vk/GrVkRenderPass.cpp
index e0daa3f..7077528 100644
--- a/src/gpu/vk/GrVkRenderPass.cpp
+++ b/src/gpu/vk/GrVkRenderPass.cpp
@@ -163,15 +163,16 @@
     this->init(gpu, colorOp, stencilOp);
 }
 
-void GrVkRenderPass::freeGPUData(const GrVkGpu* gpu) const {
-    GR_VK_CALL(gpu->vkInterface(), DestroyRenderPass(gpu->device(), fRenderPass, nullptr));
+void GrVkRenderPass::freeGPUData(GrVkGpu* gpu) const {
+    if (!(fAttachmentFlags & kExternal_AttachmentFlag)) {
+        GR_VK_CALL(gpu->vkInterface(), DestroyRenderPass(gpu->device(), fRenderPass, nullptr));
+    }
 }
 
-// Works under the assumption that color attachment will always be the first attachment in our
-// attachment array if it exists.
 bool GrVkRenderPass::colorAttachmentIndex(uint32_t* index) const {
-    *index = 0;
-    if (fAttachmentFlags & kColor_AttachmentFlag) {
+    *index = fColorAttachmentIndex;
+    if ((fAttachmentFlags & kColor_AttachmentFlag) ||
+        (fAttachmentFlags & kExternal_AttachmentFlag)) {
         return true;
     }
     return false;
@@ -192,6 +193,7 @@
 
 bool GrVkRenderPass::isCompatible(const AttachmentsDescriptor& desc,
                                   const AttachmentFlags& flags) const {
+    SkASSERT(!(fAttachmentFlags & kExternal_AttachmentFlag));
     if (flags != fAttachmentFlags) {
         return false;
     }
@@ -211,6 +213,7 @@
 }
 
 bool GrVkRenderPass::isCompatible(const GrVkRenderTarget& target) const {
+    SkASSERT(!(fAttachmentFlags & kExternal_AttachmentFlag));
     AttachmentsDescriptor desc;
     AttachmentFlags flags;
     target.getAttachmentsDescriptor(&desc, &flags);
@@ -219,11 +222,18 @@
 }
 
 bool GrVkRenderPass::isCompatible(const GrVkRenderPass& renderPass) const {
+    SkASSERT(!(fAttachmentFlags & kExternal_AttachmentFlag));
     return this->isCompatible(renderPass.fAttachmentsDescriptor, renderPass.fAttachmentFlags);
 }
 
+bool GrVkRenderPass::isCompatibleExternalRP(VkRenderPass renderPass) const {
+    SkASSERT(fAttachmentFlags & kExternal_AttachmentFlag);
+    return fRenderPass == renderPass;
+}
+
 bool GrVkRenderPass::equalLoadStoreOps(const LoadStoreOps& colorOps,
                                        const LoadStoreOps& stencilOps) const {
+    SkASSERT(!(fAttachmentFlags & kExternal_AttachmentFlag));
     if (fAttachmentFlags & kColor_AttachmentFlag) {
         if (fAttachmentsDescriptor.fColor.fLoadStoreOps != colorOps) {
             return false;
@@ -247,4 +257,10 @@
         b->add32(fAttachmentsDescriptor.fStencil.fFormat);
         b->add32(fAttachmentsDescriptor.fStencil.fSamples);
     }
+    if (fAttachmentFlags & kExternal_AttachmentFlag) {
+        SkASSERT(!(fAttachmentFlags & ~kExternal_AttachmentFlag));
+        uint64_t handle = (uint64_t)fRenderPass;
+        b->add32((uint32_t)handle);
+        b->add32((uint32_t)(handle>>32));
+    }
 }
diff --git a/src/gpu/vk/GrVkRenderPass.h b/src/gpu/vk/GrVkRenderPass.h
index d5fa722..b6e4497 100644
--- a/src/gpu/vk/GrVkRenderPass.h
+++ b/src/gpu/vk/GrVkRenderPass.h
@@ -8,10 +8,9 @@
 #ifndef GrVkRenderPass_DEFINED
 #define GrVkRenderPass_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrTypes.h"
 #include "GrVkResource.h"
+#include "vk/GrVkTypes.h"
 
 class GrProcessorKeyBuilder;
 class GrVkGpu;
@@ -21,6 +20,15 @@
 public:
     GrVkRenderPass() : INHERITED(), fRenderPass(VK_NULL_HANDLE), fClearValueCount(0) {}
 
+    // Used when importing an external render pass. In this case we have to explicitly be told the
+    // color attachment index
+    explicit GrVkRenderPass(VkRenderPass renderPass, uint32_t colorAttachmentIndex)
+            : INHERITED()
+            , fRenderPass(renderPass)
+            , fAttachmentFlags(kExternal_AttachmentFlag)
+            , fClearValueCount(0)
+            , fColorAttachmentIndex(colorAttachmentIndex) {}
+
     struct LoadStoreOps {
         VkAttachmentLoadOp  fLoadOp;
         VkAttachmentStoreOp fStoreOp;
@@ -79,6 +87,11 @@
     enum AttachmentFlags {
         kColor_AttachmentFlag = 0x1,
         kStencil_AttachmentFlag = 0x2,
+        // The external attachment flag signals that this render pass is imported from an external
+        // client. Since we don't know every attachment on the render pass we don't set any of the
+        // specific attachment flags when using external. However, the external render pass must
+        // at least have a color attachment.
+        kExternal_AttachmentFlag = 0x4,
     };
     GR_DECL_BITFIELD_OPS_FRIENDS(AttachmentFlags);
 
@@ -96,6 +109,8 @@
 
     bool isCompatible(const GrVkRenderPass& renderPass) const;
 
+    bool isCompatibleExternalRP(VkRenderPass) const;
+
     bool equalLoadStoreOps(const LoadStoreOps& colorOps,
                            const LoadStoreOps& stencilOps) const;
 
@@ -125,13 +140,15 @@
 
     bool isCompatible(const AttachmentsDescriptor&, const AttachmentFlags&) const;
 
-    void freeGPUData(const GrVkGpu* gpu) const override;
+    void freeGPUData(GrVkGpu* gpu) const override;
 
     VkRenderPass          fRenderPass;
     AttachmentFlags       fAttachmentFlags;
     AttachmentsDescriptor fAttachmentsDescriptor;
     VkExtent2D            fGranularity;
     uint32_t              fClearValueCount;
+    // For internally created render passes we assume the color attachment index is always 0.
+    uint32_t              fColorAttachmentIndex = 0;
 
     typedef GrVkResource INHERITED;
 };
diff --git a/src/gpu/vk/GrVkRenderTarget.cpp b/src/gpu/vk/GrVkRenderTarget.cpp
index d1393f9..36dcb60 100644
--- a/src/gpu/vk/GrVkRenderTarget.cpp
+++ b/src/gpu/vk/GrVkRenderTarget.cpp
@@ -30,15 +30,16 @@
                                    sk_sp<GrVkImageLayout> msaaLayout,
                                    const GrVkImageView* colorAttachmentView,
                                    const GrVkImageView* resolveAttachmentView)
-    : GrSurface(gpu, desc)
-    , GrVkImage(info, std::move(layout), GrBackendObjectOwnership::kBorrowed)
-    // for the moment we only support 1:1 color to stencil
-    , GrRenderTarget(gpu, desc)
-    , fColorAttachmentView(colorAttachmentView)
-    , fMSAAImage(new GrVkImage(msaaInfo, std::move(msaaLayout), GrBackendObjectOwnership::kOwned))
-    , fResolveAttachmentView(resolveAttachmentView)
-    , fFramebuffer(nullptr)
-    , fCachedSimpleRenderPass(nullptr) {
+        : GrSurface(gpu, desc)
+        , GrVkImage(info, std::move(layout), GrBackendObjectOwnership::kBorrowed)
+        // for the moment we only support 1:1 color to stencil
+        , GrRenderTarget(gpu, desc)
+        , fColorAttachmentView(colorAttachmentView)
+        , fMSAAImage(new GrVkImage(msaaInfo, std::move(msaaLayout),
+                                   GrBackendObjectOwnership::kOwned))
+        , fResolveAttachmentView(resolveAttachmentView)
+        , fFramebuffer(nullptr)
+        , fCachedSimpleRenderPass(nullptr) {
     SkASSERT(desc.fSampleCnt > 1);
     this->createFramebuffer(gpu);
     this->registerWithCacheWrapped();
@@ -55,15 +56,16 @@
                                    const GrVkImageView* colorAttachmentView,
                                    const GrVkImageView* resolveAttachmentView,
                                    GrBackendObjectOwnership ownership)
-    : GrSurface(gpu, desc)
-    , GrVkImage(info, std::move(layout), ownership)
-    // for the moment we only support 1:1 color to stencil
-    , GrRenderTarget(gpu, desc)
-    , fColorAttachmentView(colorAttachmentView)
-    , fMSAAImage(new GrVkImage(msaaInfo, std::move(msaaLayout), GrBackendObjectOwnership::kOwned))
-    , fResolveAttachmentView(resolveAttachmentView)
-    , fFramebuffer(nullptr)
-    , fCachedSimpleRenderPass(nullptr) {
+        : GrSurface(gpu, desc)
+        , GrVkImage(info, std::move(layout), ownership)
+        // for the moment we only support 1:1 color to stencil
+        , GrRenderTarget(gpu, desc)
+        , fColorAttachmentView(colorAttachmentView)
+        , fMSAAImage(new GrVkImage(msaaInfo, std::move(msaaLayout),
+                                   GrBackendObjectOwnership::kOwned))
+        , fResolveAttachmentView(resolveAttachmentView)
+        , fFramebuffer(nullptr)
+        , fCachedSimpleRenderPass(nullptr) {
     SkASSERT(desc.fSampleCnt > 1);
     this->createFramebuffer(gpu);
 }
@@ -75,14 +77,14 @@
                                    const GrVkImageInfo& info,
                                    sk_sp<GrVkImageLayout> layout,
                                    const GrVkImageView* colorAttachmentView)
-    : GrSurface(gpu, desc)
-    , GrVkImage(info, std::move(layout), GrBackendObjectOwnership::kBorrowed)
-    , GrRenderTarget(gpu, desc)
-    , fColorAttachmentView(colorAttachmentView)
-    , fMSAAImage(nullptr)
-    , fResolveAttachmentView(nullptr)
-    , fFramebuffer(nullptr)
-    , fCachedSimpleRenderPass(nullptr) {
+        : GrSurface(gpu, desc)
+        , GrVkImage(info, std::move(layout), GrBackendObjectOwnership::kBorrowed)
+        , GrRenderTarget(gpu, desc)
+        , fColorAttachmentView(colorAttachmentView)
+        , fMSAAImage(nullptr)
+        , fResolveAttachmentView(nullptr)
+        , fFramebuffer(nullptr)
+        , fCachedSimpleRenderPass(nullptr) {
     SkASSERT(1 == desc.fSampleCnt);
     this->createFramebuffer(gpu);
     this->registerWithCacheWrapped();
@@ -96,23 +98,39 @@
                                    sk_sp<GrVkImageLayout> layout,
                                    const GrVkImageView* colorAttachmentView,
                                    GrBackendObjectOwnership ownership)
-    : GrSurface(gpu, desc)
-    , GrVkImage(info, std::move(layout), ownership)
-    , GrRenderTarget(gpu, desc)
-    , fColorAttachmentView(colorAttachmentView)
-    , fMSAAImage(nullptr)
-    , fResolveAttachmentView(nullptr)
-    , fFramebuffer(nullptr)
-    , fCachedSimpleRenderPass(nullptr) {
+        : GrSurface(gpu, desc)
+        , GrVkImage(info, std::move(layout), ownership)
+        , GrRenderTarget(gpu, desc)
+        , fColorAttachmentView(colorAttachmentView)
+        , fMSAAImage(nullptr)
+        , fResolveAttachmentView(nullptr)
+        , fFramebuffer(nullptr)
+        , fCachedSimpleRenderPass(nullptr) {
     SkASSERT(1 == desc.fSampleCnt);
     this->createFramebuffer(gpu);
 }
 
-sk_sp<GrVkRenderTarget>
-GrVkRenderTarget::MakeWrappedRenderTarget(GrVkGpu* gpu,
-                                          const GrSurfaceDesc& desc,
-                                          const GrVkImageInfo& info,
-                                          sk_sp<GrVkImageLayout> layout) {
+GrVkRenderTarget::GrVkRenderTarget(GrVkGpu* gpu,
+                                   const GrSurfaceDesc& desc,
+                                   const GrVkImageInfo& info,
+                                   sk_sp<GrVkImageLayout> layout,
+                                   const GrVkRenderPass* renderPass,
+                                   GrVkSecondaryCommandBuffer* secondaryCommandBuffer)
+        : GrSurface(gpu, desc)
+        , GrVkImage(info, std::move(layout), GrBackendObjectOwnership::kBorrowed, true)
+        , GrRenderTarget(gpu, desc)
+        , fColorAttachmentView(nullptr)
+        , fMSAAImage(nullptr)
+        , fResolveAttachmentView(nullptr)
+        , fFramebuffer(nullptr)
+        , fCachedSimpleRenderPass(renderPass)
+        , fSecondaryCommandBuffer(secondaryCommandBuffer) {
+    this->registerWithCacheWrapped();
+}
+
+sk_sp<GrVkRenderTarget> GrVkRenderTarget::MakeWrappedRenderTarget(
+        GrVkGpu* gpu, const GrSurfaceDesc& desc, const GrVkImageInfo& info,
+        sk_sp<GrVkImageLayout> layout) {
     SkASSERT(VK_NULL_HANDLE != info.fImage);
 
     SkASSERT(1 == info.fLevelCount);
@@ -184,12 +202,42 @@
     return sk_sp<GrVkRenderTarget>(vkRT);
 }
 
+sk_sp<GrVkRenderTarget> GrVkRenderTarget::MakeSecondaryCBRenderTarget(
+        GrVkGpu* gpu, const GrSurfaceDesc& desc, const GrVkDrawableInfo& vkInfo) {
+    // We only set the few properties of the GrVkImageInfo that we know like layout and format. The
+    // others we keep at the default "null" values.
+    GrVkImageInfo info;
+    info.fImageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+    info.fFormat = vkInfo.fFormat;
+
+    sk_sp<GrVkImageLayout> layout(new GrVkImageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL));
+
+    const GrVkRenderPass* rp =
+            gpu->resourceProvider().findCompatibleExternalRenderPass(vkInfo.fCompatibleRenderPass,
+                                                                     vkInfo.fColorAttachmentIndex);
+    if (!rp) {
+        return nullptr;
+    }
+
+    GrVkSecondaryCommandBuffer* scb =
+            GrVkSecondaryCommandBuffer::Create(vkInfo.fSecondaryCommandBuffer);
+    if (!scb) {
+        return nullptr;
+    }
+
+    GrVkRenderTarget* vkRT = new GrVkRenderTarget(gpu, desc, info, std::move(layout), rp, scb);
+
+    return sk_sp<GrVkRenderTarget>(vkRT);
+}
+
 bool GrVkRenderTarget::completeStencilAttachment() {
+    SkASSERT(!this->wrapsSecondaryCommandBuffer());
     this->createFramebuffer(this->getVkGpu());
     return true;
 }
 
 void GrVkRenderTarget::createFramebuffer(GrVkGpu* gpu) {
+    SkASSERT(!this->wrapsSecondaryCommandBuffer());
     if (fFramebuffer) {
         fFramebuffer->unref(gpu);
     }
@@ -213,6 +261,7 @@
 void GrVkRenderTarget::getAttachmentsDescriptor(
                                            GrVkRenderPass::AttachmentsDescriptor* desc,
                                            GrVkRenderPass::AttachmentFlags* attachmentFlags) const {
+    SkASSERT(!this->wrapsSecondaryCommandBuffer());
     VkFormat colorFormat;
     GrPixelConfigToVkFormat(this->config(), &colorFormat);
     desc->fColor.fFormat = colorFormat;
@@ -240,6 +289,7 @@
     SkASSERT(!fColorAttachmentView);
     SkASSERT(!fFramebuffer);
     SkASSERT(!fCachedSimpleRenderPass);
+    SkASSERT(!fSecondaryCommandBuffer);
 }
 
 void GrVkRenderTarget::addResources(GrVkCommandBuffer& commandBuffer) const {
@@ -277,6 +327,10 @@
         fCachedSimpleRenderPass->unref(gpu);
         fCachedSimpleRenderPass = nullptr;
     }
+    if (fSecondaryCommandBuffer) {
+        fSecondaryCommandBuffer->unref(gpu);
+        fSecondaryCommandBuffer = nullptr;
+    }
 }
 
 void GrVkRenderTarget::abandonInternalObjects() {
@@ -301,6 +355,10 @@
         fCachedSimpleRenderPass->unrefAndAbandon();
         fCachedSimpleRenderPass = nullptr;
     }
+    if (fSecondaryCommandBuffer) {
+        fSecondaryCommandBuffer->unrefAndAbandon();
+        fSecondaryCommandBuffer = nullptr;
+    }
 }
 
 void GrVkRenderTarget::onRelease() {
@@ -317,11 +375,13 @@
 
 
 GrBackendRenderTarget GrVkRenderTarget::getBackendRenderTarget() const {
+    SkASSERT(!this->wrapsSecondaryCommandBuffer());
     return GrBackendRenderTarget(this->width(), this->height(), this->numColorSamples(),
                                  fInfo, this->grVkImageLayout());
 }
 
 const GrVkResource* GrVkRenderTarget::stencilImageResource() const {
+    SkASSERT(!this->wrapsSecondaryCommandBuffer());
     const GrStencilAttachment* stencil = this->renderTargetPriv().getStencilAttachment();
     if (stencil) {
         const GrVkStencilAttachment* vkStencil = static_cast<const GrVkStencilAttachment*>(stencil);
@@ -332,6 +392,7 @@
 }
 
 const GrVkImageView* GrVkRenderTarget::stencilAttachmentView() const {
+    SkASSERT(!this->wrapsSecondaryCommandBuffer());
     const GrStencilAttachment* stencil = this->renderTargetPriv().getStencilAttachment();
     if (stencil) {
         const GrVkStencilAttachment* vkStencil = static_cast<const GrVkStencilAttachment*>(stencil);
@@ -341,7 +402,6 @@
     return nullptr;
 }
 
-
 GrVkGpu* GrVkRenderTarget::getVkGpu() const {
     SkASSERT(!this->wasDestroyed());
     return static_cast<GrVkGpu*>(this->getGpu());
diff --git a/src/gpu/vk/GrVkRenderTarget.h b/src/gpu/vk/GrVkRenderTarget.h
index 5ce87cd..6ee547d 100644
--- a/src/gpu/vk/GrVkRenderTarget.h
+++ b/src/gpu/vk/GrVkRenderTarget.h
@@ -9,18 +9,18 @@
 #ifndef GrVkRenderTarget_DEFINED
 #define GrVkRenderTarget_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrVkImage.h"
 #include "GrRenderTarget.h"
 
 #include "GrVkRenderPass.h"
 #include "GrVkResourceProvider.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkCommandBuffer;
 class GrVkFramebuffer;
 class GrVkGpu;
 class GrVkImageView;
+class GrVkSecondaryCommandBuffer;
 class GrVkStencilAttachment;
 
 struct GrVkImageInfo;
@@ -37,6 +37,9 @@
                                                            const GrVkImageInfo&,
                                                            sk_sp<GrVkImageLayout>);
 
+    static sk_sp<GrVkRenderTarget> MakeSecondaryCBRenderTarget(GrVkGpu*, const GrSurfaceDesc&,
+                                                               const GrVkDrawableInfo& vkInfo);
+
     ~GrVkRenderTarget() override;
 
     GrBackendFormat backendFormat() const override { return this->getBackendFormat(); }
@@ -56,8 +59,19 @@
 
     const GrVkRenderPass* simpleRenderPass() const { return fCachedSimpleRenderPass; }
     GrVkResourceProvider::CompatibleRPHandle compatibleRenderPassHandle() const {
+        SkASSERT(!this->wrapsSecondaryCommandBuffer());
         return fCompatibleRPHandle;
     }
+    const GrVkRenderPass* externalRenderPass() const {
+        SkASSERT(this->wrapsSecondaryCommandBuffer());
+        // We use the cached simple render pass to hold the external render pass.
+        return fCachedSimpleRenderPass;
+    }
+
+    bool wrapsSecondaryCommandBuffer() const { return fSecondaryCommandBuffer != nullptr; }
+    GrVkSecondaryCommandBuffer* getExternalSecondaryCommandBuffer() const {
+        return fSecondaryCommandBuffer;
+    }
 
     // override of GrRenderTarget
     ResolveType getResolveType() const override {
@@ -68,7 +82,9 @@
     }
 
     bool canAttemptStencilAttachment() const override {
-        return true;
+        // We don't know the status of the stencil attachment for wrapped external secondary command
+        // buffers so we just assume we don't have one.
+        return !this->wrapsSecondaryCommandBuffer();
     }
 
     GrBackendRenderTarget getBackendRenderTarget() const override;
@@ -134,6 +150,14 @@
                      sk_sp<GrVkImageLayout> layout,
                      const GrVkImageView* colorAttachmentView);
 
+
+    GrVkRenderTarget(GrVkGpu* gpu,
+                     const GrSurfaceDesc& desc,
+                     const GrVkImageInfo& info,
+                     sk_sp<GrVkImageLayout> layout,
+                     const GrVkRenderPass* renderPass,
+                     GrVkSecondaryCommandBuffer* secondaryCommandBuffer);
+
     bool completeStencilAttachment() override;
 
     void releaseInternalObjects();
@@ -146,6 +170,12 @@
     const GrVkRenderPass*      fCachedSimpleRenderPass;
     // This is a handle to be used to quickly get compatible GrVkRenderPasses for this render target
     GrVkResourceProvider::CompatibleRPHandle fCompatibleRPHandle;
+
+    // If this render target wraps an external VkCommandBuffer, then this pointer will be non-null
+    // and will point to the GrVk object that, in turn, wraps the external VkCommandBuffer. In this
+    // case the render target will not be backed by an actual VkImage and will thus be limited in
+    // terms of what it can be used for.
+    GrVkSecondaryCommandBuffer* fSecondaryCommandBuffer = nullptr;
 };
 
 #endif
diff --git a/src/gpu/vk/GrVkResource.h b/src/gpu/vk/GrVkResource.h
index 1231996..306269b 100644
--- a/src/gpu/vk/GrVkResource.h
+++ b/src/gpu/vk/GrVkResource.h
@@ -8,7 +8,6 @@
 #ifndef GrVkResource_DEFINED
 #define GrVkResource_DEFINED
 
-#include "GrVkVulkan.h"
 
 #include "SkRandom.h"
 #include "SkTHash.h"
@@ -55,8 +54,14 @@
             });
             SkASSERT(0 == fHashSet.count());
         }
-        void add(const GrVkResource* r) { fHashSet.add(r); }
-        void remove(const GrVkResource* r) { fHashSet.remove(r); }
+
+        void add(const GrVkResource* r) {
+            fHashSet.add(r);
+        }
+
+        void remove(const GrVkResource* r) {
+            fHashSet.remove(r);
+        }
 
     private:
         SkTHashSet<const GrVkResource*, GrVkResource::Hash> fHashSet;
@@ -103,8 +108,9 @@
         Must be balanced by a call to unref() or unrefAndFreeResources().
      */
     void ref() const {
-        SkASSERT(this->getRefCnt() > 0);
-        (void)fRefCnt.fetch_add(+1, std::memory_order_relaxed);  // No barrier required.
+        // No barrier required.
+        SkDEBUGCODE(int newRefCount = )fRefCnt.fetch_add(+1, std::memory_order_relaxed);
+        SkASSERT(newRefCount >= 1);
     }
 
     /** Decrement the reference count. If the reference count is 1 before the
@@ -112,11 +118,12 @@
         the object needs to have been allocated via new, and not on the stack.
         Any GPU data associated with this resource will be freed before it's deleted.
      */
-    void unref(const GrVkGpu* gpu) const {
-        SkASSERT(this->getRefCnt() > 0);
+    void unref(GrVkGpu* gpu) const {
         SkASSERT(gpu);
         // A release here acts in place of all releases we "should" have been doing in ref().
-        if (1 == fRefCnt.fetch_add(-1, std::memory_order_acq_rel)) {
+        int newRefCount = fRefCnt.fetch_add(-1, std::memory_order_acq_rel);
+        SkASSERT(newRefCount >= 0);
+        if (newRefCount == 1) {
             // Like unique(), the acquire is only needed on success, to make sure
             // code in internal_dispose() doesn't happen before the decrement.
             this->internal_dispose(gpu);
@@ -127,13 +134,22 @@
     void unrefAndAbandon() const {
         SkASSERT(this->getRefCnt() > 0);
         // A release here acts in place of all releases we "should" have been doing in ref().
-        if (1 == fRefCnt.fetch_add(-1, std::memory_order_acq_rel)) {
+        int newRefCount = fRefCnt.fetch_add(-1, std::memory_order_acq_rel);
+        SkASSERT(newRefCount >= 0);
+        if (newRefCount == 1) {
             // Like unique(), the acquire is only needed on success, to make sure
             // code in internal_dispose() doesn't happen before the decrement.
             this->internal_dispose();
         }
     }
 
+    // Called every time this resource is added to a command buffer.
+    virtual void notifyAddedToCommandBuffer() const {}
+    // Called every time this resource is removed from a command buffer (typically because
+    // the command buffer finished execution on the GPU but also when the command buffer
+    // is abandoned.)
+    virtual void notifyRemovedFromCommandBuffer() const {}
+
 #ifdef SK_DEBUG
     void validate() const {
         SkASSERT(this->getRefCnt() > 0);
@@ -157,7 +173,7 @@
     /** Must be implemented by any subclasses.
      *  Deletes any Vk data associated with this resource
      */
-    virtual void freeGPUData(const GrVkGpu* gpu) const = 0;
+    virtual void freeGPUData(GrVkGpu* gpu) const = 0;
 
     /**
      * Called from unrefAndAbandon. Resources should do any necessary cleanup without freeing
@@ -169,7 +185,7 @@
     /**
      *  Called when the ref count goes to 0. Will free Vk resources.
      */
-    void internal_dispose(const GrVkGpu* gpu) const {
+    void internal_dispose(GrVkGpu* gpu) const {
         this->freeGPUData(gpu);
 #ifdef SK_TRACE_VK_RESOURCES
         GetTrace()->remove(this);
diff --git a/src/gpu/vk/GrVkResourceProvider.cpp b/src/gpu/vk/GrVkResourceProvider.cpp
index 75a459a..5ca2330 100644
--- a/src/gpu/vk/GrVkResourceProvider.cpp
+++ b/src/gpu/vk/GrVkResourceProvider.cpp
@@ -7,14 +7,17 @@
 
 #include "GrVkResourceProvider.h"
 
+#include "GrContextPriv.h"
 #include "GrSamplerState.h"
 #include "GrVkCommandBuffer.h"
+#include "GrVkCommandPool.h"
 #include "GrVkCopyPipeline.h"
 #include "GrVkGpu.h"
 #include "GrVkPipeline.h"
 #include "GrVkRenderTarget.h"
 #include "GrVkUniformBuffer.h"
 #include "GrVkUtil.h"
+#include "SkTaskGroup.h"
 
 #ifdef SK_TRACE_VK_RESOURCES
 std::atomic<uint32_t> GrVkResource::fKeyCounter{0};
@@ -28,26 +31,60 @@
 
 GrVkResourceProvider::~GrVkResourceProvider() {
     SkASSERT(0 == fRenderPassArray.count());
+    SkASSERT(0 == fExternalRenderPasses.count());
     SkASSERT(VK_NULL_HANDLE == fPipelineCache);
     delete fPipelineStateCache;
 }
 
-void GrVkResourceProvider::init() {
-    VkPipelineCacheCreateInfo createInfo;
-    memset(&createInfo, 0, sizeof(VkPipelineCacheCreateInfo));
-    createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
-    createInfo.pNext = nullptr;
-    createInfo.flags = 0;
-    createInfo.initialDataSize = 0;
-    createInfo.pInitialData = nullptr;
-    VkResult result = GR_VK_CALL(fGpu->vkInterface(),
-                                 CreatePipelineCache(fGpu->device(), &createInfo, nullptr,
-                                                     &fPipelineCache));
-    SkASSERT(VK_SUCCESS == result);
-    if (VK_SUCCESS != result) {
-        fPipelineCache = VK_NULL_HANDLE;
-    }
+VkPipelineCache GrVkResourceProvider::pipelineCache() {
+    if (fPipelineCache == VK_NULL_HANDLE) {
+        VkPipelineCacheCreateInfo createInfo;
+        memset(&createInfo, 0, sizeof(VkPipelineCacheCreateInfo));
+        createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
+        createInfo.pNext = nullptr;
+        createInfo.flags = 0;
 
+        auto persistentCache = fGpu->getContext()->contextPriv().getPersistentCache();
+        sk_sp<SkData> cached;
+        if (persistentCache) {
+            uint32_t key = GrVkGpu::kPipelineCache_PersistentCacheKeyType;
+            sk_sp<SkData> keyData = SkData::MakeWithoutCopy(&key, sizeof(uint32_t));
+            cached = persistentCache->load(*keyData);
+        }
+        bool usedCached = false;
+        if (cached) {
+            uint32_t* cacheHeader = (uint32_t*)cached->data();
+            if (cacheHeader[1] == VK_PIPELINE_CACHE_HEADER_VERSION_ONE) {
+                // For version one of the header, the total header size is 16 bytes plus
+                // VK_UUID_SIZE bytes. See Section 9.6 (Pipeline Cache) in the vulkan spec to see
+                // the breakdown of these bytes.
+                SkASSERT(cacheHeader[0] == 16 + VK_UUID_SIZE);
+                const VkPhysicalDeviceProperties& devProps = fGpu->physicalDeviceProperties();
+                const uint8_t* supportedPipelineCacheUUID = devProps.pipelineCacheUUID;
+                if (cacheHeader[2] == devProps.vendorID && cacheHeader[3] == devProps.deviceID &&
+                    !memcmp(&cacheHeader[4], supportedPipelineCacheUUID, VK_UUID_SIZE)) {
+                    createInfo.initialDataSize = cached->size();
+                    createInfo.pInitialData = cached->data();
+                    usedCached = true;
+                }
+            }
+        }
+        if (!usedCached) {
+            createInfo.initialDataSize = 0;
+            createInfo.pInitialData = nullptr;
+        }
+        VkResult result = GR_VK_CALL(fGpu->vkInterface(),
+                                     CreatePipelineCache(fGpu->device(), &createInfo, nullptr,
+                                                         &fPipelineCache));
+        SkASSERT(VK_SUCCESS == result);
+        if (VK_SUCCESS != result) {
+            fPipelineCache = VK_NULL_HANDLE;
+        }
+    }
+    return fPipelineCache;
+}
+
+void GrVkResourceProvider::init() {
     // Init uniform descriptor objects
     GrVkDescriptorSetManager* dsm = GrVkDescriptorSetManager::CreateUniformManager(fGpu);
     fDescriptorSetManagers.emplace_back(dsm);
@@ -65,7 +102,7 @@
                                                    VkPipelineLayout layout) {
     return GrVkPipeline::Create(fGpu, primProc, pipeline, stencil, shaderStageInfo,
                                 shaderStageCount, primitiveType, compatibleRenderPass, layout,
-                                fPipelineCache);
+                                this->pipelineCache());
 }
 
 GrVkCopyPipeline* GrVkResourceProvider::findOrCreateCopyPipeline(
@@ -84,7 +121,7 @@
                                             pipelineLayout,
                                             dst->numColorSamples(),
                                             *dst->simpleRenderPass(),
-                                            fPipelineCache);
+                                            this->pipelineCache());
         if (!pipeline) {
             return nullptr;
         }
@@ -131,6 +168,26 @@
     return renderPass;
 }
 
+const GrVkRenderPass* GrVkResourceProvider::findCompatibleExternalRenderPass(
+        VkRenderPass renderPass, uint32_t colorAttachmentIndex) {
+    for (int i = 0; i < fExternalRenderPasses.count(); ++i) {
+        if (fExternalRenderPasses[i]->isCompatibleExternalRP(renderPass)) {
+            fExternalRenderPasses[i]->ref();
+#ifdef SK_DEBUG
+            uint32_t cachedColorIndex;
+            SkASSERT(fExternalRenderPasses[i]->colorAttachmentIndex(&cachedColorIndex));
+            SkASSERT(cachedColorIndex == colorAttachmentIndex);
+#endif
+            return fExternalRenderPasses[i];
+        }
+    }
+
+    const GrVkRenderPass* newRenderPass = new GrVkRenderPass(renderPass, colorAttachmentIndex);
+    fExternalRenderPasses.push_back(newRenderPass);
+    newRenderPass->ref();
+    return newRenderPass;
+}
+
 const GrVkRenderPass* GrVkResourceProvider::findRenderPass(
                                                      const GrVkRenderTarget& target,
                                                      const GrVkRenderPass::LoadStoreOps& colorOps,
@@ -274,49 +331,42 @@
     fDescriptorSetManagers[managerIdx]->recycleDescriptorSet(descSet);
 }
 
-GrVkPrimaryCommandBuffer* GrVkResourceProvider::findOrCreatePrimaryCommandBuffer() {
-    GrVkPrimaryCommandBuffer* cmdBuffer = nullptr;
-    int count = fAvailableCommandBuffers.count();
-    if (count > 0) {
-        cmdBuffer = fAvailableCommandBuffers[count - 1];
-        SkASSERT(cmdBuffer->finished(fGpu));
-        fAvailableCommandBuffers.removeShuffle(count - 1);
+GrVkCommandPool* GrVkResourceProvider::findOrCreateCommandPool() {
+    std::unique_lock<std::recursive_mutex> lock(fBackgroundMutex);
+    GrVkCommandPool* result;
+    if (fAvailableCommandPools.count()) {
+        result = fAvailableCommandPools.back();
+        fAvailableCommandPools.pop_back();
     } else {
-        cmdBuffer = GrVkPrimaryCommandBuffer::Create(fGpu, fGpu->cmdPool());
+        result = GrVkCommandPool::Create(fGpu);
     }
-    fActiveCommandBuffers.push_back(cmdBuffer);
-    cmdBuffer->ref();
-    return cmdBuffer;
+    SkASSERT(result->unique());
+    SkDEBUGCODE(
+        for (const GrVkCommandPool* pool : fActiveCommandPools) {
+            SkASSERT(pool != result);
+        }
+        for (const GrVkCommandPool* pool : fAvailableCommandPools) {
+            SkASSERT(pool != result);
+        }
+    );
+    fActiveCommandPools.push_back(result);
+    result->ref();
+    return result;
 }
 
 void GrVkResourceProvider::checkCommandBuffers() {
-    for (int i = fActiveCommandBuffers.count()-1; i >= 0; --i) {
-        if (fActiveCommandBuffers[i]->finished(fGpu)) {
-            GrVkPrimaryCommandBuffer* cmdBuffer = fActiveCommandBuffers[i];
-            cmdBuffer->reset(fGpu);
-            fAvailableCommandBuffers.push_back(cmdBuffer);
-            fActiveCommandBuffers.removeShuffle(i);
+    for (int i = fActiveCommandPools.count() - 1; i >= 0; --i) {
+        GrVkCommandPool* pool = fActiveCommandPools[i];
+        if (!pool->isOpen()) {
+            GrVkPrimaryCommandBuffer* buffer = pool->getPrimaryCommandBuffer();
+            if (buffer->finished(fGpu)) {
+                fActiveCommandPools.removeShuffle(i);
+                this->backgroundReset(pool);
+            }
         }
     }
 }
 
-GrVkSecondaryCommandBuffer* GrVkResourceProvider::findOrCreateSecondaryCommandBuffer() {
-    GrVkSecondaryCommandBuffer* cmdBuffer = nullptr;
-    int count = fAvailableSecondaryCommandBuffers.count();
-    if (count > 0) {
-        cmdBuffer = fAvailableSecondaryCommandBuffers[count-1];
-        fAvailableSecondaryCommandBuffers.removeShuffle(count - 1);
-    } else {
-        cmdBuffer = GrVkSecondaryCommandBuffer::Create(fGpu, fGpu->cmdPool());
-    }
-    return cmdBuffer;
-}
-
-void GrVkResourceProvider::recycleSecondaryCommandBuffer(GrVkSecondaryCommandBuffer* cb) {
-    cb->reset(fGpu);
-    fAvailableSecondaryCommandBuffers.push_back(cb);
-}
-
 const GrVkResource* GrVkResourceProvider::findOrCreateStandardUniformBufferResource() {
     const GrVkResource* resource = nullptr;
     int count = fAvailableUniformBufferResources.count();
@@ -334,28 +384,10 @@
 }
 
 void GrVkResourceProvider::destroyResources(bool deviceLost) {
-    // release our active command buffers
-    for (int i = 0; i < fActiveCommandBuffers.count(); ++i) {
-        SkASSERT(deviceLost || fActiveCommandBuffers[i]->finished(fGpu));
-        SkASSERT(fActiveCommandBuffers[i]->unique());
-        fActiveCommandBuffers[i]->reset(fGpu);
-        fActiveCommandBuffers[i]->unref(fGpu);
+    SkTaskGroup* taskGroup = fGpu->getContext()->contextPriv().getTaskGroup();
+    if (taskGroup) {
+        taskGroup->wait();
     }
-    fActiveCommandBuffers.reset();
-    // release our available command buffers
-    for (int i = 0; i < fAvailableCommandBuffers.count(); ++i) {
-        SkASSERT(deviceLost || fAvailableCommandBuffers[i]->finished(fGpu));
-        SkASSERT(fAvailableCommandBuffers[i]->unique());
-        fAvailableCommandBuffers[i]->unref(fGpu);
-    }
-    fAvailableCommandBuffers.reset();
-
-    // release our available secondary command buffers
-    for (int i = 0; i < fAvailableSecondaryCommandBuffers.count(); ++i) {
-        SkASSERT(fAvailableSecondaryCommandBuffers[i]->unique());
-        fAvailableSecondaryCommandBuffers[i]->unref(fGpu);
-    }
-    fAvailableSecondaryCommandBuffers.reset();
 
     // Release all copy pipelines
     for (int i = 0; i < fCopyPipelines.count(); ++i) {
@@ -368,6 +400,11 @@
     }
     fRenderPassArray.reset();
 
+    for (int i = 0; i < fExternalRenderPasses.count(); ++i) {
+        fExternalRenderPasses[i]->unref(fGpu);
+    }
+    fExternalRenderPasses.reset();
+
     // Iterate through all store GrVkSamplers and unref them before resetting the hash.
     SkTDynamicHash<GrVkSampler, GrVkSampler::Key>::Iter iter(&fSamplers);
     for (; !iter.done(); ++iter) {
@@ -380,6 +417,18 @@
     GR_VK_CALL(fGpu->vkInterface(), DestroyPipelineCache(fGpu->device(), fPipelineCache, nullptr));
     fPipelineCache = VK_NULL_HANDLE;
 
+    for (GrVkCommandPool* pool : fActiveCommandPools) {
+        SkASSERT(pool->unique());
+        pool->unref(fGpu);
+    }
+    fActiveCommandPools.reset();
+
+    for (GrVkCommandPool* pool : fAvailableCommandPools) {
+        SkASSERT(pool->unique());
+        pool->unref(fGpu);
+    }
+    fAvailableCommandPools.reset();
+
     // We must release/destroy all command buffers and pipeline states before releasing the
     // GrVkDescriptorSetManagers
     for (int i = 0; i < fDescriptorSetManagers.count(); ++i) {
@@ -396,25 +445,22 @@
 }
 
 void GrVkResourceProvider::abandonResources() {
-    // release our active command buffers
-    for (int i = 0; i < fActiveCommandBuffers.count(); ++i) {
-        SkASSERT(fActiveCommandBuffers[i]->unique());
-        fActiveCommandBuffers[i]->unrefAndAbandon();
+    SkTaskGroup* taskGroup = fGpu->getContext()->contextPriv().getTaskGroup();
+    if (taskGroup) {
+        taskGroup->wait();
     }
-    fActiveCommandBuffers.reset();
-    // release our available command buffers
-    for (int i = 0; i < fAvailableCommandBuffers.count(); ++i) {
-        SkASSERT(fAvailableCommandBuffers[i]->unique());
-        fAvailableCommandBuffers[i]->unrefAndAbandon();
-    }
-    fAvailableCommandBuffers.reset();
 
-    // release our available secondary command buffers
-    for (int i = 0; i < fAvailableSecondaryCommandBuffers.count(); ++i) {
-        SkASSERT(fAvailableSecondaryCommandBuffers[i]->unique());
-        fAvailableSecondaryCommandBuffers[i]->unrefAndAbandon();
+    // Abandon all command pools
+    for (int i = 0; i < fActiveCommandPools.count(); ++i) {
+        SkASSERT(fActiveCommandPools[i]->unique());
+        fActiveCommandPools[i]->unrefAndAbandon();
     }
-    fAvailableSecondaryCommandBuffers.reset();
+    fActiveCommandPools.reset();
+    for (int i = 0; i < fAvailableCommandPools.count(); ++i) {
+        SkASSERT(fAvailableCommandPools[i]->unique());
+        fAvailableCommandPools[i]->unrefAndAbandon();
+    }
+    fAvailableCommandPools.reset();
 
     // Abandon all copy pipelines
     for (int i = 0; i < fCopyPipelines.count(); ++i) {
@@ -427,6 +473,11 @@
     }
     fRenderPassArray.reset();
 
+    for (int i = 0; i < fExternalRenderPasses.count(); ++i) {
+        fExternalRenderPasses[i]->unrefAndAbandon();
+    }
+    fExternalRenderPasses.reset();
+
     // Iterate through all store GrVkSamplers and unrefAndAbandon them before resetting the hash.
     SkTDynamicHash<GrVkSampler, GrVkSampler::Key>::Iter iter(&fSamplers);
     for (; !iter.done(); ++iter) {
@@ -453,6 +504,48 @@
     fAvailableUniformBufferResources.reset();
 }
 
+void GrVkResourceProvider::backgroundReset(GrVkCommandPool* pool) {
+    SkASSERT(pool->unique());
+    pool->releaseResources(fGpu);
+    SkTaskGroup* taskGroup = fGpu->getContext()->contextPriv().getTaskGroup();
+    if (taskGroup) {
+        taskGroup->add([this, pool]() {
+            this->reset(pool);
+        });
+    } else {
+        this->reset(pool);
+    }
+}
+
+void GrVkResourceProvider::reset(GrVkCommandPool* pool) {
+    SkASSERT(pool->unique());
+    pool->reset(fGpu);
+    std::unique_lock<std::recursive_mutex> providerLock(fBackgroundMutex);
+    fAvailableCommandPools.push_back(pool);
+}
+
+void GrVkResourceProvider::storePipelineCacheData() {
+    size_t dataSize = 0;
+    VkResult result = GR_VK_CALL(fGpu->vkInterface(), GetPipelineCacheData(fGpu->device(),
+                                                                           this->pipelineCache(),
+                                                                           &dataSize, nullptr));
+    SkASSERT(result == VK_SUCCESS);
+
+    std::unique_ptr<uint8_t[]> data(new uint8_t[dataSize]);
+
+    result = GR_VK_CALL(fGpu->vkInterface(), GetPipelineCacheData(fGpu->device(),
+                                                                  this->pipelineCache(),
+                                                                  &dataSize,
+                                                                  (void*)data.get()));
+    SkASSERT(result == VK_SUCCESS);
+
+    uint32_t key = GrVkGpu::kPipelineCache_PersistentCacheKeyType;
+    sk_sp<SkData> keyData = SkData::MakeWithoutCopy(&key, sizeof(uint32_t));
+
+    fGpu->getContext()->contextPriv().getPersistentCache()->store(
+            *keyData, *SkData::MakeWithoutCopy(data.get(), dataSize));
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 GrVkResourceProvider::CompatibleRenderPassSet::CompatibleRenderPassSet(
@@ -488,7 +581,7 @@
     return renderPass;
 }
 
-void GrVkResourceProvider::CompatibleRenderPassSet::releaseResources(const GrVkGpu* gpu) {
+void GrVkResourceProvider::CompatibleRenderPassSet::releaseResources(GrVkGpu* gpu) {
     for (int i = 0; i < fRenderPasses.count(); ++i) {
         if (fRenderPasses[i]) {
             fRenderPasses[i]->unref(gpu);
diff --git a/src/gpu/vk/GrVkResourceProvider.h b/src/gpu/vk/GrVkResourceProvider.h
index 7771de1..97f11b1 100644
--- a/src/gpu/vk/GrVkResourceProvider.h
+++ b/src/gpu/vk/GrVkResourceProvider.h
@@ -8,8 +8,6 @@
 #ifndef GrVkResourceProvider_DEFINED
 #define GrVkResourceProvider_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrResourceHandle.h"
 #include "GrVkDescriptorPool.h"
 #include "GrVkDescriptorSetManager.h"
@@ -23,10 +21,12 @@
 #include "SkTArray.h"
 #include "SkTDynamicHash.h"
 #include "SkTInternalLList.h"
+#include "vk/GrVkTypes.h"
 
-class GrPipeline;
-class GrPrimitiveProcessor;
-class GrSamplerState;
+#include <mutex>
+#include <thread>
+
+class GrVkCommandPool;
 class GrVkCopyPipeline;
 class GrVkGpu;
 class GrVkPipeline;
@@ -69,6 +69,9 @@
     // findCompatibleRenderPass(GrVkRenderTarget&, CompatibleRPHandle*).
     const GrVkRenderPass* findCompatibleRenderPass(const CompatibleRPHandle& compatibleHandle);
 
+    const GrVkRenderPass* findCompatibleExternalRenderPass(VkRenderPass,
+                                                           uint32_t colorAttachmentIndex);
+
     // Finds or creates a render pass that matches the target and LoadStoreOps, increments the
     // refcount, and returns. The caller can optionally pass in a pointer to a CompatibleRPHandle.
     // If this is non null it will be set to a handle that can be used in the furutre to quickly
@@ -84,11 +87,9 @@
                                          const GrVkRenderPass::LoadStoreOps& colorOps,
                                          const GrVkRenderPass::LoadStoreOps& stencilOps);
 
-    GrVkPrimaryCommandBuffer* findOrCreatePrimaryCommandBuffer();
-    void checkCommandBuffers();
+    GrVkCommandPool* findOrCreateCommandPool();
 
-    GrVkSecondaryCommandBuffer* findOrCreateSecondaryCommandBuffer();
-    void recycleSecondaryCommandBuffer(GrVkSecondaryCommandBuffer* cb);
+    void checkCommandBuffers();
 
     // Finds or creates a compatible GrVkDescriptorPool for the requested type and count.
     // The refcount is incremented and a pointer returned.
@@ -155,6 +156,8 @@
     // can be reused by the next uniform buffer resource request.
     void recycleStandardUniformBufferResource(const GrVkResource*);
 
+    void storePipelineCacheData();
+
     // Destroy any cached resources. To be called before destroying the VkDevice.
     // The assumption is that all queues are idle and all command buffers are finished.
     // For resource tracing to work properly, this should be called after unrefing all other
@@ -168,7 +171,12 @@
     // resource usages.
     void abandonResources();
 
+    void backgroundReset(GrVkCommandPool* pool);
+
+    void reset(GrVkCommandPool* pool);
+
 private:
+
 #ifdef SK_DEBUG
 #define GR_PIPELINE_STATE_CACHE_STATS
 #endif
@@ -231,7 +239,7 @@
                                       const GrVkRenderPass::LoadStoreOps& colorOps,
                                       const GrVkRenderPass::LoadStoreOps& stencilOps);
 
-        void releaseResources(const GrVkGpu* gpu);
+        void releaseResources(GrVkGpu* gpu);
         void abandonResources();
 
     private:
@@ -239,6 +247,8 @@
         int                           fLastReturnedIndex;
     };
 
+    VkPipelineCache pipelineCache();
+
     GrVkGpu* fGpu;
 
     // Central cache for creating pipelines
@@ -249,13 +259,13 @@
 
     SkSTArray<4, CompatibleRenderPassSet> fRenderPassArray;
 
-    // Array of PrimaryCommandBuffers that are currently in flight
-    SkSTArray<4, GrVkPrimaryCommandBuffer*, true> fActiveCommandBuffers;
-    // Array of available primary command buffers that are not in flight
-    SkSTArray<4, GrVkPrimaryCommandBuffer*, true> fAvailableCommandBuffers;
+    SkTArray<const GrVkRenderPass*> fExternalRenderPasses;
 
-    // Array of available secondary command buffers
-    SkSTArray<16, GrVkSecondaryCommandBuffer*, true> fAvailableSecondaryCommandBuffers;
+    // Array of command pools that we are waiting on
+    SkSTArray<4, GrVkCommandPool*, true> fActiveCommandPools;
+
+    // Array of available command pools that are not in flight
+    SkSTArray<4, GrVkCommandPool*, true> fAvailableCommandPools;
 
     // Array of available uniform buffer resources
     SkSTArray<16, const GrVkResource*, true> fAvailableUniformBufferResources;
@@ -273,6 +283,8 @@
     SkSTArray<4, std::unique_ptr<GrVkDescriptorSetManager>> fDescriptorSetManagers;
 
     GrVkDescriptorSetManager::Handle fUniformDSHandle;
+
+    std::recursive_mutex fBackgroundMutex;
 };
 
 #endif
diff --git a/src/gpu/vk/GrVkSampler.cpp b/src/gpu/vk/GrVkSampler.cpp
index 97ceadd..57bf2d7 100644
--- a/src/gpu/vk/GrVkSampler.cpp
+++ b/src/gpu/vk/GrVkSampler.cpp
@@ -19,6 +19,8 @@
             return VK_SAMPLER_ADDRESS_MODE_REPEAT;
         case GrSamplerState::WrapMode::kMirrorRepeat:
             return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
+        case GrSamplerState::WrapMode::kClampToBorder:
+            return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
     }
     SK_ABORT("Unknown wrap mode.");
     return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
@@ -110,7 +112,7 @@
     return new GrVkSampler(sampler, ycbcrConversion, GenerateKey(samplerState, ycbcrInfo));
 }
 
-void GrVkSampler::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkSampler::freeGPUData(GrVkGpu* gpu) const {
     SkASSERT(fSampler);
     GR_VK_CALL(gpu->vkInterface(), DestroySampler(gpu->device(), fSampler, nullptr));
     if (fYcbcrConversion) {
diff --git a/src/gpu/vk/GrVkSampler.h b/src/gpu/vk/GrVkSampler.h
index 9d74d8f..9fca6c9 100644
--- a/src/gpu/vk/GrVkSampler.h
+++ b/src/gpu/vk/GrVkSampler.h
@@ -8,13 +8,11 @@
 #ifndef GrVkSampler_DEFINED
 #define GrVkSampler_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrVkResource.h"
 #include "GrVkSamplerYcbcrConversion.h"
-#include "SkAtomics.h"
 #include "SkOpts.h"
 #include "vk/GrVkTypes.h"
+#include <atomic>
 
 class GrSamplerState;
 class GrVkGpu;
@@ -67,14 +65,14 @@
             , fKey(key)
             , fUniqueID(GenID()) {}
 
-    void freeGPUData(const GrVkGpu* gpu) const override;
+    void freeGPUData(GrVkGpu* gpu) const override;
     void abandonGPUData() const override;
 
     static uint32_t GenID() {
-        static int32_t gUniqueID = SK_InvalidUniqueID;
+        static std::atomic<uint32_t> nextID{1};
         uint32_t id;
         do {
-            id = static_cast<uint32_t>(sk_atomic_inc(&gUniqueID) + 1);
+            id = nextID++;
         } while (id == SK_InvalidUniqueID);
         return id;
     }
diff --git a/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp b/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp
index bc08ac1..1465d09 100644
--- a/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp
+++ b/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp
@@ -68,7 +68,7 @@
 #endif
 }
 
-void GrVkSamplerYcbcrConversion::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkSamplerYcbcrConversion::freeGPUData(GrVkGpu* gpu) const {
     SkASSERT(fYcbcrConversion);
     GR_VK_CALL(gpu->vkInterface(), DestroySamplerYcbcrConversion(gpu->device(), fYcbcrConversion,
                                                                  nullptr));
diff --git a/src/gpu/vk/GrVkSamplerYcbcrConversion.h b/src/gpu/vk/GrVkSamplerYcbcrConversion.h
index 91f6480..4b7b63b 100644
--- a/src/gpu/vk/GrVkSamplerYcbcrConversion.h
+++ b/src/gpu/vk/GrVkSamplerYcbcrConversion.h
@@ -8,8 +8,6 @@
 #ifndef GrVkSamplerYcbcrConverison_DEFINED
 #define GrVkSamplerYcbcrConverison_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrVkResource.h"
 
 #include "SkOpts.h"
@@ -62,7 +60,7 @@
             , fYcbcrConversion(ycbcrConversion)
             , fKey(key) {}
 
-    void freeGPUData(const GrVkGpu* gpu) const override;
+    void freeGPUData(GrVkGpu* gpu) const override;
 
     VkSamplerYcbcrConversion fYcbcrConversion;
     Key                      fKey;
diff --git a/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp b/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp
new file mode 100644
index 0000000..83de36d
--- /dev/null
+++ b/src/gpu/vk/GrVkSecondaryCBDrawContext.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "vk/GrVkSecondaryCBDrawContext.h"
+
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "GrRenderTargetContext.h"
+#include "SkGpuDevice.h"
+#include "SkImageInfo.h"
+#include "SkSurfaceProps.h"
+#include "vk/GrVkTypes.h"
+
+sk_sp<GrVkSecondaryCBDrawContext> GrVkSecondaryCBDrawContext::Make(GrContext* ctx,
+                                                                   const SkImageInfo& imageInfo,
+                                                                   const GrVkDrawableInfo& vkInfo,
+                                                                   const SkSurfaceProps* props) {
+    if (!ctx) {
+        return nullptr;
+    }
+
+    if (ctx->contextPriv().getBackend() != GrBackendApi::kVulkan) {
+        return nullptr;
+    }
+
+    sk_sp<GrRenderTargetContext> rtc(
+            ctx->contextPriv().makeVulkanSecondaryCBRenderTargetContext(imageInfo, vkInfo, props));
+
+    int width = rtc->width();
+    int height = rtc->height();
+
+    sk_sp<SkGpuDevice> device(SkGpuDevice::Make(ctx, std::move(rtc), width, height,
+                                                SkGpuDevice::kUninit_InitContents));
+    if (!device) {
+        return nullptr;
+    }
+
+    return sk_sp<GrVkSecondaryCBDrawContext>(new GrVkSecondaryCBDrawContext(std::move(device)));
+}
+
+GrVkSecondaryCBDrawContext::GrVkSecondaryCBDrawContext(sk_sp<SkGpuDevice> device)
+    : fDevice(device) {}
+
+GrVkSecondaryCBDrawContext::~GrVkSecondaryCBDrawContext() {
+    SkASSERT(!fDevice);
+    SkASSERT(!fCachedCanvas.get());
+}
+
+SkCanvas* GrVkSecondaryCBDrawContext::getCanvas() {
+    if (!fCachedCanvas) {
+        fCachedCanvas = std::unique_ptr<SkCanvas>(new SkCanvas(fDevice));
+    }
+    return fCachedCanvas.get();
+}
+
+void GrVkSecondaryCBDrawContext::flush() {
+    fDevice->flush();
+}
+
+bool GrVkSecondaryCBDrawContext::wait(int numSemaphores,
+                                      const GrBackendSemaphore waitSemaphores[]) {
+    return fDevice->wait(numSemaphores, waitSemaphores);
+}
+
+void GrVkSecondaryCBDrawContext::releaseResources() {
+    fCachedCanvas.reset();
+    fDevice.reset();
+}
+
diff --git a/src/gpu/vk/GrVkSecondaryCBDrawContext.h b/src/gpu/vk/GrVkSecondaryCBDrawContext.h
new file mode 100644
index 0000000..c6ea9f9
--- /dev/null
+++ b/src/gpu/vk/GrVkSecondaryCBDrawContext.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrVkSecondaryCBDrawContext_DEFINED
+#define GrVkSecondaryCBDrawContext_DEFINED
+
+#include "SkTypes.h"
+#include "SkRefCnt.h"
+
+class GrBackendSemaphore;
+class GrContext;
+struct GrVkDrawableInfo;
+class SkCanvas;
+class SkDeferredDisplayList;
+class SkGpuDevice;
+struct SkImageInfo;
+class SkSurfaceCharacterization;
+class SkSurfaceProps;
+
+/**
+ * This class is a private header that is intended to only be used inside of Chromium. This requires
+ * Chromium to burrow in and include this specifically since it is not part of skia's public include
+ * directory.
+ */
+
+/**
+ * This class is used to draw into an external Vulkan secondary command buffer that is imported
+ * by the client. The secondary command buffer that gets imported must already have had begin called
+ * on it with VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT. Thus any draws to the imported
+ * command buffer cannot require changing the render pass. This requirement means that certain types
+ * of draws will not be supported when using a GrVkSecondaryCBDrawContext. This includes:
+ *     Draws that require a dst copy for blending will be dropped
+ *     Text draws will be dropped (these may require intermediate uploads of text data)
+ *     Read and Write pixels will not work
+ *     Any other draw that requires a copy will fail (this includes using backdrop filter with save
+ *         layer).
+ *     Stenciling is also disabled, but that should not restrict any actual draws from working.
+ *
+ * While using a GrVkSecondaryCBDrawContext, the client can also draw into normal SkSurfaces and
+ * then draw those SkSufaces (as SkImages) into the GrVkSecondaryCBDrawContext. If any of the
+ * previously mentioned unsupported draws are needed by the client, they can draw them into an
+ * offscreen surface, and then draw that into the GrVkSecondaryCBDrawContext.
+ *
+ * After all drawing to the GrVkSecondaryCBDrawContext has been done, the client must call flush()
+ * on the GrVkSecondaryCBDrawContext to actually fill in the secondary VkCommandBuffer with the
+ * draws.
+ *
+ * Additionally, the client must keep the GrVkSecondaryCBDrawContext alive until the secondary
+ * VkCommandBuffer has been submitted and all work finished on the GPU. Before deleting the
+ * GrVkSecondaryCBDrawContext, the client must call releaseResources() so that Skia can cleanup
+ * any internal objects that were created for the draws into the secondary command buffer.
+ */
+class SK_API GrVkSecondaryCBDrawContext : public SkRefCnt {
+public:
+    static sk_sp<GrVkSecondaryCBDrawContext> Make(GrContext*, const SkImageInfo&,
+                                                  const GrVkDrawableInfo&,
+                                                  const SkSurfaceProps* props);
+
+    ~GrVkSecondaryCBDrawContext() override;
+
+    SkCanvas* getCanvas();
+
+    // Records all the draws to the imported secondary command buffer and sets any dependent
+    // offscreen draws to the GPU.
+    void flush();
+
+    /** Inserts a list of GPU semaphores that Skia will have the driver wait on before executing
+        commands for this secondary CB. The wait semaphores will get added to the VkCommandBuffer
+        owned by this GrContext when flush() is called, and not the command buffer which the
+        Secondary CB is from. This will guarantee that the driver waits on the semaphores before
+        the secondary command buffer gets executed. Skia will take ownership of the underlying
+        semaphores and delete them once they have been signaled and waited on. If this call returns
+        false, then the GPU back-end will not wait on any passed in semaphores, and the client will
+        still own the semaphores.
+
+        @param numSemaphores   size of waitSemaphores array
+        @param waitSemaphores  array of semaphore containers
+        @return                true if GPU is waiting on semaphores
+    */
+    bool wait(int numSemaphores, const GrBackendSemaphore waitSemaphores[]);
+
+    // This call will release all resources held by the draw context. The client must call
+    // releaseResources() before deleting the drawing context. However, the resources also include
+    // any Vulkan resources that were created and used for draws. Therefore the client must only
+    // call releaseResources() after submitting the secondary command buffer, and waiting for it to
+    // finish on the GPU. If it is called earlier then some vulkan objects may be deleted while they
+    // are still in use by the GPU.
+    void releaseResources();
+
+    // TODO: Fill out these calls to support DDL
+    bool characterize(SkSurfaceCharacterization* characterization) const;
+    bool draw(SkDeferredDisplayList* deferredDisplayList);
+
+private:
+    explicit GrVkSecondaryCBDrawContext(sk_sp<SkGpuDevice>);
+
+    sk_sp<SkGpuDevice>        fDevice;
+    std::unique_ptr<SkCanvas> fCachedCanvas;
+
+    typedef SkRefCnt INHERITED;
+};
+
+#endif
diff --git a/src/gpu/vk/GrVkSemaphore.cpp b/src/gpu/vk/GrVkSemaphore.cpp
index ef53b30..8491782 100644
--- a/src/gpu/vk/GrVkSemaphore.cpp
+++ b/src/gpu/vk/GrVkSemaphore.cpp
@@ -51,7 +51,7 @@
 
 void GrVkSemaphore::onRelease() {
     if (fResource) {
-        fResource->unref(static_cast<const GrVkGpu*>(this->getGpu()));
+        fResource->unref(static_cast<GrVkGpu*>(this->getGpu()));
         fResource = nullptr;
     }
     INHERITED::onRelease();
@@ -65,7 +65,7 @@
     INHERITED::onAbandon();
 }
 
-void GrVkSemaphore::Resource::freeGPUData(const GrVkGpu* gpu) const {
+void GrVkSemaphore::Resource::freeGPUData(GrVkGpu* gpu) const {
     if (fIsOwned) {
         GR_VK_CALL(gpu->vkInterface(),
                    DestroySemaphore(gpu->device(), fSemaphore, nullptr));
diff --git a/src/gpu/vk/GrVkSemaphore.h b/src/gpu/vk/GrVkSemaphore.h
index 6873559..d90a498 100644
--- a/src/gpu/vk/GrVkSemaphore.h
+++ b/src/gpu/vk/GrVkSemaphore.h
@@ -8,13 +8,10 @@
 #ifndef GrVkSemaphore_DEFINED
 #define GrVkSemaphore_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrSemaphore.h"
 
 #include "GrResourceProvider.h"
 #include "GrVkResource.h"
-
 #include "vk/GrVkTypes.h"
 
 class GrBackendSemaphore;
@@ -71,7 +68,7 @@
         }
 #endif
     private:
-        void freeGPUData(const GrVkGpu* gpu) const override;
+        void freeGPUData(GrVkGpu* gpu) const override;
 
         static SkMutex* GetMutex() {
             static SkMutex kMutex;
diff --git a/src/gpu/vk/GrVkStencilAttachment.h b/src/gpu/vk/GrVkStencilAttachment.h
index 79dc74f..cb47130 100644
--- a/src/gpu/vk/GrVkStencilAttachment.h
+++ b/src/gpu/vk/GrVkStencilAttachment.h
@@ -8,10 +8,9 @@
 #ifndef GrVkStencil_DEFINED
 #define GrVkStencil_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrStencilAttachment.h"
 #include "GrVkImage.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkImageView;
 class GrVkGpu;
diff --git a/src/gpu/vk/GrVkTexture.cpp b/src/gpu/vk/GrVkTexture.cpp
index 8157433..fe9723d 100644
--- a/src/gpu/vk/GrVkTexture.cpp
+++ b/src/gpu/vk/GrVkTexture.cpp
@@ -41,12 +41,16 @@
                          const GrVkImageView* view,
                          GrMipMapsStatus mipMapsStatus,
                          GrBackendObjectOwnership ownership,
+                         GrIOType ioType,
                          bool purgeImmediately)
         : GrSurface(gpu, desc)
         , GrVkImage(info, std::move(layout), ownership)
         , INHERITED(gpu, desc, GrTextureType::k2D, mipMapsStatus)
         , fTextureView(view) {
     SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == info.fLevelCount));
+    if (ioType == kRead_GrIOType) {
+        this->setReadOnly();
+    }
     this->registerWithCacheWrapped(purgeImmediately);
 }
 
@@ -92,6 +96,7 @@
 sk_sp<GrVkTexture> GrVkTexture::MakeWrappedTexture(GrVkGpu* gpu,
                                                    const GrSurfaceDesc& desc,
                                                    GrWrapOwnership wrapOwnership,
+                                                   GrIOType ioType,
                                                    bool purgeImmediately,
                                                    const GrVkImageInfo& info,
                                                    sk_sp<GrVkImageLayout> layout) {
@@ -111,7 +116,7 @@
     GrBackendObjectOwnership ownership = kBorrow_GrWrapOwnership == wrapOwnership
             ? GrBackendObjectOwnership::kBorrowed : GrBackendObjectOwnership::kOwned;
     return sk_sp<GrVkTexture>(new GrVkTexture(gpu, kWrapped, desc, info, std::move(layout),
-                                              imageView, mipMapsStatus, ownership,
+                                              imageView, mipMapsStatus, ownership, ioType,
                                               purgeImmediately));
 }
 
@@ -121,6 +126,11 @@
 }
 
 void GrVkTexture::onRelease() {
+    // When there is an idle proc, the Resource will call the proc in releaseImage() so
+    // we clear it here.
+    fIdleProc = nullptr;
+    fIdleProcContext = nullptr;
+
     // we create this and don't hand it off, so we should always destroy it
     if (fTextureView) {
         fTextureView->unref(this->getVkGpu());
@@ -133,6 +143,11 @@
 }
 
 void GrVkTexture::onAbandon() {
+    // When there is an idle proc, the Resource will call the proc in abandonImage() so
+    // we clear it here.
+    fIdleProc = nullptr;
+    fIdleProcContext = nullptr;
+    // we create this and don't hand it off, so we should always destroy it
     if (fTextureView) {
         fTextureView->unrefAndAbandon();
         fTextureView = nullptr;
@@ -155,3 +170,27 @@
     return fTextureView;
 }
 
+void GrVkTexture::setIdleProc(IdleProc proc, void* context) {
+    fIdleProc = proc;
+    fIdleProcContext = context;
+    if (auto* resource = this->resource()) {
+        resource->setIdleProc(proc ? this : nullptr, proc, context);
+    }
+}
+
+void GrVkTexture::becamePurgeable() {
+    if (!fIdleProc) {
+        return;
+    }
+    // This is called when the GrTexture is purgeable. However, we need to check whether the
+    // Resource is still owned by any command buffers. If it is then it will call the proc.
+    auto* resource = this->resource();
+    SkASSERT(resource);
+    if (resource->isOwnedByCommandBuffer()) {
+        return;
+    }
+    fIdleProc(fIdleProcContext);
+    fIdleProc = nullptr;
+    fIdleProcContext = nullptr;
+    resource->setIdleProc(nullptr, nullptr, nullptr);
+}
diff --git a/src/gpu/vk/GrVkTexture.h b/src/gpu/vk/GrVkTexture.h
index d841bf3..d913aed 100644
--- a/src/gpu/vk/GrVkTexture.h
+++ b/src/gpu/vk/GrVkTexture.h
@@ -8,10 +8,9 @@
 #ifndef GrVkTexture_DEFINED
 #define GrVkTexture_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrTexture.h"
 #include "GrVkImage.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkGpu;
 class GrVkImageView;
@@ -25,8 +24,8 @@
                                              const GrVkImage::ImageDesc&,
                                              GrMipMapsStatus);
 
-    static sk_sp<GrVkTexture> MakeWrappedTexture(GrVkGpu*, const GrSurfaceDesc&,
-                                                 GrWrapOwnership, bool purgeImmediatley,
+    static sk_sp<GrVkTexture> MakeWrappedTexture(GrVkGpu*, const GrSurfaceDesc&, GrWrapOwnership,
+                                                 GrIOType, bool purgeImmediately,
                                                  const GrVkImageInfo&, sk_sp<GrVkImageLayout>);
 
     ~GrVkTexture() override;
@@ -46,6 +45,8 @@
         this->setResourceRelease(std::move(releaseHelper));
     }
 
+    void setIdleProc(IdleProc, void* context) override;
+
 protected:
     GrVkTexture(GrVkGpu*, const GrSurfaceDesc&, const GrVkImageInfo&, sk_sp<GrVkImageLayout>,
                 const GrVkImageView*, GrMipMapsStatus, GrBackendObjectOwnership);
@@ -66,9 +67,13 @@
                 GrMipMapsStatus);
     GrVkTexture(GrVkGpu*, Wrapped, const GrSurfaceDesc&, const GrVkImageInfo&,
                 sk_sp<GrVkImageLayout> layout, const GrVkImageView* imageView, GrMipMapsStatus,
-                GrBackendObjectOwnership, bool purgeImmediately);
+                GrBackendObjectOwnership, GrIOType ioType, bool purgeImmediately);
 
-    const GrVkImageView*     fTextureView;
+    void becamePurgeable() override;
+
+    const GrVkImageView* fTextureView;
+    GrTexture::IdleProc* fIdleProc = nullptr;
+    void* fIdleProcContext = nullptr;
 
     typedef GrTexture INHERITED;
 };
diff --git a/src/gpu/vk/GrVkTextureRenderTarget.h b/src/gpu/vk/GrVkTextureRenderTarget.h
index d774296..5026213 100644
--- a/src/gpu/vk/GrVkTextureRenderTarget.h
+++ b/src/gpu/vk/GrVkTextureRenderTarget.h
@@ -9,10 +9,9 @@
 #ifndef GrVkTextureRenderTarget_DEFINED
 #define GrVkTextureRenderTarget_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrVkTexture.h"
 #include "GrVkRenderTarget.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkGpu;
 
diff --git a/src/gpu/vk/GrVkTransferBuffer.h b/src/gpu/vk/GrVkTransferBuffer.h
index 988267c..af9ed1b 100644
--- a/src/gpu/vk/GrVkTransferBuffer.h
+++ b/src/gpu/vk/GrVkTransferBuffer.h
@@ -8,10 +8,9 @@
 #ifndef GrVkTransferBuffer_DEFINED
 #define GrVkTransferBuffer_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrBuffer.h"
 #include "GrVkBuffer.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkGpu;
 
diff --git a/src/gpu/vk/GrVkTypesPriv.cpp b/src/gpu/vk/GrVkTypesPriv.cpp
index 5a2379e..ec75e58 100644
--- a/src/gpu/vk/GrVkTypesPriv.cpp
+++ b/src/gpu/vk/GrVkTypesPriv.cpp
@@ -5,8 +5,6 @@
  * found in the LICENSE file.
  */
 
-#include "GrVkVulkan.h"
-
 #include "GrVkTypesPriv.h"
 
 #include "GrVkImageLayout.h"
diff --git a/src/gpu/vk/GrVkUniformBuffer.h b/src/gpu/vk/GrVkUniformBuffer.h
index 5ffd13b..1991f06 100644
--- a/src/gpu/vk/GrVkUniformBuffer.h
+++ b/src/gpu/vk/GrVkUniformBuffer.h
@@ -8,9 +8,8 @@
 #ifndef GrVkUniformBuffer_DEFINED
 #define GrVkUniformBuffer_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrVkBuffer.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkGpu;
 
diff --git a/src/gpu/vk/GrVkUtil.cpp b/src/gpu/vk/GrVkUtil.cpp
index 674a1b9..90798c9 100644
--- a/src/gpu/vk/GrVkUtil.cpp
+++ b/src/gpu/vk/GrVkUtil.cpp
@@ -25,6 +25,9 @@
         case kRGB_888_GrPixelConfig:
             *format = VK_FORMAT_R8G8B8_UNORM;
             return true;
+        case kRG_88_GrPixelConfig:
+            *format = VK_FORMAT_R8G8_UNORM;
+            return true;
         case kBGRA_8888_GrPixelConfig:
             *format = VK_FORMAT_B8G8R8A8_UNORM;
             return true;
@@ -88,6 +91,8 @@
             return kSBGRA_8888_GrPixelConfig == config;
         case VK_FORMAT_R8G8B8_UNORM:
             return kRGB_888_GrPixelConfig == config;
+        case VK_FORMAT_R8G8_UNORM:
+            return kRG_88_GrPixelConfig == config;
         case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
             return kRGBA_1010102_GrPixelConfig == config;
         case VK_FORMAT_R5G6B5_UNORM_PACK16:
@@ -124,6 +129,7 @@
         case VK_FORMAT_B8G8R8A8_SRGB:
         case VK_FORMAT_R8G8B8A8_SINT:
         case VK_FORMAT_R8G8B8_UNORM:
+        case VK_FORMAT_R8G8_UNORM:
         case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
         case VK_FORMAT_R5G6B5_UNORM_PACK16:
         case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
@@ -178,23 +184,13 @@
     return SkSL::Program::kFragment_Kind;
 }
 
-VkShaderStageFlagBits skiasl_kind_to_vk_shader_stage(SkSL::Program::Kind kind) {
-    if (SkSL::Program::kVertex_Kind == kind) {
-        return VK_SHADER_STAGE_VERTEX_BIT;
-    }
-    if (SkSL::Program::kGeometry_Kind == kind) {
-        return VK_SHADER_STAGE_GEOMETRY_BIT;
-    }
-    SkASSERT(SkSL::Program::kFragment_Kind == kind);
-    return VK_SHADER_STAGE_FRAGMENT_BIT;
-}
-
 bool GrCompileVkShaderModule(const GrVkGpu* gpu,
                              const char* shaderString,
                              VkShaderStageFlagBits stage,
                              VkShaderModule* shaderModule,
                              VkPipelineShaderStageCreateInfo* stageInfo,
                              const SkSL::Program::Settings& settings,
+                             SkSL::String* outSPIRV,
                              SkSL::Program::Inputs* outInputs) {
     std::unique_ptr<SkSL::Program> program = gpu->shaderCompiler()->convertProgram(
                                                               vk_shader_stage_to_skiasl_kind(stage),
@@ -205,19 +201,26 @@
         SkASSERT(false);
     }
     *outInputs = program->fInputs;
-    SkSL::String code;
-    if (!gpu->shaderCompiler()->toSPIRV(*program, &code)) {
+    if (!gpu->shaderCompiler()->toSPIRV(*program, outSPIRV)) {
         SkDebugf("%s\n", gpu->shaderCompiler()->errorText().c_str());
         return false;
     }
 
+    return GrInstallVkShaderModule(gpu, *outSPIRV, stage, shaderModule, stageInfo);
+}
+
+bool GrInstallVkShaderModule(const GrVkGpu* gpu,
+                             const SkSL::String& spirv,
+                             VkShaderStageFlagBits stage,
+                             VkShaderModule* shaderModule,
+                             VkPipelineShaderStageCreateInfo* stageInfo) {
     VkShaderModuleCreateInfo moduleCreateInfo;
     memset(&moduleCreateInfo, 0, sizeof(VkShaderModuleCreateInfo));
     moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
     moduleCreateInfo.pNext = nullptr;
     moduleCreateInfo.flags = 0;
-    moduleCreateInfo.codeSize = code.size();
-    moduleCreateInfo.pCode = (const uint32_t*)code.c_str();
+    moduleCreateInfo.codeSize = spirv.size();
+    moduleCreateInfo.pCode = (const uint32_t*)spirv.c_str();
 
     VkResult err = GR_VK_CALL(gpu->vkInterface(), CreateShaderModule(gpu->device(),
                                                                      &moduleCreateInfo,
@@ -231,7 +234,7 @@
     stageInfo->sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
     stageInfo->pNext = nullptr;
     stageInfo->flags = 0;
-    stageInfo->stage = skiasl_kind_to_vk_shader_stage(program->fKind);
+    stageInfo->stage = stage;
     stageInfo->module = *shaderModule;
     stageInfo->pName = "main";
     stageInfo->pSpecializationInfo = nullptr;
diff --git a/src/gpu/vk/GrVkUtil.h b/src/gpu/vk/GrVkUtil.h
index 78683c5..b279546 100644
--- a/src/gpu/vk/GrVkUtil.h
+++ b/src/gpu/vk/GrVkUtil.h
@@ -8,13 +8,12 @@
 #ifndef GrVkUtil_DEFINED
 #define GrVkUtil_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrColor.h"
 #include "GrTypes.h"
 #include "GrVkInterface.h"
 #include "SkMacros.h"
 #include "ir/SkSLProgram.h"
+#include "vk/GrVkTypes.h"
 
 class GrVkGpu;
 
@@ -51,6 +50,13 @@
                              VkShaderModule* shaderModule,
                              VkPipelineShaderStageCreateInfo* stageInfo,
                              const SkSL::Program::Settings& settings,
+                             SkSL::String* outSPIRV,
                              SkSL::Program::Inputs* outInputs);
 
+bool GrInstallVkShaderModule(const GrVkGpu* gpu,
+                             const SkSL::String& spirv,
+                             VkShaderStageFlagBits stage,
+                             VkShaderModule* shaderModule,
+                             VkPipelineShaderStageCreateInfo* stageInfo);
+
 #endif
diff --git a/src/gpu/vk/GrVkVaryingHandler.h b/src/gpu/vk/GrVkVaryingHandler.h
index 29b38ad..cebf455 100644
--- a/src/gpu/vk/GrVkVaryingHandler.h
+++ b/src/gpu/vk/GrVkVaryingHandler.h
@@ -8,8 +8,6 @@
 #ifndef GrVkVaryingHandler_DEFINED
 #define GrVkVaryingHandler_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "glsl/GrGLSLVarying.h"
 
 class GrVkVaryingHandler : public GrGLSLVaryingHandler {
diff --git a/src/gpu/vk/GrVkVertexBuffer.h b/src/gpu/vk/GrVkVertexBuffer.h
index 02b3451..cae781e 100644
--- a/src/gpu/vk/GrVkVertexBuffer.h
+++ b/src/gpu/vk/GrVkVertexBuffer.h
@@ -8,8 +8,6 @@
 #ifndef GrVkVertexBuffer_DEFINED
 #define GrVkVertexBuffer_DEFINED
 
-#include "GrVkVulkan.h"
-
 #include "GrBuffer.h"
 #include "GrVkBuffer.h"
 
diff --git a/src/gpu/vk/GrVkVulkan.h b/src/gpu/vk/GrVkVulkan.h
deleted file mode 100644
index 9cf1444..0000000
--- a/src/gpu/vk/GrVkVulkan.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef GrVkVulkan_DEFINED
-#define GrVkVulkan_DEFINED
-
-#include "SkTypes.h"
-
-#ifdef VULKAN_CORE_H_
-#error "Skia's private vulkan header must be included before any other vulkan header."
-#endif
-
-#include "../../../third_party/vulkan/vulkan/vulkan_core.h"
-
-#ifdef SK_BUILD_FOR_ANDROID
-#ifdef VULKAN_ANDROID_H_
-#error "Skia's private vulkan android header must be included before any other vulkan header."
-#endif
-// This is needed to get android extensions for external memory
-#include "../../../third_party/vulkan/vulkan/vulkan_android.h"
-#endif
-
-#endif
diff --git a/src/image/SkImage_Gpu.cpp b/src/image/SkImage_Gpu.cpp
index 600ba39..8e00eb4 100644
--- a/src/image/SkImage_Gpu.cpp
+++ b/src/image/SkImage_Gpu.cpp
@@ -38,15 +38,15 @@
 #include "SkImageInfoPriv.h"
 #include "SkImage_Gpu.h"
 #include "SkMipMap.h"
+#include "SkScopeExit.h"
 #include "SkTraceEvent.h"
 #include "effects/GrYUVtoRGBEffect.h"
 #include "gl/GrGLTexture.h"
 
 SkImage_Gpu::SkImage_Gpu(sk_sp<GrContext> context, uint32_t uniqueID, SkAlphaType at,
-                         sk_sp<GrTextureProxy> proxy, sk_sp<SkColorSpace> colorSpace,
-                         SkBudgeted budgeted)
+                         sk_sp<GrTextureProxy> proxy, sk_sp<SkColorSpace> colorSpace)
         : INHERITED(std::move(context), proxy->worstCaseWidth(), proxy->worstCaseHeight(), uniqueID,
-                    at, budgeted, colorSpace)
+                    at, colorSpace)
         , fProxy(std::move(proxy)) {}
 
 SkImage_Gpu::~SkImage_Gpu() {}
@@ -74,13 +74,13 @@
     }
 
     GrProxyProvider* proxyProvider = ctx->contextPriv().proxyProvider();
-    sk_sp<GrTextureProxy> proxy = proxyProvider->wrapBackendTexture(backendTex, origin, ownership,
-                                                                    releaseProc, releaseCtx);
+    sk_sp<GrTextureProxy> proxy = proxyProvider->wrapBackendTexture(
+            backendTex, origin, ownership, kRead_GrIOType, releaseProc, releaseCtx);
     if (!proxy) {
         return nullptr;
     }
     return sk_make_sp<SkImage_Gpu>(sk_ref_sp(ctx), kNeedNewImageUniqueID, at, std::move(proxy),
-                                   std::move(colorSpace), SkBudgeted::kNo);
+                                   std::move(colorSpace));
 }
 
 sk_sp<SkImage> SkImage::MakeFromTexture(GrContext* ctx,
@@ -114,10 +114,11 @@
                                       kAdopt_GrWrapOwnership, nullptr, nullptr);
 }
 
-sk_sp<SkImage> SkImage_Gpu::ConvertYUVATexturesToRGB(
-        GrContext* ctx, SkYUVColorSpace yuvColorSpace, const GrBackendTexture yuvaTextures[],
-        const SkYUVAIndex yuvaIndices[4], SkISize size, GrSurfaceOrigin origin,
-        SkBudgeted isBudgeted, GrRenderTargetContext* renderTargetContext) {
+sk_sp<SkImage> SkImage_Gpu::ConvertYUVATexturesToRGB(GrContext* ctx, SkYUVColorSpace yuvColorSpace,
+                                                     const GrBackendTexture yuvaTextures[],
+                                                     const SkYUVAIndex yuvaIndices[4], SkISize size,
+                                                     GrSurfaceOrigin origin,
+                                                     GrRenderTargetContext* renderTargetContext) {
     SkASSERT(renderTargetContext);
 
     int numTextures;
@@ -141,8 +142,7 @@
     // MDB: this call is okay bc we know 'renderTargetContext' was exact
     return sk_make_sp<SkImage_Gpu>(sk_ref_sp(ctx), kNeedNewImageUniqueID, at,
                                    renderTargetContext->asTextureProxyRef(),
-                                   renderTargetContext->colorSpaceInfo().refColorSpace(),
-                                   isBudgeted);
+                                   renderTargetContext->colorSpaceInfo().refColorSpace());
 }
 
 sk_sp<SkImage> SkImage::MakeFromYUVATexturesCopy(GrContext* ctx,
@@ -168,8 +168,7 @@
     }
 
     return SkImage_Gpu::ConvertYUVATexturesToRGB(ctx, yuvColorSpace, yuvaTextures, yuvaIndices,
-                                                 imageSize, imageOrigin, SkBudgeted::kYes,
-                                                 renderTargetContext.get());
+                                                 imageSize, imageOrigin, renderTargetContext.get());
 }
 
 sk_sp<SkImage> SkImage::MakeFromYUVATexturesCopyWithExternalBackend(
@@ -201,8 +200,7 @@
     }
 
     return SkImage_Gpu::ConvertYUVATexturesToRGB(ctx, yuvColorSpace, yuvaTextures, yuvaIndices,
-                                                 imageSize, imageOrigin, SkBudgeted::kNo,
-                                                 renderTargetContext.get());
+                                                 imageSize, imageOrigin, renderTargetContext.get());
 }
 
 sk_sp<SkImage> SkImage::MakeFromYUVTexturesCopy(GrContext* ctx, SkYUVColorSpace yuvColorSpace,
@@ -276,7 +274,7 @@
         return nullptr;
     }
     return sk_make_sp<SkImage_Gpu>(sk_ref_sp(context), id, at, std::move(proxy),
-                                   sk_ref_sp(producer->colorSpace()), SkBudgeted::kNo);
+                                   sk_ref_sp(producer->colorSpace()));
 }
 
 sk_sp<SkImage> SkImage::makeTextureImage(GrContext* context, SkColorSpace* dstColorSpace,
@@ -329,13 +327,17 @@
                                                PromiseDoneProc promiseDoneProc,
                                                TextureContext textureContext) {
     // The contract here is that if 'promiseDoneProc' is passed in it should always be called,
-    // even if creation of the SkImage fails.
+    // even if creation of the SkImage fails. Once we call MakePromiseImageLazyProxy it takes
+    // responsibility for calling the done proc.
     if (!promiseDoneProc) {
         return nullptr;
     }
+    SkScopeExit callDone([promiseDoneProc, textureContext]() { promiseDoneProc(textureContext); });
 
-    SkPromiseImageHelper promiseHelper(textureFulfillProc, textureReleaseProc, promiseDoneProc,
-                                       textureContext);
+    SkImageInfo info = SkImageInfo::Make(width, height, colorType, alphaType, colorSpace);
+    if (!SkImageInfoIsValid(info)) {
+        return nullptr;
+    }
 
     if (!context) {
         return nullptr;
@@ -345,204 +347,21 @@
         return nullptr;
     }
 
-    if (!textureFulfillProc || !textureReleaseProc) {
+    GrPixelConfig config =
+            context->contextPriv().caps()->getConfigFromBackendFormat(backendFormat, colorType);
+    if (config == kUnknown_GrPixelConfig) {
         return nullptr;
     }
 
-    SkImageInfo info = SkImageInfo::Make(width, height, colorType, alphaType, colorSpace);
-    if (!SkImageInfoIsValid(info)) {
-        return nullptr;
-    }
-    GrPixelConfig config = kUnknown_GrPixelConfig;
-    if (!context->contextPriv().caps()->getConfigFromBackendFormat(backendFormat, colorType,
-                                                                   &config)) {
-        return nullptr;
-    }
-
-    if (mipMapped == GrMipMapped::kYes &&
-        GrTextureTypeHasRestrictedSampling(backendFormat.textureType())) {
-        // It is invalid to have a GL_TEXTURE_EXTERNAL or GL_TEXTURE_RECTANGLE and have mips as
-        // well.
-        return nullptr;
-    }
-
-    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
-
-    GrSurfaceDesc desc;
-    desc.fWidth = width;
-    desc.fHeight = height;
-    desc.fConfig = config;
-
-    sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy(
-            [promiseHelper, config](GrResourceProvider* resourceProvider) mutable {
-                if (!resourceProvider) {
-                    promiseHelper.reset();
-                    return sk_sp<GrTexture>();
-                }
-
-                return promiseHelper.getTexture(resourceProvider, config);
-            },
-            backendFormat, desc, origin, mipMapped, GrInternalSurfaceFlags::kNone,
-            SkBackingFit::kExact, SkBudgeted::kNo,
-            GrSurfaceProxy::LazyInstantiationType::kUninstantiate);
-
+    callDone.clear();
+    auto proxy = MakePromiseImageLazyProxy(context, width, height, origin, config, backendFormat,
+                                           mipMapped, textureFulfillProc, textureReleaseProc,
+                                           promiseDoneProc, textureContext);
     if (!proxy) {
         return nullptr;
     }
-
     return sk_make_sp<SkImage_Gpu>(sk_ref_sp(context), kNeedNewImageUniqueID, alphaType,
-                                   std::move(proxy), std::move(colorSpace), SkBudgeted::kNo);
-}
-
-sk_sp<SkImage> SkImage_Gpu::MakePromiseYUVATexture(GrContext* context,
-                                                   SkYUVColorSpace yuvColorSpace,
-                                                   const GrBackendFormat yuvaFormats[],
-                                                   const SkYUVAIndex yuvaIndices[4],
-                                                   int imageWidth,
-                                                   int imageHeight,
-                                                   GrSurfaceOrigin imageOrigin,
-                                                   sk_sp<SkColorSpace> imageColorSpace,
-                                                   TextureFulfillProc textureFulfillProc,
-                                                   TextureReleaseProc textureReleaseProc,
-                                                   PromiseDoneProc promiseDoneProc,
-                                                   TextureContext textureContexts[]) {
-    // The contract here is that if 'promiseDoneProc' is passed in it should always be called,
-    // even if creation of the SkImage fails.
-    if (!promiseDoneProc) {
-        return nullptr;
-    }
-
-    // Temporarily work around an MSVC compiler bug. Copying the arrays directly into the lambda
-    // doesn't work on some older tool chains
-    struct {
-        GrPixelConfig fConfigs[4] = { kUnknown_GrPixelConfig, kUnknown_GrPixelConfig,
-                                      kUnknown_GrPixelConfig, kUnknown_GrPixelConfig };
-        SkPromiseImageHelper fPromiseHelpers[4];
-        SkYUVAIndex fLocalIndices[4];
-    } params;
-
-    // Determine which of the slots in 'yuvaFormats' and 'textureContexts' are actually used
-    bool slotUsed[4] = { false, false, false, false };
-    for (int i = 0; i < 4; ++i) {
-        if (yuvaIndices[i].fIndex < 0) {
-            SkASSERT(SkYUVAIndex::kA_Index == i); // We had better have YUV channels
-            continue;
-        }
-
-        SkASSERT(yuvaIndices[i].fIndex < 4);
-        slotUsed[yuvaIndices[i].fIndex] = true;
-    }
-
-    for (int i = 0; i < 4; ++i) {
-        params.fLocalIndices[i] = yuvaIndices[i];
-
-        if (slotUsed[i]) {
-            params.fPromiseHelpers[i].set(textureFulfillProc, textureReleaseProc,
-                                          promiseDoneProc, textureContexts[i]);
-        }
-    }
-
-    // DDL TODO: we need to create a SkImage_GpuYUVA here! This implementation just
-    // returns the Y-plane.
-    if (!context) {
-        return nullptr;
-    }
-
-    if (imageWidth <= 0 || imageHeight <= 0) {
-        return nullptr;
-    }
-
-    if (!textureFulfillProc || !textureReleaseProc) {
-        return nullptr;
-    }
-
-    SkImageInfo info = SkImageInfo::Make(imageWidth, imageHeight, kRGBA_8888_SkColorType,
-                                         kPremul_SkAlphaType, imageColorSpace);
-    if (!SkImageInfoIsValid(info)) {
-        return nullptr;
-    }
-
-    for (int i = 0; i < 4; ++i) {
-        if (slotUsed[i]) {
-            // DDL TODO: This (the kAlpha_8) only works for non-NV12 YUV textures
-            if (!context->contextPriv().caps()->getConfigFromBackendFormat(yuvaFormats[i],
-                                                                           kAlpha_8_SkColorType,
-                                                                           &params.fConfigs[i])) {
-                return nullptr;
-            }
-        }
-    }
-
-    GrSurfaceDesc desc;
-    desc.fFlags = kNone_GrSurfaceFlags;
-    desc.fWidth = imageWidth;
-    desc.fHeight = imageHeight;
-    // Hack since we're just returning the Y-plane
-    desc.fConfig = params.fConfigs[params.fLocalIndices[SkYUVAIndex::kY_Index].fIndex];
-    desc.fSampleCnt = 1;
-
-    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
-
-    sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy(
-            [params](GrResourceProvider* resourceProvider) mutable {
-                if (!resourceProvider) {
-                    for (int i = 0; i < 4; ++i) {
-                        if (params.fPromiseHelpers[i].isValid()) {
-                            params.fPromiseHelpers[i].reset();
-                        }
-                    }
-                    return sk_sp<GrTexture>();
-                }
-
-                // We need to collect the YUVA planes as backend textures (vs. GrTextures) to
-                // feed into the SkImage_GpuYUVA factory.
-                GrBackendTexture yuvaTextures[4];
-                for (int i = 0; i < 4; ++i) {
-                    if (params.fPromiseHelpers[i].isValid()) {
-                        sk_sp<GrTexture> tmp = params.fPromiseHelpers[i].getTexture(
-                            resourceProvider, params.fConfigs[i]);
-                        if (!tmp) {
-                            return sk_sp<GrTexture>();
-                        }
-                        yuvaTextures[i] = tmp->getBackendTexture();
-                    }
-                }
-
-#if 1
-                // For the time being, simply return the Y-plane. The reason for this is that
-                // this lazy proxy is instantiated at flush time, after the sort, therefore
-                // we cannot be introducing a new opList (in order to render the YUV texture).
-                int yIndex = params.fLocalIndices[SkYUVAIndex::kY_Index].fIndex;
-                return params.fPromiseHelpers[yIndex].getTexture(resourceProvider,
-                                                                 params.fConfigs[yIndex]);
-#else
-                GrGpu* gpu = resourceProvider->priv().gpu();
-                GrContext* context = gpu->getContext();
-
-                sk_sp<SkImage> tmp = SkImage_Gpu::MakeFromYUVATexturesCopyImpl(context,
-                                                                               yuvColorSpace,
-                                                                               yuvaTextures,
-                                                                               localIndices,
-                                                                               imageSize,
-                                                                               imageOrigin,
-                                                                               imageColorSpace);
-                if (!tmp) {
-                    return sk_sp<GrTexture>();
-                }
-                return sk_ref_sp<GrTexture>(tmp->getTexture());
-#endif
-            },
-            yuvaFormats[yuvaIndices[SkYUVAIndex::kY_Index].fIndex],
-            desc, imageOrigin, GrMipMapped::kNo,
-            GrInternalSurfaceFlags::kNone, SkBackingFit::kExact, SkBudgeted::kNo,
-            GrSurfaceProxy::LazyInstantiationType::kUninstantiate);
-
-    if (!proxy) {
-        return nullptr;
-    }
-
-    return sk_make_sp<SkImage_Gpu>(sk_ref_sp(context), kNeedNewImageUniqueID, kPremul_SkAlphaType,
-                                   std::move(proxy), std::move(imageColorSpace), SkBudgeted::kNo);
+                                   std::move(proxy), std::move(colorSpace));
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/image/SkImage_Gpu.h b/src/image/SkImage_Gpu.h
index e184e17..de0407d 100644
--- a/src/image/SkImage_Gpu.h
+++ b/src/image/SkImage_Gpu.h
@@ -23,7 +23,7 @@
 class SkImage_Gpu : public SkImage_GpuBase {
 public:
     SkImage_Gpu(sk_sp<GrContext>, uint32_t uniqueID, SkAlphaType, sk_sp<GrTextureProxy>,
-                sk_sp<SkColorSpace>, SkBudgeted);
+                sk_sp<SkColorSpace>);
     ~SkImage_Gpu() override;
 
     SkImageInfo onImageInfo() const override;
@@ -94,25 +94,11 @@
                                              PromiseDoneProc promiseDoneProc,
                                              TextureContext textureContext);
 
-    /** To be deprecated. Use SkImage_GpuYUVA::MakePromiseYUVATexture instead.
-     */
-    static sk_sp<SkImage> MakePromiseYUVATexture(GrContext* context,
-                                                 SkYUVColorSpace yuvColorSpace,
-                                                 const GrBackendFormat yuvaFormats[],
-                                                 const SkYUVAIndex yuvaIndices[4],
-                                                 int imageWidth,
-                                                 int imageHeight,
-                                                 GrSurfaceOrigin imageOrigin,
-                                                 sk_sp<SkColorSpace> imageColorSpace,
-                                                 TextureFulfillProc textureFulfillProc,
-                                                 TextureReleaseProc textureReleaseProc,
-                                                 PromiseDoneProc promiseDoneProc,
-                                                 TextureContext textureContexts[]);
-
-    static sk_sp<SkImage> ConvertYUVATexturesToRGB(
-            GrContext*, SkYUVColorSpace yuvColorSpace, const GrBackendTexture yuvaTextures[],
-            const SkYUVAIndex yuvaIndices[4], SkISize imageSize, GrSurfaceOrigin imageOrigin,
-            SkBudgeted, GrRenderTargetContext*);
+    static sk_sp<SkImage> ConvertYUVATexturesToRGB(GrContext*, SkYUVColorSpace yuvColorSpace,
+                                                   const GrBackendTexture yuvaTextures[],
+                                                   const SkYUVAIndex yuvaIndices[4],
+                                                   SkISize imageSize, GrSurfaceOrigin imageOrigin,
+                                                   GrRenderTargetContext*);
 
 private:
     sk_sp<GrTextureProxy> fProxy;
diff --git a/src/image/SkImage_GpuBase.cpp b/src/image/SkImage_GpuBase.cpp
index 4dfc483..1c013dd 100644
--- a/src/image/SkImage_GpuBase.cpp
+++ b/src/image/SkImage_GpuBase.cpp
@@ -19,11 +19,10 @@
 #include "SkReadPixelsRec.h"
 
 SkImage_GpuBase::SkImage_GpuBase(sk_sp<GrContext> context, int width, int height, uint32_t uniqueID,
-                                 SkAlphaType at, SkBudgeted budgeted, sk_sp<SkColorSpace> cs)
+                                 SkAlphaType at, sk_sp<SkColorSpace> cs)
         : INHERITED(width, height, uniqueID)
         , fContext(std::move(context))
         , fAlphaType(at)
-        , fBudgeted(budgeted)
         , fColorSpace(std::move(cs)) {}
 
 SkImage_GpuBase::~SkImage_GpuBase() {}
@@ -42,8 +41,12 @@
     if (!SkImageInfoIsValid(info)) {
         return false;
     }
-
-    return ctx->contextPriv().caps()->validateBackendTexture(tex, ct, config);
+    GrBackendFormat backendFormat = tex.getBackendFormat();
+    if (!backendFormat.isValid()) {
+        return false;
+    }
+    *config = ctx->contextPriv().caps()->getConfigFromBackendFormat(backendFormat, ct);
+    return *config != kUnknown_GrPixelConfig;
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////////////
@@ -105,8 +108,10 @@
         return nullptr;
     }
 
+    // TODO: Should this inherit our proxy's budgeted status?
     sk_sp<GrSurfaceContext> sContext(fContext->contextPriv().makeDeferredSurfaceContext(
-        format, desc, proxy->origin(), GrMipMapped::kNo, SkBackingFit::kExact, fBudgeted));
+            format, desc, proxy->origin(), GrMipMapped::kNo, SkBackingFit::kExact,
+            proxy->isBudgeted()));
     if (!sContext) {
         return nullptr;
     }
@@ -116,9 +121,8 @@
     }
 
     // MDB: this call is okay bc we know 'sContext' was kExact
-    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID,
-                                   fAlphaType, sContext->asTextureProxyRef(),
-                                   fColorSpace, fBudgeted);
+    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID, fAlphaType,
+                                   sContext->asTextureProxyRef(), fColorSpace);
 }
 
 static void apply_premul(const SkImageInfo& info, void* pixels, size_t rowBytes) {
@@ -283,9 +287,8 @@
     }
 
     // MDB: this call is okay bc we know 'renderTargetContext' was exact
-    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID,
-                                   fAlphaType, renderTargetContext->asTextureProxyRef(),
-                                   std::move(target), fBudgeted);
+    return sk_make_sp<SkImage_Gpu>(fContext, kNeedNewImageUniqueID, fAlphaType,
+                                   renderTargetContext->asTextureProxyRef(), std::move(target));
 }
 
 bool SkImage_GpuBase::onIsValid(GrContext* context) const {
@@ -312,15 +315,20 @@
     GrBackendTexture yuvaTexturesCopy[4];
     for (int textureIndex = 0; textureIndex < numTextures; ++textureIndex) {
         yuvaTexturesCopy[textureIndex] = yuvaTextures[textureIndex];
-        if (!ctx->contextPriv().caps()->getYUVAConfigFromBackendTexture(
-            yuvaTexturesCopy[textureIndex],
-            &yuvaTexturesCopy[textureIndex].fConfig)) {
+        GrBackendFormat backendFormat = yuvaTexturesCopy[textureIndex].getBackendFormat();
+        if (!backendFormat.isValid()) {
+            return false;
+        }
+        yuvaTexturesCopy[textureIndex].fConfig =
+                ctx->contextPriv().caps()->getYUVAConfigFromBackendFormat(backendFormat);
+        if (yuvaTexturesCopy[textureIndex].fConfig == kUnknown_GrPixelConfig) {
             return false;
         }
         SkASSERT(yuvaTexturesCopy[textureIndex].isValid());
 
         tempTextureProxies[textureIndex] =
-            proxyProvider->wrapBackendTexture(yuvaTexturesCopy[textureIndex], imageOrigin);
+                proxyProvider->wrapBackendTexture(yuvaTexturesCopy[textureIndex], imageOrigin,
+                                                  kBorrow_GrWrapOwnership, kRead_GrIOType);
         if (!tempTextureProxies[textureIndex]) {
             return false;
         }
@@ -380,51 +388,152 @@
     return true;
 }
 
-/////////////////////////////////////////////////////////////////////////////////////////////////
-sk_sp<GrTexture> SkPromiseImageHelper::getTexture(GrResourceProvider* resourceProvider,
-                                                  GrPixelConfig config) {
-    // Releases the promise helper if there are no outstanding hard refs. This means that we
-    // don't have any ReleaseProcs waiting to be called so we will need to do a fulfill.
-    if (fReleaseHelper && fReleaseHelper->weak_expired()) {
-        this->resetReleaseHelper();
+sk_sp<GrTextureProxy> SkImage_GpuBase::MakePromiseImageLazyProxy(
+        GrContext* context, int width, int height, GrSurfaceOrigin origin, GrPixelConfig config,
+        GrBackendFormat backendFormat, GrMipMapped mipMapped,
+        SkImage_GpuBase::TextureFulfillProc fulfillProc,
+        SkImage_GpuBase::TextureReleaseProc releaseProc, SkImage_GpuBase::PromiseDoneProc doneProc,
+        SkImage_GpuBase::TextureContext textureContext) {
+    SkASSERT(context);
+    SkASSERT(width > 0 && height > 0);
+    SkASSERT(doneProc);
+    SkASSERT(config != kUnknown_GrPixelConfig);
+
+    if (!fulfillProc || !releaseProc) {
+        doneProc(textureContext);
+        return nullptr;
     }
 
-    sk_sp<GrTexture> tex;
-    if (!fReleaseHelper) {
-        fFulfillProc(fContext, &fBackendTex);
-        fBackendTex.fConfig = config;
-        if (!fBackendTex.isValid()) {
-            // Even though the GrBackendTexture is not valid, we must call the release
-            // proc to keep our contract of always calling Fulfill and Release in pairs.
-            fReleaseProc(fContext);
-            return sk_sp<GrTexture>();
-        }
-
-        tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership);
-        if (!tex) {
-            // Even though the GrBackendTexture is not valid, we must call the release
-            // proc to keep our contract of always calling Fulfill and Release in pairs.
-            fReleaseProc(fContext);
-            return sk_sp<GrTexture>();
-        }
-        fReleaseHelper = new SkPromiseReleaseProcHelper(fReleaseProc, fContext, fDoneHelper);
-        // Take a weak ref
-        fReleaseHelper->weak_ref();
-    } else {
-        SkASSERT(fBackendTex.isValid());
-        tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership);
-        if (!tex) {
-            // We weren't able to make a texture here, but since we are in this branch
-            // of the calls (promiseHelper.fReleaseHelper is valid) there is already a
-            // texture out there which will call the release proc so we don't need to
-            // call it here.
-            return sk_sp<GrTexture>();
-        }
-
-        SkAssertResult(fReleaseHelper->try_ref());
+    if (mipMapped == GrMipMapped::kYes &&
+        GrTextureTypeHasRestrictedSampling(backendFormat.textureType())) {
+        // It is invalid to have a GL_TEXTURE_EXTERNAL or GL_TEXTURE_RECTANGLE and have mips as
+        // well.
+        doneProc(textureContext);
+        return nullptr;
     }
-    SkASSERT(tex);
-    // Pass the hard ref off to the texture
-    tex->setRelease(sk_sp<GrReleaseProcHelper>(fReleaseHelper));
-    return tex;
+
+    /**
+     * This helper class manages the ref counting for the the ReleaseProc and DoneProc for promise
+     * images. It holds a weak ref on the ReleaseProc (hard refs are owned by GrTextures). The weak
+     * ref allows us to reuse an outstanding ReleaseProc (because we dropped our GrTexture but the
+     * GrTexture isn't done on the GPU) without needing to call FulfillProc again. It also holds a
+     * hard ref on the DoneProc. The idea is that after every flush we may call the ReleaseProc so
+     * that the client can free up their GPU memory if they want to. The life time of the DoneProc
+     * matches that of any outstanding ReleaseProc as well as the PromiseLazyInstantiateCallback.
+     * Thus we won't call the DoneProc until all ReleaseProcs are finished and we are finished with
+     * the PromiseImageHelper (i.e. won't call FulfillProc again).
+     */
+    class PromiseLazyInstantiateCallback {
+    public:
+        PromiseLazyInstantiateCallback(SkImage_GpuBase::TextureFulfillProc fulfillProc,
+                                       SkImage_GpuBase::TextureReleaseProc releaseProc,
+                                       SkImage_GpuBase::PromiseDoneProc doneProc,
+                                       SkImage_GpuBase::TextureContext context,
+                                       GrPixelConfig config)
+                : fFulfillProc(fulfillProc)
+                , fReleaseProc(releaseProc)
+                , fContext(context)
+                , fConfig(config) {
+            fDoneHelper.reset(new GrReleaseProcHelper(doneProc, context));
+        }
+
+        sk_sp<GrSurface> operator()(GrResourceProvider* resourceProvider) {
+            if (!resourceProvider) {
+                this->reset();
+                return sk_sp<GrTexture>();
+            }
+
+            // Releases the promise helper if there are no outstanding hard refs. This means that we
+            // don't have any ReleaseProcs waiting to be called so we will need to do a fulfill.
+            if (fReleaseHelper && fReleaseHelper->weak_expired()) {
+                this->resetReleaseHelper();
+            }
+
+            sk_sp<GrTexture> tex;
+            if (!fReleaseHelper) {
+                fFulfillProc(fContext, &fBackendTex);
+                fBackendTex.fConfig = fConfig;
+                if (!fBackendTex.isValid()) {
+                    // Even though the GrBackendTexture is not valid, we must call the release
+                    // proc to keep our contract of always calling Fulfill and Release in pairs.
+                    fReleaseProc(fContext);
+                    return sk_sp<GrTexture>();
+                }
+
+                tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership,
+                                                           kRead_GrIOType);
+                if (!tex) {
+                    // Even though the GrBackendTexture is not valid, we must call the release
+                    // proc to keep our contract of always calling Fulfill and Release in pairs.
+                    fReleaseProc(fContext);
+                    return sk_sp<GrTexture>();
+                }
+                fReleaseHelper =
+                        new SkPromiseReleaseProcHelper(fReleaseProc, fContext, fDoneHelper);
+                // Take a weak ref
+                fReleaseHelper->weak_ref();
+            } else {
+                SkASSERT(fBackendTex.isValid());
+                tex = resourceProvider->wrapBackendTexture(fBackendTex, kBorrow_GrWrapOwnership,
+                                                           kRead_GrIOType);
+                if (!tex) {
+                    // We weren't able to make a texture here, but since we are in this branch
+                    // of the calls (promiseHelper.fReleaseHelper is valid) there is already a
+                    // texture out there which will call the release proc so we don't need to
+                    // call it here.
+                    return sk_sp<GrTexture>();
+                }
+
+                SkAssertResult(fReleaseHelper->try_ref());
+            }
+            SkASSERT(tex);
+            // Pass the hard ref off to the texture
+            tex->setRelease(sk_sp<GrReleaseProcHelper>(fReleaseHelper));
+            return std::move(tex);
+        }
+
+    private:
+        void reset() {
+            this->resetReleaseHelper();
+            fDoneHelper.reset();
+        }
+
+        // Weak unrefs fReleaseHelper and sets it to null
+        void resetReleaseHelper() {
+            if (fReleaseHelper) {
+                fReleaseHelper->weak_unref();
+                fReleaseHelper = nullptr;
+            }
+        }
+
+        SkImage_GpuBase::TextureFulfillProc fFulfillProc;
+        SkImage_GpuBase::TextureReleaseProc fReleaseProc;
+        SkImage_GpuBase::TextureContext fContext;
+
+        GrPixelConfig fConfig;
+        // We cache the GrBackendTexture so that if we deleted the GrTexture but the the release
+        // proc has yet not been called (this can happen on Vulkan), then we can create a new
+        // texture without needing to call the fulfill proc again.
+        GrBackendTexture fBackendTex;
+        // The fReleaseHelper is used to track a weak ref on the release proc. This helps us make
+        // sure we are always pairing fulfill and release proc calls correctly.
+        SkPromiseReleaseProcHelper* fReleaseHelper = nullptr;
+        // We don't want to call the fDoneHelper until we are done with the PromiseImageHelper and
+        // all ReleaseHelpers are finished. Thus we hold a hard ref here and we will pass a hard ref
+        // to each fReleaseHelper we make.
+        sk_sp<GrReleaseProcHelper> fDoneHelper;
+    } callback(fulfillProc, releaseProc, doneProc, textureContext, config);
+
+    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
+
+    GrSurfaceDesc desc;
+    desc.fWidth = width;
+    desc.fHeight = height;
+    desc.fConfig = config;
+
+    // We pass kReadOnly here since we should treat content of the client's texture as immutable.
+    return proxyProvider->createLazyProxy(std::move(callback), backendFormat, desc, origin,
+                                          mipMapped, GrInternalSurfaceFlags::kReadOnly,
+                                          SkBackingFit::kExact, SkBudgeted::kNo,
+                                          GrSurfaceProxy::LazyInstantiationType::kDeinstantiate);
 }
diff --git a/src/image/SkImage_GpuBase.h b/src/image/SkImage_GpuBase.h
index 9a85e56..7b183b7 100644
--- a/src/image/SkImage_GpuBase.h
+++ b/src/image/SkImage_GpuBase.h
@@ -18,8 +18,8 @@
 
 class SkImage_GpuBase : public SkImage_Base {
 public:
-    SkImage_GpuBase(sk_sp<GrContext>, int width, int height, uint32_t uniqueID,
-                    SkAlphaType, SkBudgeted, sk_sp<SkColorSpace>);
+    SkImage_GpuBase(sk_sp<GrContext>, int width, int height, uint32_t uniqueID, SkAlphaType,
+                    sk_sp<SkColorSpace>);
     ~SkImage_GpuBase() override;
 
     GrContext* context() const final { return fContext.get(); }
@@ -77,6 +77,15 @@
     typedef void(*PromiseDoneProc)(TextureContext textureContext);
 
 protected:
+    // Helper for making a lazy proxy for a promise image. The PromiseDoneProc we be called,
+    // if not null, immediately if this function fails. Othwerwise, it is installed in the
+    // proxy along with the TextureFulfillProc and TextureReleaseProc. PromiseDoneProc must not
+    // be null.
+    static sk_sp<GrTextureProxy> MakePromiseImageLazyProxy(
+            GrContext*, int width, int height, GrSurfaceOrigin, GrPixelConfig, GrBackendFormat,
+            GrMipMapped, SkImage_GpuBase::TextureFulfillProc, SkImage_GpuBase::TextureReleaseProc,
+            SkImage_GpuBase::PromiseDoneProc, SkImage_GpuBase::TextureContext);
+
     static bool RenderYUVAToRGBA(GrContext* ctx, GrRenderTargetContext* renderTargetContext,
                                  const SkRect& rect, SkYUVColorSpace yuvColorSpace,
                                  const sk_sp<GrTextureProxy> proxies[4],
@@ -84,7 +93,6 @@
 
     sk_sp<GrContext>      fContext;
     const SkAlphaType     fAlphaType;  // alpha type for final image
-    const SkBudgeted      fBudgeted;
     sk_sp<SkColorSpace>   fColorSpace; // color space for final image
 
 private:
@@ -118,79 +126,4 @@
     typedef GrReleaseProcHelper INHERITED;
 };
 
-/**
- * This helper class manages the ref counting for the the ReleaseProc and DoneProc for promise
- * images. It holds a weak ref on the ReleaseProc (hard refs are owned by GrTextures). The weak ref
- * allows us to reuse an outstanding ReleaseProc (because we dropped our GrTexture but the GrTexture
- * isn't done on the GPU) without needing to call FulfillProc again. It also holds a hard ref on the
- * DoneProc. The idea is that after every flush we may call the ReleaseProc so that the client can
- * free up their GPU memory if they want to. The life time of the DoneProc matches that of any
- * outstanding ReleaseProc as well as the PromiseImageHelper. Thus we won't call the DoneProc until
- * all ReleaseProcs are finished and we are finished with the PromiseImageHelper (i.e. won't call
- * FulfillProc again).
- */
-class SkPromiseImageHelper {
-public:
-    SkPromiseImageHelper()
-        : fFulfillProc(nullptr)
-        , fReleaseProc(nullptr)
-        , fContext(nullptr)
-        , fDoneHelper(nullptr) {
-    }
-
-    void set(SkImage_GpuBase::TextureFulfillProc fulfillProc,
-             SkImage_GpuBase::TextureReleaseProc releaseProc,
-             SkImage_GpuBase::PromiseDoneProc doneProc,
-             SkImage_GpuBase::TextureContext context) {
-        fFulfillProc = fulfillProc;
-        fReleaseProc = releaseProc;
-        fContext = context;
-        fDoneHelper.reset(new GrReleaseProcHelper(doneProc, context));
-    }
-
-    SkPromiseImageHelper(SkImage_GpuBase::TextureFulfillProc fulfillProc,
-                         SkImage_GpuBase::TextureReleaseProc releaseProc,
-                         SkImage_GpuBase::PromiseDoneProc doneProc,
-                         SkImage_GpuBase::TextureContext context)
-        : fFulfillProc(fulfillProc)
-        , fReleaseProc(releaseProc)
-        , fContext(context)
-        , fDoneHelper(new GrReleaseProcHelper(doneProc, context)) {
-    }
-
-    bool isValid() { return SkToBool(fDoneHelper); }
-
-    void reset() {
-        this->resetReleaseHelper();
-        fDoneHelper.reset();
-    }
-
-    sk_sp<GrTexture> getTexture(GrResourceProvider* resourceProvider, GrPixelConfig config);
-
-private:
-    // Weak unrefs fReleaseHelper and sets it to null
-    void resetReleaseHelper() {
-        if (fReleaseHelper) {
-            fReleaseHelper->weak_unref();
-            fReleaseHelper = nullptr;
-        }
-    }
-
-    SkImage_GpuBase::TextureFulfillProc fFulfillProc;
-    SkImage_GpuBase::TextureReleaseProc fReleaseProc;
-    SkImage_GpuBase::TextureContext     fContext;
-
-    // We cache the GrBackendTexture so that if we deleted the GrTexture but the the release proc
-    // has yet not been called (this can happen on Vulkan), then we can create a new texture without
-    // needing to call the fulfill proc again.
-    GrBackendTexture           fBackendTex;
-    // The fReleaseHelper is used to track a weak ref on the release proc. This helps us make sure
-    // we are always pairing fulfill and release proc calls correctly.
-    SkPromiseReleaseProcHelper*  fReleaseHelper = nullptr;
-    // We don't want to call the fDoneHelper until we are done with the PromiseImageHelper and all
-    // ReleaseHelpers are finished. Thus we hold a hard ref here and we will pass a hard ref to each
-    // fReleaseHelper we make.
-    sk_sp<GrReleaseProcHelper> fDoneHelper;
-};
-
 #endif
diff --git a/src/image/SkImage_GpuYUVA.cpp b/src/image/SkImage_GpuYUVA.cpp
index 3113c1c..aa1d0b3 100644
--- a/src/image/SkImage_GpuYUVA.cpp
+++ b/src/image/SkImage_GpuYUVA.cpp
@@ -21,20 +21,19 @@
 #include "SkImage_Gpu.h"
 #include "SkImage_GpuYUVA.h"
 #include "SkMipMap.h"
+#include "SkScopeExit.h"
 #include "SkYUVASizeInfo.h"
 #include "effects/GrYUVtoRGBEffect.h"
 
 SkImage_GpuYUVA::SkImage_GpuYUVA(sk_sp<GrContext> context, int width, int height, uint32_t uniqueID,
                                  SkYUVColorSpace colorSpace, sk_sp<GrTextureProxy> proxies[],
                                  int numProxies, const SkYUVAIndex yuvaIndices[4],
-                                 GrSurfaceOrigin origin, sk_sp<SkColorSpace> imageColorSpace,
-                                 SkBudgeted budgeted)
+                                 GrSurfaceOrigin origin, sk_sp<SkColorSpace> imageColorSpace)
         : INHERITED(std::move(context), width, height, uniqueID,
                     // If an alpha channel is present we always switch to kPremul. This is because,
                     // although the planar data is always un-premul, the final interleaved RGB image
                     // is/would-be premul.
-                    GetAlphaTypeFromYUVAIndices(yuvaIndices),
-                    budgeted, imageColorSpace)
+                    GetAlphaTypeFromYUVAIndices(yuvaIndices), imageColorSpace)
         , fNumProxies(numProxies)
         , fYUVColorSpace(colorSpace)
         , fOrigin(origin) {
@@ -142,8 +141,7 @@
 
     return sk_make_sp<SkImage_GpuYUVA>(sk_ref_sp(ctx), imageSize.width(), imageSize.height(),
                                        kNeedNewImageUniqueID, colorSpace, tempTextureProxies,
-                                       numTextures, yuvaIndices, imageOrigin, imageColorSpace,
-                                       SkBudgeted::kYes);
+                                       numTextures, yuvaIndices, imageOrigin, imageColorSpace);
 }
 
 sk_sp<SkImage> SkImage::MakeFromYUVAPixmaps(
@@ -181,7 +179,8 @@
             SkBitmap bmp;
             bmp.installPixels(*pixmap);
             tempTextureProxies[i] = proxyProvider->createMipMapProxyFromBitmap(bmp);
-        } else {
+        }
+        if (!tempTextureProxies[i]) {
             if (SkImageInfoIsValid(pixmap->info())) {
                 ATRACE_ANDROID_FRAMEWORK("Upload Texture [%ux%u]",
                                          pixmap->width(), pixmap->height());
@@ -202,8 +201,7 @@
 
     return sk_make_sp<SkImage_GpuYUVA>(sk_ref_sp(context), imageSize.width(), imageSize.height(),
                                        kNeedNewImageUniqueID, yuvColorSpace, tempTextureProxies,
-                                       numPixmaps, yuvaIndices, imageOrigin, imageColorSpace,
-                                       SkBudgeted::kYes);
+                                       numPixmaps, yuvaIndices, imageOrigin, imageColorSpace);
 }
 
 
@@ -221,21 +219,21 @@
                                                        TextureReleaseProc textureReleaseProc,
                                                        PromiseDoneProc promiseDoneProc,
                                                        TextureContext textureContexts[]) {
-    // The contract here is that if 'promiseDoneProc' is passed in it should always be called,
-    // even if creation of the SkImage fails.
-    if (!promiseDoneProc) {
-        return nullptr;
-    }
-
     int numTextures;
     bool valid = SkYUVAIndex::AreValidIndices(yuvaIndices, &numTextures);
 
-    // Set up promise helpers
-    SkPromiseImageHelper promiseHelpers[4];
-    for (int texIdx = 0; texIdx < numTextures; ++texIdx) {
-        promiseHelpers[texIdx].set(textureFulfillProc, textureReleaseProc, promiseDoneProc,
-                                   textureContexts[texIdx]);
+    // The contract here is that if 'promiseDoneProc' is passed in it should always be called,
+    // even if creation of the SkImage fails. Once we call MakePromiseImageLazyProxy it takes
+    // responsibility for calling the done proc.
+    if (!promiseDoneProc) {
+        return nullptr;
     }
+    int proxiesCreated = 0;
+    SkScopeExit callDone([promiseDoneProc, textureContexts, numTextures, &proxiesCreated]() {
+        for (int i = proxiesCreated; i < numTextures; ++i) {
+            promiseDoneProc(textureContexts[i]);
+        }
+    });
 
     if (!valid) {
         return nullptr;
@@ -249,10 +247,6 @@
         return nullptr;
     }
 
-    if (!textureFulfillProc || !textureReleaseProc) {
-        return nullptr;
-    }
-
     SkAlphaType at = (-1 != yuvaIndices[SkYUVAIndex::kA_Index].fIndex) ? kPremul_SkAlphaType
                                                                        : kOpaque_SkAlphaType;
     SkImageInfo info = SkImageInfo::Make(imageWidth, imageHeight, kRGBA_8888_SkColorType,
@@ -274,44 +268,18 @@
     }
 
     // Get lazy proxies
-    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
     sk_sp<GrTextureProxy> proxies[4];
     for (int texIdx = 0; texIdx < numTextures; ++texIdx) {
-        // for each proxy we need to fill in
-        struct {
-            GrPixelConfig fConfig;
-            SkPromiseImageHelper fPromiseHelper;
-        } params;
-        bool res = context->contextPriv().caps()->getYUVAConfigFromBackendFormat(
-                       yuvaFormats[texIdx],
-                       &params.fConfig);
-        if (!res) {
+        GrPixelConfig config =
+                context->contextPriv().caps()->getYUVAConfigFromBackendFormat(yuvaFormats[texIdx]);
+        if (config == kUnknown_GrPixelConfig) {
             return nullptr;
         }
-        params.fPromiseHelper = promiseHelpers[texIdx];
-
-        GrProxyProvider::LazyInstantiateCallback lazyInstCallback =
-            [params](GrResourceProvider* resourceProvider) mutable {
-            if (!resourceProvider || !params.fPromiseHelper.isValid()) {
-                if (params.fPromiseHelper.isValid()) {
-                    params.fPromiseHelper.reset();
-                }
-                return sk_sp<GrTexture>();
-            }
-
-            return params.fPromiseHelper.getTexture(resourceProvider, params.fConfig);
-        };
-        GrSurfaceDesc desc;
-        desc.fFlags = kNone_GrSurfaceFlags;
-        desc.fWidth = yuvaSizes[texIdx].width();
-        desc.fHeight = yuvaSizes[texIdx].height();
-        desc.fConfig = params.fConfig;
-        desc.fSampleCnt = 1;
-        proxies[texIdx] = proxyProvider->createLazyProxy(
-                            std::move(lazyInstCallback), yuvaFormats[texIdx], desc, imageOrigin,
-                            GrMipMapped::kNo, GrInternalSurfaceFlags::kNone,
-                            SkBackingFit::kExact, SkBudgeted::kNo,
-                            GrSurfaceProxy::LazyInstantiationType::kUninstantiate);
+        proxies[texIdx] = MakePromiseImageLazyProxy(
+                context, yuvaSizes[texIdx].width(), yuvaSizes[texIdx].height(), imageOrigin, config,
+                yuvaFormats[texIdx], GrMipMapped::kNo, textureFulfillProc, textureReleaseProc,
+                promiseDoneProc, textureContexts[texIdx]);
+        ++proxiesCreated;
         if (!proxies[texIdx]) {
             return nullptr;
         }
@@ -319,7 +287,6 @@
 
     return sk_make_sp<SkImage_GpuYUVA>(sk_ref_sp(context), imageWidth, imageHeight,
                                        kNeedNewImageUniqueID, yuvColorSpace, proxies, numTextures,
-                                       yuvaIndices, imageOrigin, std::move(imageColorSpace),
-                                       SkBudgeted::kNo);
+                                       yuvaIndices, imageOrigin, std::move(imageColorSpace));
 }
 
diff --git a/src/image/SkImage_GpuYUVA.h b/src/image/SkImage_GpuYUVA.h
index e5a4469..c580284 100644
--- a/src/image/SkImage_GpuYUVA.h
+++ b/src/image/SkImage_GpuYUVA.h
@@ -25,8 +25,8 @@
     friend class GrYUVAImageTextureMaker;
 
     SkImage_GpuYUVA(sk_sp<GrContext>, int width, int height, uint32_t uniqueID, SkYUVColorSpace,
-                    sk_sp<GrTextureProxy> proxies[], int numProxies, const SkYUVAIndex [4],
-                    GrSurfaceOrigin, sk_sp<SkColorSpace>, SkBudgeted);
+                    sk_sp<GrTextureProxy> proxies[], int numProxies, const SkYUVAIndex[4],
+                    GrSurfaceOrigin, sk_sp<SkColorSpace>);
     ~SkImage_GpuYUVA() override;
 
     SkImageInfo onImageInfo() const override;
diff --git a/src/image/SkImage_Lazy.cpp b/src/image/SkImage_Lazy.cpp
index fef4bf4..a1557e1 100644
--- a/src/image/SkImage_Lazy.cpp
+++ b/src/image/SkImage_Lazy.cpp
@@ -323,7 +323,8 @@
             // If we had an originalProxy with a valid key, that means there already is a proxy in
             // the cache which matches the key, but it does not have mip levels and we require them.
             // Thus we must remove the unique key from that proxy.
-            proxyProvider->removeUniqueKeyFromProxy(key, originalProxy);
+            SkASSERT(originalProxy->getUniqueKey() == key);
+            proxyProvider->removeUniqueKeyFromProxy(originalProxy);
         }
         proxyProvider->assignUniqueKeyToProxy(key, proxy);
     }
diff --git a/src/image/SkSurface.cpp b/src/image/SkSurface.cpp
index e4a1986..c51ce9b 100644
--- a/src/image/SkSurface.cpp
+++ b/src/image/SkSurface.cpp
@@ -5,13 +5,12 @@
  * found in the LICENSE file.
  */
 
-#include "SkAtomics.h"
+#include "GrBackendSurface.h"
 #include "SkCanvas.h"
 #include "SkFontLCDConfig.h"
 #include "SkImagePriv.h"
 #include "SkSurface_Base.h"
-
-#include "GrBackendSurface.h"
+#include <atomic>
 
 static SkPixelGeometry compute_default_geometry() {
     SkFontLCDConfig::LCDOrder order = SkFontLCDConfig::GetSubpixelOrder();
@@ -122,8 +121,8 @@
 
 uint32_t SkSurface_Base::newGenerationID() {
     SkASSERT(!fCachedCanvas || fCachedCanvas->getSurfaceBase() == this);
-    static int32_t gID;
-    return sk_atomic_inc(&gID) + 1;
+    static std::atomic<uint32_t> nextID{1};
+    return nextID++;
 }
 
 static SkSurface_Base* asSB(SkSurface* surface) {
diff --git a/src/image/SkSurface_Gpu.cpp b/src/image/SkSurface_Gpu.cpp
index 41f36a8..8890382 100644
--- a/src/image/SkSurface_Gpu.cpp
+++ b/src/image/SkSurface_Gpu.cpp
@@ -101,14 +101,15 @@
 
     if (subset) {
         srcProxy = GrSurfaceProxy::Copy(ctx, rtc->asSurfaceProxy(), rtc->mipMapped(), *subset,
-                                        budgeted);
+                                        SkBackingFit::kExact, budgeted);
     } else if (!srcProxy || rtc->priv().refsWrappedObjects()) {
         // If the original render target is a buffer originally created by the client, then we don't
         // want to ever retarget the SkSurface at another buffer we create. Force a copy now to avoid
         // copy-on-write.
         SkASSERT(rtc->origin() == rtc->asSurfaceProxy()->origin());
 
-        srcProxy = GrSurfaceProxy::Copy(ctx, rtc->asSurfaceProxy(), rtc->mipMapped(), budgeted);
+        srcProxy = GrSurfaceProxy::Copy(ctx, rtc->asSurfaceProxy(), rtc->mipMapped(),
+                                        SkBackingFit::kExact, budgeted);
     }
 
     const SkImageInfo info = fDevice->imageInfo();
@@ -118,7 +119,7 @@
         // above copy creates a kExact surfaceContext.
         SkASSERT(srcProxy->priv().isExact());
         image = sk_make_sp<SkImage_Gpu>(sk_ref_sp(ctx), kNeedNewImageUniqueID, info.alphaType(),
-                                        std::move(srcProxy), info.refColorSpace(), budgeted);
+                                        std::move(srcProxy), info.refColorSpace());
     }
     return image;
 }
@@ -190,7 +191,9 @@
                           rtc->colorSpaceInfo().config(), rtc->fsaaType(), rtc->numStencilSamples(),
                           SkSurfaceCharacterization::Textureable(SkToBool(rtc->asTextureProxy())),
                           SkSurfaceCharacterization::MipMapped(mipmapped),
-                          SkSurfaceCharacterization::UsesGLFBO0(usesGLFBO0), this->props());
+                          SkSurfaceCharacterization::UsesGLFBO0(usesGLFBO0),
+                          SkSurfaceCharacterization::VulkanSecondaryCBCompatible(false),
+                          this->props());
 
     return true;
 }
@@ -393,7 +396,12 @@
         return false;
     }
 
-    if (!ctx->contextPriv().caps()->validateBackendTexture(tex, ct, config)) {
+    GrBackendFormat backendFormat = tex.getBackendFormat();
+    if (!backendFormat.isValid()) {
+        return false;
+    }
+    *config = ctx->contextPriv().caps()->getConfigFromBackendFormat(backendFormat, ct);
+    if (*config == kUnknown_GrPixelConfig) {
         return false;
     }
 
@@ -461,7 +469,8 @@
         return false;
     }
 
-    if (!ctx->contextPriv().caps()->validateBackendRenderTarget(rt, ct, config)) {
+    *config = ctx->contextPriv().caps()->validateBackendRenderTarget(rt, ct);
+    if (*config == kUnknown_GrPixelConfig) {
         return false;
     }
 
diff --git a/src/images/SkJPEGWriteUtility.cpp b/src/images/SkJPEGWriteUtility.cpp
index 118662d..29994ba 100644
--- a/src/images/SkJPEGWriteUtility.cpp
+++ b/src/images/SkJPEGWriteUtility.cpp
@@ -24,7 +24,7 @@
     if (!dest->fStream->write(dest->fBuffer,
             skjpeg_destination_mgr::kBufferSize)) {
         ERREXIT(cinfo, JERR_FILE_WRITE);
-        return false;
+        return FALSE;
     }
 
     dest->next_output_byte = dest->fBuffer;
diff --git a/src/opts/Sk4px_NEON.h b/src/opts/Sk4px_NEON.h
index 62f1deb..81e3f60 100644
--- a/src/opts/Sk4px_NEON.h
+++ b/src/opts/Sk4px_NEON.h
@@ -5,47 +5,13 @@
  * found in the LICENSE file.
  */
 
-namespace { // See Sk4px.h
+namespace {  // NOLINT(google-build-namespaces)
 
-inline Sk4px Sk4px::DupPMColor(SkPMColor px) { return Sk16b((uint8x16_t)vdupq_n_u32(px)); }
-
-inline Sk4px Sk4px::Load4(const SkPMColor px[4]) {
-    return Sk16b((uint8x16_t)vld1q_u32(px));
-}
-inline Sk4px Sk4px::Load2(const SkPMColor px[2]) {
-    uint32x2_t px2 = vld1_u32(px);
-    return Sk16b((uint8x16_t)vcombine_u32(px2, px2));
-}
-inline Sk4px Sk4px::Load1(const SkPMColor px[1]) {
-    return Sk16b((uint8x16_t)vdupq_n_u32(*px));
-}
-
-inline void Sk4px::store4(SkPMColor px[4]) const {
-    vst1q_u32(px, (uint32x4_t)this->fVec);
-}
-inline void Sk4px::store2(SkPMColor px[2]) const {
-    vst1_u32(px, (uint32x2_t)vget_low_u8(this->fVec));
-}
-inline void Sk4px::store1(SkPMColor px[1]) const {
-    vst1q_lane_u32(px, (uint32x4_t)this->fVec, 0);
-}
-
-inline Sk4px::Wide Sk4px::widenLo() const {
+inline Sk4px::Wide Sk4px::widen() const {
     return Sk16h(vmovl_u8(vget_low_u8 (this->fVec)),
                  vmovl_u8(vget_high_u8(this->fVec)));
 }
 
-inline Sk4px::Wide Sk4px::widenHi() const {
-    return Sk16h(vshll_n_u8(vget_low_u8 (this->fVec), 8),
-                 vshll_n_u8(vget_high_u8(this->fVec), 8));
-}
-
-inline Sk4px::Wide Sk4px::widenLoHi() const {
-    auto zipped = vzipq_u8(this->fVec, this->fVec);
-    return Sk16h((uint16x8_t)zipped.val[0],
-                 (uint16x8_t)zipped.val[1]);
-}
-
 inline Sk4px::Wide Sk4px::mulWiden(const Sk16b& other) const {
     return Sk16h(vmull_u8(vget_low_u8 (this->fVec), vget_low_u8 (other.fVec)),
                  vmull_u8(vget_high_u8(this->fVec), vget_high_u8(other.fVec)));
@@ -86,14 +52,5 @@
     return Sk16b((uint8x16_t)vmulq_n_u32(a32, 0x01010101));  // ____ ____ 1111 0000
 }
 
-inline Sk4px Sk4px::zeroColors() const {
-    return Sk16b(vandq_u8(this->fVec, (uint8x16_t)vdupq_n_u32(0xFF << SK_A32_SHIFT)));
-}
-
-inline Sk4px Sk4px::zeroAlphas() const {
-    // vbic(a,b) == a & ~b
-    return Sk16b(vbicq_u8(this->fVec, (uint8x16_t)vdupq_n_u32(0xFF << SK_A32_SHIFT)));
-}
-
 } // namespace
 
diff --git a/src/opts/Sk4px_SSE2.h b/src/opts/Sk4px_SSE2.h
index 624fc22..6ce6dd5 100644
--- a/src/opts/Sk4px_SSE2.h
+++ b/src/opts/Sk4px_SSE2.h
@@ -5,39 +5,15 @@
  * found in the LICENSE file.
  */
 
-namespace { // See Sk4px.h
+namespace {  // NOLINT(google-build-namespaces)
 
-inline Sk4px Sk4px::DupPMColor(SkPMColor px) { return Sk16b(_mm_set1_epi32(px)); }
-
-inline Sk4px Sk4px::Load4(const SkPMColor px[4]) {
-    return Sk16b(_mm_loadu_si128((const __m128i*)px));
-}
-inline Sk4px Sk4px::Load2(const SkPMColor px[2]) {
-    return Sk16b(_mm_loadl_epi64((const __m128i*)px));
-}
-inline Sk4px Sk4px::Load1(const SkPMColor px[1]) { return Sk16b(_mm_cvtsi32_si128(*px)); }
-
-inline void Sk4px::store4(SkPMColor px[4]) const { _mm_storeu_si128((__m128i*)px, this->fVec); }
-inline void Sk4px::store2(SkPMColor px[2]) const { _mm_storel_epi64((__m128i*)px, this->fVec); }
-inline void Sk4px::store1(SkPMColor px[1]) const { *px = _mm_cvtsi128_si32(this->fVec); }
-
-inline Sk4px::Wide Sk4px::widenLo() const {
+inline Sk4px::Wide Sk4px::widen() const {
     return Sk16h(_mm_unpacklo_epi8(this->fVec, _mm_setzero_si128()),
                  _mm_unpackhi_epi8(this->fVec, _mm_setzero_si128()));
 }
 
-inline Sk4px::Wide Sk4px::widenHi() const {
-    return Sk16h(_mm_unpacklo_epi8(_mm_setzero_si128(), this->fVec),
-                 _mm_unpackhi_epi8(_mm_setzero_si128(), this->fVec));
-}
-
-inline Sk4px::Wide Sk4px::widenLoHi() const {
-    return Sk16h(_mm_unpacklo_epi8(this->fVec, this->fVec),
-                 _mm_unpackhi_epi8(this->fVec, this->fVec));
-}
-
 inline Sk4px::Wide Sk4px::mulWiden(const Sk16b& other) const {
-    return this->widenLo() * Sk4px(other).widenLo();
+    return this->widen() * Sk4px(other).widen();
 }
 
 inline Sk4px Sk4px::Wide::addNarrowHi(const Sk16h& other) const {
@@ -97,13 +73,4 @@
     return Load4Alphas((const SkAlpha*)&alphas_and_two_zeros);
 }
 
-inline Sk4px Sk4px::zeroColors() const {
-    return Sk16b(_mm_and_si128(_mm_set1_epi32(0xFF << SK_A32_SHIFT), this->fVec));
-}
-
-inline Sk4px Sk4px::zeroAlphas() const {
-    // andnot(a,b) == ~a & b
-    return Sk16b(_mm_andnot_si128(_mm_set1_epi32(0xFF << SK_A32_SHIFT), this->fVec));
-}
-
 }  // namespace
diff --git a/src/opts/Sk4px_none.h b/src/opts/Sk4px_none.h
index 10c3ded..edd1609 100644
--- a/src/opts/Sk4px_none.h
+++ b/src/opts/Sk4px_none.h
@@ -7,51 +7,17 @@
 
 #include "SkUtils.h"
 
-namespace { // See Sk4px.h
+namespace {  // NOLINT(google-build-namespaces)
 
-static_assert(sizeof(Sk4px) == 16, "This file uses memcpy / sk_memset32, so exact size matters.");
-
-inline Sk4px Sk4px::DupPMColor(SkPMColor px) {
-    Sk4px px4 = Sk16b();
-    sk_memset32((uint32_t*)&px4, px, 4);
-    return px4;
-}
-
-inline Sk4px Sk4px::Load4(const SkPMColor px[4]) {
-    Sk4px px4 = Sk16b();
-    memcpy(&px4, px, 16);
-    return px4;
-}
-
-inline Sk4px Sk4px::Load2(const SkPMColor px[2]) {
-    Sk4px px2 = Sk16b();
-    memcpy(&px2, px, 8);
-    return px2;
-}
-
-inline Sk4px Sk4px::Load1(const SkPMColor px[1]) {
-    Sk4px px1 = Sk16b();
-    memcpy(&px1, px, 4);
-    return px1;
-}
-
-inline void Sk4px::store4(SkPMColor px[4]) const { memcpy(px, this, 16); }
-inline void Sk4px::store2(SkPMColor px[2]) const { memcpy(px, this,  8); }
-inline void Sk4px::store1(SkPMColor px[1]) const { memcpy(px, this,  4); }
-
-inline Sk4px::Wide Sk4px::widenLo() const {
+inline Sk4px::Wide Sk4px::widen() const {
     return Sk16h((*this)[ 0], (*this)[ 1], (*this)[ 2], (*this)[ 3],
                  (*this)[ 4], (*this)[ 5], (*this)[ 6], (*this)[ 7],
                  (*this)[ 8], (*this)[ 9], (*this)[10], (*this)[11],
                  (*this)[12], (*this)[13], (*this)[14], (*this)[15]);
 }
 
-inline Sk4px::Wide Sk4px::widenHi() const { return this->widenLo() << 8; }
-
-inline Sk4px::Wide Sk4px::widenLoHi() const { return this->widenLo() + this->widenHi(); }
-
 inline Sk4px::Wide Sk4px::mulWiden(const Sk16b& other) const {
-    return this->widenLo() * Sk4px(other).widenLo();
+    return this->widen() * Sk4px(other).widen();
 }
 
 inline Sk4px Sk4px::Wide::addNarrowHi(const Sk16h& other) const {
@@ -90,20 +56,4 @@
                  0,0,0,0);
 }
 
-inline Sk4px Sk4px::zeroAlphas() const {
-    static_assert(SK_A32_SHIFT == 24, "This method assumes little-endian.");
-    return Sk16b((*this)[ 0], (*this)[ 1], (*this)[ 2], 0,
-                 (*this)[ 4], (*this)[ 5], (*this)[ 6], 0,
-                 (*this)[ 8], (*this)[ 9], (*this)[10], 0,
-                 (*this)[12], (*this)[13], (*this)[14], 0);
-}
-
-inline Sk4px Sk4px::zeroColors() const {
-    static_assert(SK_A32_SHIFT == 24, "This method assumes little-endian.");
-    return Sk16b(0,0,0, (*this)[ 3],
-                 0,0,0, (*this)[ 7],
-                 0,0,0, (*this)[11],
-                 0,0,0, (*this)[15]);
-}
-
 }  // namespace
diff --git a/src/opts/SkBlitMask_opts.h b/src/opts/SkBlitMask_opts.h
index 644bae4..64c7c1e 100644
--- a/src/opts/SkBlitMask_opts.h
+++ b/src/opts/SkBlitMask_opts.h
@@ -15,7 +15,31 @@
 #if defined(SK_ARM_HAS_NEON)
     // The Sk4px versions below will work fine with NEON, but we have had many indications
     // that it doesn't perform as well as this NEON-specific code.  TODO(mtklein): why?
-    #include "SkColor_opts_neon.h"
+
+    #define NEON_A (SK_A32_SHIFT / 8)
+    #define NEON_R (SK_R32_SHIFT / 8)
+    #define NEON_G (SK_G32_SHIFT / 8)
+    #define NEON_B (SK_B32_SHIFT / 8)
+
+    static inline uint16x8_t SkAlpha255To256_neon8(uint8x8_t alpha) {
+        return vaddw_u8(vdupq_n_u16(1), alpha);
+    }
+
+    static inline uint8x8_t SkAlphaMul_neon8(uint8x8_t color, uint16x8_t scale) {
+        return vshrn_n_u16(vmovl_u8(color) * scale, 8);
+    }
+
+    static inline uint8x8x4_t SkAlphaMulQ_neon8(uint8x8x4_t color, uint16x8_t scale) {
+        uint8x8x4_t ret;
+
+        ret.val[0] = SkAlphaMul_neon8(color.val[0], scale);
+        ret.val[1] = SkAlphaMul_neon8(color.val[1], scale);
+        ret.val[2] = SkAlphaMul_neon8(color.val[2], scale);
+        ret.val[3] = SkAlphaMul_neon8(color.val[3], scale);
+
+        return ret;
+    }
+
 
     template <bool isColor>
     static void D32_A8_Opaque_Color_neon(void* SK_RESTRICT dst, size_t dstRB,
@@ -179,7 +203,8 @@
             //   ~~~>
             // a = 1*aa + d(1-1*aa) = aa + d(1-aa)
             // c = 0*aa + d(1-1*aa) =      d(1-aa)
-            return aa.zeroColors() + d.approxMulDiv255(aa.inv());
+            return Sk4px(Sk16b(aa) & Sk16b(0,0,0,255, 0,0,0,255, 0,0,0,255, 0,0,0,255))
+                 + d.approxMulDiv255(aa.inv());
         };
         while (h --> 0) {
             Sk4px::MapDstAlpha(w, dst, mask, fn);
diff --git a/src/opts/SkBlitRow_opts.h b/src/opts/SkBlitRow_opts.h
index 1b9ad30..a64730d 100644
--- a/src/opts/SkBlitRow_opts.h
+++ b/src/opts/SkBlitRow_opts.h
@@ -12,8 +12,30 @@
 #include "SkMSAN.h"
 
 #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2
-    #include "SkColor_opts_SSE2.h"
     #include <immintrin.h>
+
+    static inline __m128i SkPMSrcOver_SSE2(const __m128i& src, const __m128i& dst) {
+        auto SkAlphaMulQ_SSE2 = [](const __m128i& c, const __m128i& scale) {
+            const __m128i mask = _mm_set1_epi32(0xFF00FF);
+            __m128i s = _mm_or_si128(_mm_slli_epi32(scale, 16), scale);
+
+            // uint32_t rb = ((c & mask) * scale) >> 8
+            __m128i rb = _mm_and_si128(mask, c);
+            rb = _mm_mullo_epi16(rb, s);
+            rb = _mm_srli_epi16(rb, 8);
+
+            // uint32_t ag = ((c >> 8) & mask) * scale
+            __m128i ag = _mm_srli_epi16(c, 8);
+            ag = _mm_mullo_epi16(ag, s);
+
+            // (rb & mask) | (ag & ~mask)
+            ag = _mm_andnot_si128(mask, ag);
+            return _mm_or_si128(rb, ag);
+        };
+        return _mm_add_epi32(src,
+                             SkAlphaMulQ_SSE2(dst, _mm_sub_epi32(_mm_set1_epi32(256),
+                                                                 _mm_srli_epi32(src, 24))));
+    }
 #endif
 
 namespace SK_OPTS_NS {
diff --git a/src/opts/SkColor_opts_SSE2.h b/src/opts/SkColor_opts_SSE2.h
deleted file mode 100644
index d998ed5..0000000
--- a/src/opts/SkColor_opts_SSE2.h
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright 2014 The Android Open Source Project
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkColor_opts_SSE2_DEFINED
-#define SkColor_opts_SSE2_DEFINED
-
-#include <emmintrin.h>
-
-#define ASSERT_EQ(a,b) SkASSERT(0xffff == _mm_movemask_epi8(_mm_cmpeq_epi8((a), (b))))
-
-// Because no _mm_mul_epi32() in SSE2, we emulate it here.
-// Multiplies 4 32-bit integers from a by 4 32-bit intergers from b.
-// The 4 multiplication results should be represented within 32-bit
-// integers, otherwise they would be overflow.
-static inline  __m128i Multiply32_SSE2(const __m128i& a, const __m128i& b) {
-    // Calculate results of a0 * b0 and a2 * b2.
-    __m128i r1 = _mm_mul_epu32(a, b);
-    // Calculate results of a1 * b1 and a3 * b3.
-    __m128i r2 = _mm_mul_epu32(_mm_srli_si128(a, 4), _mm_srli_si128(b, 4));
-    // Shuffle results to [63..0] and interleave the results.
-    __m128i r = _mm_unpacklo_epi32(_mm_shuffle_epi32(r1, _MM_SHUFFLE(0,0,2,0)),
-                                   _mm_shuffle_epi32(r2, _MM_SHUFFLE(0,0,2,0)));
-    return r;
-}
-
-static inline __m128i SkAlpha255To256_SSE2(const __m128i& alpha) {
-    return _mm_add_epi32(alpha, _mm_set1_epi32(1));
-}
-
-// See #define SkAlphaMulAlpha(a, b)  SkMulDiv255Round(a, b) in SkXfermode.cpp.
-static inline __m128i SkAlphaMulAlpha_SSE2(const __m128i& a,
-                                           const __m128i& b) {
-    __m128i prod = _mm_mullo_epi16(a, b);
-    prod = _mm_add_epi32(prod, _mm_set1_epi32(128));
-    prod = _mm_add_epi32(prod, _mm_srli_epi32(prod, 8));
-    prod = _mm_srli_epi32(prod, 8);
-
-    return prod;
-}
-
-// Portable version SkAlphaMulQ is in SkColorData.h.
-static inline __m128i SkAlphaMulQ_SSE2(const __m128i& c, const __m128i& scale) {
-    const __m128i mask = _mm_set1_epi32(0xFF00FF);
-    __m128i s = _mm_or_si128(_mm_slli_epi32(scale, 16), scale);
-
-    // uint32_t rb = ((c & mask) * scale) >> 8
-    __m128i rb = _mm_and_si128(mask, c);
-    rb = _mm_mullo_epi16(rb, s);
-    rb = _mm_srli_epi16(rb, 8);
-
-    // uint32_t ag = ((c >> 8) & mask) * scale
-    __m128i ag = _mm_srli_epi16(c, 8);
-    ASSERT_EQ(ag, _mm_and_si128(mask, ag));  // ag = _mm_srli_epi16(c, 8) did this for us.
-    ag = _mm_mullo_epi16(ag, s);
-
-    // (rb & mask) | (ag & ~mask)
-    ASSERT_EQ(rb, _mm_and_si128(mask, rb));  // rb = _mm_srli_epi16(rb, 8) did this for us.
-    ag = _mm_andnot_si128(mask, ag);
-    return _mm_or_si128(rb, ag);
-}
-
-// Fast path for SkAlphaMulQ_SSE2 with a constant scale factor.
-static inline __m128i SkAlphaMulQ_SSE2(const __m128i& c, const unsigned scale) {
-    const __m128i mask = _mm_set1_epi32(0xFF00FF);
-    __m128i s = _mm_set1_epi16(scale << 8); // Move scale factor to upper byte of word.
-
-    // With mulhi, red and blue values are already in the right place and
-    // don't need to be divided by 256.
-    __m128i rb = _mm_and_si128(mask, c);
-    rb = _mm_mulhi_epu16(rb, s);
-
-    __m128i ag = _mm_andnot_si128(mask, c);
-    ag = _mm_mulhi_epu16(ag, s);     // Alpha and green values are in the higher byte of each word.
-    ag = _mm_andnot_si128(mask, ag);
-
-    return _mm_or_si128(rb, ag);
-}
-
-// Portable version SkFastFourByteInterp256 is in SkColorData.h.
-static inline __m128i SkFastFourByteInterp256_SSE2(const __m128i& src, const __m128i& dst, const unsigned src_scale) {
-    // Computes dst + (((src - dst)*src_scale)>>8)
-    const __m128i mask = _mm_set1_epi32(0x00FF00FF);
-
-    // Unpack the 16x8-bit source into 2 8x16-bit splayed halves.
-    __m128i src_rb = _mm_and_si128(mask, src);
-    __m128i src_ag = _mm_srli_epi16(src, 8);
-    __m128i dst_rb = _mm_and_si128(mask, dst);
-    __m128i dst_ag = _mm_srli_epi16(dst, 8);
-
-    // Compute scaled differences.
-    __m128i diff_rb = _mm_sub_epi16(src_rb, dst_rb);
-    __m128i diff_ag = _mm_sub_epi16(src_ag, dst_ag);
-    __m128i s = _mm_set1_epi16(src_scale);
-    diff_rb = _mm_mullo_epi16(diff_rb, s);
-    diff_ag = _mm_mullo_epi16(diff_ag, s);
-
-    // Pack the differences back together.
-    diff_rb = _mm_srli_epi16(diff_rb, 8);
-    diff_ag = _mm_andnot_si128(mask, diff_ag);
-    __m128i diff = _mm_or_si128(diff_rb, diff_ag);
-
-    // Add difference to destination.
-    return _mm_add_epi8(dst, diff);
-}
-
-// Portable version SkPMLerp is in SkColorData.h
-static inline __m128i SkPMLerp_SSE2(const __m128i& src, const __m128i& dst, const unsigned scale) {
-    return SkFastFourByteInterp256_SSE2(src, dst, scale);
-}
-
-static inline __m128i SkGetPackedA32_SSE2(const __m128i& src) {
-#if SK_A32_SHIFT == 24                // It's very common (universal?) that alpha is the top byte.
-    return _mm_srli_epi32(src, 24);   // You'd hope the compiler would remove the left shift then,
-#else                                 // but I've seen Clang just do a dumb left shift of zero. :(
-    __m128i a = _mm_slli_epi32(src, (24 - SK_A32_SHIFT));
-    return _mm_srli_epi32(a, 24);
-#endif
-}
-
-static inline __m128i SkGetPackedR32_SSE2(const __m128i& src) {
-    __m128i r = _mm_slli_epi32(src, (24 - SK_R32_SHIFT));
-    return _mm_srli_epi32(r, 24);
-}
-
-static inline __m128i SkGetPackedG32_SSE2(const __m128i& src) {
-    __m128i g = _mm_slli_epi32(src, (24 - SK_G32_SHIFT));
-    return _mm_srli_epi32(g, 24);
-}
-
-static inline __m128i SkGetPackedB32_SSE2(const __m128i& src) {
-    __m128i b = _mm_slli_epi32(src, (24 - SK_B32_SHIFT));
-    return _mm_srli_epi32(b, 24);
-}
-
-static inline __m128i SkMul16ShiftRound_SSE2(const __m128i& a,
-                                             const __m128i& b, int shift) {
-    __m128i prod = _mm_mullo_epi16(a, b);
-    prod = _mm_add_epi16(prod, _mm_set1_epi16(1 << (shift - 1)));
-    prod = _mm_add_epi16(prod, _mm_srli_epi16(prod, shift));
-    prod = _mm_srli_epi16(prod, shift);
-
-    return prod;
-}
-
-static inline __m128i SkPackRGB16_SSE2(const __m128i& r,
-                                       const __m128i& g, const __m128i& b) {
-    __m128i dr = _mm_slli_epi16(r, SK_R16_SHIFT);
-    __m128i dg = _mm_slli_epi16(g, SK_G16_SHIFT);
-    __m128i db = _mm_slli_epi16(b, SK_B16_SHIFT);
-
-    __m128i c = _mm_or_si128(dr, dg);
-    return _mm_or_si128(c, db);
-}
-
-static inline __m128i SkPackARGB32_SSE2(const __m128i& a, const __m128i& r,
-                                        const __m128i& g, const __m128i& b) {
-    __m128i da = _mm_slli_epi32(a, SK_A32_SHIFT);
-    __m128i dr = _mm_slli_epi32(r, SK_R32_SHIFT);
-    __m128i dg = _mm_slli_epi32(g, SK_G32_SHIFT);
-    __m128i db = _mm_slli_epi32(b, SK_B32_SHIFT);
-
-    __m128i c = _mm_or_si128(da, dr);
-    c = _mm_or_si128(c, dg);
-    return _mm_or_si128(c, db);
-}
-
-static inline __m128i SkPacked16ToR32_SSE2(const __m128i& src) {
-    __m128i r = _mm_srli_epi32(src, SK_R16_SHIFT);
-    r = _mm_and_si128(r, _mm_set1_epi32(SK_R16_MASK));
-    r = _mm_or_si128(_mm_slli_epi32(r, (8 - SK_R16_BITS)),
-                     _mm_srli_epi32(r, (2 * SK_R16_BITS - 8)));
-
-    return r;
-}
-
-static inline __m128i SkPacked16ToG32_SSE2(const __m128i& src) {
-    __m128i g = _mm_srli_epi32(src, SK_G16_SHIFT);
-    g = _mm_and_si128(g, _mm_set1_epi32(SK_G16_MASK));
-    g = _mm_or_si128(_mm_slli_epi32(g, (8 - SK_G16_BITS)),
-                     _mm_srli_epi32(g, (2 * SK_G16_BITS - 8)));
-
-    return g;
-}
-
-static inline __m128i SkPacked16ToB32_SSE2(const __m128i& src) {
-    __m128i b = _mm_srli_epi32(src, SK_B16_SHIFT);
-    b = _mm_and_si128(b, _mm_set1_epi32(SK_B16_MASK));
-    b = _mm_or_si128(_mm_slli_epi32(b, (8 - SK_B16_BITS)),
-                     _mm_srli_epi32(b, (2 * SK_B16_BITS - 8)));
-
-    return b;
-}
-
-static inline __m128i SkPixel16ToPixel32_SSE2(const __m128i& src) {
-    __m128i r = SkPacked16ToR32_SSE2(src);
-    __m128i g = SkPacked16ToG32_SSE2(src);
-    __m128i b = SkPacked16ToB32_SSE2(src);
-
-    return SkPackARGB32_SSE2(_mm_set1_epi32(0xFF), r, g, b);
-}
-
-static inline __m128i SkPixel32ToPixel16_ToU16_SSE2(const __m128i& src_pixel1,
-                                                    const __m128i& src_pixel2) {
-    // Calculate result r.
-    __m128i r1 = _mm_srli_epi32(src_pixel1,
-                                SK_R32_SHIFT + (8 - SK_R16_BITS));
-    r1 = _mm_and_si128(r1, _mm_set1_epi32(SK_R16_MASK));
-    __m128i r2 = _mm_srli_epi32(src_pixel2,
-                                SK_R32_SHIFT + (8 - SK_R16_BITS));
-    r2 = _mm_and_si128(r2, _mm_set1_epi32(SK_R16_MASK));
-    __m128i r = _mm_packs_epi32(r1, r2);
-
-    // Calculate result g.
-    __m128i g1 = _mm_srli_epi32(src_pixel1,
-                                SK_G32_SHIFT + (8 - SK_G16_BITS));
-    g1 = _mm_and_si128(g1, _mm_set1_epi32(SK_G16_MASK));
-    __m128i g2 = _mm_srli_epi32(src_pixel2,
-                                SK_G32_SHIFT + (8 - SK_G16_BITS));
-    g2 = _mm_and_si128(g2, _mm_set1_epi32(SK_G16_MASK));
-    __m128i g = _mm_packs_epi32(g1, g2);
-
-    // Calculate result b.
-    __m128i b1 = _mm_srli_epi32(src_pixel1,
-                                SK_B32_SHIFT + (8 - SK_B16_BITS));
-    b1 = _mm_and_si128(b1, _mm_set1_epi32(SK_B16_MASK));
-    __m128i b2 = _mm_srli_epi32(src_pixel2,
-                                SK_B32_SHIFT + (8 - SK_B16_BITS));
-    b2 = _mm_and_si128(b2, _mm_set1_epi32(SK_B16_MASK));
-    __m128i b = _mm_packs_epi32(b1, b2);
-
-    // Store 8 16-bit colors in dst.
-    __m128i d_pixel = SkPackRGB16_SSE2(r, g, b);
-
-    return d_pixel;
-}
-
-// Portable version is SkPMSrcOver in SkColorData.h.
-static inline __m128i SkPMSrcOver_SSE2(const __m128i& src, const __m128i& dst) {
-    return _mm_add_epi32(src,
-                         SkAlphaMulQ_SSE2(dst, _mm_sub_epi32(_mm_set1_epi32(256),
-                                                             SkGetPackedA32_SSE2(src))));
-}
-
-// Fast path for SkBlendARGB32_SSE2 with a constant alpha factor.
-static inline __m128i SkBlendARGB32_SSE2(const __m128i& src, const __m128i& dst,
-                                         const unsigned aa) {
-    unsigned alpha = SkAlpha255To256(aa);
-    __m128i src_scale = _mm_set1_epi16(alpha);
-    // SkAlphaMulInv256(SkGetPackedA32(src), src_scale)
-    __m128i dst_scale = SkGetPackedA32_SSE2(src);
-    // High words in dst_scale are 0, so it's safe to multiply with 16-bit src_scale.
-    dst_scale = _mm_mullo_epi16(dst_scale, src_scale);
-    dst_scale = _mm_sub_epi32(_mm_set1_epi32(0xFFFF), dst_scale);
-    dst_scale = _mm_add_epi32(dst_scale, _mm_srli_epi32(dst_scale, 8));
-    dst_scale = _mm_srli_epi32(dst_scale, 8);
-    // Duplicate scales into 2x16-bit pattern per pixel.
-    dst_scale = _mm_shufflelo_epi16(dst_scale, _MM_SHUFFLE(2, 2, 0, 0));
-    dst_scale = _mm_shufflehi_epi16(dst_scale, _MM_SHUFFLE(2, 2, 0, 0));
-
-    const __m128i mask = _mm_set1_epi32(0x00FF00FF);
-
-    // Unpack the 16x8-bit source/destination into 2 8x16-bit splayed halves.
-    __m128i src_rb = _mm_and_si128(mask, src);
-    __m128i src_ag = _mm_srli_epi16(src, 8);
-    __m128i dst_rb = _mm_and_si128(mask, dst);
-    __m128i dst_ag = _mm_srli_epi16(dst, 8);
-
-    // Scale them.
-    src_rb = _mm_mullo_epi16(src_rb, src_scale);
-    src_ag = _mm_mullo_epi16(src_ag, src_scale);
-    dst_rb = _mm_mullo_epi16(dst_rb, dst_scale);
-    dst_ag = _mm_mullo_epi16(dst_ag, dst_scale);
-
-    // Add the scaled source and destination.
-    dst_rb = _mm_add_epi16(src_rb, dst_rb);
-    dst_ag = _mm_add_epi16(src_ag, dst_ag);
-
-    // Unsplay the halves back together.
-    dst_rb = _mm_srli_epi16(dst_rb, 8);
-    dst_ag = _mm_andnot_si128(mask, dst_ag);
-    return _mm_or_si128(dst_rb, dst_ag);
-}
-
-#undef ASSERT_EQ
-#endif // SkColor_opts_SSE2_DEFINED
diff --git a/src/opts/SkColor_opts_neon.h b/src/opts/SkColor_opts_neon.h
deleted file mode 100644
index 27520af..0000000
--- a/src/opts/SkColor_opts_neon.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkColor_opts_neon_DEFINED
-#define SkColor_opts_neon_DEFINED
-
-#include "SkTypes.h"
-#include "SkColorData.h"
-
-#include <arm_neon.h>
-
-#define NEON_A (SK_A32_SHIFT / 8)
-#define NEON_R (SK_R32_SHIFT / 8)
-#define NEON_G (SK_G32_SHIFT / 8)
-#define NEON_B (SK_B32_SHIFT / 8)
-
-static inline uint16x8_t SkAlpha255To256_neon8(uint8x8_t alpha) {
-    return vaddw_u8(vdupq_n_u16(1), alpha);
-}
-
-static inline uint8x8_t SkAlphaMul_neon8(uint8x8_t color, uint16x8_t scale) {
-    return vshrn_n_u16(vmovl_u8(color) * scale, 8);
-}
-
-static inline uint8x8x4_t SkAlphaMulQ_neon8(uint8x8x4_t color, uint16x8_t scale) {
-    uint8x8x4_t ret;
-
-    ret.val[NEON_A] = SkAlphaMul_neon8(color.val[NEON_A], scale);
-    ret.val[NEON_R] = SkAlphaMul_neon8(color.val[NEON_R], scale);
-    ret.val[NEON_G] = SkAlphaMul_neon8(color.val[NEON_G], scale);
-    ret.val[NEON_B] = SkAlphaMul_neon8(color.val[NEON_B], scale);
-
-    return ret;
-}
-
-/* This function expands 8 pixels from RGB565 (R, G, B from high to low) to
- * SkPMColor (all possible configurations supported) in the exact same way as
- * SkPixel16ToPixel32.
- */
-static inline uint8x8x4_t SkPixel16ToPixel32_neon8(uint16x8_t vsrc) {
-
-    uint8x8x4_t ret;
-    uint8x8_t vr, vg, vb;
-
-    vr = vmovn_u16(vshrq_n_u16(vsrc, SK_R16_SHIFT));
-    vg = vmovn_u16(vshrq_n_u16(vshlq_n_u16(vsrc, SK_R16_BITS), SK_R16_BITS + SK_B16_BITS));
-    vb = vmovn_u16(vsrc & vdupq_n_u16(SK_B16_MASK));
-
-    ret.val[NEON_A] = vdup_n_u8(0xFF);
-    ret.val[NEON_R] = vshl_n_u8(vr, 8 - SK_R16_BITS) | vshr_n_u8(vr, 2 * SK_R16_BITS - 8);
-    ret.val[NEON_G] = vshl_n_u8(vg, 8 - SK_G16_BITS) | vshr_n_u8(vg, 2 * SK_G16_BITS - 8);
-    ret.val[NEON_B] = vshl_n_u8(vb, 8 - SK_B16_BITS) | vshr_n_u8(vb, 2 * SK_B16_BITS - 8);
-
-    return ret;
-}
-
-/* This function packs 8 pixels from SkPMColor (all possible configurations
- * supported) to RGB565 (R, G, B from high to low) in the exact same way as
- * SkPixel32ToPixel16.
- */
-static inline uint16x8_t SkPixel32ToPixel16_neon8(uint8x8x4_t vsrc) {
-
-    uint16x8_t ret;
-
-    ret = vshll_n_u8(vsrc.val[NEON_R], 8);
-    ret = vsriq_n_u16(ret, vshll_n_u8(vsrc.val[NEON_G], 8), SK_R16_BITS);
-    ret = vsriq_n_u16(ret, vshll_n_u8(vsrc.val[NEON_B], 8), SK_R16_BITS + SK_G16_BITS);
-
-    return ret;
-}
-
-static inline SkPMColor SkFourByteInterp256_neon(SkPMColor src, SkPMColor dst,
-                                                 unsigned srcScale) {
-    SkASSERT(srcScale <= 256);
-    int16x8_t vscale = vdupq_n_s16(srcScale);
-    int16x8_t vsrc_wide, vdst_wide, vdiff;
-    uint8x8_t res;
-
-    vsrc_wide = vreinterpretq_s16_u16(vmovl_u8(vreinterpret_u8_u32(vdup_n_u32(src))));
-    vdst_wide = vreinterpretq_s16_u16(vmovl_u8(vreinterpret_u8_u32(vdup_n_u32(dst))));
-
-    vdiff = vsrc_wide - vdst_wide;
-    vdiff *= vscale;
-
-    vdiff = vshrq_n_s16(vdiff, 8);
-
-    vdst_wide += vdiff;
-
-    res = vmovn_u16(vreinterpretq_u16_s16(vdst_wide));
-
-    return vget_lane_u32(vreinterpret_u32_u8(res), 0);
-}
-
-static inline SkPMColor SkFourByteInterp_neon(SkPMColor src, SkPMColor dst,
-                                              U8CPU srcWeight) {
-    SkASSERT(srcWeight <= 255);
-    unsigned scale = SkAlpha255To256(srcWeight);
-    return SkFourByteInterp256_neon(src, dst, scale);
-}
-
-#endif /* #ifndef SkColor_opts_neon_DEFINED */
diff --git a/src/opts/SkRasterPipeline_opts.h b/src/opts/SkRasterPipeline_opts.h
index 01322c5..4c02958 100644
--- a/src/opts/SkRasterPipeline_opts.h
+++ b/src/opts/SkRasterPipeline_opts.h
@@ -2330,18 +2330,22 @@
 // and will have (x,y) geometry and/or (r,g,b,a, dr,dg,db,da) pixel arguments as appropriate.
 
 #if JUMPER_NARROW_STAGES
-    #define STAGE_GG(name, ...)                                                            \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y);      \
-        static void ABI name(Params* params, void** program, U16 r, U16 g, U16 b, U16 a) { \
-            auto x = join<F>(r,g),                                                         \
-                 y = join<F>(b,a);                                                         \
-            name##_k(Ctx{program}, params->dx,params->dy,params->tail, x,y);               \
-            split(x, &r,&g);                                                               \
-            split(y, &b,&a);                                                               \
-            auto next = (Stage)load_and_inc(program);                                      \
-            next(params,program, r,g,b,a);                                                 \
-        }                                                                                  \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y)
+    #define STAGE_GG(name, ...)                                                                \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y,           \
+                         U16    , U16    , U16    , U16    ,                                   \
+                         U16    , U16    , U16    , U16    );                                  \
+        static void ABI name(Params* params, void** program, U16 r, U16 g, U16 b, U16 a) {     \
+            auto x = join<F>(r,g),                                                             \
+                 y = join<F>(b,a);                                                             \
+            name##_k(Ctx{program}, params->dx,params->dy,params->tail, x,y, 0,0,0,0, 0,0,0,0); \
+            split(x, &r,&g);                                                                   \
+            split(y, &b,&a);                                                                   \
+            auto next = (Stage)load_and_inc(program);                                          \
+            next(params,program, r,g,b,a);                                                     \
+        }                                                                                      \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y,           \
+                         U16    , U16    , U16    , U16    ,                                   \
+                         U16    , U16    , U16    , U16    )
 
     #define STAGE_GP(name, ...)                                                            \
         SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F x, F y,         \
@@ -2360,33 +2364,37 @@
                          U16& dr, U16& dg, U16& db, U16& da)
 
     #define STAGE_PP(name, ...)                                                            \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail,                   \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F  , F  ,         \
                          U16&  r, U16&  g, U16&  b, U16&  a,                               \
                          U16& dr, U16& dg, U16& db, U16& da);                              \
         static void ABI name(Params* params, void** program, U16 r, U16 g, U16 b, U16 a) { \
-            name##_k(Ctx{program}, params->dx,params->dy,params->tail, r,g,b,a,            \
+            name##_k(Ctx{program}, params->dx,params->dy,params->tail, 0,0, r,g,b,a,       \
                      params->dr,params->dg,params->db,params->da);                         \
             auto next = (Stage)load_and_inc(program);                                      \
             next(params,program, r,g,b,a);                                                 \
         }                                                                                  \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail,                   \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F  , F  ,         \
                          U16&  r, U16&  g, U16&  b, U16&  a,                               \
                          U16& dr, U16& dg, U16& db, U16& da)
 #else
     #define STAGE_GG(name, ...)                                                            \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y);      \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y,       \
+                         U16    , U16    , U16    , U16    ,                               \
+                         U16    , U16    , U16    , U16    );                              \
         static void ABI name(size_t tail, void** program, size_t dx, size_t dy,            \
                              U16  r, U16  g, U16  b, U16  a,                               \
                              U16 dr, U16 dg, U16 db, U16 da) {                             \
             auto x = join<F>(r,g),                                                         \
                  y = join<F>(b,a);                                                         \
-            name##_k(Ctx{program}, dx,dy,tail, x,y);                                       \
+            name##_k(Ctx{program}, dx,dy,tail, x,y, 0,0,0,0, 0,0,0,0);                     \
             split(x, &r,&g);                                                               \
             split(y, &b,&a);                                                               \
             auto next = (Stage)load_and_inc(program);                                      \
             next(tail,program,dx,dy, r,g,b,a, dr,dg,db,da);                                \
         }                                                                                  \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y)
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F& x, F& y,       \
+                         U16    , U16    , U16    , U16    ,                               \
+                         U16    , U16    , U16    , U16    )
 
     #define STAGE_GP(name, ...)                                                            \
         SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F x, F y,         \
@@ -2406,17 +2414,17 @@
                          U16& dr, U16& dg, U16& db, U16& da)
 
     #define STAGE_PP(name, ...)                                                            \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail,                   \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F  , F  ,         \
                          U16&  r, U16&  g, U16&  b, U16&  a,                               \
                          U16& dr, U16& dg, U16& db, U16& da);                              \
         static void ABI name(size_t tail, void** program, size_t dx, size_t dy,            \
                              U16  r, U16  g, U16  b, U16  a,                               \
                              U16 dr, U16 dg, U16 db, U16 da) {                             \
-            name##_k(Ctx{program}, dx,dy,tail, r,g,b,a, dr,dg,db,da);                      \
+            name##_k(Ctx{program}, dx,dy,tail, 0,0, r,g,b,a, dr,dg,db,da);                 \
             auto next = (Stage)load_and_inc(program);                                      \
             next(tail,program,dx,dy, r,g,b,a, dr,dg,db,da);                                \
         }                                                                                  \
-        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail,                   \
+        SI void name##_k(__VA_ARGS__, size_t dx, size_t dy, size_t tail, F  , F  ,         \
                          U16&  r, U16&  g, U16&  b, U16&  a,                               \
                          U16& dr, U16& dg, U16& db, U16& da)
 #endif
@@ -3262,45 +3270,80 @@
     a = a + div255( da*inv(a) );
     store_8888_(ptr, tail, r,g,b,a);
 }
+
 // Now we'll add null stand-ins for stages we haven't implemented in lowp.
 // If a pipeline uses these stages, it'll boot it out of lowp into highp.
-
-using NotImplemented = void(*)(void);
-
-static NotImplemented
-        callback, load_rgba, store_rgba,
-        unbounded_set_rgb, unbounded_uniform_color,
-        unpremul, dither,
-        from_srgb, from_srgb_dst, to_srgb,
-        load_f16    , load_f16_dst    , store_f16    , gather_f16,
-        load_f32    , load_f32_dst    , store_f32    , gather_f32,
-        load_1010102, load_1010102_dst, store_1010102, gather_1010102,
-        store_u16_be,
-        byte_tables,
-        colorburn, colordodge, softlight, hue, saturation, color, luminosity,
-        matrix_3x3, matrix_3x4, matrix_4x5, matrix_4x3,
-        parametric, gamma,
-        rgb_to_hsl, hsl_to_rgb,
-        gauss_a_to_rgba,
-        mirror_x, repeat_x,
-        mirror_y, repeat_y,
-        negate_x,
-        bilerp_clamp_8888,
-        bilinear_nx, bilinear_ny, bilinear_px, bilinear_py,
-        bicubic_n3x, bicubic_n1x, bicubic_p1x, bicubic_p3x,
-        bicubic_n3y, bicubic_n1y, bicubic_p1y, bicubic_p3y,
-        save_xy, accumulate,
-        xy_to_2pt_conical_well_behaved,
-        xy_to_2pt_conical_strip,
-        xy_to_2pt_conical_focal_on_circle,
-        xy_to_2pt_conical_smaller,
-        xy_to_2pt_conical_greater,
-        xy_to_2pt_conical_compensate_focal,
-        alter_2pt_conical_compensate_focal,
-        alter_2pt_conical_unswap,
-        mask_2pt_conical_nan,
-        mask_2pt_conical_degenerates,
-        apply_vector_mask;
+#define NOT_IMPLEMENTED(st) static void (*st)(void) = nullptr;
+    NOT_IMPLEMENTED(callback)
+    NOT_IMPLEMENTED(load_rgba)
+    NOT_IMPLEMENTED(store_rgba)
+    NOT_IMPLEMENTED(unbounded_set_rgb)
+    NOT_IMPLEMENTED(unbounded_uniform_color)
+    NOT_IMPLEMENTED(unpremul)
+    NOT_IMPLEMENTED(dither)
+    NOT_IMPLEMENTED(from_srgb)
+    NOT_IMPLEMENTED(to_srgb)
+    NOT_IMPLEMENTED(load_f16)
+    NOT_IMPLEMENTED(load_f16_dst)
+    NOT_IMPLEMENTED(store_f16)
+    NOT_IMPLEMENTED(gather_f16)
+    NOT_IMPLEMENTED(load_f32)
+    NOT_IMPLEMENTED(load_f32_dst)
+    NOT_IMPLEMENTED(store_f32)
+    NOT_IMPLEMENTED(gather_f32)
+    NOT_IMPLEMENTED(load_1010102)
+    NOT_IMPLEMENTED(load_1010102_dst)
+    NOT_IMPLEMENTED(store_1010102)
+    NOT_IMPLEMENTED(gather_1010102)
+    NOT_IMPLEMENTED(store_u16_be)
+    NOT_IMPLEMENTED(byte_tables)
+    NOT_IMPLEMENTED(colorburn)
+    NOT_IMPLEMENTED(colordodge)
+    NOT_IMPLEMENTED(softlight)
+    NOT_IMPLEMENTED(hue)
+    NOT_IMPLEMENTED(saturation)
+    NOT_IMPLEMENTED(color)
+    NOT_IMPLEMENTED(luminosity)
+    NOT_IMPLEMENTED(matrix_3x3)
+    NOT_IMPLEMENTED(matrix_3x4)
+    NOT_IMPLEMENTED(matrix_4x5)
+    NOT_IMPLEMENTED(matrix_4x3)
+    NOT_IMPLEMENTED(parametric)
+    NOT_IMPLEMENTED(gamma)
+    NOT_IMPLEMENTED(rgb_to_hsl)
+    NOT_IMPLEMENTED(hsl_to_rgb)
+    NOT_IMPLEMENTED(gauss_a_to_rgba)
+    NOT_IMPLEMENTED(mirror_x)
+    NOT_IMPLEMENTED(repeat_x)
+    NOT_IMPLEMENTED(mirror_y)
+    NOT_IMPLEMENTED(repeat_y)
+    NOT_IMPLEMENTED(negate_x)
+    NOT_IMPLEMENTED(bilerp_clamp_8888)
+    NOT_IMPLEMENTED(bilinear_nx)
+    NOT_IMPLEMENTED(bilinear_ny)
+    NOT_IMPLEMENTED(bilinear_px)
+    NOT_IMPLEMENTED(bilinear_py)
+    NOT_IMPLEMENTED(bicubic_n3x)
+    NOT_IMPLEMENTED(bicubic_n1x)
+    NOT_IMPLEMENTED(bicubic_p1x)
+    NOT_IMPLEMENTED(bicubic_p3x)
+    NOT_IMPLEMENTED(bicubic_n3y)
+    NOT_IMPLEMENTED(bicubic_n1y)
+    NOT_IMPLEMENTED(bicubic_p1y)
+    NOT_IMPLEMENTED(bicubic_p3y)
+    NOT_IMPLEMENTED(save_xy)
+    NOT_IMPLEMENTED(accumulate)
+    NOT_IMPLEMENTED(xy_to_2pt_conical_well_behaved)
+    NOT_IMPLEMENTED(xy_to_2pt_conical_strip)
+    NOT_IMPLEMENTED(xy_to_2pt_conical_focal_on_circle)
+    NOT_IMPLEMENTED(xy_to_2pt_conical_smaller)
+    NOT_IMPLEMENTED(xy_to_2pt_conical_greater)
+    NOT_IMPLEMENTED(alter_2pt_conical_compensate_focal)
+    NOT_IMPLEMENTED(alter_2pt_conical_unswap)
+    NOT_IMPLEMENTED(mask_2pt_conical_nan)
+    NOT_IMPLEMENTED(mask_2pt_conical_degenerates)
+    NOT_IMPLEMENTED(apply_vector_mask)
+#undef NOT_IMPLEMENTED
 
 #endif//defined(JUMPER_IS_SCALAR) controlling whether we build lowp stages
 }  // namespace lowp
diff --git a/src/opts/SkXfermode_opts.h b/src/opts/SkXfermode_opts.h
index 5c35991..2178041 100644
--- a/src/opts/SkXfermode_opts.h
+++ b/src/opts/SkXfermode_opts.h
@@ -21,7 +21,7 @@
 
 #else
 
-namespace {
+namespace {  // NOLINT(google-build-namespaces)
 
 // Most xfermodes can be done most efficiently 4 pixels at a time in 8 or 16-bit fixed point.
 #define XFERMODE(Xfermode) \
diff --git a/src/pathops/SkOpAngle.cpp b/src/pathops/SkOpAngle.cpp
index e25d7c4..2aca983 100644
--- a/src/pathops/SkOpAngle.cpp
+++ b/src/pathops/SkOpAngle.cpp
@@ -180,21 +180,21 @@
             // if only one pair are the same, the third point touches neither of the pair
             if (ltShare + lrShare + trShare == 1) {
                 if (lrShare) {
-                    int ltOOrder = lh->allOnOriginalSide(this);
-                    int rtOOrder = rh->allOnOriginalSide(this);
+                    int ltOOrder = lh->linesOnOriginalSide(this);
+                    int rtOOrder = rh->linesOnOriginalSide(this);
                     if ((rtOOrder ^ ltOOrder) == 1) {
                         return ltOOrder;
                     }
                 } else if (trShare) {
-                    int tlOOrder = this->allOnOriginalSide(lh);
-                    int rlOOrder = rh->allOnOriginalSide(lh);
+                    int tlOOrder = this->linesOnOriginalSide(lh);
+                    int rlOOrder = rh->linesOnOriginalSide(lh);
                     if ((tlOOrder ^ rlOOrder) == 1) {
                         return rlOOrder;
                     }
                 } else {
                     SkASSERT(ltShare);
-                    int trOOrder = rh->allOnOriginalSide(this);
-                    int lrOOrder = lh->allOnOriginalSide(rh);
+                    int trOOrder = rh->linesOnOriginalSide(this);
+                    int lrOOrder = lh->linesOnOriginalSide(rh);
                     // result must be 0 and 1 or 1 and 0 to be valid
                     if ((lrOOrder ^ trOOrder) == 1) {
                         return trOOrder;
@@ -212,18 +212,13 @@
     return COMPARE_RESULT(13, !lrOrder);
 }
 
-// given a line, see if the opposite curve's convex hull is all on one side
-// returns -1=not on one side    0=this CW of test   1=this CCW of test
-int SkOpAngle::allOnOneSide(const SkOpAngle* test) {
-    SkASSERT(!fPart.isCurve());
-    SkASSERT(test->fPart.isCurve());
-    SkDPoint origin = fPart.fCurve[0];
-    SkDVector line = fPart.fCurve[1] - origin;
+int SkOpAngle::lineOnOneSide(const SkDPoint& origin, const SkDVector& line, const SkOpAngle* test,
+        bool useOriginal) const {
     double crosses[3];
     SkPath::Verb testVerb = test->segment()->verb();
     int iMax = SkPathOpsVerbToPoints(testVerb);
 //    SkASSERT(origin == test.fCurveHalf[0]);
-    const SkDCurve& testCurve = test->fPart.fCurve;
+    const SkDCurve& testCurve = useOriginal ? test->fOriginalCurvePart : test->fPart.fCurve;
     for (int index = 1; index <= iMax; ++index) {
         double xy1 = line.fX * (testCurve[index].fY - origin.fY);
         double xy2 = line.fY * (testCurve[index].fX - origin.fX);
@@ -246,12 +241,26 @@
     if (SkPath::kCubic_Verb == testVerb && crosses[2]) {
         return crosses[2] < 0;
     }
-    fUnorderable = true;
-    return -1;
+    return -2;
+}
+
+// given a line, see if the opposite curve's convex hull is all on one side
+// returns -1=not on one side    0=this CW of test   1=this CCW of test
+int SkOpAngle::lineOnOneSide(const SkOpAngle* test, bool useOriginal) {
+    SkASSERT(!fPart.isCurve());
+    SkASSERT(test->fPart.isCurve());
+    SkDPoint origin = fPart.fCurve[0];
+    SkDVector line = fPart.fCurve[1] - origin;
+    int result = this->lineOnOneSide(origin, line, test, useOriginal);
+    if (-2 == result) {
+        fUnorderable = true;
+        result = -1;
+    }
+    return result;
 }
 
 // experiment works only with lines for now
-int SkOpAngle::allOnOriginalSide(const SkOpAngle* test) {
+int SkOpAngle::linesOnOriginalSide(const SkOpAngle* test) {
     SkASSERT(!fPart.isCurve());
     SkASSERT(!test->fPart.isCurve());
     SkDPoint origin = fOriginalCurvePart[0];
@@ -578,7 +587,32 @@
         }
         double maxWidth = SkTMax(maxX - minX, maxY - minY);
         delta = sk_ieee_double_divide(delta, maxWidth);
-        if (delta > 1e-3 && (useIntersect ^= true)) {  // FIXME: move this magic number
+        // FIXME: move these magic numbers
+        // This fixes skbug.com/8380
+        // Larger changes (like changing the constant in the next block) cause other
+        // tests to fail as documented in the bug.
+        // This could probably become a more general test: e.g., if translating the
+        // curve causes the cross product of any control point or end point to change
+        // sign with regard to the opposite curve's hull, treat the curves as parallel.
+
+        // Moreso, this points to the general fragility of this approach of assigning
+        // winding by sorting the angles of curves sharing a common point, as mentioned
+        // in the bug.
+        if (delta < 4e-3 && delta > 1e-3 && !useIntersect && fPart.isCurve()
+                && rh->fPart.isCurve() && fOriginalCurvePart[0] != fPart.fCurve.fLine[0]) {
+            // see if original curve is on one side of hull; translated is on the other
+            const SkDPoint& origin = rh->fOriginalCurvePart[0];
+            int count = SkPathOpsVerbToPoints(rh->segment()->verb());
+            const SkDVector line = rh->fOriginalCurvePart[count] - origin;
+            int originalSide = rh->lineOnOneSide(origin, line, this, true);
+            if (originalSide >= 0) {
+                int translatedSide = rh->lineOnOneSide(origin, line, this, false);
+                if (originalSide != translatedSide) {
+                    continue;
+                }
+            }
+        }
+        if (delta > 1e-3 && (useIntersect ^= true)) {
             sRayLonger = rayLonger;
             sCept = cept;
             sCeptT = smallTs[index];
@@ -881,14 +915,14 @@
             SkASSERT(x_ry != rx_y); // indicates an undetected coincidence -- worth finding earlier
             return x_ry < rx_y ? 1 : 0;
         }
-        if ((result = this->allOnOneSide(rh)) >= 0) {
+        if ((result = this->lineOnOneSide(rh, false)) >= 0) {
             return result;
         }
         if (fUnorderable || approximately_zero(rh->fSide)) {
             goto unorderable;
         }
     } else if (!rh->fPart.isCurve()) {
-        if ((result = rh->allOnOneSide(this)) >= 0) {
+        if ((result = rh->lineOnOneSide(this, false)) >= 0) {
             return result ? 0 : 1;
         }
         if (rh->fUnorderable || approximately_zero(fSide)) {
diff --git a/src/pathops/SkOpAngle.h b/src/pathops/SkOpAngle.h
index eb77757..375844a 100644
--- a/src/pathops/SkOpAngle.h
+++ b/src/pathops/SkOpAngle.h
@@ -98,8 +98,6 @@
 private:
     bool after(SkOpAngle* test);
     void alignmentSameSide(const SkOpAngle* test, int* order) const;
-    int allOnOneSide(const SkOpAngle* test);
-    int allOnOriginalSide(const SkOpAngle* test);
     bool checkCrossesZero() const;
     bool checkParallel(SkOpAngle* );
     bool computeSector();
@@ -108,6 +106,10 @@
     bool endsIntersect(SkOpAngle* );
     int findSector(SkPath::Verb verb, double x, double y) const;
     SkOpGlobalState* globalState() const;
+    int lineOnOneSide(const SkDPoint& origin, const SkDVector& line, const SkOpAngle* test,
+                      bool useOriginal) const;
+    int lineOnOneSide(const SkOpAngle* test, bool useOriginal);
+    int linesOnOriginalSide(const SkOpAngle* test);
     bool merge(SkOpAngle* );
     double midT() const;
     bool midToSide(const SkOpAngle* rh, bool* inside) const;
diff --git a/src/pathops/SkPathOpsDebug.cpp b/src/pathops/SkPathOpsDebug.cpp
index b7174da..3471ec0 100644
--- a/src/pathops/SkPathOpsDebug.cpp
+++ b/src/pathops/SkPathOpsDebug.cpp
@@ -439,7 +439,7 @@
         case kAddCorruptCoin_Glitch: SkDebugf(" AddCorruptCoin"); break;
         case kAddExpandedCoin_Glitch: SkDebugf(" AddExpandedCoin"); break;
         case kAddExpandedFail_Glitch: SkDebugf(" AddExpandedFail"); break;
-        case kAddIfCollapsed_Glitch: SkDebugf(" AddIfCollapsed"); break;; break;
+        case kAddIfCollapsed_Glitch: SkDebugf(" AddIfCollapsed"); break;
         case kAddIfMissingCoin_Glitch: SkDebugf(" AddIfMissingCoin"); break;
         case kAddMissingCoin_Glitch: SkDebugf(" AddMissingCoin"); break;
         case kAddMissingExtend_Glitch: SkDebugf(" AddMissingExtend"); break;
diff --git a/src/pdf/SkPDFBitmap.cpp b/src/pdf/SkPDFBitmap.cpp
index a5754ef..4f7ae95 100644
--- a/src/pdf/SkPDFBitmap.cpp
+++ b/src/pdf/SkPDFBitmap.cpp
@@ -10,6 +10,7 @@
 #include "SkColorData.h"
 #include "SkData.h"
 #include "SkDeflate.h"
+#include "SkExecutor.h"
 #include "SkImage.h"
 #include "SkImageInfoPriv.h"
 #include "SkJpegInfo.h"
@@ -81,7 +82,14 @@
         pdfDict.insertRef("SMask", *smask);
     }
     pdfDict.insertInt("BitsPerComponent", 8);
+    #ifdef SK_PDF_BASE85_BINARY
+    auto filters = SkPDFMakeArray();
+    filters->appendName("ASCII85Decode");
+    filters->appendName("FlateDecode");
+    pdfDict.insertObject("Filter", std::move(filters));
+    #else
     pdfDict.insertName("Filter", "FlateDecode");
+    #endif
     pdfDict.insertInt("Length", length);
     pdfDict.emitObject(stream);
 }
@@ -113,6 +121,10 @@
         deflateWStream.write(byteBuffer, dst - byteBuffer);
     }
     deflateWStream.finalize();
+
+    #ifdef SK_PDF_BASE85_BINARY
+    SkPDFUtils::Base85Encode(buffer.detachAsStream(), &buffer);
+    #endif
     SkWStream* stream = doc->beginObject(ref);
     emit_dict(stream, pm.info().dimensions(), "DeviceGray", nullptr, buffer.bytesWritten());
     emit_stream(&buffer, stream);
@@ -120,12 +132,13 @@
     return ref;
 }
 
-static SkPDFIndirectReference do_deflated_image(const SkPixmap& pm,
-                                                SkPDFDocument* doc,
-                                                bool isOpaque) {
+static void  do_deflated_image(const SkPixmap& pm,
+                               SkPDFDocument* doc,
+                               bool isOpaque,
+                               SkPDFIndirectReference ref) {
     SkPDFIndirectReference sMask;
     if (!isOpaque) {
-        sMask = doc->reserve();
+        sMask = doc->reserveRef();
     }
     SkDynamicMemoryWStream buffer;
     SkDeflateWStream deflateWStream(&buffer);
@@ -167,7 +180,9 @@
             deflateWStream.write(byteBuffer, dst - byteBuffer);
     }
     deflateWStream.finalize();
-    SkPDFIndirectReference ref = doc->reserve();
+    #ifdef SK_PDF_BASE85_BINARY
+    SkPDFUtils::Base85Encode(buffer.detachAsStream(), &buffer);
+    #endif
     SkWStream* stream = doc->beginObject(ref);
     emit_dict(stream, pm.info().dimensions(), colorSpace,
               sMask.fValue != -1 ? &sMask : nullptr,
@@ -177,11 +192,10 @@
     if (!isOpaque) {
         do_deflated_alpha(pm, doc, sMask);
     }
-    return ref;
 }
 
 static bool do_jpeg(const SkData& data, SkPDFDocument* doc, SkISize size,
-                    SkPDFIndirectReference* result) {
+                    SkPDFIndirectReference ref) {
     SkISize jpegSize;
     SkEncodedInfo::Color jpegColorType;
     SkEncodedOrigin exifOrientation;
@@ -199,10 +213,14 @@
     #ifdef SK_PDF_IMAGE_STATS
     gJpegImageObjects.fetch_add(1);
     #endif
-    SkPDFIndirectReference ref = doc->reserve();
-    *result = ref;
-    SkWStream* stream = doc->beginObject(ref);
 
+    #ifdef SK_PDF_BASE85_BINARY
+    SkDynamicMemoryWStream buffer;
+    SkPDFUtils::Base85Encode(SkMemoryStream::MakeDirect(data.data(), data.size()), &buffer);
+    int length = SkToInt(buffer.bytesWritten());
+    #else
+    int length = SkToInt(data.size());
+    #endif
     SkPDFDict pdfDict("XObject");
     pdfDict.insertName("Subtype", "Image");
     pdfDict.insertInt("Width", jpegSize.width());
@@ -213,12 +231,25 @@
         pdfDict.insertName("ColorSpace", "DeviceGray");
     }
     pdfDict.insertInt("BitsPerComponent", 8);
+    #ifdef SK_PDF_BASE85_BINARY
+    auto filters = SkPDFMakeArray();
+    filters->appendName("ASCII85Decode");
+    filters->appendName("DCTDecode");
+    pdfDict.insertObject("Filter", std::move(filters));
+    #else
     pdfDict.insertName("Filter", "DCTDecode");
+    #endif
     pdfDict.insertInt("ColorTransform", 0);
-    pdfDict.insertInt("Length", SkToInt(data.size()));
+    pdfDict.insertInt("Length", length);
+
+    SkWStream* stream = doc->beginObject(ref);
     pdfDict.emitObject(stream);
     stream->writeText(" stream\n");
+    #ifdef SK_PDF_BASE85_BINARY
+    buffer.writeToAndReset(stream);
+    #else
     stream->write(data.data(), data.size());
+    #endif
     stream->writeText("\nendstream");
     doc->endObject();
     return true;
@@ -247,26 +278,46 @@
     return bm;
 }
 
-SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img,
-                                           SkPDFDocument* doc,
-                                           int encodingQuality) {
-    SkPDFIndirectReference result;
+void serialize_image(const SkImage* img,
+                     int encodingQuality,
+                     SkPDFDocument* doc,
+                     SkPDFIndirectReference ref) {
     SkASSERT(img);
     SkASSERT(doc);
     SkASSERT(encodingQuality >= 0);
     SkISize dimensions = img->dimensions();
     sk_sp<SkData> data = img->refEncodedData();
-    if (data && do_jpeg(*data, doc, dimensions, &result)) {
-        return result;
+    if (data && do_jpeg(*data, doc, dimensions, ref)) {
+        return;
     }
     SkBitmap bm = to_pixels(img);
     SkPixmap pm = bm.pixmap();
     bool isOpaque = pm.isOpaque() || pm.computeIsOpaque();
     if (encodingQuality <= 100 && isOpaque) {
         sk_sp<SkData> data = img->encodeToData(SkEncodedImageFormat::kJPEG, encodingQuality);
-        if (data && do_jpeg(*data, doc, dimensions, &result)) {
-            return result;
+        if (data && do_jpeg(*data, doc, dimensions, ref)) {
+            return;
         }
     }
-    return do_deflated_image(pm, doc, isOpaque);
+    do_deflated_image(pm, doc, isOpaque, ref);
+}
+
+SkPDFIndirectReference SkPDFSerializeImage(const SkImage* img,
+                                           SkPDFDocument* doc,
+                                           int encodingQuality) {
+    SkASSERT(img);
+    SkASSERT(doc);
+    SkPDFIndirectReference ref = doc->reserveRef();
+    if (SkExecutor* executor = doc->executor()) {
+        SkRef(img);
+        doc->incrementJobCount();
+        executor->add([img, encodingQuality, doc, ref]() {
+            serialize_image(img, encodingQuality, doc, ref);
+            SkSafeUnref(img);
+            doc->signalJobComplete();
+        });
+        return ref;
+    }
+    serialize_image(img, encodingQuality, doc, ref);
+    return ref;
 }
diff --git a/src/pdf/SkPDFCanon.h b/src/pdf/SkPDFCanon.h
index b4782c2..1d93711 100644
--- a/src/pdf/SkPDFCanon.h
+++ b/src/pdf/SkPDFCanon.h
@@ -34,11 +34,11 @@
     SkPDFCanon& operator=(SkPDFCanon&&);
     SkPDFCanon& operator=(const SkPDFCanon&) = delete;
 
-    SkTHashMap<SkPDFImageShaderKey, sk_sp<SkPDFObject>> fImageShaderMap;
+    SkTHashMap<SkPDFImageShaderKey, SkPDFIndirectReference> fImageShaderMap;
 
     SkPDFGradientShader::HashMap fGradientPatternMap;
 
-    SkTHashMap<SkBitmapKey, sk_sp<SkPDFObject>> fPDFBitmapMap;
+    SkTHashMap<SkBitmapKey, SkPDFIndirectReference> fPDFBitmapMap;
 
     SkTHashMap<uint32_t, std::unique_ptr<SkAdvancedTypefaceMetrics>> fTypefaceMetrics;
     SkTHashMap<uint32_t, std::vector<SkString>> fType1GlyphNames;
@@ -47,12 +47,11 @@
     SkTHashMap<uint32_t, SkPDFIndirectReference> fType3FontDescriptors;
     SkTHashMap<uint64_t, SkPDFFont> fFontMap;
 
-    SkTHashMap<SkPDFStrokeGraphicState, sk_sp<SkPDFDict>> fStrokeGSMap;
-    SkTHashMap<SkPDFFillGraphicState, sk_sp<SkPDFDict>> fFillGSMap;
+    SkTHashMap<SkPDFStrokeGraphicState, SkPDFIndirectReference> fStrokeGSMap;
+    SkTHashMap<SkPDFFillGraphicState, SkPDFIndirectReference> fFillGSMap;
 
-    sk_sp<SkPDFStream> fInvertFunction;
-    sk_sp<SkPDFDict> fNoSmaskGraphicState;
-    sk_sp<SkPDFArray> fRangeObject;
+    SkPDFIndirectReference fInvertFunction;
+    SkPDFIndirectReference fNoSmaskGraphicState;
 };
 
 #endif  // SkPDFCanon_DEFINED
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index d5fff5d..5cd0247 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -17,6 +17,7 @@
 #include "SkColor.h"
 #include "SkColorFilter.h"
 #include "SkDraw.h"
+#include "SkFontPriv.h"
 #include "SkGlyphCache.h"
 #include "SkGlyphRun.h"
 #include "SkImageFilterCache.h"
@@ -107,6 +108,11 @@
     return SkImage::MakeFromBitmap(greyBitmap);
 }
 
+static int add_resource(SkTHashSet<SkPDFIndirectReference>& resources, SkPDFIndirectReference ref) {
+    resources.add(ref);
+    return ref.fValue;
+}
+
 static void draw_points(SkCanvas::PointMode mode,
                         size_t count,
                         const SkPoint* points,
@@ -367,30 +373,17 @@
             fContentStream->writeText(" Tz\n");
             currentEntry()->fTextScaleX = state.fTextScaleX;
         }
-        if (state.fTextFill != currentEntry()->fTextFill) {
-            static_assert(SkPaint::kFill_Style == 0, "enum_must_match_value");
-            static_assert(SkPaint::kStroke_Style == 1, "enum_must_match_value");
-            static_assert(SkPaint::kStrokeAndFill_Style == 2, "enum_must_match_value");
-            fContentStream->writeDecAsText(state.fTextFill);
-            fContentStream->writeText(" Tr\n");
-            currentEntry()->fTextFill = state.fTextFill;
-        }
     }
 }
 
-static bool not_supported_for_layers(const SkPaint& layerPaint) {
+SkBaseDevice* SkPDFDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint* layerPaint) {
     // PDF does not support image filters, so render them on CPU.
     // Note that this rendering is done at "screen" resolution (100dpi), not
     // printer resolution.
+
     // TODO: It may be possible to express some filters natively using PDF
     // to improve quality and file size (https://bug.skia.org/3043)
-
-    // TODO: should we return true if there is a colorfilter?
-    return layerPaint.getImageFilter() != nullptr;
-}
-
-SkBaseDevice* SkPDFDevice::onCreateDevice(const CreateInfo& cinfo, const SkPaint* layerPaint) {
-    if (layerPaint && not_supported_for_layers(*layerPaint)) {
+    if (layerPaint && (layerPaint->getImageFilter() || layerPaint->getColorFilter())) {
         // need to return a raster device, which we will detect in drawDevice()
         return SkBitmapDevice::Create(cinfo.fInfo, SkSurfaceProps(0, kUnknown_SkPixelGeometry));
     }
@@ -407,7 +400,7 @@
                        const SkClipStack* clipStack,
                        const SkMatrix& matrix,
                        const SkPaint& paint,
-                       bool hasText = false)
+                       SkScalar textScale = 0)
         : fDevice(device)
         , fBlendMode(SkBlendMode::kSrcOver)
         , fClipStack(clipStack)
@@ -418,10 +411,10 @@
         }
         fBlendMode = paint.getBlendMode();
         fContentStream =
-            fDevice->setUpContentEntry(clipStack, matrix, paint, hasText, &fDstFormXObject);
+            fDevice->setUpContentEntry(clipStack, matrix, paint, textScale, &fDstFormXObject);
     }
-    ScopedContentEntry(SkPDFDevice* dev, const SkPaint& paint, bool hasText = false)
-        : ScopedContentEntry(dev, &dev->cs(), dev->ctm(), paint, hasText) {}
+    ScopedContentEntry(SkPDFDevice* dev, const SkPaint& paint, SkScalar textScale = 0)
+        : ScopedContentEntry(dev, &dev->cs(), dev->ctm(), paint, textScale) {}
 
     ~ScopedContentEntry() {
         if (fContentStream) {
@@ -429,7 +422,7 @@
             if (shape->isEmpty()) {
                 shape = nullptr;
             }
-            fDevice->finishContentEntry(fClipStack, fBlendMode, std::move(fDstFormXObject), shape);
+            fDevice->finishContentEntry(fClipStack, fBlendMode, fDstFormXObject, shape);
         }
     }
 
@@ -474,7 +467,7 @@
     SkPDFDevice* fDevice = nullptr;
     SkDynamicMemoryWStream* fContentStream = nullptr;
     SkBlendMode fBlendMode;
-    sk_sp<SkPDFObject> fDstFormXObject;
+    SkPDFIndirectReference fDstFormXObject;
     SkPath fShape;
     const SkClipStack* fClipStack;
 };
@@ -497,9 +490,9 @@
     fLinkToURLs = std::vector<RectWithData>();
     fLinkToDestinations = std::vector<RectWithData>();
     fNamedDestinations = std::vector<NamedDestination>();
-    fGraphicStateResources = std::vector<sk_sp<SkPDFObject>>();
-    fXObjectResources = std::vector<sk_sp<SkPDFObject>>();
-    fShaderResources = std::vector<sk_sp<SkPDFObject>>();
+    fGraphicStateResources.reset();
+    fXObjectResources.reset();
+    fShaderResources.reset();
     fFontResources.reset();
     fContent.reset();
     fActiveStackState = GraphicStackState();
@@ -641,8 +634,8 @@
     }
 }
 
-static sk_sp<SkPDFDict> create_link_annotation(const SkRect& translatedRect) {
-    auto annotation = sk_make_sp<SkPDFDict>("Annot");
+static std::unique_ptr<SkPDFDict> create_link_annotation(const SkRect& translatedRect) {
+    auto annotation = SkPDFMakeDict("Annot");
     annotation->insertName("Subtype", "Link");
     annotation->insertInt("F", 4);  // required by ISO 19005
     // Border: 0 = Horizontal corner radius.
@@ -657,20 +650,20 @@
     return annotation;
 }
 
-static sk_sp<SkPDFDict> create_link_to_url(const SkData* urlData, const SkRect& r) {
-    sk_sp<SkPDFDict> annotation = create_link_annotation(r);
+static std::unique_ptr<SkPDFDict> create_link_to_url(const SkData* urlData, const SkRect& r) {
+    std::unique_ptr<SkPDFDict> annotation = create_link_annotation(r);
     SkString url(static_cast<const char *>(urlData->data()),
                  urlData->size() - 1);
-    auto action = sk_make_sp<SkPDFDict>("Action");
+    auto action = SkPDFMakeDict("Action");
     action->insertName("S", "URI");
     action->insertString("URI", url);
     annotation->insertObject("A", std::move(action));
     return annotation;
 }
 
-static sk_sp<SkPDFDict> create_link_named_dest(const SkData* nameData,
-                                               const SkRect& r) {
-    sk_sp<SkPDFDict> annotation = create_link_annotation(r);
+static std::unique_ptr<SkPDFDict> create_link_named_dest(const SkData* nameData,
+                                                         const SkRect& r) {
+    std::unique_ptr<SkPDFDict> annotation = create_link_annotation(r);
     SkString name(static_cast<const char *>(nameData->data()),
                   nameData->size() - 1);
     annotation->insertName("Dest", name);
@@ -781,39 +774,24 @@
     this->clearMaskOnGraphicState(content.stream());
 }
 
-template <typename T,
-          typename U,
-          typename = typename std::enable_if<std::is_convertible<U*, T*>::value>::type>
-static int find_or_add(std::vector<sk_sp<T>>* vec, sk_sp<U> object) {
-    SkASSERT(vec);
-    SkASSERT(object);
-    for (size_t i = 0; i < vec->size(); ++i) {
-        if ((*vec)[i].get() == object.get()) {
-            return SkToInt(i);
-        }
-    }
-    int index = SkToInt(vec->size());
-    vec->push_back(sk_sp<T>(std::move(object)));
-    return index;
-}
-
-void SkPDFDevice::setGraphicState(sk_sp<SkPDFObject> gs, SkDynamicMemoryWStream* content) {
-    SkPDFUtils::ApplyGraphicState(find_or_add(&fGraphicStateResources, std::move(gs)), content);
+void SkPDFDevice::setGraphicState(SkPDFIndirectReference gs, SkDynamicMemoryWStream* content) {
+    SkPDFUtils::ApplyGraphicState(add_resource(fGraphicStateResources, gs), content);
 }
 
 void SkPDFDevice::addSMaskGraphicState(sk_sp<SkPDFDevice> maskDevice,
                                        SkDynamicMemoryWStream* contentStream) {
     this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
             maskDevice->makeFormXObjectFromDevice(true), false,
-            SkPDFGraphicState::kLuminosity_SMaskMode, this->getCanon()), contentStream);
+            SkPDFGraphicState::kLuminosity_SMaskMode, fDocument), contentStream);
 }
 
 void SkPDFDevice::clearMaskOnGraphicState(SkDynamicMemoryWStream* contentStream) {
     // The no-softmask graphic state is used to "turn off" the mask for later draw calls.
-    sk_sp<SkPDFDict>& noSMaskGS = this->getCanon()->fNoSmaskGraphicState;
+    SkPDFIndirectReference& noSMaskGS = this->getCanon()->fNoSmaskGraphicState;
     if (!noSMaskGS) {
-        noSMaskGS = sk_make_sp<SkPDFDict>("ExtGState");
-        noSMaskGS->insertName("SMask", "None");
+        SkPDFDict tmp("ExtGState");
+        tmp.insertName("SMask", "None");
+        noSMaskGS = fDocument->emit(tmp);
     }
     this->setGraphicState(noSMaskGS, contentStream);
 }
@@ -1031,35 +1009,44 @@
           r.top()  <= p.y() && p.y() <= r.bottom();
 }
 
-void SkPDFDevice::drawGlyphRunAsPath(const SkGlyphRun& glyphRun, SkPoint offset) {
-    SkPaint paint{glyphRun.paint()};
-    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+void SkPDFDevice::drawGlyphRunAsPath(
+        const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint) {
+    const SkFont& font = glyphRun.font();
     SkPath path;
 
-    paint.getPosTextPath(glyphRun.glyphsIDs().data(),
-                         glyphRun.glyphsIDs().size() * sizeof(SkGlyphID),
-                         glyphRun.positions().data(),
-                         &path);
-    path.offset(offset.x(), offset.y());
-    this->drawPath(path, paint, true);
+    struct Rec {
+        SkPath* fPath;
+        SkPoint fOffset;
+        const SkPoint* fPos;
+    } rec = {&path, offset, glyphRun.positions().data()};
+
+    font.getPaths(glyphRun.glyphsIDs().data(), glyphRun.glyphsIDs().size(),
+                  [](const SkPath* path, const SkMatrix& mx, void* ctx) {
+                      Rec* rec = reinterpret_cast<Rec*>(ctx);
+                      if (path) {
+                          SkMatrix total = mx;
+                          total.postTranslate(rec->fPos->fX + rec->fOffset.fX,
+                                              rec->fPos->fY + rec->fOffset.fY);
+                          rec->fPath->addPath(*path, total);
+                      }
+                      rec->fPos += 1; // move to the next glyph's position
+                  }, &rec);
+    this->drawPath(path, runPaint, true);
+
+    SkFont transparentFont = glyphRun.font();
+    transparentFont.setEmbolden(false); // Stop Recursion
+    SkGlyphRun tmpGlyphRun(glyphRun, transparentFont);
 
     SkPaint transparent;
-    transparent.setTypeface(paint.getTypeface() ? paint.refTypeface()
-                                                : SkTypeface::MakeDefault());
-    transparent.setTextEncoding(kGlyphID_SkTextEncoding);
     transparent.setColor(SK_ColorTRANSPARENT);
-    transparent.setTextSize(paint.getTextSize());
-    transparent.setTextScaleX(paint.getTextScaleX());
-    transparent.setTextSkewX(paint.getTextSkewX());
-    SkGlyphRun tmp(glyphRun, transparent);
 
     if (this->ctm().hasPerspective()) {
         SkMatrix prevCTM = this->ctm();
         this->setCTM(SkMatrix::I());
-        this->internalDrawGlyphRun(tmp, offset);
+        this->internalDrawGlyphRun(tmpGlyphRun, offset, transparent);
         this->setCTM(prevCTM);
     } else {
-        this->internalDrawGlyphRun(tmp, offset);
+        this->internalDrawGlyphRun(tmpGlyphRun, offset, transparent);
     }
 }
 
@@ -1081,36 +1068,31 @@
     return convertedToType3 != bitmapOnly;
 }
 
-void SkPDFDevice::internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offset) {
+void SkPDFDevice::internalDrawGlyphRun(
+        const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint) {
 
     const SkGlyphID* glyphs = glyphRun.glyphsIDs().data();
     uint32_t glyphCount = SkToU32(glyphRun.glyphsIDs().size());
-    SkPaint srcPaint{glyphRun.paint()};
-    srcPaint.setTextEncoding(kGlyphID_SkTextEncoding);
+    const SkFont& glyphRunFont = glyphRun.font();
 
-    if (!glyphCount || !glyphs || srcPaint.getTextSize() <= 0 || this->hasEmptyClip()) {
+    if (!glyphCount || !glyphs || glyphRunFont.getSize() <= 0 || this->hasEmptyClip()) {
         return;
     }
-    if (srcPaint.getPathEffect()
-        || srcPaint.getMaskFilter()
-        || srcPaint.isFakeBoldText()
+    if (runPaint.getPathEffect()
+        || runPaint.getMaskFilter()
+        || glyphRunFont.isEmbolden()
         || this->ctm().hasPerspective()
-        || SkPaint::kFill_Style != srcPaint.getStyle()) {
+        || SkPaint::kFill_Style != runPaint.getStyle()) {
         // Stroked Text doesn't work well with Type3 fonts.
-        this->drawGlyphRunAsPath(glyphRun, offset);
+        this->drawGlyphRunAsPath(glyphRun, offset, runPaint);
+        return;
     }
-    SkPaint paint(srcPaint);
-    remove_color_filter(&paint);
-    replace_srcmode_on_opaque_paint(&paint);
-    paint.setHinting(kNo_SkFontHinting);
-    if (!paint.getTypeface()) {
-        paint.setTypeface(SkTypeface::MakeDefault());
-    }
-    SkTypeface* typeface = paint.getTypeface();
+    SkTypeface* typeface = SkFontPriv::GetTypefaceOrDefault(glyphRunFont);
     if (!typeface) {
         SkDebugf("SkPDF: SkTypeface::MakeDefault() returned nullptr.\n");
         return;
     }
+
     const SkAdvancedTypefaceMetrics* metrics = SkPDFFont::GetMetrics(typeface, fDocument->canon());
     if (!metrics) {
         return;
@@ -1125,131 +1107,131 @@
     int emSize;
     auto glyphCache = SkPDFFont::MakeVectorCache(typeface, &emSize);
 
-    SkScalar textSize = paint.getTextSize();
-    SkScalar advanceScale = textSize * paint.getTextScaleX() / emSize;
+    SkScalar textSize = glyphRunFont.getSize();
+    SkScalar advanceScale = textSize * glyphRunFont.getScaleX() / emSize;
 
     // textScaleX and textScaleY are used to get a conservative bounding box for glyphs.
     SkScalar textScaleY = textSize / emSize;
-    SkScalar textScaleX = advanceScale + paint.getTextSkewX() * textScaleY;
+    SkScalar textScaleX = advanceScale + glyphRunFont.getSkewX() * textScaleY;
 
     SkRect clipStackBounds = this->cs().bounds(this->bounds());
-    {
-        ScopedContentEntry content(this, paint, true);
-        if (!content) {
-            return;
-        }
-        SkDynamicMemoryWStream* out = content.stream();
 
-        out->writeText("BT\n");
+    SkPaint paint(runPaint);
+    remove_color_filter(&paint);
+    replace_srcmode_on_opaque_paint(&paint);
+    ScopedContentEntry content(this, paint, glyphRunFont.getScaleX());
+    if (!content) {
+        return;
+    }
+    SkDynamicMemoryWStream* out = content.stream();
 
-        int markId = -1;
-        if (fNodeId) {
-            markId = fDocument->getMarkIdForNodeId(fNodeId);
-        }
+    out->writeText("BT\n");
 
-        if (markId != -1) {
-            out->writeText("/P <</MCID ");
-            out->writeDecAsText(markId);
-            out->writeText(" >>BDC\n");
-        }
-        SK_AT_SCOPE_EXIT(if (markId != -1) out->writeText("EMC\n"));
+    int markId = -1;
+    if (fNodeId) {
+        markId = fDocument->getMarkIdForNodeId(fNodeId);
+    }
 
-        SK_AT_SCOPE_EXIT(out->writeText("ET\n"));
+    if (markId != -1) {
+        out->writeText("/P <</MCID ");
+        out->writeDecAsText(markId);
+        out->writeText(" >>BDC\n");
+    }
+    SK_AT_SCOPE_EXIT(if (markId != -1) out->writeText("EMC\n"));
 
-        const SkGlyphID maxGlyphID = SkToU16(typeface->countGlyphs() - 1);
+    SK_AT_SCOPE_EXIT(out->writeText("ET\n"));
 
-        if (clusterator.reversedChars()) {
-            out->writeText("/ReversedChars BMC\n");
-        }
-        SK_AT_SCOPE_EXIT(if (clusterator.reversedChars()) { out->writeText("EMC\n"); } );
-        GlyphPositioner glyphPositioner(out, paint.getTextSkewX(), offset);
-        SkPDFFont* font = nullptr;
+    const SkGlyphID maxGlyphID = SkToU16(typeface->countGlyphs() - 1);
 
-        while (SkClusterator::Cluster c = clusterator.next()) {
-            int index = c.fGlyphIndex;
-            int glyphLimit = index + c.fGlyphCount;
+    if (clusterator.reversedChars()) {
+        out->writeText("/ReversedChars BMC\n");
+    }
+    SK_AT_SCOPE_EXIT(if (clusterator.reversedChars()) { out->writeText("EMC\n"); } );
+    GlyphPositioner glyphPositioner(out, glyphRunFont.getSkewX(), offset);
+    SkPDFFont* font = nullptr;
 
-            bool actualText = false;
-            SK_AT_SCOPE_EXIT(if (actualText) {
-                                 glyphPositioner.flush();
-                                 out->writeText("EMC\n");
-                             });
-            if (c.fUtf8Text) {  // real cluster
-                // Check if `/ActualText` needed.
-                const char* textPtr = c.fUtf8Text;
-                const char* textEnd = c.fUtf8Text + c.fTextByteLength;
-                SkUnichar unichar = SkUTF::NextUTF8(&textPtr, textEnd);
-                if (unichar < 0) {
-                    return;
-                }
-                if (textPtr < textEnd ||                                  // more characters left
-                    glyphLimit > index + 1 ||                             // toUnicode wouldn't work
-                    unichar != map_glyph(glyphToUnicode, glyphs[index]))  // test single Unichar map
-                {
-                    glyphPositioner.flush();
-                    out->writeText("/Span<</ActualText <");
-                    SkPDFUtils::WriteUTF16beHex(out, 0xFEFF);  // U+FEFF = BYTE ORDER MARK
-                    // the BOM marks this text as UTF-16BE, not PDFDocEncoding.
-                    SkPDFUtils::WriteUTF16beHex(out, unichar);  // first char
-                    while (textPtr < textEnd) {
-                        unichar = SkUTF::NextUTF8(&textPtr, textEnd);
-                        if (unichar < 0) {
-                            break;
-                        }
-                        SkPDFUtils::WriteUTF16beHex(out, unichar);
-                    }
-                    out->writeText("> >> BDC\n");  // begin marked-content sequence
-                                                   // with an associated property list.
-                    actualText = true;
-                }
+    while (SkClusterator::Cluster c = clusterator.next()) {
+        int index = c.fGlyphIndex;
+        int glyphLimit = index + c.fGlyphCount;
+
+        bool actualText = false;
+        SK_AT_SCOPE_EXIT(if (actualText) {
+                             glyphPositioner.flush();
+                             out->writeText("EMC\n");
+                         });
+        if (c.fUtf8Text) {  // real cluster
+            // Check if `/ActualText` needed.
+            const char* textPtr = c.fUtf8Text;
+            const char* textEnd = c.fUtf8Text + c.fTextByteLength;
+            SkUnichar unichar = SkUTF::NextUTF8(&textPtr, textEnd);
+            if (unichar < 0) {
+                return;
             }
-            for (; index < glyphLimit; ++index) {
-                SkGlyphID gid = glyphs[index];
-                if (gid > maxGlyphID) {
+            if (textPtr < textEnd ||                                  // more characters left
+                glyphLimit > index + 1 ||                             // toUnicode wouldn't work
+                unichar != map_glyph(glyphToUnicode, glyphs[index]))  // test single Unichar map
+            {
+                glyphPositioner.flush();
+                out->writeText("/Span<</ActualText <");
+                SkPDFUtils::WriteUTF16beHex(out, 0xFEFF);  // U+FEFF = BYTE ORDER MARK
+                // the BOM marks this text as UTF-16BE, not PDFDocEncoding.
+                SkPDFUtils::WriteUTF16beHex(out, unichar);  // first char
+                while (textPtr < textEnd) {
+                    unichar = SkUTF::NextUTF8(&textPtr, textEnd);
+                    if (unichar < 0) {
+                        break;
+                    }
+                    SkPDFUtils::WriteUTF16beHex(out, unichar);
+                }
+                out->writeText("> >> BDC\n");  // begin marked-content sequence
+                                               // with an associated property list.
+                actualText = true;
+            }
+        }
+        for (; index < glyphLimit; ++index) {
+            SkGlyphID gid = glyphs[index];
+            if (gid > maxGlyphID) {
+                continue;
+            }
+            SkPoint xy = glyphRun.positions()[index];
+            // Do a glyph-by-glyph bounds-reject if positions are absolute.
+            SkRect glyphBounds = get_glyph_bounds_device_space(
+                    gid, glyphCache.get(), textScaleX, textScaleY,
+                    xy + offset, this->ctm());
+            if (glyphBounds.isEmpty()) {
+                if (!contains(clipStackBounds, {glyphBounds.x(), glyphBounds.y()})) {
                     continue;
                 }
-                SkPoint xy = glyphRun.positions()[index];
-                // Do a glyph-by-glyph bounds-reject if positions are absolute.
-                SkRect glyphBounds = get_glyph_bounds_device_space(
-                        gid, glyphCache.get(), textScaleX, textScaleY,
-                        xy + offset, this->ctm());
-                if (glyphBounds.isEmpty()) {
-                    if (!contains(clipStackBounds, {glyphBounds.x(), glyphBounds.y()})) {
-                        continue;
-                    }
-                } else {
-                    if (!clipStackBounds.intersects(glyphBounds)) {
-                        continue;  // reject glyphs as out of bounds
-                    }
+            } else {
+                if (!clipStackBounds.intersects(glyphBounds)) {
+                    continue;  // reject glyphs as out of bounds
                 }
-                if (needs_new_font(font, gid, glyphCache.get(), fontType)) {
-                    // Not yet specified font or need to switch font.
-                    font = SkPDFFont::GetFontResource(fDocument, glyphCache.get(), typeface, gid);
-                    SkASSERT(font);  // All preconditions for SkPDFFont::GetFontResource are met.
-                    SkPDFIndirectReference ref = font->indirectReference();
-                    fFontResources.add(ref);
-
-                    glyphPositioner.flush();
-                    glyphPositioner.setWideChars(font->multiByteGlyphs());
-                    SkPDFWriteResourceName(out, SkPDFResourceType::kFont, ref.fValue);
-                    out->writeText(" ");
-                    SkPDFUtils::AppendScalar(textSize, out);
-                    out->writeText(" Tf\n");
-
-                }
-                font->noteGlyphUsage(gid);
-                SkGlyphID encodedGlyph = font->multiByteGlyphs()
-                                       ? gid : font->glyphToPDFFontEncoding(gid);
-                SkScalar advance = advanceScale * glyphCache->getGlyphIDAdvance(gid).fAdvanceX;
-                glyphPositioner.writeGlyph(xy, advance, encodedGlyph);
             }
+            if (needs_new_font(font, gid, glyphCache.get(), fontType)) {
+                // Not yet specified font or need to switch font.
+                font = SkPDFFont::GetFontResource(fDocument, glyphCache.get(), typeface, gid);
+                SkASSERT(font);  // All preconditions for SkPDFFont::GetFontResource are met.
+                glyphPositioner.flush();
+                glyphPositioner.setWideChars(font->multiByteGlyphs());
+                SkPDFWriteResourceName(out, SkPDFResourceType::kFont,
+                                       add_resource(fFontResources, font->indirectReference()));
+                out->writeText(" ");
+                SkPDFUtils::AppendScalar(textSize, out);
+                out->writeText(" Tf\n");
+
+            }
+            font->noteGlyphUsage(gid);
+            SkGlyphID encodedGlyph = font->multiByteGlyphs()
+                                   ? gid : font->glyphToPDFFontEncoding(gid);
+            SkScalar advance = advanceScale * glyphCache->getGlyphIDAdvance(gid).fAdvanceX;
+            glyphPositioner.writeGlyph(xy, advance, encodedGlyph);
         }
     }
 }
 
 void SkPDFDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList) {
     for (const SkGlyphRun& glyphRun : glyphRunList) {
-        this->internalDrawGlyphRun(glyphRun, glyphRunList.origin());
+        this->internalDrawGlyphRun(glyphRun, glyphRunList.origin(), glyphRunList.paint());
     }
 }
 
@@ -1261,9 +1243,10 @@
     // TODO: implement drawVertices
 }
 
-void SkPDFDevice::drawFormXObject(sk_sp<SkPDFObject> xObject, SkDynamicMemoryWStream* content) {
+void SkPDFDevice::drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream* content) {
+    SkASSERT(xObject);
     SkPDFWriteResourceName(content, SkPDFResourceType::kXObject,
-                           find_or_add(&fXObjectResources, std::move(xObject)));
+                           add_resource(fXObjectResources, xObject));
     content->writeText(" Do\n");
 }
 
@@ -1321,18 +1304,20 @@
     return SkSurface::MakeRaster(info, &props);
 }
 
-
-sk_sp<SkPDFDict> SkPDFDevice::makeResourceDict() {
-    std::vector<SkPDFIndirectReference> fonts;
-    fonts.reserve(fFontResources.count());
-    fFontResources.foreach([&fonts](SkPDFIndirectReference ref) { fonts.push_back(ref); } );
-    fFontResources.reset();
-    std::sort(fonts.begin(), fonts.end(),
+static std::vector<SkPDFIndirectReference> sort(const SkTHashSet<SkPDFIndirectReference>& src) {
+    std::vector<SkPDFIndirectReference> dst;
+    dst.reserve(src.count());
+    src.foreach([&dst](SkPDFIndirectReference ref) { dst.push_back(ref); } );
+    std::sort(dst.begin(), dst.end(),
             [](SkPDFIndirectReference a, SkPDFIndirectReference b) { return a.fValue < b.fValue; });
-    return SkPDFMakeResourceDict(std::move(fGraphicStateResources),
-                                 std::move(fShaderResources),
-                                 std::move(fXObjectResources),
-                                 std::move(fonts));
+    return dst;
+}
+
+std::unique_ptr<SkPDFDict> SkPDFDevice::makeResourceDict() {
+    return SkPDFMakeResourceDict(sort(fGraphicStateResources),
+                                 sort(fShaderResources),
+                                 sort(fXObjectResources),
+                                 sort(fFontResources));
 }
 
 std::unique_ptr<SkStreamAsset> SkPDFDevice::content() {
@@ -1421,44 +1406,44 @@
     return true;
 }
 
-sk_sp<SkPDFArray> SkPDFDevice::getAnnotations() {
-    sk_sp<SkPDFArray> array;
+std::unique_ptr<SkPDFArray> SkPDFDevice::getAnnotations() {
+    std::unique_ptr<SkPDFArray> array;
     size_t count = fLinkToURLs.size() + fLinkToDestinations.size();
     if (0 == count) {
         return array;
     }
-    array = sk_make_sp<SkPDFArray>();
+    array = SkPDFMakeArray();
     array->reserve(count);
     for (const RectWithData& rectWithURL : fLinkToURLs) {
         SkRect r;
         fInitialTransform.mapRect(&r, rectWithURL.rect);
-        array->appendObjRef(create_link_to_url(rectWithURL.data.get(), r));
+        array->appendRef(fDocument->emit(*create_link_to_url(rectWithURL.data.get(), r)));
     }
     for (const RectWithData& linkToDestination : fLinkToDestinations) {
         SkRect r;
         fInitialTransform.mapRect(&r, linkToDestination.rect);
-        array->appendObjRef(
-                create_link_named_dest(linkToDestination.data.get(), r));
+        array->appendRef(
+                fDocument->emit(*create_link_named_dest(linkToDestination.data.get(), r)));
     }
     return array;
 }
 
-void SkPDFDevice::appendDestinations(SkPDFDict* dict, SkPDFObject* page) const {
+void SkPDFDevice::appendDestinations(SkPDFDict* dict, SkPDFIndirectReference page) const {
     for (const NamedDestination& dest : fNamedDestinations) {
-        auto pdfDest = sk_make_sp<SkPDFArray>();
-        pdfDest->reserve(5);
-        pdfDest->appendObjRef(sk_ref_sp(page));
-        pdfDest->appendName("XYZ");
         SkPoint p = fInitialTransform.mapXY(dest.point.x(), dest.point.y());
+        auto pdfDest = SkPDFMakeArray();
+        pdfDest->reserve(5);
+        pdfDest->appendRef(page);
+        pdfDest->appendName("XYZ");
         pdfDest->appendScalar(p.x());
         pdfDest->appendScalar(p.y());
         pdfDest->appendInt(0);  // Leave zoom unchanged
-        SkString name(static_cast<const char*>(dest.nameData->data()));
-        dict->insertObject(name, std::move(pdfDest));
+        dict->insertObject(SkString(static_cast<const char*>(dest.nameData->data())),
+                           std::move(pdfDest));
     }
 }
 
-sk_sp<SkPDFObject> SkPDFDevice::makeFormXObjectFromDevice(bool alpha) {
+SkPDFIndirectReference SkPDFDevice::makeFormXObjectFromDevice(bool alpha) {
     SkMatrix inverseTransform = SkMatrix::I();
     if (!fInitialTransform.isIdentity()) {
         if (!fInitialTransform.invert(&inverseTransform)) {
@@ -1468,8 +1453,8 @@
     }
     const char* colorSpace = alpha ? "DeviceGray" : nullptr;
 
-    sk_sp<SkPDFObject> xobject =
-        SkPDFMakeFormXObject(this->content(),
+    SkPDFIndirectReference xobject =
+        SkPDFMakeFormXObject(fDocument, this->content(),
                              SkPDFMakeArray(0, 0, this->width(), this->height()),
                              this->makeResourceDict(), inverseTransform, colorSpace);
     // We always draw the form xobjects that we create back into the device, so
@@ -1479,10 +1464,11 @@
     return xobject;
 }
 
-void SkPDFDevice::drawFormXObjectWithMask(sk_sp<SkPDFObject> xObject,
-                                          sk_sp<SkPDFObject> mask,
+void SkPDFDevice::drawFormXObjectWithMask(SkPDFIndirectReference xObject,
+                                          SkPDFIndirectReference sMask,
                                           SkBlendMode mode,
                                           bool invertClip) {
+    SkASSERT(sMask);
     SkPaint paint;
     paint.setBlendMode(mode);
     ScopedContentEntry content(this, nullptr, SkMatrix::I(), paint);
@@ -1490,9 +1476,9 @@
         return;
     }
     this->setGraphicState(SkPDFGraphicState::GetSMaskGraphicState(
-            std::move(mask), invertClip, SkPDFGraphicState::kAlpha_SMaskMode,
-            fDocument->canon()), content.stream());
-    this->drawFormXObject(std::move(xObject), content.stream());
+            sMask, invertClip, SkPDFGraphicState::kAlpha_SMaskMode,
+            fDocument), content.stream());
+    this->drawFormXObject(xObject, content.stream());
     this->clearMaskOnGraphicState(content.stream());
 }
 
@@ -1501,12 +1487,87 @@
     return nullptr != SkPDFUtils::BlendModeName(blendMode);
 }
 
+static void populate_graphic_state_entry_from_paint(
+        SkPDFDocument* doc,
+        const SkMatrix& matrix,
+        const SkClipStack* clipStack,
+        SkIRect deviceBounds,
+        const SkPaint& paint,
+        const SkMatrix& initialTransform,
+        SkScalar textScale,
+        SkPDFDevice::GraphicStateEntry* entry,
+        SkTHashSet<SkPDFIndirectReference>* shaderResources,
+        SkTHashSet<SkPDFIndirectReference>* graphicStateResources) {
+    NOT_IMPLEMENTED(paint.getPathEffect() != nullptr, false);
+    NOT_IMPLEMENTED(paint.getMaskFilter() != nullptr, false);
+    NOT_IMPLEMENTED(paint.getColorFilter() != nullptr, false);
+
+    entry->fMatrix = matrix;
+    entry->fClipStackGenID = clipStack ? clipStack->getTopmostGenID()
+                                       : SkClipStack::kWideOpenGenID;
+    SkColor4f color = paint.getColor4f();
+    entry->fColor = {color.fR, color.fG, color.fB, 1};
+    entry->fShaderIndex = -1;
+
+    // PDF treats a shader as a color, so we only set one or the other.
+    SkShader* shader = paint.getShader();
+    if (shader) {
+        if (SkShader::kColor_GradientType == shader->asAGradient(nullptr)) {
+            // We don't have to set a shader just for a color.
+            SkShader::GradientInfo gradientInfo;
+            SkColor gradientColor = SK_ColorBLACK;
+            gradientInfo.fColors = &gradientColor;
+            gradientInfo.fColorOffsets = nullptr;
+            gradientInfo.fColorCount = 1;
+            SkAssertResult(shader->asAGradient(&gradientInfo) == SkShader::kColor_GradientType);
+            color = SkColor4f::FromColor(gradientColor);
+            entry->fColor ={color.fR, color.fG, color.fB, 1};
+
+        } else {
+            // PDF positions patterns relative to the initial transform, so
+            // we need to apply the current transform to the shader parameters.
+            SkMatrix transform = matrix;
+            transform.postConcat(initialTransform);
+
+            // PDF doesn't support kClamp_TileMode, so we simulate it by making
+            // a pattern the size of the current clip.
+            SkRect clipStackBounds = clipStack ? clipStack->bounds(deviceBounds)
+                                               : SkRect::Make(deviceBounds);
+
+            // We need to apply the initial transform to bounds in order to get
+            // bounds in a consistent coordinate system.
+            initialTransform.mapRect(&clipStackBounds);
+            SkIRect bounds;
+            clipStackBounds.roundOut(&bounds);
+
+            SkPDFIndirectReference pdfShader
+                = SkPDFMakeShader(doc, shader, transform, bounds, paint.getColor());
+
+            if (pdfShader) {
+                // pdfShader has been canonicalized so we can directly compare pointers.
+                entry->fShaderIndex = add_resource(*shaderResources, pdfShader);
+            }
+        }
+    }
+
+    SkPDFIndirectReference newGraphicState;
+    if (color == paint.getColor4f()) {
+        newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(doc, paint);
+    } else {
+        SkPaint newPaint = paint;
+        newPaint.setColor4f(color, nullptr);
+        newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(doc, newPaint);
+    }
+    entry->fGraphicStateIndex = add_resource(*graphicStateResources, newGraphicState);
+    entry->fTextScaleX = textScale;
+}
+
 SkDynamicMemoryWStream* SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
                                                        const SkMatrix& matrix,
                                                        const SkPaint& paint,
-                                                       bool hasText,
-                                                       sk_sp<SkPDFObject>* dst) {
-    *dst = nullptr;
+                                                       SkScalar textScale,
+                                                       SkPDFIndirectReference* dst) {
+    SkASSERT(!*dst);
     SkBlendMode blendMode = paint.getBlendMode();
 
     // Dst xfer mode doesn't draw source at all.
@@ -1546,7 +1607,17 @@
     }
     SkASSERT(fActiveStackState.fContentStream);
     GraphicStateEntry entry;
-    this->populateGraphicStateEntryFromPaint(matrix, clipStack, paint, hasText, &entry);
+    populate_graphic_state_entry_from_paint(
+            fDocument,
+            matrix,
+            clipStack,
+            this->bounds(),
+            paint,
+            fInitialTransform,
+            textScale,
+            &entry,
+            &fShaderResources,
+            &fGraphicStateResources);
     fActiveStackState.updateClip(clipStack, this->bounds());
     fActiveStackState.updateMatrix(entry.fMatrix);
     fActiveStackState.updateDrawingState(entry);
@@ -1556,7 +1627,7 @@
 
 void SkPDFDevice::finishContentEntry(const SkClipStack* clipStack,
                                      SkBlendMode blendMode,
-                                     sk_sp<SkPDFObject> dst,
+                                     SkPDFIndirectReference dst,
                                      SkPath* shape) {
     SkASSERT(blendMode != SkBlendMode::kDst);
     if (treat_as_regular_pdf_blend_mode(blendMode)) {
@@ -1604,7 +1675,7 @@
 
     SkPaint stockPaint;
 
-    sk_sp<SkPDFObject> srcFormXObject;
+    SkPDFIndirectReference srcFormXObject;
     if (this->isContentEmpty()) {
         // If nothing was drawn and there's no shape, then the draw was a
         // no-op, but dst needs to be restored for that to be true.
@@ -1614,7 +1685,7 @@
         if (shape == nullptr || blendMode == SkBlendMode::kDstOut ||
                 blendMode == SkBlendMode::kSrcATop) {
             ScopedContentEntry content(this, nullptr, SkMatrix::I(), stockPaint);
-            this->drawFormXObject(std::move(dst), content.stream());
+            this->drawFormXObject(dst, content.stream());
             return;
         } else {
             blendMode = SkBlendMode::kClear;
@@ -1677,8 +1748,8 @@
     if (blendMode == SkBlendMode::kSrcIn ||
             blendMode == SkBlendMode::kSrcOut ||
             blendMode == SkBlendMode::kSrcATop) {
-        this->drawFormXObjectWithMask(std::move(srcFormXObject), std::move(dst),
-                                      SkBlendMode::kSrcOver, blendMode == SkBlendMode::kSrcOut);
+        this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver,
+                                      blendMode == SkBlendMode::kSrcOut);
         return;
     } else {
         SkBlendMode mode = SkBlendMode::kSrcOver;
@@ -1686,8 +1757,7 @@
             this->drawFormXObjectWithMask(srcFormXObject, dst, SkBlendMode::kSrcOver, false);
             mode = SkBlendMode::kMultiply;
         }
-        this->drawFormXObjectWithMask(std::move(dst), std::move(srcFormXObject), mode,
-                                      blendMode == SkBlendMode::kDstOut);
+        this->drawFormXObjectWithMask(dst, srcFormXObject, mode, blendMode == SkBlendMode::kDstOut);
         return;
     }
 }
@@ -1696,82 +1766,6 @@
     return fContent.bytesWritten() == 0 && fContentBuffer.bytesWritten() == 0;
 }
 
-void SkPDFDevice::populateGraphicStateEntryFromPaint(
-        const SkMatrix& matrix,
-        const SkClipStack* clipStack,
-        const SkPaint& paint,
-        bool hasText,
-        SkPDFDevice::GraphicStateEntry* entry) {
-    NOT_IMPLEMENTED(paint.getPathEffect() != nullptr, false);
-    NOT_IMPLEMENTED(paint.getMaskFilter() != nullptr, false);
-    NOT_IMPLEMENTED(paint.getColorFilter() != nullptr, false);
-
-    entry->fMatrix = matrix;
-    entry->fClipStackGenID = clipStack ? clipStack->getTopmostGenID()
-                                       : SkClipStack::kWideOpenGenID;
-    SkColor4f color = paint.getColor4f();
-    entry->fColor = {color.fR, color.fG, color.fB, 1};
-    entry->fShaderIndex = -1;
-
-    // PDF treats a shader as a color, so we only set one or the other.
-    sk_sp<SkPDFObject> pdfShader;
-    SkShader* shader = paint.getShader();
-    if (shader) {
-        if (SkShader::kColor_GradientType == shader->asAGradient(nullptr)) {
-            // We don't have to set a shader just for a color.
-            SkShader::GradientInfo gradientInfo;
-            SkColor gradientColor = SK_ColorBLACK;
-            gradientInfo.fColors = &gradientColor;
-            gradientInfo.fColorOffsets = nullptr;
-            gradientInfo.fColorCount = 1;
-            SkAssertResult(shader->asAGradient(&gradientInfo) == SkShader::kColor_GradientType);
-            color = SkColor4f::FromColor(gradientColor);
-            entry->fColor ={color.fR, color.fG, color.fB, 1};
-
-        } else {
-            // PDF positions patterns relative to the initial transform, so
-            // we need to apply the current transform to the shader parameters.
-            SkMatrix transform = matrix;
-            transform.postConcat(fInitialTransform);
-
-            // PDF doesn't support kClamp_TileMode, so we simulate it by making
-            // a pattern the size of the current clip.
-            SkRect clipStackBounds = clipStack ? clipStack->bounds(this->bounds())
-                                               : SkRect::Make(this->bounds());
-
-            // We need to apply the initial transform to bounds in order to get
-            // bounds in a consistent coordinate system.
-            fInitialTransform.mapRect(&clipStackBounds);
-            SkIRect bounds;
-            clipStackBounds.roundOut(&bounds);
-
-            pdfShader = SkPDFMakeShader(fDocument, shader, transform, bounds, paint.getColor());
-
-            if (pdfShader) {
-                // pdfShader has been canonicalized so we can directly compare pointers.
-                entry->fShaderIndex = find_or_add(&fShaderResources, std::move(pdfShader));
-            }
-        }
-    }
-
-    sk_sp<SkPDFDict> newGraphicState;
-    if (color == paint.getColor4f()) {
-        newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), paint);
-    } else {
-        SkPaint newPaint = paint;
-        newPaint.setColor4f(color, nullptr);
-        newGraphicState = SkPDFGraphicState::GetGraphicStateForPaint(fDocument->canon(), newPaint);
-    }
-    entry->fGraphicStateIndex = find_or_add(&fGraphicStateResources, std::move(newGraphicState));
-
-    if (hasText) {
-        entry->fTextScaleX = paint.getTextScaleX();
-        entry->fTextFill = paint.getStyle();
-    } else {
-        entry->fTextScaleX = 0;
-    }
-}
-
 static SkSize rect_to_size(const SkRect& r) { return {r.width(), r.height()}; }
 
 static sk_sp<SkImage> color_filter(const SkImage* image,
@@ -1800,15 +1794,6 @@
            is_integer(r.bottom());
 }
 
-namespace {
-// This struct will go away when fIndirectReference goes away.
-struct PDFObj final : public SkPDFObject {
-    PDFObj(SkPDFIndirectReference ref) { fIndirectReference = ref; }
-    // emitObject() is never called since the Object already has a indirect ref.
-    void emitObject(SkWStream*) const override { SK_ABORT("DO NOT REACH HERE"); }
-};
-} // namespace
-
 void SkPDFDevice::internalDrawImageRect(SkKeyedImage imageSubset,
                                         const SkRect* src,
                                         const SkRect& dst,
@@ -2032,18 +2017,17 @@
     }
 
     SkBitmapKey key = imageSubset.key();
-    sk_sp<SkPDFObject>* pdfimagePtr = fDocument->canon()->fPDFBitmapMap.find(key);
-    sk_sp<SkPDFObject> pdfimage = pdfimagePtr ? *pdfimagePtr : nullptr;
-    if (!pdfimage) {
+    SkPDFIndirectReference* pdfimagePtr = fDocument->canon()->fPDFBitmapMap.find(key);
+    SkPDFIndirectReference pdfimage = pdfimagePtr ? *pdfimagePtr : SkPDFIndirectReference();
+    if (!pdfimagePtr) {
         SkASSERT(imageSubset);
-        auto ref = SkPDFSerializeImage(imageSubset.image().get(), fDocument,
+        pdfimage = SkPDFSerializeImage(imageSubset.image().get(), fDocument,
                                        fDocument->metadata().fEncodingQuality);
-        SkASSERT(ref.fValue > 0);
-        pdfimage = sk_make_sp<PDFObj>(ref);
         SkASSERT((key != SkBitmapKey{{0, 0, 0, 0}, 0}));
         fDocument->canon()->fPDFBitmapMap.set(key, pdfimage);
     }
-    this->drawFormXObject(std::move(pdfimage), content.stream());
+    SkASSERT(pdfimage != SkPDFIndirectReference());
+    this->drawFormXObject(pdfimage, content.stream());
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/pdf/SkPDFDevice.h b/src/pdf/SkPDFDevice.h
index 47ea976..55897ed 100644
--- a/src/pdf/SkPDFDevice.h
+++ b/src/pdf/SkPDFDevice.h
@@ -14,6 +14,7 @@
 #include "SkClipStackDevice.h"
 #include "SkData.h"
 #include "SkKeyedImage.h"
+#include "SkPDFTypes.h"
 #include "SkPaint.h"
 #include "SkRect.h"
 #include "SkRefCnt.h"
@@ -33,7 +34,6 @@
 class SkPDFDict;
 class SkPDFFont;
 class SkPDFObject;
-class SkPDFStream;
 class SkRRect;
 struct SkPDFIndirectReference;
 
@@ -100,16 +100,16 @@
     // PDF specific methods.
 
     /** Create the resource dictionary for this device. Destructive. */
-    sk_sp<SkPDFDict> makeResourceDict();
+    std::unique_ptr<SkPDFDict> makeResourceDict();
 
     /** return annotations (link to urls and destinations) or nulltpr */
-    sk_sp<SkPDFArray> getAnnotations();
+    std::unique_ptr<SkPDFArray> getAnnotations();
 
     /** Add our named destinations to the supplied dictionary.
      *  @param dict  Dictionary to add destinations to.
      *  @param page  The PDF object representing the page for this device.
      */
-    void appendDestinations(SkPDFDict* dict, SkPDFObject* page) const;
+    void appendDestinations(SkPDFDict* dict, SkPDFIndirectReference page) const;
 
     /** Returns a SkStream with the page contents.
      */
@@ -167,9 +167,9 @@
     std::vector<RectWithData> fLinkToDestinations;
     std::vector<NamedDestination> fNamedDestinations;
 
-    std::vector<sk_sp<SkPDFObject>> fGraphicStateResources;
-    std::vector<sk_sp<SkPDFObject>> fXObjectResources;
-    std::vector<sk_sp<SkPDFObject>> fShaderResources;
+    SkTHashSet<SkPDFIndirectReference> fGraphicStateResources;
+    SkTHashSet<SkPDFIndirectReference> fXObjectResources;
+    SkTHashSet<SkPDFIndirectReference> fShaderResources;
     SkTHashSet<SkPDFIndirectReference> fFontResources;
     int fNodeId;
 
@@ -199,10 +199,10 @@
     SkBaseDevice* onCreateDevice(const CreateInfo&, const SkPaint*) override;
 
     // Set alpha to true if making a transparency group form x-objects.
-    sk_sp<SkPDFObject> makeFormXObjectFromDevice(bool alpha = false);
+    SkPDFIndirectReference makeFormXObjectFromDevice(bool alpha = false);
 
-    void drawFormXObjectWithMask(sk_sp<SkPDFObject> xObject,
-                                 sk_sp<SkPDFObject> mask,
+    void drawFormXObjectWithMask(SkPDFIndirectReference xObject,
+                                 SkPDFIndirectReference sMask,
                                  SkBlendMode,
                                  bool invertClip);
 
@@ -211,21 +211,15 @@
     // setUpContentEntry and finishContentEntry can be used directly, but
     // the preferred method is to use the ScopedContentEntry helper class.
     SkDynamicMemoryWStream* setUpContentEntry(const SkClipStack* clipStack,
-                                    const SkMatrix& matrix,
-                                    const SkPaint& paint,
-                                    bool hasText,
-                                    sk_sp<SkPDFObject>* dst);
-    void finishContentEntry(const SkClipStack*, SkBlendMode, sk_sp<SkPDFObject> dst, SkPath* shape);
+                                              const SkMatrix& matrix,
+                                              const SkPaint& paint,
+                                              SkScalar,
+                                              SkPDFIndirectReference* dst);
+    void finishContentEntry(const SkClipStack*, SkBlendMode, SkPDFIndirectReference, SkPath*);
     bool isContentEmpty();
 
-    void populateGraphicStateEntryFromPaint(const SkMatrix& matrix,
-                                            const SkClipStack* clipStack,
-                                            const SkPaint& paint,
-                                            bool hasText,
-                                            GraphicStateEntry* entry);
-
-    void internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offset);
-    void drawGlyphRunAsPath(const SkGlyphRun& glyphRun, SkPoint offset);
+    void internalDrawGlyphRun(const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint);
+    void drawGlyphRunAsPath(const SkGlyphRun& glyphRun, SkPoint offset, const SkPaint& runPaint);
 
     void internalDrawImageRect(SkKeyedImage,
                                const SkRect* src,
@@ -248,8 +242,8 @@
 
     void addSMaskGraphicState(sk_sp<SkPDFDevice> maskDevice, SkDynamicMemoryWStream*);
     void clearMaskOnGraphicState(SkDynamicMemoryWStream*);
-    void setGraphicState(sk_sp<SkPDFObject>, SkDynamicMemoryWStream*);
-    void drawFormXObject(sk_sp<SkPDFObject>, SkDynamicMemoryWStream*);
+    void setGraphicState(SkPDFIndirectReference gs, SkDynamicMemoryWStream*);
+    void drawFormXObject(SkPDFIndirectReference xObject, SkDynamicMemoryWStream*);
 
     bool hasEmptyClip() const { return this->cs().isEmpty(this->bounds()); }
 
diff --git a/src/pdf/SkPDFDocument.cpp b/src/pdf/SkPDFDocument.cpp
index d2bb292..8a472d7 100644
--- a/src/pdf/SkPDFDocument.cpp
+++ b/src/pdf/SkPDFDocument.cpp
@@ -24,33 +24,41 @@
     return key;
 }
 
-void SkPDFOffsetMap::set(SkPDFIndirectReference iRef, SkPDFFileOffset offset) {
-    SkASSERT(iRef.fValue > 0);
-    size_t index = SkToSizeT(iRef.fValue - 1);
-    if (index == fOffsets.size()) {
-        fOffsets.push_back(offset);
-    } else if (index < fOffsets.size()) {
-        fOffsets[index] = offset;
-    } else {
+////////////////////////////////////////////////////////////////////////////////
+
+void SkPDFOffsetMap::markStartOfDocument(const SkWStream* s) { fBaseOffset = s->bytesWritten(); }
+
+static size_t difference(size_t minuend, size_t subtrahend) {
+    return SkASSERT(minuend >= subtrahend), minuend - subtrahend;
+}
+
+void SkPDFOffsetMap::markStartOfObject(int referenceNumber, const SkWStream* s) {
+    SkASSERT(referenceNumber > 0);
+    size_t index = SkToSizeT(referenceNumber - 1);
+    if (index >= fOffsets.size()) {
         fOffsets.resize(index + 1);
-        fOffsets[index] = offset;
     }
+    fOffsets[index] = SkToInt(difference(s->bytesWritten(), fBaseOffset));
 }
 
-SkPDFFileOffset SkPDFOffsetMap::get(SkPDFIndirectReference r) {
-    return SkASSERT(r.fValue > 0),
-           SkASSERT(r.fValue <= (int)fOffsets.size()),
-           fOffsets[r.fValue - 1];
+int SkPDFOffsetMap::objectCount() const {
+    return SkToInt(fOffsets.size() + 1); // Include the special zeroth object in the count.
 }
 
-SkPDFObjectSerializer::SkPDFObjectSerializer() = default;
-
-SkPDFObjectSerializer::~SkPDFObjectSerializer() = default;
-
-SkPDFObjectSerializer::SkPDFObjectSerializer(SkPDFObjectSerializer&&) = default;
-
-SkPDFObjectSerializer& SkPDFObjectSerializer::operator=(SkPDFObjectSerializer&&) = default;
-
+int SkPDFOffsetMap::emitCrossReferenceTable(SkWStream* s) const {
+    int xRefFileOffset = SkToInt(difference(s->bytesWritten(), fBaseOffset));
+    s->writeText("xref\n0 ");
+    s->writeDecAsText(this->objectCount());
+    s->writeText("\n0000000000 65535 f \n");
+    for (int offset : fOffsets) {
+        SkASSERT(offset > 0);  // Offset was set.
+        s->writeBigDecAsText(offset, 10);
+        s->writeText(" 00000 n \n");
+    }
+    return xRefFileOffset;
+}
+//
+////////////////////////////////////////////////////////////////////////////////
 
 #define SKPDF_MAGIC "\xD3\xEB\xE9\xE1"
 #ifndef SK_BUILD_FOR_WIN
@@ -59,71 +67,40 @@
 static_assert((SKPDF_MAGIC[2] & 0x7F) == "Skia"[2], "");
 static_assert((SKPDF_MAGIC[3] & 0x7F) == "Skia"[3], "");
 #endif
-void SkPDFObjectSerializer::serializeHeader(SkWStream* wStream,
-                                            const SkPDF::Metadata& md) {
-    fBaseOffset = wStream->bytesWritten();
-    static const char kHeader[] = "%PDF-1.4\n%" SKPDF_MAGIC "\n";
-    wStream->writeText(kHeader);
+static void serializeHeader(SkPDFOffsetMap* offsetMap, SkWStream* wStream) {
+    offsetMap->markStartOfDocument(wStream);
+    wStream->writeText("%PDF-1.4\n%" SKPDF_MAGIC "\n");
     // The PDF spec recommends including a comment with four
     // bytes, all with their high bits set.  "\xD3\xEB\xE9\xE1" is
     // "Skia" with the high bits set.
-    fInfoDict = SkPDFMetadata::MakeDocumentInformationDict(md);
-    this->serializeObject(fInfoDict, wStream);
 }
 #undef SKPDF_MAGIC
 
-SkWStream* SkPDFObjectSerializer::beginObject(SkPDFIndirectReference ref, SkWStream* wStream) {
-    SkASSERT(ref.fValue > 0);
-    fOffsets.set(ref, this->offset(wStream));
-    wStream->writeDecAsText(ref.fValue);
-    wStream->writeText(" 0 obj\n");  // Generation number is always 0.
-    return wStream;
+static void begin_indirect_object(SkPDFOffsetMap* offsetMap,
+                                  SkPDFIndirectReference ref,
+                                  SkWStream* s) {
+    offsetMap->markStartOfObject(ref.fValue, s);
+    s->writeDecAsText(ref.fValue);
+    s->writeText(" 0 obj\n");  // Generation number is always 0.
 }
 
-void SkPDFObjectSerializer::endObject(SkWStream* wStream) {
-    wStream->writeText("\nendobj\n");
-}
-
-void SkPDFObjectSerializer::serializeObject(const sk_sp<SkPDFObject>& object,
-                                            SkWStream* wStream) {
-    SkPDFObjNumMap objNumMap;
-    objNumMap.fNextObjectNumber = fNextObjectNumber;
-    objNumMap.addObjectRecursively(object.get());
-
-    for (const sk_sp<SkPDFObject>& object : objNumMap.fObjects) {
-        this->beginObject(object->fIndirectReference, wStream);
-        object->emitObject(wStream);
-        this->endObject(wStream);
-        object->drop();
-    }
-    fNextObjectNumber = objNumMap.fNextObjectNumber; // save for later.
-}
-
+static void end_indirect_object(SkWStream* s) { s->writeText("\nendobj\n"); }
 
 // Xref table and footer
-void SkPDFObjectSerializer::serializeFooter(SkWStream* wStream,
-                                            const sk_sp<SkPDFObject> docCatalog,
-                                            sk_sp<SkPDFObject> id) {
-    int xRefFileOffset = this->offset(wStream).fValue;
-    // Include the special zeroth object in the count.
-
-    int objCount = SkToS32(fNextObjectNumber);
-    wStream->writeText("xref\n0 ");
-    wStream->writeDecAsText(objCount);
-    wStream->writeText("\n0000000000 65535 f \n");
-    for (int i = 1; i < objCount; ++i) {
-        SkASSERT(fOffsets.get(SkPDFIndirectReference{i}).fValue > 0);
-        wStream->writeBigDecAsText(fOffsets.get(SkPDFIndirectReference{i}).fValue, 10);
-        wStream->writeText(" 00000 n \n");
-    }
+static void serialize_footer(const SkPDFOffsetMap& offsetMap,
+                             SkWStream* wStream,
+                             SkPDFIndirectReference infoDict,
+                             SkPDFIndirectReference docCatalog,
+                             SkUUID uuid) {
+    int xRefFileOffset = offsetMap.emitCrossReferenceTable(wStream);
     SkPDFDict trailerDict;
-    trailerDict.insertInt("Size", objCount);
-    SkASSERT(docCatalog);
-    trailerDict.insertObjRef("Root", docCatalog);
-    SkASSERT(fInfoDict);
-    trailerDict.insertObjRef("Info", std::move(fInfoDict));
-    if (id) {
-        trailerDict.insertObject("ID", std::move(id));
+    trailerDict.insertInt("Size", offsetMap.objectCount());
+    SkASSERT(docCatalog != SkPDFIndirectReference());
+    trailerDict.insertRef("Root", docCatalog);
+    SkASSERT(infoDict != SkPDFIndirectReference());
+    trailerDict.insertRef("Info", infoDict);
+    if (SkUUID() != uuid) {
+        trailerDict.insertObject("ID", SkPDFMetadata::MakePdfId(uuid, uuid));
     }
     wStream->writeText("trailer\n");
     trailerDict.emitObject(wStream);
@@ -132,14 +109,10 @@
     wStream->writeText("\n%%EOF");
 }
 
-SkPDFFileOffset SkPDFObjectSerializer::offset(SkWStream* wStream) {
-    size_t offset = wStream->bytesWritten();
-    SkASSERT(fBaseOffset != SIZE_MAX);
-    SkASSERT(offset > fBaseOffset);
-    return SkPDFFileOffset{SkToInt(offset - fBaseOffset)};
-}
-
-static sk_sp<SkPDFDict> generate_page_tree(const std::vector<sk_sp<SkPDFDict>>& pages) {
+static SkPDFIndirectReference generate_page_tree(
+        SkPDFDocument* doc,
+        std::vector<std::unique_ptr<SkPDFDict>> pages,
+        const std::vector<SkPDFIndirectReference>& pageRefs) {
     // PDF wants a tree describing all the pages in the document.  We arbitrary
     // choose 8 (kNodeSize) as the number of allowed children.  The internal
     // nodes have type "Pages" with an array of children, a parent pointer, and
@@ -149,13 +122,14 @@
     // one child.
     SkASSERT(pages.size() > 0);
     struct PageTreeNode {
-        sk_sp<SkPDFDict> fNode;
+        std::unique_ptr<SkPDFDict> fNode;
+        SkPDFIndirectReference fReservedRef;
         int fPageObjectDescendantCount;
 
-        static void Layer(std::vector<PageTreeNode>* vec) {
+        static std::vector<PageTreeNode> Layer(std::vector<PageTreeNode> vec, SkPDFDocument* doc) {
             std::vector<PageTreeNode> result;
             static constexpr size_t kMaxNodeSize = 8;
-            const size_t n = vec->size();
+            const size_t n = vec.size();
             SkASSERT(n >= 1);
             const size_t result_len = (n - 1) / kMaxNodeSize + 1;
             SkASSERT(result_len >= 1);
@@ -164,36 +138,39 @@
             size_t index = 0;
             for (size_t i = 0; i < result_len; ++i) {
                 if (n != 1 && index + 1 == n) {  // No need to create a new node.
-                    result.push_back(std::move((*vec)[index++]));
+                    result.push_back(std::move(vec[index++]));
                     continue;
                 }
-                auto next = sk_make_sp<SkPDFDict>("Pages");
-                auto kids_list = sk_make_sp<SkPDFArray>();
+                SkPDFIndirectReference parent = doc->reserveRef();
+                auto kids_list = SkPDFMakeArray();
                 int descendantCount = 0;
                 for (size_t j = 0; j < kMaxNodeSize && index < n; ++j) {
-                    PageTreeNode& node = (*vec)[index++];
-                    node.fNode->insertObjRef("Parent", next);
-                    kids_list->appendObjRef(std::move(node.fNode));
+                    PageTreeNode& node = vec[index++];
+                    node.fNode->insertRef("Parent", parent);
+                    kids_list->appendRef(doc->emit(*node.fNode, node.fReservedRef));
                     descendantCount += node.fPageObjectDescendantCount;
                 }
+                auto next = SkPDFMakeDict("Pages");
                 next->insertInt("Count", descendantCount);
                 next->insertObject("Kids", std::move(kids_list));
-                result.push_back(PageTreeNode{std::move(next), descendantCount});
+                result.push_back(PageTreeNode{std::move(next), parent, descendantCount});
             }
-            *vec = result;
+            return result;
         }
     };
     std::vector<PageTreeNode> currentLayer;
     currentLayer.reserve(pages.size());
-    for (const sk_sp<SkPDFDict>& page : pages) {
-        currentLayer.push_back(PageTreeNode{page, 1});
+    SkASSERT(pages.size() == pageRefs.size());
+    for (size_t i = 0; i < pages.size(); ++i) {
+        currentLayer.push_back(PageTreeNode{std::move(pages[i]), pageRefs[i], 1});
     }
-    PageTreeNode::Layer(&currentLayer);
+    currentLayer = PageTreeNode::Layer(std::move(currentLayer), doc);
     while (currentLayer.size() > 1) {
-        PageTreeNode::Layer(&currentLayer);
+        currentLayer = PageTreeNode::Layer(std::move(currentLayer), doc);
     }
     SkASSERT(currentLayer.size() == 1);
-    return std::move(currentLayer[0].fNode);
+    const PageTreeNode& root = currentLayer[0];
+    return doc->emit(*root.fNode, root.fReservedRef);
 }
 
 template<typename T, typename... Args>
@@ -214,8 +191,9 @@
         fRasterScale        = fMetadata.fRasterDPI / kDpiForRasterScaleOne;
     }
     if (fMetadata.fStructureElementTreeRoot) {
-        fTagRoot = recursiveBuildTagTree(*fMetadata.fStructureElementTreeRoot, nullptr);
+        fTagTree.init(fMetadata.fStructureElementTreeRoot);
     }
+    fExecutor = metadata.fExecutor;
 }
 
 SkPDFDocument::~SkPDFDocument() {
@@ -223,30 +201,21 @@
     this->close();
 }
 
-SkPDFIndirectReference SkPDFDocument::serialize(const sk_sp<SkPDFObject>& object) {
-    SkASSERT(object);
-    fObjectSerializer.serializeObject(object, this->getStream());
-    return object->fIndirectReference;
-}
-
-SkPDFIndirectReference SkPDFDocument::emit(const SkPDFObject& object){
-    SkPDFIndirectReference ref = this->reserve();
+SkPDFIndirectReference SkPDFDocument::emit(const SkPDFObject& object, SkPDFIndirectReference ref){
     object.emitObject(this->beginObject(ref));
     this->endObject();
     return ref;
 }
 
-SkPDFIndirectReference SkPDFDocument::reserve() {
-    ++fOutstandingRefs;
-    return SkPDFIndirectReference{fObjectSerializer.fNextObjectNumber++};
+SkWStream* SkPDFDocument::beginObject(SkPDFIndirectReference ref) {
+    fMutex.acquire();
+    begin_indirect_object(&fOffsetMap, ref, this->getStream());
+    return this->getStream();
 };
 
-SkWStream* SkPDFDocument::beginObject(SkPDFIndirectReference ref) {
-    --fOutstandingRefs;
-    return fObjectSerializer.beginObject(ref, this->getStream());
-};
 void SkPDFDocument::endObject() {
-    fObjectSerializer.endObject(this->getStream());
+    end_indirect_object(this->getStream());
+    fMutex.release();
 };
 
 static SkSize operator*(SkISize u, SkScalar s) { return SkSize{u.width() * s, u.height() * s}; }
@@ -256,18 +225,21 @@
     SkASSERT(fCanvas.imageInfo().dimensions().isZero());
     if (fPages.empty()) {
         // if this is the first page if the document.
-        fObjectSerializer.serializeHeader(this->getStream(), fMetadata);
-        fDests = sk_make_sp<SkPDFDict>();
+        {
+            SkAutoMutexAcquire autoMutexAcquire(fMutex);
+            serializeHeader(&fOffsetMap, this->getStream());
+
+        }
+
+        fInfoDict = this->emit(*SkPDFMetadata::MakeDocumentInformationDict(fMetadata));
         if (fMetadata.fPDFA) {
-            SkPDFMetadata::UUID uuid = SkPDFMetadata::CreateUUID(fMetadata);
+            fUUID = SkPDFMetadata::CreateUUID(fMetadata);
             // We use the same UUID for Document ID and Instance ID since this
             // is the first revision of this document (and Skia does not
             // support revising existing PDF documents).
             // If we are not in PDF/A mode, don't use a UUID since testing
             // works best with reproducible outputs.
-            fID = SkPDFMetadata::MakePdfId(uuid, uuid);
-            fXMP = SkPDFMetadata::MakeXMPObject(fMetadata, uuid, uuid);
-            fObjectSerializer.serializeObject(fXMP, this->getStream());
+            fXMP = SkPDFMetadata::MakeXMPObject(fMetadata, fUUID, fUUID, this);
         }
     }
     // By scaling the page at the device level, we will create bitmap layer
@@ -283,6 +255,7 @@
     fPageDevice = sk_make_sp<SkPDFDevice>(pageSize, this, initialTransform);
     reset_object(&fCanvas, fPageDevice);
     fCanvas.scale(fRasterScale, fRasterScale);
+    fPageRefs.push_back(this->reserveRef());
     return &fCanvas;
 }
 
@@ -292,42 +265,44 @@
     reset_object(&fCanvas);
     SkASSERT(fPageDevice);
 
-    auto page = sk_make_sp<SkPDFDict>("Page");
+    auto page = SkPDFMakeDict("Page");
 
     SkSize mediaSize = fPageDevice->imageInfo().dimensions() * fInverseRasterScale;
-    auto contentObject = sk_make_sp<SkPDFStream>(fPageDevice->content());
+    std::unique_ptr<SkStreamAsset> pageContent = fPageDevice->content();
     auto resourceDict = fPageDevice->makeResourceDict();
     auto annotations = fPageDevice->getAnnotations();
-    fPageDevice->appendDestinations(fDests.get(), page.get());
+    SkASSERT(fPageRefs.size() > 0);
+    fPageDevice->appendDestinations(&fDests, fPageRefs.back());
     fPageDevice = nullptr;
 
-    page->insertObject("Resources", resourceDict);
+    page->insertObject("Resources", std::move(resourceDict));
     page->insertObject("MediaBox", SkPDFUtils::RectToArray(SkRect::MakeSize(mediaSize)));
 
     if (annotations) {
         page->insertObject("Annots", std::move(annotations));
     }
-    this->serialize(contentObject);
-    page->insertObjRef("Contents", std::move(contentObject));
+    page->insertRef("Contents", SkPDFStreamOut(nullptr, std::move(pageContent), this));
     // The StructParents unique identifier for each page is just its
     // 0-based page index.
-    page->insertInt("StructParents", static_cast<int>(fPages.size()));
+    page->insertInt("StructParents", SkToInt(this->currentPageIndex()));
     fPages.emplace_back(std::move(page));
 }
 
 void SkPDFDocument::onAbort() {
+    this->waitForJobs();
     this->reset();
 }
 
 void SkPDFDocument::reset() {
-    fObjectSerializer = SkPDFObjectSerializer();
+    reset_object(&fOffsetMap);
     fCanon = SkPDFCanon();
     reset_object(&fCanvas);
-    fPages = std::vector<sk_sp<SkPDFDict>>();
-    fDests = nullptr;
+    fPages = std::vector<std::unique_ptr<SkPDFDict>>();
+    fPageRefs = std::vector<SkPDFIndirectReference>();
+    reset_object(&fDests);
     fPageDevice = nullptr;
-    fID = nullptr;
-    fXMP = nullptr;
+    fUUID = SkUUID();
+    fXMP = SkPDFIndirectReference();
     fMetadata = SkPDF::Metadata();
     fRasterScale = 1;
     fInverseRasterScale = 1;
@@ -447,71 +422,34 @@
     return SkData::MakeWithoutCopy(kProfile, kProfileLength);
 }
 
-static sk_sp<SkPDFStream> make_srgb_color_profile() {
-    sk_sp<SkPDFStream> stream = sk_make_sp<SkPDFStream>(SkSrgbIcm());
-    stream->dict()->insertInt("N", 3);
-    stream->dict()->insertObject("Range", SkPDFMakeArray(0, 1, 0, 1, 0, 1));
-    return stream;
+static SkPDFIndirectReference make_srgb_color_profile(SkPDFDocument* doc) {
+    std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
+    dict->insertInt("N", 3);
+    dict->insertObject("Range", SkPDFMakeArray(0, 1, 0, 1, 0, 1));
+    return SkPDFStreamOut(std::move(dict), SkMemoryStream::Make(SkSrgbIcm()), doc, true);
 }
 
-static sk_sp<SkPDFArray> make_srgb_output_intents() {
+static std::unique_ptr<SkPDFArray> make_srgb_output_intents(SkPDFDocument* doc) {
     // sRGB is specified by HTML, CSS, and SVG.
-    auto outputIntent = sk_make_sp<SkPDFDict>("OutputIntent");
+    auto outputIntent = SkPDFMakeDict("OutputIntent");
     outputIntent->insertName("S", "GTS_PDFA1");
     outputIntent->insertString("RegistryName", "http://www.color.org");
     outputIntent->insertString("OutputConditionIdentifier",
                                "Custom");
     outputIntent->insertString("Info","sRGB IEC61966-2.1");
-    outputIntent->insertObjRef("DestOutputProfile",
-                               make_srgb_color_profile());
-    auto intentArray = sk_make_sp<SkPDFArray>();
+    outputIntent->insertRef("DestOutputProfile", make_srgb_color_profile(doc));
+    auto intentArray = SkPDFMakeArray();
     intentArray->appendObject(std::move(outputIntent));
     return intentArray;
 }
 
-static sk_sp<SkPDFDict> make_top_resource_dict() {
-    sk_sp<SkPDFDict> dict = sk_make_sp<SkPDFDict>();
-    sk_sp<SkPDFArray> procSet = sk_make_sp<SkPDFArray>();
-    static const char* kProcs[] = {"PDF", "Text", "ImageB", "ImageC", "ImageI"};
-    procSet->reserve(SK_ARRAY_COUNT(kProcs));
-    for (const char* proc : kProcs) {
-        procSet->appendName(proc);
-    }
-    dict->insertObject("ProcSet", std::move(procSet));
-    return dict;
-}
-
-sk_sp<SkPDFDict> SkPDFDocument::getPage(int pageIndex) const {
-    SkASSERT(pageIndex >= 0 && pageIndex < static_cast<int>(fPages.size()));
-    return fPages[pageIndex];
+SkPDFIndirectReference SkPDFDocument::getPage(size_t pageIndex) const {
+    SkASSERT(pageIndex < fPageRefs.size());
+    return fPageRefs[pageIndex];
 }
 
 int SkPDFDocument::getMarkIdForNodeId(int nodeId) {
-    sk_sp<SkPDFTag>* tagPtr = fNodeIdToTag.find(nodeId);
-    if (tagPtr == nullptr) {
-        return -1;
-    }
-
-    sk_sp<SkPDFTag> tag = *tagPtr;
-    int pageIndex = static_cast<int>(fPages.size());
-    while (fMarksPerPage.count() < pageIndex + 1) {
-        fMarksPerPage.push_back();
-    }
-    int markId = fMarksPerPage[pageIndex].count();
-    tag->addMarkedContent(pageIndex, markId);
-    fMarksPerPage[pageIndex].push_back(std::move(tag));
-    return markId;
-}
-
-sk_sp<SkPDFTag> SkPDFDocument::recursiveBuildTagTree(
-        const SkPDF::StructureElementNode& node, sk_sp<SkPDFTag> parent) {
-    sk_sp<SkPDFTag> tag = sk_make_sp<SkPDFTag>(node.fNodeId, node.fType, parent);
-    fNodeIdToTag.set(tag->fNodeId, tag);
-    tag->fChildren.reserve(node.fChildCount);
-    for (size_t i = 0; i < node.fChildCount; i++) {
-        tag->appendChild(recursiveBuildTagTree(node.fChildren[i], tag));
-    }
-    return tag;
+    return fTagTree.getMarkIdForNodeId(nodeId, SkToUInt(this->currentPageIndex()));
 }
 
 static std::vector<const SkPDFFont*> get_fonts(const SkPDFCanon& canon) {
@@ -528,80 +466,61 @@
 void SkPDFDocument::onClose(SkWStream* stream) {
     SkASSERT(fCanvas.imageInfo().dimensions().isZero());
     if (fPages.empty()) {
+        this->waitForJobs();
         this->reset();
         return;
     }
-    auto docCatalog = sk_make_sp<SkPDFDict>("Catalog");
+    auto docCatalog = SkPDFMakeDict("Catalog");
     if (fMetadata.fPDFA) {
-        SkASSERT(fXMP);
-        docCatalog->insertObjRef("Metadata", fXMP);
+        SkASSERT(fXMP != SkPDFIndirectReference());
+        docCatalog->insertRef("Metadata", fXMP);
         // Don't specify OutputIntents if we are not in PDF/A mode since
         // no one has ever asked for this feature.
-        docCatalog->insertObject("OutputIntents", make_srgb_output_intents());
+        docCatalog->insertObject("OutputIntents", make_srgb_output_intents(this));
     }
 
-    sk_sp<SkPDFDict> pageTree = generate_page_tree(fPages);
-    pageTree->insertObject("Resources", make_top_resource_dict());
-    docCatalog->insertObjRef("Pages", std::move(pageTree));
-    if (fDests->size() > 0) {
-        docCatalog->insertObjRef("Dests", std::move(fDests));
+    docCatalog->insertRef("Pages", generate_page_tree(this, std::move(fPages), fPageRefs));
+
+    if (fDests.size() > 0) {
+        docCatalog->insertRef("Dests", this->emit(fDests));
+        reset_object(&fDests);
     }
 
     // Handle tagged PDFs.
-    if (fTagRoot) {
+    if (SkPDFIndirectReference root = fTagTree.makeStructTreeRoot(this)) {
         // In the document catalog, indicate that this PDF is tagged.
-        auto markInfo = sk_make_sp<SkPDFDict>("MarkInfo");
+        auto markInfo = SkPDFMakeDict("MarkInfo");
         markInfo->insertBool("Marked", true);
-        docCatalog->insertObject("MarkInfo", markInfo);
-
-        // Prepare the tag tree, this automatically skips over any
-        // tags that weren't referenced from any marked content.
-        bool success = fTagRoot->prepareTagTreeToEmit(*this);
-        if (!success) {
-            SkDEBUGFAIL("PDF has tag tree but no marked content.");
-        }
-
-        // Build the StructTreeRoot.
-        auto structTreeRoot = sk_make_sp<SkPDFDict>("StructTreeRoot");
-        docCatalog->insertObjRef("StructTreeRoot", structTreeRoot);
-        structTreeRoot->insertObjRef("K", fTagRoot);
-        int pageCount = static_cast<int>(fPages.size());
-        structTreeRoot->insertInt("ParentTreeNextKey", pageCount);
-
-        // The parent of the tag root is the StructTreeRoot.
-        fTagRoot->insertObjRef("P", structTreeRoot);
-
-        // Build the parent tree, which is a mapping from the marked
-        // content IDs on each page to their corressponding tags.
-        auto parentTree = sk_make_sp<SkPDFDict>("ParentTree");
-        structTreeRoot->insertObjRef("ParentTree", parentTree);
-        structTreeRoot->insertInt("ParentTreeNextKey", pageCount);
-        auto parentTreeNums = sk_make_sp<SkPDFArray>();
-        parentTree->insertObject("Nums", parentTreeNums);
-        for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
-            // Exit now if there are no more pages with marked content.
-            if (fMarksPerPage.count() <= pageIndex) {
-                break;
-            }
-
-            parentTreeNums->appendInt(pageIndex);
-            auto markToTagArray = sk_make_sp<SkPDFArray>();
-            parentTreeNums->appendObjRef(markToTagArray);
-
-            for (int i = 0; i < fMarksPerPage[pageIndex].count(); i++) {
-                markToTagArray->appendObjRef(fMarksPerPage[pageIndex][i]);
-            }
-        }
+        docCatalog->insertObject("MarkInfo", std::move(markInfo));
+        docCatalog->insertRef("StructTreeRoot", root);
     }
-    fObjectSerializer.serializeObject(docCatalog, this->getStream());
+
+    auto docCatalogRef = this->emit(*docCatalog);
+
     for (const SkPDFFont* f : get_fonts(fCanon)) {
         f->emitSubset(this);
     }
-    SkASSERT(fOutstandingRefs == 0);
-    fObjectSerializer.serializeFooter(this->getStream(), docCatalog, fID);
+
+    this->waitForJobs();
+    {
+        SkAutoMutexAcquire autoMutexAcquire(fMutex);
+        serialize_footer(fOffsetMap, this->getStream(), fInfoDict, docCatalogRef, fUUID);
+    }
     this->reset();
 }
 
+void SkPDFDocument::incrementJobCount() { fJobCount++; }
+
+void SkPDFDocument::signalJobComplete() { fSemaphore.signal(); }
+
+void SkPDFDocument::waitForJobs() {
+     // fJobCount can increase while we wait.
+     while (fJobCount > 0) {
+         fSemaphore.wait();
+         --fJobCount;
+     }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 void SkPDF::SetNodeId(SkCanvas* canvas, int nodeID) {
diff --git a/src/pdf/SkPDFDocumentPriv.h b/src/pdf/SkPDFDocumentPriv.h
index c23e026..8b58aac 100644
--- a/src/pdf/SkPDFDocumentPriv.h
+++ b/src/pdf/SkPDFDocumentPriv.h
@@ -8,47 +8,31 @@
 #define SkPDFDocumentPriv_DEFINED
 
 #include "SkCanvas.h"
-#include "SkPDFDocument.h"
 #include "SkPDFCanon.h"
+#include "SkPDFDocument.h"
 #include "SkPDFFont.h"
 #include "SkPDFMetadata.h"
+#include "SkPDFTag.h"
+#include "SkUUID.h"
+
+#include <atomic>
 
 class SkPDFDevice;
-class SkPDFTag;
+class SkExecutor;
 
 const char* SkPDFGetNodeIdKey();
 
-struct SkPDFFileOffset {
-    int fValue;
-};
-
-struct SkPDFOffsetMap {
-    void set(SkPDFIndirectReference, SkPDFFileOffset);
-    SkPDFFileOffset get(SkPDFIndirectReference r);
-    std::vector<SkPDFFileOffset> fOffsets;
-};
-
 // Logically part of SkPDFDocument (like SkPDFCanon), but separate to
 // keep similar functionality together.
-struct SkPDFObjectSerializer {
-    int fNextObjectNumber = 1;
-    SkPDFOffsetMap fOffsets;
-    sk_sp<SkPDFObject> fInfoDict;
+class SkPDFOffsetMap {
+public:
+    void markStartOfDocument(const SkWStream*);
+    void markStartOfObject(int referenceNumber, const SkWStream*);
+    int objectCount() const;
+    int emitCrossReferenceTable(SkWStream* s) const;
+private:
+    std::vector<int> fOffsets;
     size_t fBaseOffset = SIZE_MAX;
-
-    SkPDFObjectSerializer();
-    ~SkPDFObjectSerializer();
-    SkPDFObjectSerializer(SkPDFObjectSerializer&&);
-    SkPDFObjectSerializer& operator=(SkPDFObjectSerializer&&);
-    SkPDFObjectSerializer(const SkPDFObjectSerializer&) = delete;
-    SkPDFObjectSerializer& operator=(const SkPDFObjectSerializer&) = delete;
-
-    SkWStream* beginObject(SkPDFIndirectReference, SkWStream*);
-    void endObject(SkWStream*);
-    void serializeHeader(SkWStream*, const SkPDF::Metadata&);
-    void serializeObject(const sk_sp<SkPDFObject>&, SkWStream*);
-    void serializeFooter(SkWStream*, const sk_sp<SkPDFObject>, sk_sp<SkPDFObject>);
-    SkPDFFileOffset offset(SkWStream*);
 };
 
 /** Concrete implementation of SkDocument that creates PDF files. This
@@ -72,47 +56,51 @@
        It might go without saying that objects should not be changed
        after calling serialize, since those changes will be too late.
      */
-    SkPDFIndirectReference serialize(const sk_sp<SkPDFObject>&);
-    SkPDFIndirectReference emit(const SkPDFObject&);
+    SkPDFIndirectReference emit(const SkPDFObject&, SkPDFIndirectReference);
+    SkPDFIndirectReference emit(const SkPDFObject& o) { return this->emit(o, this->reserveRef()); }
     SkPDFCanon* canon() { return &fCanon; }
     const SkPDF::Metadata& metadata() const { return fMetadata; }
 
-    sk_sp<SkPDFDict> getPage(int pageIndex) const;
+    SkPDFIndirectReference getPage(size_t pageIndex) const;
     // Returns -1 if no mark ID.
     int getMarkIdForNodeId(int nodeId);
 
-    SkPDFIndirectReference reserve();
+    SkPDFIndirectReference reserveRef() { return SkPDFIndirectReference{fNextObjectNumber++}; }
     SkWStream* beginObject(SkPDFIndirectReference);
     void endObject();
 
-private:
-    sk_sp<SkPDFTag> recursiveBuildTagTree(const SkPDF::StructureElementNode& node,
-                                          sk_sp<SkPDFTag> parent);
+    SkExecutor* executor() const { return fExecutor; }
+    void incrementJobCount();
+    void signalJobComplete();
+    size_t currentPageIndex() { return fPages.size(); }
+    size_t pageCount() { return fPageRefs.size(); }
 
-    SkPDFObjectSerializer fObjectSerializer;
+private:
+    SkPDFOffsetMap fOffsetMap;
     SkPDFCanon fCanon;
     SkCanvas fCanvas;
-    std::vector<sk_sp<SkPDFDict>> fPages;
-    sk_sp<SkPDFDict> fDests;
+    std::vector<std::unique_ptr<SkPDFDict>> fPages;
+    std::vector<SkPDFIndirectReference> fPageRefs;
+    SkPDFDict fDests;
     sk_sp<SkPDFDevice> fPageDevice;
-    sk_sp<SkPDFObject> fID;
-    sk_sp<SkPDFObject> fXMP;
+    std::atomic<int> fNextObjectNumber = {1};
+    std::atomic<int> fJobCount = {0};
+    SkUUID fUUID;
+    SkPDFIndirectReference fInfoDict;
+    SkPDFIndirectReference fXMP;
     SkPDF::Metadata fMetadata;
     SkScalar fRasterScale = 1;
     SkScalar fInverseRasterScale = 1;
+    SkExecutor* fExecutor = nullptr;
 
     // For tagged PDFs.
+    SkPDFTagTree fTagTree;
 
-    // The tag root, which owns its child tags and so on.
-    sk_sp<SkPDFTag> fTagRoot;
-    // Array of page -> array of marks mapping to tags.
-    SkTArray<SkTArray<sk_sp<SkPDFTag>>> fMarksPerPage;
-    // A mapping from node ID to tag for fast lookup.
-    SkTHashMap<int, sk_sp<SkPDFTag>> fNodeIdToTag;
-
-    int fOutstandingRefs = 0;
+    SkMutex fMutex;
+    SkSemaphore fSemaphore;
 
     void reset();
+    void waitForJobs();
 };
 
 #endif  // SkPDFDocumentPriv_DEFINED
diff --git a/src/pdf/SkPDFFont.cpp b/src/pdf/SkPDFFont.cpp
index d3f3457..71696d0 100644
--- a/src/pdf/SkPDFFont.cpp
+++ b/src/pdf/SkPDFFont.cpp
@@ -123,17 +123,18 @@
     }
 
     if (0 == metrics->fStemV || 0 == metrics->fCapHeight) {
-        SkPaint tmpPaint;
-        tmpPaint.setHinting(kNo_SkFontHinting);
-        tmpPaint.setTypeface(sk_ref_sp(typeface));
-        tmpPaint.setTextSize(1000);  // glyph coordinate system
+        SkFont font;
+        font.setHinting(kNo_SkFontHinting);
+        font.setTypeface(sk_ref_sp(typeface));
+        font.setSize(1000);  // glyph coordinate system
         if (0 == metrics->fStemV) {
             // Figure out a good guess for StemV - Min width of i, I, !, 1.
             // This probably isn't very good with an italic font.
             int16_t stemV = SHRT_MAX;
             for (char c : {'i', 'I', '!', '1'}) {
+                uint16_t g = font.unicharToGlyph(c);
                 SkRect bounds;
-                tmpPaint.measureText(&c, 1, &bounds);
+                font.getBounds(&g, 1, &bounds, nullptr);
                 stemV = SkTMin(stemV, SkToS16(SkScalarRoundToInt(bounds.width())));
             }
             metrics->fStemV = stemV;
@@ -142,8 +143,9 @@
             // Figure out a good guess for CapHeight: average the height of M and X.
             SkScalar capHeight = 0;
             for (char c : {'M', 'X'}) {
+                uint16_t g = font.unicharToGlyph(c);
                 SkRect bounds;
-                tmpPaint.measureText(&c, 1, &bounds);
+                font.getBounds(&g, 1, &bounds, nullptr);
                 capHeight += bounds.height();
             }
             metrics->fCapHeight = SkToS16(SkScalarRoundToInt(capHeight / 2));
@@ -223,7 +225,7 @@
         firstNonZeroGlyph = subsetCode;
         lastGlyph = SkToU16(SkTMin<int>((int)lastGlyph, 254 + (int)subsetCode));
     }
-    auto ref = doc->reserve();
+    auto ref = doc->reserveRef();
     return canon->fFontMap.set(
             fontID, SkPDFFont(std::move(typeface), firstNonZeroGlyph, lastGlyph, type, ref));
 }
@@ -297,7 +299,7 @@
     SkTypeface* face = font.typeface();
     SkASSERT(face);
 
-    auto descriptor = sk_make_sp<SkPDFDict>("FontDescriptor");
+    auto descriptor = SkPDFMakeDict("FontDescriptor");
     uint16_t emSize = SkToU16(font.typeface()->getUnitsPerEm());
     add_common_font_descriptor_entries(descriptor.get(), metrics, emSize , 0);
 
@@ -319,11 +321,13 @@
                             stream_to_data(std::move(fontAsset)), font.glyphUsage(),
                             metrics.fFontName.c_str(), ttcIndex);
                     if (subsetFontData) {
-                        size_t len = subsetFontData->size();
-                        sk_sp<SkPDFStream> subsetStream = sk_make_sp<SkPDFStream>(
-                                std::move(subsetFontData));
-                        subsetStream->dict()->insertInt("Length1", SkToInt(len));
-                        descriptor->insertRef("FontFile2", doc->serialize(subsetStream));
+                        std::unique_ptr<SkPDFDict> tmp = SkPDFMakeDict();
+                        tmp->insertInt("Length1", SkToInt(subsetFontData->size()));
+                        descriptor->insertRef(
+                                "FontFile2",
+                                SkPDFStreamOut(std::move(tmp),
+                                               SkMemoryStream::Make(std::move(subsetFontData)),
+                                               doc, true));
                         break;
                     }
                     // If subsetting fails, fall back to original font data.
@@ -333,15 +337,19 @@
                     if (!fontAsset || fontAsset->getLength() == 0) { break; }
                 }
                 #endif  // SK_PDF_SUBSET_SUPPORTED
-                auto fontStream = sk_make_sp<SkPDFSharedStream>(std::move(fontAsset));
-                fontStream->dict()->insertInt("Length1", fontSize);
-                descriptor->insertRef("FontFile2", doc->serialize(fontStream));
+                std::unique_ptr<SkPDFDict> tmp = SkPDFMakeDict();
+                tmp->insertInt("Length1", fontSize);
+                descriptor->insertRef("FontFile2",
+                                      SkPDFStreamOut(std::move(tmp), std::move(fontAsset),
+                                                     doc, true));
                 break;
             }
             case SkAdvancedTypefaceMetrics::kType1CID_Font: {
-                auto fontStream = sk_make_sp<SkPDFSharedStream>(std::move(fontAsset));
-                fontStream->dict()->insertName("Subtype", "CIDFontType0C");
-                descriptor->insertRef("FontFile3", doc->serialize(fontStream));
+                std::unique_ptr<SkPDFDict> tmp = SkPDFMakeDict();
+                tmp->insertName("Subtype", "CIDFontType0C");
+                descriptor->insertRef("FontFile3",
+                                      SkPDFStreamOut(std::move(tmp), std::move(fontAsset),
+                                                     doc, true));
                 break;
             }
             default:
@@ -349,8 +357,8 @@
         }
     }
 
-    auto newCIDFont = sk_make_sp<SkPDFDict>("Font");
-    newCIDFont->insertRef("FontDescriptor", doc->serialize(descriptor));
+    auto newCIDFont = SkPDFMakeDict("Font");
+    newCIDFont->insertRef("FontDescriptor", doc->emit(*descriptor));
     newCIDFont->insertName("BaseFont", metrics.fPostScriptName);
 
     switch (type) {
@@ -364,7 +372,7 @@
         default:
             SkASSERT(false);
     }
-    auto sysInfo = sk_make_sp<SkPDFDict>();
+    auto sysInfo = SkPDFMakeDict();
     sysInfo->insertString("Registry", "Adobe");
     sysInfo->insertString("Ordering", "Identity");
     sysInfo->insertInt("Supplement", 0);
@@ -374,7 +382,7 @@
     {
         int emSize;
         auto glyphCache = SkPDFFont::MakeVectorCache(face, &emSize);
-        sk_sp<SkPDFArray> widths = SkPDFMakeCIDGlyphWidthsArray(
+        std::unique_ptr<SkPDFArray> widths = SkPDFMakeCIDGlyphWidthsArray(
                 glyphCache.get(), &font.glyphUsage(), SkToS16(emSize), &defaultWidth);
         if (widths && widths->size() > 0) {
             newCIDFont->insertObject("W", std::move(widths));
@@ -389,24 +397,22 @@
     fontDict.insertName("Subtype", "Type0");
     fontDict.insertName("BaseFont", metrics.fPostScriptName);
     fontDict.insertName("Encoding", "Identity-H");
-    auto descendantFonts = sk_make_sp<SkPDFArray>();
-    descendantFonts->appendRef(doc->serialize(newCIDFont));
+    auto descendantFonts = SkPDFMakeArray();
+    descendantFonts->appendRef(doc->emit(*newCIDFont));
     fontDict.insertObject("DescendantFonts", std::move(descendantFonts));
 
     const std::vector<SkUnichar>& glyphToUnicode =
         SkPDFFont::GetUnicodeMap(font.typeface(), canon);
     SkASSERT(SkToSizeT(font.typeface()->countGlyphs()) == glyphToUnicode.size());
-    fontDict.insertRef("ToUnicode",
-                    doc->serialize(
-                       SkPDFMakeToUnicodeCmap(glyphToUnicode.data(),
-                                              &font.glyphUsage(),
-                                              font.multiByteGlyphs(),
-                                              font.firstGlyphID(),
-                                              font.lastGlyphID())));
+    std::unique_ptr<SkStreamAsset> toUnicode =
+            SkPDFMakeToUnicodeCmap(glyphToUnicode.data(),
+                                   &font.glyphUsage(),
+                                   font.multiByteGlyphs(),
+                                   font.firstGlyphID(),
+                                   font.lastGlyphID());
+    fontDict.insertRef("ToUnicode", SkPDFStreamOut(nullptr, std::move(toUnicode), doc));
 
-    SkWStream* stream = doc->beginObject(font.indirectReference());
-    fontDict.emitObject(stream);
-    doc->endObject();
+    doc->emit(fontDict, font.indirectReference());
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -429,11 +435,13 @@
             sk_sp<SkData> fontData = SkPDFConvertType1FontStream(std::move(rawFontData),
                                                                  &header, &data, &trailer);
             if (fontData) {
-                SkPDFStream fontStream(std::move(fontData));
-                fontStream.dict()->insertInt("Length1", header);
-                fontStream.dict()->insertInt("Length2", data);
-                fontStream.dict()->insertInt("Length3", trailer);
-                descriptor.insertRef("FontFile", doc->emit(fontStream));
+                std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
+                dict->insertInt("Length1", header);
+                dict->insertInt("Length2", data);
+                dict->insertInt("Length3", trailer);
+                auto fontStream = SkMemoryStream::Make(std::move(fontData));
+                descriptor.insertRef("FontFile", SkPDFStreamOut(std::move(dict),
+                                                                std::move(fontStream), doc, true));
             }
         }
     }
@@ -488,7 +496,7 @@
     {
         int emSize;
         auto glyphCache = SkPDFFont::MakeVectorCache(typeface, &emSize);
-        auto widths = sk_make_sp<SkPDFArray>();
+        auto widths = SkPDFMakeArray();
         SkScalar advance = glyphCache->getGlyphIDAdvance(0).fAdvanceX;
         widths->appendScalar(from_font_units(advance, SkToU16(emSize)));
         for (unsigned gID = firstGlyphID; gID <= lastGlyphID; gID++) {
@@ -497,7 +505,7 @@
         }
         font.insertObject("Widths", std::move(widths));
     }
-    auto encDiffs = sk_make_sp<SkPDFArray>();
+    auto encDiffs = SkPDFMakeArray();
     encDiffs->reserve(lastGlyphID - firstGlyphID + 3);
     encDiffs->appendInt(0);
 
@@ -508,13 +516,11 @@
         encDiffs->appendName(glyphNames[gID].isEmpty() ? unknown : glyphNames[gID]);
     }
 
-    auto encoding = sk_make_sp<SkPDFDict>("Encoding");
+    auto encoding = SkPDFMakeDict("Encoding");
     encoding->insertObject("Differences", std::move(encDiffs));
     font.insertObject("Encoding", std::move(encoding));
 
-    SkWStream* stream = doc->beginObject(pdfFont.indirectReference());
-    font.emitObject(stream);
-    doc->endObject();
+    doc->emit(font, pdfFont.indirectReference());
 }
 
 void SkPDFFont::GetType1GlyphNames(const SkTypeface& face, SkString* dst) {
@@ -599,6 +605,7 @@
             return {nullptr, {0, 0}};
     }
 }
+
 static SkPDFIndirectReference type3_descriptor(SkPDFDocument* doc,
                                                const SkTypeface* typeface,
                                                SkGlyphCache* cache) {
@@ -654,10 +661,10 @@
     fontMatrix.setScale(SkScalarInvert(emSize), -SkScalarInvert(emSize));
     font.insertObject("FontMatrix", SkPDFUtils::MatrixToArray(fontMatrix));
 
-    auto charProcs = sk_make_sp<SkPDFDict>();
-    auto encoding = sk_make_sp<SkPDFDict>("Encoding");
+    auto charProcs = SkPDFMakeDict();
+    auto encoding = SkPDFMakeDict("Encoding");
 
-    auto encDiffs = sk_make_sp<SkPDFArray>();
+    auto encDiffs = SkPDFMakeArray();
     // length(firstGlyphID .. lastGlyphID) ==  lastGlyphID - firstGlyphID + 1
     // plus 1 for glyph 0;
     SkASSERT(firstGlyphID > 0);
@@ -667,11 +674,12 @@
     encDiffs->reserve(glyphCount + 1);
     encDiffs->appendInt(0);  // index of first glyph
 
-    auto widthArray = sk_make_sp<SkPDFArray>();
+    auto widthArray = SkPDFMakeArray();
     widthArray->reserve(glyphCount);
 
     SkIRect bbox = SkIRect::MakeEmpty();
 
+    std::vector<std::pair<SkGlyphID, SkPDFIndirectReference>> imageGlyphs;
     for (SkGlyphID gID : SingleByteGlyphIdIterator(firstGlyphID, lastGlyphID)) {
         bool skipGlyph = gID != 0 && !subset.has(gID);
         SkString characterName;
@@ -687,25 +695,18 @@
                                           glyph.fWidth, glyph.fHeight);
             bbox.join(glyphBBox);
             const SkPath* path = cache->findPath(glyph);
+            SkDynamicMemoryWStream content;
             if (path && !path->isEmpty()) {
-                SkDynamicMemoryWStream content;
-                setGlyphWidthAndBoundingBox(SkFloatToScalar(glyph.fAdvanceX), glyphBBox,
-                                            &content);
+                setGlyphWidthAndBoundingBox(SkFloatToScalar(glyph.fAdvanceX), glyphBBox, &content);
                 SkPDFUtils::EmitPath(*path, SkPaint::kFill_Style, &content);
-                SkPDFUtils::PaintPath(SkPaint::kFill_Style, path->getFillType(),
-                                      &content);
-                SkPDFStream charProc(std::unique_ptr<SkStreamAsset>(content.detachAsStream()));
-                charProcs->insertRef(characterName, doc->emit(charProc));
+                SkPDFUtils::PaintPath(SkPaint::kFill_Style, path->getFillType(), &content);
             } else {
                 auto pimg = to_image(gID, cache.get());
                 if (!pimg.fImage) {
-                    SkDynamicMemoryWStream content;
                     setGlyphWidthAndBoundingBox(SkFloatToScalar(glyph.fAdvanceX), glyphBBox,
                                                 &content);
-                    SkPDFStream charProc(std::unique_ptr<SkStreamAsset>(content.detachAsStream()));
-                    charProcs->insertRef(characterName, doc->emit(charProc));
                 } else {
-                    SkDynamicMemoryWStream content;
+                    imageGlyphs.emplace_back(gID, SkPDFSerializeImage(pimg.fImage.get(), doc));
                     SkPDFUtils::AppendScalar(SkFloatToScalar(glyph.fAdvanceX), &content);
                     content.writeText(" 0 d0\n");
                     content.writeDecAsText(pimg.fImage->width());
@@ -715,22 +716,28 @@
                     content.writeDecAsText(pimg.fOffset.x());
                     content.writeText(" ");
                     content.writeDecAsText(pimg.fImage->height() + pimg.fOffset.y());
-                    content.writeText(" cm\n");
-                    content.writeText("/X Do\n");
-                    SkPDFStream charProc(std::unique_ptr<SkStreamAsset>(content.detachAsStream()));
-                    auto d0 = sk_make_sp<SkPDFDict>();
-                    d0->insertRef("X", SkPDFSerializeImage(pimg.fImage.get(), doc));
-                    auto d1 = sk_make_sp<SkPDFDict>();
-                    d1->insertObject("XObject", std::move(d0));
-                    charProc.dict()->insertObject("Resources", std::move(d1));
-                    charProcs->insertRef(characterName, doc->emit(charProc));
+                    content.writeText(" cm\n/X");
+                    content.write(characterName.c_str(), characterName.size());
+                    content.writeText(" Do\n");
                 }
             }
+            charProcs->insertRef(characterName, SkPDFStreamOut(nullptr,
+                                                               content.detachAsStream(), doc));
         }
         encDiffs->appendName(std::move(characterName));
         widthArray->appendScalar(advance);
     }
 
+    if (!imageGlyphs.empty()) {
+        auto d0 = SkPDFMakeDict();
+        for (const auto& pair : imageGlyphs) {
+            d0->insertRef(SkStringPrintf("Xg%X", pair.first), pair.second);
+        }
+        auto d1 = SkPDFMakeDict();
+        d1->insertObject("XObject", std::move(d0));
+        font.insertObject("Resources", std::move(d1));
+    }
+
     encoding->insertObject("Differences", std::move(encDiffs));
     font.insertInt("FirstChar", 0);
     font.insertInt("LastChar", lastGlyphID - firstGlyphID + 1);
@@ -748,20 +755,18 @@
 
     const std::vector<SkUnichar>& glyphToUnicode = SkPDFFont::GetUnicodeMap(typeface, canon);
     SkASSERT(glyphToUnicode.size() == SkToSizeT(typeface->countGlyphs()));
-    sk_sp<SkPDFStream> toUnicodeCmap = SkPDFMakeToUnicodeCmap(glyphToUnicode.data(),
-                                                              &subset,
-                                                              false,
-                                                              firstGlyphID,
-                                                              lastGlyphID);
-    font.insertRef("ToUnicode", doc->serialize(toUnicodeCmap));
+    auto toUnicodeCmap = SkPDFMakeToUnicodeCmap(glyphToUnicode.data(),
+                                                &subset,
+                                                false,
+                                                firstGlyphID,
+                                                lastGlyphID);
+    font.insertRef("ToUnicode", SkPDFStreamOut(nullptr, std::move(toUnicodeCmap), doc));
     font.insertRef("FontDescriptor", type3_descriptor(doc, typeface, cache.get()));
     font.insertObject("Widths", std::move(widthArray));
     font.insertObject("Encoding", std::move(encoding));
     font.insertObject("CharProcs", std::move(charProcs));
 
-    SkWStream* stream = doc->beginObject(pdfFont.indirectReference());
-    font.emitObject(stream);
-    doc->endObject();
+    doc->emit(font, pdfFont.indirectReference());
 }
 
 
diff --git a/src/pdf/SkPDFFormXObject.cpp b/src/pdf/SkPDFFormXObject.cpp
index 99146a2..1586063 100644
--- a/src/pdf/SkPDFFormXObject.cpp
+++ b/src/pdf/SkPDFFormXObject.cpp
@@ -9,31 +9,31 @@
 #include "SkPDFFormXObject.h"
 #include "SkPDFUtils.h"
 
-sk_sp<SkPDFObject> SkPDFMakeFormXObject(std::unique_ptr<SkStreamAsset> content,
-                                        sk_sp<SkPDFArray> mediaBox,
-                                        sk_sp<SkPDFDict> resourceDict,
-                                        const SkMatrix& inverseTransform,
-                                        const char* colorSpace) {
-    auto form = sk_make_sp<SkPDFStream>(std::move(content));
-    form->dict()->insertName("Type", "XObject");
-    form->dict()->insertName("Subtype", "Form");
+SkPDFIndirectReference SkPDFMakeFormXObject(SkPDFDocument* doc,
+                                            std::unique_ptr<SkStreamAsset> content,
+                                            std::unique_ptr<SkPDFArray> mediaBox,
+                                            std::unique_ptr<SkPDFDict> resourceDict,
+                                            const SkMatrix& inverseTransform,
+                                            const char* colorSpace) {
+    std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
+    dict->insertName("Type", "XObject");
+    dict->insertName("Subtype", "Form");
     if (!inverseTransform.isIdentity()) {
-        sk_sp<SkPDFObject> mat(SkPDFUtils::MatrixToArray(inverseTransform));
-        form->dict()->insertObject("Matrix", std::move(mat));
+        dict->insertObject("Matrix", SkPDFUtils::MatrixToArray(inverseTransform));
     }
-    form->dict()->insertObject("Resources", std::move(resourceDict));
-    form->dict()->insertObject("BBox", std::move(mediaBox));
+    dict->insertObject("Resources", std::move(resourceDict));
+    dict->insertObject("BBox", std::move(mediaBox));
 
     // Right now FormXObject is only used for saveLayer, which implies
     // isolated blending.  Do this conditionally if that changes.
     // TODO(halcanary): Is this comment obsolete, since we use it for
     // alpha masks?
-    auto group = sk_make_sp<SkPDFDict>("Group");
+    auto group = SkPDFMakeDict("Group");
     group->insertName("S", "Transparency");
     if (colorSpace != nullptr) {
         group->insertName("CS", colorSpace);
     }
     group->insertBool("I", true);  // Isolated.
-    form->dict()->insertObject("Group", std::move(group));
-    return std::move(form);
+    dict->insertObject("Group", std::move(group));
+    return SkPDFStreamOut(std::move(dict), std::move(content), doc);
 }
diff --git a/src/pdf/SkPDFFormXObject.h b/src/pdf/SkPDFFormXObject.h
index e62b69c..1304960 100644
--- a/src/pdf/SkPDFFormXObject.h
+++ b/src/pdf/SkPDFFormXObject.h
@@ -12,14 +12,17 @@
 #include "SkPDFDevice.h"
 #include "SkPDFTypes.h"
 
+class SkPDFDocument;
+
 /** A form XObject is a self contained description of a graphics
     object.  A form XObject is a page object with slightly different
     syntax, that can be drawn into a page content stream, just like a
     bitmap XObject can be drawn into a page content stream.
 */
-sk_sp<SkPDFObject> SkPDFMakeFormXObject(std::unique_ptr<SkStreamAsset> content,
-                                        sk_sp<SkPDFArray> mediaBox,
-                                        sk_sp<SkPDFDict> resourceDict,
-                                        const SkMatrix& inverseTransform,
-                                        const char* colorSpace);
+SkPDFIndirectReference SkPDFMakeFormXObject(SkPDFDocument* doc,
+                                            std::unique_ptr<SkStreamAsset> content,
+                                            std::unique_ptr<SkPDFArray> mediaBox,
+                                            std::unique_ptr<SkPDFDict> resourceDict,
+                                            const SkMatrix& inverseTransform,
+                                            const char* colorSpace);
 #endif
diff --git a/src/pdf/SkPDFGradientShader.cpp b/src/pdf/SkPDFGradientShader.cpp
index d61b5f1..21f1300 100644
--- a/src/pdf/SkPDFGradientShader.cpp
+++ b/src/pdf/SkPDFGradientShader.cpp
@@ -13,6 +13,7 @@
 #include "SkPDFFormXObject.h"
 #include "SkPDFGraphicState.h"
 #include "SkPDFResourceDict.h"
+#include "SkPDFTypes.h"
 #include "SkPDFUtils.h"
 
 static uint32_t hash(const SkShader::GradientInfo& v) {
@@ -194,17 +195,17 @@
     }
 }
 
-static sk_sp<SkPDFDict> createInterpolationFunction(const ColorTuple& color1,
+static std::unique_ptr<SkPDFDict> createInterpolationFunction(const ColorTuple& color1,
                                                     const ColorTuple& color2) {
-    auto retval = sk_make_sp<SkPDFDict>();
+    auto retval = SkPDFMakeDict();
 
-    auto c0 = sk_make_sp<SkPDFArray>();
+    auto c0 = SkPDFMakeArray();
     c0->appendColorComponent(color1[0]);
     c0->appendColorComponent(color1[1]);
     c0->appendColorComponent(color1[2]);
     retval->insertObject("C0", std::move(c0));
 
-    auto c1 = sk_make_sp<SkPDFArray>();
+    auto c1 = SkPDFMakeArray();
     c1->appendColorComponent(color2[0]);
     c1->appendColorComponent(color2[1]);
     c1->appendColorComponent(color2[2]);
@@ -218,8 +219,8 @@
     return retval;
 }
 
-static sk_sp<SkPDFDict> gradientStitchCode(const SkShader::GradientInfo& info) {
-    auto retval = sk_make_sp<SkPDFDict>();
+static std::unique_ptr<SkPDFDict> gradientStitchCode(const SkShader::GradientInfo& info) {
+    auto retval = SkPDFMakeDict();
 
     // normalize color stops
     int colorCount = info.fColorCount;
@@ -265,9 +266,9 @@
     if (colorCount == 2)
         return createInterpolationFunction(colorData[0], colorData[1]);
 
-    auto encode = sk_make_sp<SkPDFArray>();
-    auto bounds = sk_make_sp<SkPDFArray>();
-    auto functions = sk_make_sp<SkPDFArray>();
+    auto encode = SkPDFMakeArray();
+    auto bounds = SkPDFMakeArray();
+    auto functions = SkPDFMakeArray();
 
     retval->insertObject("Domain", SkPDFMakeArray(0, 1));
     retval->insertInt("FunctionType", 3);
@@ -573,20 +574,19 @@
     return true;
 }
 
-static sk_sp<SkPDFStream> make_ps_function(
-        std::unique_ptr<SkStreamAsset> psCode,
-        sk_sp<SkPDFArray> domain,
-        sk_sp<SkPDFObject> range) {
-    auto result = sk_make_sp<SkPDFStream>(std::move(psCode));
-    result->dict()->insertInt("FunctionType", 4);
-    result->dict()->insertObject("Domain", std::move(domain));
-    result->dict()->insertObject("Range", std::move(range));
-    return result;
+static SkPDFIndirectReference make_ps_function(std::unique_ptr<SkStreamAsset> psCode,
+                                               std::unique_ptr<SkPDFArray> domain,
+                                               std::unique_ptr<SkPDFObject> range,
+                                               SkPDFDocument* doc) {
+    std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
+    dict->insertInt("FunctionType", 4);
+    dict->insertObject("Domain", std::move(domain));
+    dict->insertObject("Range", std::move(range));
+    return SkPDFStreamOut(std::move(dict), std::move(psCode), doc);
 }
 
-
-static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon,
-                                             const SkPDFGradientShader::Key& state) {
+static SkPDFIndirectReference make_function_shader(SkPDFDocument* doc,
+                                                   const SkPDFGradientShader::Key& state) {
     SkPoint transformPoints[2];
     const SkShader::GradientInfo& info = state.fInfo;
     SkMatrix finalMatrix = state.fCanvasTransform;
@@ -599,7 +599,7 @@
                              !finalMatrix.hasPerspective();
 
     int32_t shadingType = 1;
-    auto pdfShader = sk_make_sp<SkPDFDict>();
+    auto pdfShader = SkPDFMakeDict();
     // The two point radial gradient further references
     // state.fInfo
     // in translating from x, y coordinates to the t parameter. So, we have
@@ -608,13 +608,13 @@
         pdfShader->insertObject("Function", gradientStitchCode(info));
         shadingType = (state.fType == SkShader::kLinear_GradientType) ? 2 : 3;
 
-        auto extend = sk_make_sp<SkPDFArray>();
+        auto extend = SkPDFMakeArray();
         extend->reserve(2);
         extend->appendBool(true);
         extend->appendBool(true);
         pdfShader->insertObject("Extend", std::move(extend));
 
-        sk_sp<SkPDFArray> coords;
+        std::unique_ptr<SkPDFArray> coords;
         if (state.fType == SkShader::kConical_GradientType) {
             SkScalar r1 = info.fRadius[0];
             SkScalar r2 = info.fRadius[1];
@@ -670,7 +670,7 @@
             case SkShader::kColor_GradientType:
             case SkShader::kNone_GradientType:
             default:
-                return nullptr;
+                return SkPDFIndirectReference();
         }
 
         // Move any scaling (assuming a unit gradient) or translation
@@ -691,14 +691,14 @@
         if (finalMatrix.hasPerspective()) {
             if (!split_perspective(finalMatrix,
                                    &finalMatrix, &perspectiveInverseOnly)) {
-                return nullptr;
+                return SkPDFIndirectReference();
             }
         }
 
         SkRect bbox;
         bbox.set(state.fBBox);
         if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &bbox)) {
-            return nullptr;
+            return SkPDFIndirectReference();
         }
         SkDynamicMemoryWStream functionCode;
 
@@ -707,7 +707,7 @@
         if (state.fType == SkShader::kConical_GradientType) {
             SkMatrix inverseMapperMatrix;
             if (!mapperMatrix.invert(&inverseMapperMatrix)) {
-                return nullptr;
+                return SkPDFIndirectReference();
             }
             inverseMapperMatrix.mapPoints(infoCopy.fPoint, 2);
             infoCopy.fRadius[0] = inverseMapperMatrix.mapRadius(info.fRadius[0]);
@@ -729,61 +729,57 @@
             default:
                 SkASSERT(false);
         }
-        auto domain = SkPDFMakeArray(bbox.left(),
-                                     bbox.right(),
-                                     bbox.top(),
-                                     bbox.bottom());
-        pdfShader->insertObject("Domain", domain);
+        pdfShader->insertObject(
+                "Domain", SkPDFMakeArray(bbox.left(), bbox.right(), bbox.top(), bbox.bottom()));
 
-        sk_sp<SkPDFArray>& rangeObject = canon->fRangeObject;
-        if (!rangeObject) {
-            rangeObject = SkPDFMakeArray(0, 1, 0, 1, 0, 1);
-        }
-        pdfShader->insertObjRef("Function",
-                                make_ps_function(functionCode.detachAsStream(), std::move(domain),
-                                                 rangeObject));
+        auto domain = SkPDFMakeArray(bbox.left(), bbox.right(), bbox.top(), bbox.bottom());
+        std::unique_ptr<SkPDFArray> rangeObject = SkPDFMakeArray(0, 1, 0, 1, 0, 1);
+        pdfShader->insertRef("Function",
+                             make_ps_function(functionCode.detachAsStream(), std::move(domain),
+                                              std::move(rangeObject), doc));
     }
 
     pdfShader->insertInt("ShadingType", shadingType);
     pdfShader->insertName("ColorSpace", "DeviceRGB");
 
-    auto pdfFunctionShader = sk_make_sp<SkPDFDict>("Pattern");
-    pdfFunctionShader->insertInt("PatternType", 2);
-    pdfFunctionShader->insertObject("Matrix", SkPDFUtils::MatrixToArray(finalMatrix));
-    pdfFunctionShader->insertObject("Shading", std::move(pdfShader));
-
-    return pdfFunctionShader;
+    SkPDFDict pdfFunctionShader("Pattern");
+    pdfFunctionShader.insertInt("PatternType", 2);
+    pdfFunctionShader.insertObject("Matrix", SkPDFUtils::MatrixToArray(finalMatrix));
+    pdfFunctionShader.insertObject("Shading", std::move(pdfShader));
+    return doc->emit(pdfFunctionShader);
 }
 
-static sk_sp<SkPDFObject> find_pdf_shader(SkPDFDocument* doc,
-                                          SkPDFGradientShader::Key key,
-                                          bool keyHasAlpha);
+static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc,
+                                              SkPDFGradientShader::Key key,
+                                              bool keyHasAlpha);
 
-static sk_sp<SkPDFDict> get_gradient_resource_dict(sk_sp<SkPDFObject> functionShader,
-                                                   sk_sp<SkPDFObject> gState) {
-    std::vector<sk_sp<SkPDFObject>> patternShaders;
-    if (functionShader) {
-        patternShaders.push_back(std::move(functionShader));
+static std::unique_ptr<SkPDFDict> get_gradient_resource_dict(SkPDFIndirectReference functionShader,
+                                                   SkPDFIndirectReference gState) {
+    std::vector<SkPDFIndirectReference> patternShaders;
+    if (functionShader != SkPDFIndirectReference()) {
+        patternShaders.push_back(functionShader);
     }
-    std::vector<sk_sp<SkPDFObject>> graphicStates;
-    if (gState) {
-        graphicStates.push_back(std::move(gState));
+    std::vector<SkPDFIndirectReference> graphicStates;
+    if (gState != SkPDFIndirectReference()) {
+        graphicStates.push_back(gState);
     }
     return SkPDFMakeResourceDict(std::move(graphicStates),
                                  std::move(patternShaders),
-                                 std::vector<sk_sp<SkPDFObject>>(),
+                                 std::vector<SkPDFIndirectReference>(),
                                  std::vector<SkPDFIndirectReference>());
 }
 
 // Creates a content stream which fills the pattern P0 across bounds.
 // @param gsIndex A graphics state resource index to apply, or <0 if no
 // graphics state to apply.
-static std::unique_ptr<SkStreamAsset> create_pattern_fill_content(int gsIndex, SkRect& bounds) {
+static std::unique_ptr<SkStreamAsset> create_pattern_fill_content(int gsIndex,
+                                                                  int patternIndex,
+                                                                  SkRect& bounds) {
     SkDynamicMemoryWStream content;
     if (gsIndex >= 0) {
         SkPDFUtils::ApplyGraphicState(gsIndex, &content);
     }
-    SkPDFUtils::ApplyPattern(0, &content);
+    SkPDFUtils::ApplyPattern(patternIndex, &content);
     SkPDFUtils::AppendRectangle(bounds, &content);
     SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType, &content);
     return content.detachAsStream();
@@ -818,7 +814,7 @@
     return clone;
 }
 
-static sk_sp<SkPDFObject> create_smask_graphic_state(SkPDFDocument* doc,
+static SkPDFIndirectReference create_smask_graphic_state(SkPDFDocument* doc,
                                                      const SkPDFGradientShader::Key& state) {
     SkASSERT(state.fType != SkShader::kNone_GradientType);
     SkPDFGradientShader::Key luminosityState = clone_key(state);
@@ -829,21 +825,23 @@
     luminosityState.fHash = hash(luminosityState);
 
     SkASSERT(!gradient_has_alpha(luminosityState));
-    sk_sp<SkPDFObject> luminosityShader = find_pdf_shader(doc, std::move(luminosityState), false);
-    sk_sp<SkPDFDict> resources = get_gradient_resource_dict(std::move(luminosityShader), nullptr);
+    SkPDFIndirectReference luminosityShader = find_pdf_shader(doc, std::move(luminosityState), false);
+    std::unique_ptr<SkPDFDict> resources = get_gradient_resource_dict(luminosityShader,
+                                                            SkPDFIndirectReference());
     SkRect bbox = SkRect::Make(state.fBBox);
-    sk_sp<SkPDFObject> alphaMask = SkPDFMakeFormXObject(create_pattern_fill_content(-1, bbox),
-                                                        SkPDFUtils::RectToArray(bbox),
-                                                        std::move(resources),
-                                                        SkMatrix::I(),
-                                                        "DeviceRGB");
+    SkPDFIndirectReference alphaMask =
+            SkPDFMakeFormXObject(doc,
+                                 create_pattern_fill_content(-1, luminosityShader.fValue, bbox),
+                                 SkPDFUtils::RectToArray(bbox),
+                                 std::move(resources),
+                                 SkMatrix::I(),
+                                 "DeviceRGB");
     return SkPDFGraphicState::GetSMaskGraphicState(
-            std::move(alphaMask), false,
-            SkPDFGraphicState::kLuminosity_SMaskMode, doc->canon());
+            alphaMask, false, SkPDFGraphicState::kLuminosity_SMaskMode, doc);
 }
 
-static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc,
-                                                     const SkPDFGradientShader::Key& state) {
+static SkPDFIndirectReference make_alpha_function_shader(SkPDFDocument* doc,
+                                                         const SkPDFGradientShader::Key& state) {
     SkASSERT(state.fType != SkShader::kNone_GradientType);
     SkPDFGradientShader::Key opaqueState = clone_key(state);
     for (int i = 0; i < opaqueState.fInfo.fColorCount; i++) {
@@ -853,24 +851,22 @@
 
     SkASSERT(!gradient_has_alpha(opaqueState));
     SkRect bbox = SkRect::Make(state.fBBox);
-    sk_sp<SkPDFObject> colorShader = find_pdf_shader(doc, std::move(opaqueState), false);
+    SkPDFIndirectReference colorShader = find_pdf_shader(doc, std::move(opaqueState), false);
     if (!colorShader) {
-        return nullptr;
+        return SkPDFIndirectReference();
     }
-
     // Create resource dict with alpha graphics state as G0 and
     // pattern shader as P0, then write content stream.
-    sk_sp<SkPDFObject> alphaGs = create_smask_graphic_state(doc, state);
+    SkPDFIndirectReference alphaGsRef = create_smask_graphic_state(doc, state);
 
-    sk_sp<SkPDFDict> resourceDict =
-            get_gradient_resource_dict(std::move(colorShader), std::move(alphaGs));
+    std::unique_ptr<SkPDFDict> resourceDict = get_gradient_resource_dict(colorShader, alphaGsRef);
 
-    std::unique_ptr<SkStreamAsset> colorStream(create_pattern_fill_content(0, bbox));
-    auto alphaFunctionShader = sk_make_sp<SkPDFStream>(std::move(colorStream));
-
-    SkPDFUtils::PopulateTilingPatternDict(alphaFunctionShader->dict(), bbox,
+    std::unique_ptr<SkStreamAsset> colorStream =
+            create_pattern_fill_content(alphaGsRef.fValue, colorShader.fValue, bbox);
+    std::unique_ptr<SkPDFDict> alphaFunctionShader = SkPDFMakeDict();
+    SkPDFUtils::PopulateTilingPatternDict(alphaFunctionShader.get(), bbox,
                                  std::move(resourceDict), SkMatrix::I());
-    return alphaFunctionShader;
+    return SkPDFStreamOut(std::move(alphaFunctionShader), std::move(colorStream), doc);
 }
 
 static SkPDFGradientShader::Key make_key(const SkShader* shader,
@@ -896,25 +892,25 @@
     return key;
 }
 
-static sk_sp<SkPDFObject> find_pdf_shader(SkPDFDocument* doc,
-                                          SkPDFGradientShader::Key key,
-                                          bool keyHasAlpha) {
+static SkPDFIndirectReference find_pdf_shader(SkPDFDocument* doc,
+                                              SkPDFGradientShader::Key key,
+                                              bool keyHasAlpha) {
     SkASSERT(gradient_has_alpha(key) == keyHasAlpha);
-    SkPDFCanon* canon = doc->canon();
-    if (sk_sp<SkPDFObject>* ptr = canon->fGradientPatternMap.find(key)) {
+    auto& gradientPatternMap = doc->canon()->fGradientPatternMap;
+    if (SkPDFIndirectReference* ptr = gradientPatternMap.find(key)) {
         return *ptr;
     }
-    sk_sp<SkPDFObject> pdfShader;
+    SkPDFIndirectReference pdfShader;
     if (keyHasAlpha) {
         pdfShader = make_alpha_function_shader(doc, key);
     } else {
-        pdfShader = make_function_shader(canon, key);
+        pdfShader = make_function_shader(doc, key);
     }
-    canon->fGradientPatternMap.set(std::move(key), pdfShader);
+    gradientPatternMap.set(std::move(key), pdfShader);
     return pdfShader;
 }
 
-sk_sp<SkPDFObject> SkPDFGradientShader::Make(SkPDFDocument* doc,
+SkPDFIndirectReference SkPDFGradientShader::Make(SkPDFDocument* doc,
                                              SkShader* shader,
                                              const SkMatrix& canvasTransform,
                                              const SkIRect& bbox) {
diff --git a/src/pdf/SkPDFGradientShader.h b/src/pdf/SkPDFGradientShader.h
index 0cc059c..7050e09 100644
--- a/src/pdf/SkPDFGradientShader.h
+++ b/src/pdf/SkPDFGradientShader.h
@@ -17,10 +17,10 @@
 
 namespace SkPDFGradientShader {
 
-sk_sp<SkPDFObject> Make(SkPDFDocument* doc,
-                        SkShader* shader,
-                        const SkMatrix& matrix,
-                        const SkIRect& surfaceBBox);
+SkPDFIndirectReference Make(SkPDFDocument* doc,
+                            SkShader* shader,
+                            const SkMatrix& matrix,
+                            const SkIRect& surfaceBBox);
 
 struct Key {
     SkShader::GradientType fType;
@@ -37,7 +37,7 @@
     uint32_t operator()(const Key& k) const { return k.fHash; }
 };
 
-using HashMap = SkTHashMap<Key, sk_sp<SkPDFObject>, KeyHash>;
+using HashMap = SkTHashMap<Key, SkPDFIndirectReference, KeyHash>;
 
 inline bool operator==(const SkShader::GradientInfo& u, const SkShader::GradientInfo& v) {
     return u.fColorCount    == v.fColorCount
diff --git a/src/pdf/SkPDFGraphicState.cpp b/src/pdf/SkPDFGraphicState.cpp
index b9a5eb3..dc5b2f0 100644
--- a/src/pdf/SkPDFGraphicState.cpp
+++ b/src/pdf/SkPDFGraphicState.cpp
@@ -9,6 +9,7 @@
 
 #include "SkData.h"
 #include "SkPDFCanon.h"
+#include "SkPDFDocumentPriv.h"
 #include "SkPDFFormXObject.h"
 #include "SkPDFUtils.h"
 #include "SkPaint.h"
@@ -52,21 +53,23 @@
     return SkToU8((unsigned)mode);
 }
 
-sk_sp<SkPDFDict> SkPDFGraphicState::GetGraphicStateForPaint(SkPDFCanon* canon,
-                                                            const SkPaint& p) {
+SkPDFIndirectReference SkPDFGraphicState::GetGraphicStateForPaint(SkPDFDocument* doc,
+                                                                  const SkPaint& p) {
+    SkPDFCanon* canon = doc->canon();
     SkASSERT(canon);
     if (SkPaint::kFill_Style == p.getStyle()) {
         SkPDFFillGraphicState fillKey = {p.getColor4f().fA, pdf_blend_mode(p.getBlendMode())};
         auto& fillMap = canon->fFillGSMap;
-        if (sk_sp<SkPDFDict>* statePtr = fillMap.find(fillKey)) {
+        if (SkPDFIndirectReference* statePtr = fillMap.find(fillKey)) {
             return *statePtr;
         }
-        auto state = sk_make_sp<SkPDFDict>();
-        state->reserve(2);
-        state->insertColorComponentF("ca", fillKey.fAlpha);
-        state->insertName("BM", as_pdf_blend_mode_name((SkBlendMode)fillKey.fBlendMode));
-        fillMap.set(fillKey, state);
-        return state;
+        SkPDFDict state;
+        state.reserve(2);
+        state.insertColorComponentF("ca", fillKey.fAlpha);
+        state.insertName("BM", as_pdf_blend_mode_name((SkBlendMode)fillKey.fBlendMode));
+        SkPDFIndirectReference ref = doc->emit(state);
+        fillMap.set(fillKey, ref);
+        return ref;
     } else {
         SkPDFStrokeGraphicState strokeKey = {
             p.getStrokeWidth(),
@@ -77,65 +80,63 @@
             pdf_blend_mode(p.getBlendMode())
         };
         auto& sMap = canon->fStrokeGSMap;
-        if (sk_sp<SkPDFDict>* statePtr = sMap.find(strokeKey)) {
+        if (SkPDFIndirectReference* statePtr = sMap.find(strokeKey)) {
             return *statePtr;
         }
-        auto state = sk_make_sp<SkPDFDict>();
-        state->reserve(8);
-        state->insertColorComponentF("CA", strokeKey.fAlpha);
-        state->insertColorComponentF("ca", strokeKey.fAlpha);
-        state->insertInt("LC", to_stroke_cap(strokeKey.fStrokeCap));
-        state->insertInt("LJ", to_stroke_join(strokeKey.fStrokeJoin));
-        state->insertScalar("LW", strokeKey.fStrokeWidth);
-        state->insertScalar("ML", strokeKey.fStrokeMiter);
-        state->insertBool("SA", true);  // SA = Auto stroke adjustment.
-        state->insertName("BM", as_pdf_blend_mode_name((SkBlendMode)strokeKey.fBlendMode));
-        sMap.set(strokeKey, state);
-        return state;
+        SkPDFDict state;
+        state.reserve(8);
+        state.insertColorComponentF("CA", strokeKey.fAlpha);
+        state.insertColorComponentF("ca", strokeKey.fAlpha);
+        state.insertInt("LC", to_stroke_cap(strokeKey.fStrokeCap));
+        state.insertInt("LJ", to_stroke_join(strokeKey.fStrokeJoin));
+        state.insertScalar("LW", strokeKey.fStrokeWidth);
+        state.insertScalar("ML", strokeKey.fStrokeMiter);
+        state.insertBool("SA", true);  // SA = Auto stroke adjustment.
+        state.insertName("BM", as_pdf_blend_mode_name((SkBlendMode)strokeKey.fBlendMode));
+        SkPDFIndirectReference ref = doc->emit(state);
+        sMap.set(strokeKey, ref);
+        return ref;
     }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
-static sk_sp<SkPDFStream> make_invert_function() {
+static SkPDFIndirectReference make_invert_function(SkPDFDocument* doc) {
     // Acrobat crashes if we use a type 0 function, kpdf crashes if we use
     // a type 2 function, so we use a type 4 function.
-    auto domainAndRange = SkPDFMakeArray(0, 1);
-
     static const char psInvert[] = "{1 exch sub}";
     // Do not copy the trailing '\0' into the SkData.
-    auto invertFunction = sk_make_sp<SkPDFStream>(
-            SkData::MakeWithoutCopy(psInvert, strlen(psInvert)));
-    invertFunction->dict()->insertInt("FunctionType", 4);
-    invertFunction->dict()->insertObject("Domain", domainAndRange);
-    invertFunction->dict()->insertObject("Range", std::move(domainAndRange));
-    return invertFunction;
+    auto invertFunction = SkData::MakeWithoutCopy(psInvert, strlen(psInvert));
+
+    std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
+    dict->insertInt("FunctionType", 4);
+    dict->insertObject("Domain", SkPDFMakeArray(0, 1));
+    dict->insertObject("Range", SkPDFMakeArray(0, 1));
+    return SkPDFStreamOut(std::move(dict), SkMemoryStream::Make(std::move(invertFunction)), doc);
 }
 
-sk_sp<SkPDFDict> SkPDFGraphicState::GetSMaskGraphicState(
-        sk_sp<SkPDFObject> sMask,
-        bool invert,
-        SkPDFSMaskMode sMaskMode,
-        SkPDFCanon* canon) {
+SkPDFIndirectReference SkPDFGraphicState::GetSMaskGraphicState(SkPDFIndirectReference sMask,
+                                                               bool invert,
+                                                               SkPDFSMaskMode sMaskMode,
+                                                               SkPDFDocument* doc) {
     // The practical chances of using the same mask more than once are unlikely
     // enough that it's not worth canonicalizing.
-    auto sMaskDict = sk_make_sp<SkPDFDict>("Mask");
+    SkPDFCanon* canon = doc->canon();
+    auto sMaskDict = SkPDFMakeDict("Mask");
     if (sMaskMode == kAlpha_SMaskMode) {
         sMaskDict->insertName("S", "Alpha");
     } else if (sMaskMode == kLuminosity_SMaskMode) {
         sMaskDict->insertName("S", "Luminosity");
     }
-    sMaskDict->insertObjRef("G", std::move(sMask));
+    sMaskDict->insertRef("G", sMask);
     if (invert) {
-        // Instead of calling SkPDFGraphicState::MakeInvertFunction,
         // let the canon deduplicate this object.
-        sk_sp<SkPDFStream>& invertFunction = canon->fInvertFunction;
-        if (!invertFunction) {
-            invertFunction = make_invert_function();
+        if (canon->fInvertFunction == SkPDFIndirectReference()) {
+            canon->fInvertFunction = make_invert_function(doc);
         }
-        sMaskDict->insertObjRef("TR", invertFunction);
+        sMaskDict->insertRef("TR", canon->fInvertFunction);
     }
-    auto result = sk_make_sp<SkPDFDict>("ExtGState");
-    result->insertObject("SMask", std::move(sMaskDict));
-    return result;
+    SkPDFDict result("ExtGState");
+    result.insertObject("SMask", std::move(sMaskDict));
+    return doc->emit(result);
 }
diff --git a/src/pdf/SkPDFGraphicState.h b/src/pdf/SkPDFGraphicState.h
index 86f4eab..9c22e17 100644
--- a/src/pdf/SkPDFGraphicState.h
+++ b/src/pdf/SkPDFGraphicState.h
@@ -29,7 +29,7 @@
 
     /** Get the graphic state for the passed SkPaint.
      */
-    sk_sp<SkPDFDict> GetGraphicStateForPaint(SkPDFCanon*, const SkPaint&);
+    SkPDFIndirectReference GetGraphicStateForPaint(SkPDFDocument*, const SkPaint&);
 
     /** Make a graphic state that only sets the passed soft mask.
      *  @param sMask     The form xobject to use as a soft mask.
@@ -38,12 +38,10 @@
      *
      *  These are not de-duped.
      */
-    sk_sp<SkPDFDict> GetSMaskGraphicState(sk_sp<SkPDFObject> sMask,
-                                          bool invert,
-                                          SkPDFSMaskMode sMaskMode,
-                                          SkPDFCanon* canon);
-
-    sk_sp<SkPDFStream> MakeInvertFunction();
+    SkPDFIndirectReference GetSMaskGraphicState(SkPDFIndirectReference sMask,
+                                                bool invert,
+                                                SkPDFSMaskMode sMaskMode,
+                                                SkPDFDocument* doc);
 }
 
 SK_BEGIN_REQUIRE_DENSE
diff --git a/src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp b/src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp
index 1090e36..8834f6e 100644
--- a/src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp
+++ b/src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp
@@ -121,7 +121,7 @@
             break;
         }
         case AdvanceMetric::kRange: {
-            auto advanceArray = sk_make_sp<SkPDFArray>();
+            auto advanceArray = SkPDFMakeArray();
             for (size_t j = 0; j < range.fAdvance.size(); j++)
                 advanceArray->appendScalar(
                         scale_from_font_units(range.fAdvance[j], emSize));
@@ -143,10 +143,10 @@
 /** Retrieve advance data for glyphs. Used by the PDF backend. */
 // TODO(halcanary): this function is complex enough to need its logic
 // tested with unit tests.
-sk_sp<SkPDFArray> SkPDFMakeCIDGlyphWidthsArray(SkGlyphCache* cache,
-                                               const SkPDFGlyphUse* subset,
-                                               uint16_t emSize,
-                                               int16_t* defaultAdvance) {
+std::unique_ptr<SkPDFArray> SkPDFMakeCIDGlyphWidthsArray(SkGlyphCache* cache,
+                                                         const SkPDFGlyphUse* subset,
+                                                         uint16_t emSize,
+                                                         int16_t* defaultAdvance) {
     // Assuming that on average, the ASCII representation of an advance plus
     // a space is 8 characters and the ASCII representation of a glyph id is 3
     // characters, then the following cut offs for using different range types
@@ -162,7 +162,7 @@
     //  d. Removing a leading 0/don't cares is a win because it is omitted
     //  e. Removing 2 repeating advances is a win
 
-    auto result = sk_make_sp<SkPDFArray>();
+    auto result = SkPDFMakeArray();
     int num_glyphs = SkToInt(cache->getGlyphCount());
 
     bool prevRange = false;
diff --git a/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h b/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h
index bfa4a38..657fc9d 100644
--- a/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h
+++ b/src/pdf/SkPDFMakeCIDGlyphWidthsArray.h
@@ -15,9 +15,9 @@
 /* PDF 32000-1:2008, page 270: "The array's elements have a variable
    format that can specify individual widths for consecutive CIDs or
    one width for a range of CIDs". */
-sk_sp<SkPDFArray> SkPDFMakeCIDGlyphWidthsArray(SkGlyphCache* cache,
-                                               const SkPDFGlyphUse* subset,
-                                               uint16_t emSize,
-                                               int16_t* defaultWidth);
+std::unique_ptr<SkPDFArray> SkPDFMakeCIDGlyphWidthsArray(SkGlyphCache* cache,
+                                                         const SkPDFGlyphUse* subset,
+                                                         uint16_t emSize,
+                                                         int16_t* defaultWidth);
 
 #endif  // SkPDFMakeCIDGlyphWidthsArray_DEFINED
diff --git a/src/pdf/SkPDFMakeToUnicodeCmap.cpp b/src/pdf/SkPDFMakeToUnicodeCmap.cpp
index a329547..73cdc1d 100644
--- a/src/pdf/SkPDFMakeToUnicodeCmap.cpp
+++ b/src/pdf/SkPDFMakeToUnicodeCmap.cpp
@@ -205,7 +205,7 @@
     append_bfrange_section(bfrangeEntries, multiByteGlyphs, cmap);
 }
 
-sk_sp<SkPDFStream> SkPDFMakeToUnicodeCmap(
+std::unique_ptr<SkStreamAsset> SkPDFMakeToUnicodeCmap(
         const SkUnichar* glyphToUnicode,
         const SkPDFGlyphUse* subset,
         bool multiByteGlyphs,
@@ -216,6 +216,5 @@
     SkPDFAppendCmapSections(glyphToUnicode, subset, &cmap, multiByteGlyphs,
                             firstGlyphID, lastGlyphID);
     append_cmap_footer(&cmap);
-    return sk_make_sp<SkPDFStream>(
-            std::unique_ptr<SkStreamAsset>(cmap.detachAsStream()));
+    return cmap.detachAsStream();
 }
diff --git a/src/pdf/SkPDFMakeToUnicodeCmap.h b/src/pdf/SkPDFMakeToUnicodeCmap.h
index f52862f..6614c01 100644
--- a/src/pdf/SkPDFMakeToUnicodeCmap.h
+++ b/src/pdf/SkPDFMakeToUnicodeCmap.h
@@ -10,7 +10,7 @@
 #include "SkPDFFont.h"
 #include "SkStream.h"
 
-sk_sp<SkPDFStream> SkPDFMakeToUnicodeCmap(
+std::unique_ptr<SkStreamAsset> SkPDFMakeToUnicodeCmap(
         const SkUnichar* glyphToUnicode,
         const SkPDFGlyphUse* subset,
         bool multiByteGlyphs,
diff --git a/src/pdf/SkPDFMetadata.cpp b/src/pdf/SkPDFMetadata.cpp
index 06d7471..efae8bb 100644
--- a/src/pdf/SkPDFMetadata.cpp
+++ b/src/pdf/SkPDFMetadata.cpp
@@ -126,9 +126,9 @@
 };
 }  // namespace
 
-sk_sp<SkPDFObject> SkPDFMetadata::MakeDocumentInformationDict(
+std::unique_ptr<SkPDFObject> SkPDFMetadata::MakeDocumentInformationDict(
         const SkPDF::Metadata& metadata) {
-    auto dict = sk_make_sp<SkPDFDict>();
+    auto dict = SkPDFMakeDict();
     for (const auto keyValuePtr : gMetadataKeys) {
         const SkString& value = metadata.*(keyValuePtr.valuePtr);
         if (value.size() > 0) {
@@ -150,8 +150,7 @@
     return std::move(dict);
 }
 
-SkPDFMetadata::UUID SkPDFMetadata::CreateUUID(
-        const SkPDF::Metadata& metadata) {
+SkUUID SkPDFMetadata::CreateUUID(const SkPDF::Metadata& metadata) {
     // The main requirement is for the UUID to be unique; the exact
     // format of the data that will be hashed is not important.
     SkMD5 md5;
@@ -177,22 +176,22 @@
     // See RFC 4122, page 6-7.
     digest.data[6] = (digest.data[6] & 0x0F) | 0x30;
     digest.data[8] = (digest.data[6] & 0x3F) | 0x80;
-    static_assert(sizeof(digest) == sizeof(UUID), "uuid_size");
-    SkPDFMetadata::UUID uuid;
+    static_assert(sizeof(digest) == sizeof(SkUUID), "uuid_size");
+    SkUUID uuid;
     memcpy(&uuid, &digest, sizeof(digest));
     return uuid;
 }
 
-sk_sp<SkPDFObject> SkPDFMetadata::MakePdfId(const UUID& doc,
-                                            const UUID& instance) {
+std::unique_ptr<SkPDFObject> SkPDFMetadata::MakePdfId(const SkUUID& doc,
+                                            const SkUUID& instance) {
     // /ID [ <81b14aafa313db63dbd6f981e49f94f4>
     //       <81b14aafa313db63dbd6f981e49f94f4> ]
-    auto array = sk_make_sp<SkPDFArray>();
-    static_assert(sizeof(SkPDFMetadata::UUID) == 16, "uuid_size");
+    auto array = SkPDFMakeArray();
+    static_assert(sizeof(SkUUID) == 16, "uuid_size");
     array->appendString(
-            SkString(reinterpret_cast<const char*>(&doc), sizeof(UUID)));
+            SkString(reinterpret_cast<const char*>(&doc), sizeof(SkUUID)));
     array->appendString(
-            SkString(reinterpret_cast<const char*>(&instance), sizeof(UUID)));
+            SkString(reinterpret_cast<const char*>(&instance), sizeof(SkUUID)));
     return std::move(array);
 }
 
@@ -208,7 +207,7 @@
     }
 }
 
-static SkString uuid_to_string(const SkPDFMetadata::UUID& uuid) {
+static SkString uuid_to_string(const SkUUID& uuid) {
     //  8-4-4-4-12
     char buffer[36];  // [32 + 4]
     char* ptr = buffer;
@@ -304,10 +303,11 @@
     return output;
 }
 
-sk_sp<SkPDFObject> SkPDFMetadata::MakeXMPObject(
+SkPDFIndirectReference SkPDFMetadata::MakeXMPObject(
         const SkPDF::Metadata& metadata,
-        const UUID& doc,
-        const UUID& instance) {
+        const SkUUID& doc,
+        const SkUUID& instance,
+        SkPDFDocument* docPtr) {
     static const char templateString[] =
             "<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n"
             "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"\n"
@@ -392,11 +392,19 @@
     SkASSERT(0 == count_xml_escape_size(documentID));
     SkString instanceID = uuid_to_string(instance);
     SkASSERT(0 == count_xml_escape_size(instanceID));
-    return sk_make_sp<PDFXMLObject>(SkStringPrintf(
+
+
+    auto value = SkStringPrintf(
             templateString, modificationDate.c_str(), creationDate.c_str(),
             creator.c_str(), title.c_str(), subject.c_str(), author.c_str(),
             keywords1.c_str(), documentID.c_str(), instanceID.c_str(),
-            producer.c_str(), keywords2.c_str()));
+            producer.c_str(), keywords2.c_str());
+
+    std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict("Metadata");
+    dict->insertName("Subtype", "XML");
+    return SkPDFStreamOut(std::move(dict),
+                          SkMemoryStream::MakeCopy(value.c_str(), value.size()),
+                          docPtr, false);
 }
 
 #undef SKPDF_CUSTOM_PRODUCER_KEY
diff --git a/src/pdf/SkPDFMetadata.h b/src/pdf/SkPDFMetadata.h
index 314b57e..323a222 100644
--- a/src/pdf/SkPDFMetadata.h
+++ b/src/pdf/SkPDFMetadata.h
@@ -9,22 +9,21 @@
 #define SkPDFMetadata_DEFINED
 
 #include "SkPDFDocument.h"
+#include "SkPDFTypes.h"
+#include "SkUUID.h"
 
 class SkPDFObject;
 
 namespace SkPDFMetadata {
-sk_sp<SkPDFObject> MakeDocumentInformationDict(const SkPDF::Metadata&);
+std::unique_ptr<SkPDFObject> MakeDocumentInformationDict(const SkPDF::Metadata&);
 
-struct UUID {
-    uint8_t fData[16];
-};
+SkUUID CreateUUID(const SkPDF::Metadata&);
 
-UUID CreateUUID(const SkPDF::Metadata&);
+std::unique_ptr<SkPDFObject> MakePdfId(const SkUUID& doc, const SkUUID& instance);
 
-sk_sp<SkPDFObject> MakePdfId(const UUID& doc, const UUID& instance);
-
-sk_sp<SkPDFObject> MakeXMPObject(const SkPDF::Metadata&,
-                                 const UUID& doc,
-                                 const UUID& instance);
+SkPDFIndirectReference MakeXMPObject(const SkPDF::Metadata& metadata,
+                                     const SkUUID& doc,
+                                     const SkUUID& instance,
+                                     SkPDFDocument*);
 }
 #endif  // SkPDFMetadata_DEFINED
diff --git a/src/pdf/SkPDFResourceDict.cpp b/src/pdf/SkPDFResourceDict.cpp
index fbd5492..0754219 100644
--- a/src/pdf/SkPDFResourceDict.cpp
+++ b/src/pdf/SkPDFResourceDict.cpp
@@ -59,23 +59,11 @@
     return SkString(buffer, (size_t)(end - buffer));
 }
 
-static void add_subdict(std::vector<sk_sp<SkPDFObject>> resourceList,
-                        SkPDFResourceType type,
-                        SkPDFDict* dst) {
-    if (!resourceList.empty()) {
-        auto resources = sk_make_sp<SkPDFDict>();
-        for (size_t i = 0; i < resourceList.size(); i++) {
-            resources->insertObjRef(resource(type, SkToInt(i)), std::move(resourceList[i]));
-        }
-        dst->insertObject(resource_name(type), std::move(resources));
-    }
-}
-
 static void add_subdict(const std::vector<SkPDFIndirectReference>& resourceList,
                         SkPDFResourceType type,
                         SkPDFDict* dst) {
     if (!resourceList.empty()) {
-        auto resources = sk_make_sp<SkPDFDict>();
+        auto resources = SkPDFMakeDict();
         for (SkPDFIndirectReference ref : resourceList) {
             resources->insertRef(resource(type, ref.fValue), ref);
         }
@@ -83,14 +71,26 @@
     }
 }
 
-sk_sp<SkPDFDict> SkPDFMakeResourceDict(std::vector<sk_sp<SkPDFObject>> graphicStateResources,
-                                       std::vector<sk_sp<SkPDFObject>> shaderResources,
-                                       std::vector<sk_sp<SkPDFObject>> xObjectResources,
-                                       std::vector<SkPDFIndirectReference> fontResources) {
-    auto dict = sk_make_sp<SkPDFDict>();
-    add_subdict(std::move(graphicStateResources), SkPDFResourceType::kExtGState, dict.get());
-    add_subdict(std::move(shaderResources),       SkPDFResourceType::kPattern,   dict.get());
-    add_subdict(std::move(xObjectResources),      SkPDFResourceType::kXObject,   dict.get());
-    add_subdict(fontResources,                    SkPDFResourceType::kFont,      dict.get());
+static std::unique_ptr<SkPDFArray> make_proc_set() {
+    auto procSets = SkPDFMakeArray();
+    static const char kProcs[][7] = { "PDF", "Text", "ImageB", "ImageC", "ImageI"};
+    procSets->reserve(SK_ARRAY_COUNT(kProcs));
+    for (const char* proc : kProcs) {
+        procSets->appendName(proc);
+    }
+    return procSets;
+}
+
+std::unique_ptr<SkPDFDict> SkPDFMakeResourceDict(
+        const std::vector<SkPDFIndirectReference>& graphicStateResources,
+        const std::vector<SkPDFIndirectReference>& shaderResources,
+        const std::vector<SkPDFIndirectReference>& xObjectResources,
+        const std::vector<SkPDFIndirectReference>& fontResources) {
+    auto dict = SkPDFMakeDict();
+    dict->insertObject("ProcSets", make_proc_set());
+    add_subdict(graphicStateResources, SkPDFResourceType::kExtGState, dict.get());
+    add_subdict(shaderResources,       SkPDFResourceType::kPattern,   dict.get());
+    add_subdict(xObjectResources,      SkPDFResourceType::kXObject,   dict.get());
+    add_subdict(fontResources,         SkPDFResourceType::kFont,      dict.get());
     return dict;
 }
diff --git a/src/pdf/SkPDFResourceDict.h b/src/pdf/SkPDFResourceDict.h
index 40a17f9..5cce6a8 100644
--- a/src/pdf/SkPDFResourceDict.h
+++ b/src/pdf/SkPDFResourceDict.h
@@ -32,10 +32,11 @@
  *
  *  Any arguments can be nullptr.
  */
-sk_sp<SkPDFDict> SkPDFMakeResourceDict(std::vector<sk_sp<SkPDFObject>> graphicStateResources,
-                                       std::vector<sk_sp<SkPDFObject>> shaderResources,
-                                       std::vector<sk_sp<SkPDFObject>> xObjectResources,
-                                       std::vector<SkPDFIndirectReference> fontResources);
+std::unique_ptr<SkPDFDict> SkPDFMakeResourceDict(
+        const std::vector<SkPDFIndirectReference>& graphicStateResources,
+        const std::vector<SkPDFIndirectReference>& shaderResources,
+        const std::vector<SkPDFIndirectReference>& xObjectResources,
+        const std::vector<SkPDFIndirectReference>& fontResources);
 
 /**
  * Writes the name for the resource that will be generated by the resource
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index 7448d86..0f2ac72 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -37,9 +37,9 @@
     canvas->drawBitmap(bm, 0, 0, &paint);
 }
 
-static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc,
-                                            const SkPDFImageShaderKey& key,
-                                            SkImage* image) {
+static SkPDFIndirectReference make_image_shader(SkPDFDocument* doc,
+                                                const SkPDFImageShaderKey& key,
+                                                SkImage* image) {
     SkASSERT(image);
 
     // The image shader pattern cell will be drawn into a separate device
@@ -52,7 +52,7 @@
     finalMatrix.preConcat(key.fShaderTransform);
     SkRect deviceBounds = SkRect::Make(key.fBBox);
     if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &deviceBounds)) {
-        return nullptr;
+        return SkPDFIndirectReference();
     }
 
     SkRect bitmapBounds = SkRect::Make(image->bounds());
@@ -244,22 +244,23 @@
         }
     }
 
-    auto imageShader = sk_make_sp<SkPDFStream>(patternDevice->content());
-    sk_sp<SkPDFDict> resourceDict = patternDevice->makeResourceDict();
-    SkPDFUtils::PopulateTilingPatternDict(imageShader->dict(), patternBBox,
+    auto imageShader = patternDevice->content();
+    std::unique_ptr<SkPDFDict> resourceDict = patternDevice->makeResourceDict();
+    std::unique_ptr<SkPDFDict> dict = SkPDFMakeDict();
+    SkPDFUtils::PopulateTilingPatternDict(dict.get(), patternBBox,
                                           std::move(resourceDict), finalMatrix);
-    return imageShader;
+    return SkPDFStreamOut(std::move(dict), std::move(imageShader), doc);
 }
 
 // Generic fallback for unsupported shaders:
 //  * allocate a surfaceBBox-sized bitmap
 //  * shade the whole area
 //  * use the result as a bitmap shader
-static sk_sp<SkPDFObject> make_fallback_shader(SkPDFDocument* doc,
-                                               SkShader* shader,
-                                               const SkMatrix& canvasTransform,
-                                               const SkIRect& surfaceBBox,
-                                               SkColor paintColor) {
+static SkPDFIndirectReference make_fallback_shader(SkPDFDocument* doc,
+                                                   SkShader* shader,
+                                                   const SkMatrix& canvasTransform,
+                                                   const SkIRect& surfaceBBox,
+                                                   SkColor paintColor) {
     // TODO(vandebo) This drops SKComposeShader on the floor.  We could
     // handle compose shader by pulling things up to a layer, drawing with
     // the first shader, applying the xfer mode and drawing again with the
@@ -280,7 +281,7 @@
     // MakeImageShader's behavior).
     SkRect shaderRect = SkRect::Make(surfaceBBox);
     if (!SkPDFUtils::InverseTransformBBox(canvasTransform, &shaderRect)) {
-        return nullptr;
+        return SkPDFIndirectReference();
     }
     // Clamp the bitmap size to about 1M pixels
     static const SkScalar kMaxBitmapArea = 1024 * 1024;
@@ -324,18 +325,18 @@
     return paintColor & SK_ColorBLACK;
 }
 
-sk_sp<SkPDFObject> SkPDFMakeShader(SkPDFDocument* doc,
-                                  SkShader* shader,
-                                  const SkMatrix& canvasTransform,
-                                  const SkIRect& surfaceBBox,
-                                  SkColor paintColor) {
+SkPDFIndirectReference SkPDFMakeShader(SkPDFDocument* doc,
+                                       SkShader* shader,
+                                       const SkMatrix& canvasTransform,
+                                       const SkIRect& surfaceBBox,
+                                       SkColor paintColor) {
     SkASSERT(shader);
     SkASSERT(doc);
     if (SkShader::kNone_GradientType != shader->asAGradient(nullptr)) {
         return SkPDFGradientShader::Make(doc, shader, canvasTransform, surfaceBBox);
     }
     if (surfaceBBox.isEmpty()) {
-        return nullptr;
+        return SkPDFIndirectReference();
     }
     SkBitmap image;
     SkPDFImageShaderKey key = {
@@ -350,11 +351,11 @@
     if (SkImage* skimg = shader->isAImage(&key.fShaderTransform, key.fImageTileModes)) {
         key.fBitmapKey = SkBitmapKeyFromImage(skimg);
         SkPDFCanon* canon = doc->canon();
-        sk_sp<SkPDFObject>* shaderPtr = canon->fImageShaderMap.find(key);
+        SkPDFIndirectReference* shaderPtr = canon->fImageShaderMap.find(key);
         if (shaderPtr) {
             return *shaderPtr;
         }
-        sk_sp<SkPDFObject> pdfShader = make_image_shader(doc, key, skimg);
+        SkPDFIndirectReference pdfShader = make_image_shader(doc, key, skimg);
         canon->fImageShaderMap.set(std::move(key), pdfShader);
         return pdfShader;
     }
diff --git a/src/pdf/SkPDFShader.h b/src/pdf/SkPDFShader.h
index b003e34..2a150c3 100644
--- a/src/pdf/SkPDFShader.h
+++ b/src/pdf/SkPDFShader.h
@@ -38,11 +38,11 @@
  *  @param paintColor  Color+Alpha of the paint.  Color is usually ignored,
  *                     unless it is a alpha shader.
  */
-sk_sp<SkPDFObject> SkPDFMakeShader(SkPDFDocument* doc,
-                                  SkShader* shader,
-                                  const SkMatrix& ctm,
-                                  const SkIRect& surfaceBBox,
-                                  SkColor paintColor);
+SkPDFIndirectReference SkPDFMakeShader(SkPDFDocument* doc,
+                                       SkShader* shader,
+                                       const SkMatrix& ctm,
+                                       const SkIRect& surfaceBBox,
+                                       SkColor paintColor);
 
 SK_BEGIN_REQUIRE_DENSE
 struct SkPDFImageShaderKey {
diff --git a/src/pdf/SkPDFTag.cpp b/src/pdf/SkPDFTag.cpp
index 3bc3401..24366ab 100644
--- a/src/pdf/SkPDFTag.cpp
+++ b/src/pdf/SkPDFTag.cpp
@@ -8,199 +8,220 @@
 #include "SkPDFDocumentPriv.h"
 #include "SkPDFTag.h"
 
-namespace {
-
 // Table 333 in PDF 32000-1:2008
-const char* tagNameFromType(SkPDF::DocumentStructureType type) {
+static const char* tag_name_from_type(SkPDF::DocumentStructureType type) {
     switch (type) {
-        case SkPDF::DocumentStructureType::kDocument:
-            return "Document";
-        case SkPDF::DocumentStructureType::kPart:
-            return "Part";
-        case SkPDF::DocumentStructureType::kArt:
-            return "Art";
-        case SkPDF::DocumentStructureType::kSect:
-            return "Sect";
-        case SkPDF::DocumentStructureType::kDiv:
-            return "Div";
-        case SkPDF::DocumentStructureType::kBlockQuote:
-            return "BlockQuote";
-        case SkPDF::DocumentStructureType::kCaption:
-            return "Caption";
-        case SkPDF::DocumentStructureType::kTOC:
-            return "TOC";
-        case SkPDF::DocumentStructureType::kTOCI:
-            return "TOCI";
-        case SkPDF::DocumentStructureType::kIndex:
-            return "Index";
-        case SkPDF::DocumentStructureType::kNonStruct:
-            return "NonStruct";
-        case SkPDF::DocumentStructureType::kPrivate:
-            return "Private";
-        case SkPDF::DocumentStructureType::kH:
-            return "H";
-        case SkPDF::DocumentStructureType::kH1:
-            return "H1";
-        case SkPDF::DocumentStructureType::kH2:
-            return "H2";
-        case SkPDF::DocumentStructureType::kH3:
-            return "H3";
-        case SkPDF::DocumentStructureType::kH4:
-            return "H4";
-        case SkPDF::DocumentStructureType::kH5:
-            return "H5";
-        case SkPDF::DocumentStructureType::kH6:
-            return "H6";
-        case SkPDF::DocumentStructureType::kP:
-            return "P";
-        case SkPDF::DocumentStructureType::kL:
-            return "L";
-        case SkPDF::DocumentStructureType::kLI:
-            return "LI";
-        case SkPDF::DocumentStructureType::kLbl:
-            return "Lbl";
-        case SkPDF::DocumentStructureType::kLBody:
-            return "LBody";
-        case SkPDF::DocumentStructureType::kTable:
-            return "Table";
-        case SkPDF::DocumentStructureType::kTR:
-            return "TR";
-        case SkPDF::DocumentStructureType::kTH:
-            return "TH";
-        case SkPDF::DocumentStructureType::kTD:
-            return "TD";
-        case SkPDF::DocumentStructureType::kTHead:
-            return "THead";
-        case SkPDF::DocumentStructureType::kTBody:
-            return "TBody";
-        case SkPDF::DocumentStructureType::kTFoot:
-            return "TFoot";
-        case SkPDF::DocumentStructureType::kSpan:
-            return "Span";
-        case SkPDF::DocumentStructureType::kQuote:
-            return "Quote";
-        case SkPDF::DocumentStructureType::kNote:
-            return "Note";
-        case SkPDF::DocumentStructureType::kReference:
-            return "Reference";
-        case SkPDF::DocumentStructureType::kBibEntry:
-            return "BibEntry";
-        case SkPDF::DocumentStructureType::kCode:
-            return "Code";
-        case SkPDF::DocumentStructureType::kLink:
-            return "Link";
-        case SkPDF::DocumentStructureType::kAnnot:
-            return "Annot";
-        case SkPDF::DocumentStructureType::kRuby:
-            return "Ruby";
-        case SkPDF::DocumentStructureType::kWarichu:
-            return "Warichu";
-        case SkPDF::DocumentStructureType::kFigure:
-            return "Figure";
-        case SkPDF::DocumentStructureType::kFormula:
-            return "Formula";
-        case SkPDF::DocumentStructureType::kForm:
-            return "Form";
+        #define M(X) case SkPDF::DocumentStructureType::k ## X: return #X
+        M(Document);
+        M(Part);
+        M(Art);
+        M(Sect);
+        M(Div);
+        M(BlockQuote);
+        M(Caption);
+        M(TOC);
+        M(TOCI);
+        M(Index);
+        M(NonStruct);
+        M(Private);
+        M(H);
+        M(H1);
+        M(H2);
+        M(H3);
+        M(H4);
+        M(H5);
+        M(H6);
+        M(P);
+        M(L);
+        M(LI);
+        M(Lbl);
+        M(LBody);
+        M(Table);
+        M(TR);
+        M(TH);
+        M(TD);
+        M(THead);
+        M(TBody);
+        M(TFoot);
+        M(Span);
+        M(Quote);
+        M(Note);
+        M(Reference);
+        M(BibEntry);
+        M(Code);
+        M(Link);
+        M(Annot);
+        M(Ruby);
+        M(RB);
+        M(RT);
+        M(RP);
+        M(Warichu);
+        M(WT);
+        M(WP);
+        M(Figure);
+        M(Formula);
+        M(Form);
+        #undef M
     }
-
     SK_ABORT("bad tag");
     return "";
 }
 
-}  // namespace
+struct SkPDFTagNode {
+    SkPDFTagNode* fChildren = nullptr;
+    size_t fChildCount = 0;
+    struct MarkedContentInfo {
+        unsigned fPageIndex;
+        int fMarkId;
+    };
+    SkTArray<MarkedContentInfo> fMarkedContent;
+    int fNodeId;
+    SkPDF::DocumentStructureType fType;
+    SkPDFIndirectReference fRef;
+    enum State {
+        kUnknown,
+        kYes,
+        kNo,
+    } fCanDiscard = kUnknown;
+};
 
-SkPDFTag::SkPDFTag(int nodeId, SkPDF::DocumentStructureType type, sk_sp<SkPDFTag> parent)
-    : SkPDFDict("StructElem")
-    , fNodeId(nodeId) {
-    insertName("S", tagNameFromType(type));
-    if (parent) {
-        insertObjRef("P", std::move(parent));
+SkPDFTagTree::SkPDFTagTree() : fArena(4 * sizeof(SkPDFTagNode)) {}
+
+SkPDFTagTree::~SkPDFTagTree() = default;
+
+static void copy(const SkPDF::StructureElementNode& node,
+                 SkPDFTagNode* dst,
+                 SkArenaAlloc* arena,
+                 SkTHashMap<int, SkPDFTagNode*>* nodeMap) {
+    nodeMap->set(node.fNodeId, dst);
+    size_t childCount = node.fChildCount;
+    SkPDFTagNode* children = arena->makeArray<SkPDFTagNode>(childCount);
+    dst->fChildCount = childCount;
+    dst->fNodeId = node.fNodeId;
+    dst->fType = node.fType;
+    dst->fChildren = children;
+    for (size_t i = 0; i < childCount; ++i) {
+        copy(node.fChildren[i], &children[i], arena, nodeMap);
     }
 }
 
-SkPDFTag::~SkPDFTag() {
-}
-
-void SkPDFTag::appendChild(sk_sp<SkPDFTag> child) {
-    fChildren.emplace_back(child);
-}
-
-void SkPDFTag::drop() {
-    // Disconnect the tree so as not to cause reference count loops.
-    fChildren.reset();
-
-    SkPDFDict::drop();
-}
-
-void SkPDFTag::addMarkedContent(int pageIndex, int markId) {
-    MarkedContentInfo mark;
-    mark.pageIndex = pageIndex;
-    mark.markId = markId;
-    fMarkedContent.emplace_back(mark);
-}
-
-bool SkPDFTag::prepareTagTreeToEmit(const SkPDFDocument& document) {
-    // Scan the marked content. If it's all on the page, output a
-    // Pg to the dict. If not, we'll use MCR dicts, below.
-    bool allSamePage = true;
-    if (fMarkedContent.count() > 0) {
-        int firstPageIndex = fMarkedContent[0].pageIndex;
-        for (int i = 1; i < fMarkedContent.count(); i++) {
-            if (fMarkedContent[i].pageIndex != firstPageIndex) {
-                allSamePage = false;
-                break;
-            }
-        }
-
-        if (allSamePage) {
-            insertObjRef("Pg", document.getPage(firstPageIndex));
-        }
+void SkPDFTagTree::init(const SkPDF::StructureElementNode* node) {
+    if (node) {
+        fRoot = fArena.make<SkPDFTagNode>();
+        copy(*node, fRoot, &fArena, &fNodeMap);
     }
+}
 
-    // Recursively prepare all child tags of this node.
-    SkTArray<sk_sp<SkPDFTag>> validChildren;
-    for (int i = 0; i < fChildren.count(); i++) {
-        if (fChildren[i]->prepareTagTreeToEmit(document)) {
-            validChildren.push_back(fChildren[i]);
-        }
+void SkPDFTagTree::reset() {
+    fArena.reset();
+    fNodeMap.reset();
+    fMarksPerPage.reset();
+    fRoot = nullptr;
+}
+
+int SkPDFTagTree::getMarkIdForNodeId(int nodeId, unsigned pageIndex) {
+    if (!fRoot) {
+        return -1;
     }
+    SkPDFTagNode** tagPtr = fNodeMap.find(nodeId);
+    if (!tagPtr) {
+        return -1;
+    }
+    SkPDFTagNode* tag = *tagPtr;
+    SkASSERT(tag);
+    while (fMarksPerPage.size() < pageIndex + 1) {
+        fMarksPerPage.push_back();
+    }
+    SkTArray<SkPDFTagNode*>& pageMarks = fMarksPerPage[pageIndex];
+    int markId = pageMarks.count();
+    tag->fMarkedContent.push_back({pageIndex, markId});
+    pageMarks.push_back(tag);
+    return markId;
+}
 
-    // fChildren is no longer needed.
-    fChildren.reset();
-
-    // Now set the kids of this node, which includes both child tags
-    // and marked content IDs.
-    if (validChildren.count() + fMarkedContent.count() == 1) {
-        // If there's just one valid kid, or one marked content,
-        // we can just output the reference directly with no array.
-        if (validChildren.count() == 1) {
-            insertObjRef("K", validChildren[0]);
-        } else {
-            insertInt("K", fMarkedContent[0].markId);
-        }
-        return true;
-    } else if (validChildren.count() + fMarkedContent.count() > 1) {
-        // If there's more than one kid, output them in an array.
-        auto kids = sk_make_sp<SkPDFArray>();
-        for (int i = 0; i < validChildren.count(); i++) {
-            kids->appendObjRef(validChildren[i]);
-        }
-        for (int i = 0; i < fMarkedContent.count(); i++) {
-            if (allSamePage) {
-                kids->appendInt(fMarkedContent[i].markId);
-            } else {
-                auto mcr = sk_make_sp<SkPDFDict>("MCR");
-                mcr->insertObjRef("Pg", document.getPage(fMarkedContent[i].pageIndex));
-                mcr->insertInt("MCID", fMarkedContent[i].markId);
-                kids->appendObject(mcr);
-            }
-        }
-        insertObject("K", kids);
+static bool can_discard(SkPDFTagNode* node) {
+    if (node->fCanDiscard == SkPDFTagNode::kYes) {
         return true;
     }
-
-    // This tag didn't have any marked content or any children with
-    // marked content, so return false. This subtree will be omitted
-    // from the structure tree.
-    return false;
+    if (node->fCanDiscard == SkPDFTagNode::kNo) {
+        return false;
+    }
+    if (!node->fMarkedContent.empty()) {
+        node->fCanDiscard = SkPDFTagNode::kNo;
+        return false;
+    }
+    for (size_t i = 0; i < node->fChildCount; ++i) {
+        if (!can_discard(&node->fChildren[i])) {
+            node->fCanDiscard = SkPDFTagNode::kNo;
+            return false;
+        }
+    }
+    node->fCanDiscard = SkPDFTagNode::kYes;
+    return true;
 }
+
+
+SkPDFIndirectReference prepare_tag_tree_to_emit(SkPDFIndirectReference parent,
+                                                SkPDFTagNode* node,
+                                                SkPDFDocument* doc) {
+    SkPDFIndirectReference ref = doc->reserveRef();
+    std::unique_ptr<SkPDFArray> kids = SkPDFMakeArray();
+    SkPDFTagNode* children = node->fChildren;
+    size_t childCount = node->fChildCount;
+    for (size_t i = 0; i < childCount; ++i) {
+        SkPDFTagNode* child = &children[i];
+        if (!(can_discard(child))) {
+            kids->appendRef(prepare_tag_tree_to_emit(ref, child, doc));
+        }
+    }
+    for (const SkPDFTagNode::MarkedContentInfo& info : node->fMarkedContent) {
+        std::unique_ptr<SkPDFDict> mcr = SkPDFMakeDict("MCR");
+        mcr->insertRef("Pg", doc->getPage(info.fPageIndex));
+        mcr->insertInt("MCID", info.fMarkId);
+        kids->appendObject(std::move(mcr));
+    }
+    node->fRef = ref;
+    SkPDFDict dict("StructElem");
+    dict.insertName("S", tag_name_from_type(node->fType));
+    dict.insertRef("P", parent);
+    dict.insertObject("K", std::move(kids));
+    return doc->emit(dict, ref);
+}
+
+SkPDFIndirectReference SkPDFTagTree::makeStructTreeRoot(SkPDFDocument* doc) {
+    if (!fRoot) {
+        return SkPDFIndirectReference();
+    }
+    if (can_discard(fRoot)) {
+        SkDEBUGFAIL("PDF has tag tree but no marked content.");
+    }
+    SkPDFIndirectReference ref = doc->reserveRef();
+
+    unsigned pageCount = SkToUInt(doc->pageCount());
+
+    // Build the StructTreeRoot.
+    SkPDFDict structTreeRoot("StructTreeRoot");
+    structTreeRoot.insertRef("K", prepare_tag_tree_to_emit(ref, fRoot, doc));
+    structTreeRoot.insertInt("ParentTreeNextKey", SkToInt(pageCount));
+
+    // Build the parent tree, which is a mapping from the marked
+    // content IDs on each page to their corressponding tags.
+    SkPDFDict parentTree("ParentTree");
+    auto parentTreeNums = SkPDFMakeArray();
+
+    SkASSERT(fMarksPerPage.size() <= pageCount);
+    for (size_t j = 0; j < fMarksPerPage.size(); ++j) {
+        const SkTArray<SkPDFTagNode*>& pageMarks = fMarksPerPage[j];
+        SkPDFArray markToTagArray;
+        for (SkPDFTagNode* mark : pageMarks) {
+            SkASSERT(mark->fRef);
+            markToTagArray.appendRef(mark->fRef);
+        }
+        parentTreeNums->appendInt(j);
+        parentTreeNums->appendRef(doc->emit(markToTagArray));
+    }
+    parentTree.insertObject("Nums", std::move(parentTreeNums));
+    structTreeRoot.insertRef("ParentTree", doc->emit(parentTree));
+    return doc->emit(structTreeRoot, ref);
+}
+
diff --git a/src/pdf/SkPDFTag.h b/src/pdf/SkPDFTag.h
index e6bf5b3..771e065 100644
--- a/src/pdf/SkPDFTag.h
+++ b/src/pdf/SkPDFTag.h
@@ -8,60 +8,31 @@
 #ifndef SkPDFTag_DEFINED
 #define SkPDFTag_DEFINED
 
-#include "SkDocument.h"
-#include "SkPDFTypes.h"
-#include "SkRefCnt.h"
+#include "SkPDFDocument.h"
+#include "SkTArray.h"
+#include "SkArenaAlloc.h"
+#include "SkTHash.h"
 
 class SkPDFDocument;
+struct SkPDFTagNode;
 
-/** \class SkPDFTag
-
-    A PDF Tag represents a semantic tag in the tag tree for an
-    accessible tagged PDF. Documents can create an accessible PDF by
-    creating a tree of SkPDFTags representing the semantic tree
-    structure of the overall document, and then calling
-    SkPDF::SetNodeId with the SkCanvas used to draw to the page and
-    the same corresponding node IDs to mark the content for each
-    page. It's allowed for the marked content for one tag to span
-    multiple pages.
-*/
-class SkPDFTag final : public SkPDFDict {
+class SkPDFTagTree {
 public:
-    SkPDFTag(int nodeId, SkPDF::DocumentStructureType type, sk_sp<SkPDFTag> parent);
-    ~SkPDFTag() override;
-
-    void appendChild(sk_sp<SkPDFTag> child);
+    SkPDFTagTree();
+    ~SkPDFTagTree();
+    void init(const SkPDF::StructureElementNode*);
+    void reset();
+    int getMarkIdForNodeId(int nodeId, unsigned pageIndex);
+    SkPDFIndirectReference makeStructTreeRoot(SkPDFDocument* doc);
 
 private:
-    friend class SkPDFDocument;
+    SkArenaAlloc fArena;
+    SkTHashMap<int, SkPDFTagNode*> fNodeMap;
+    SkPDFTagNode* fRoot = nullptr;
+    SkTArray<SkTArray<SkPDFTagNode*>> fMarksPerPage;
 
-    void drop() override;
-
-    void addMarkedContent(int pageIndex, int markId);
-
-    // Should be called after all content has been emitted. Fills in
-    // all of the SkPDFDict fields in this tag and all descendants.
-    // Returns true if this tag is valid, and false if no tag in this
-    // subtree was referred to by any marked content.
-    bool prepareTagTreeToEmit(const SkPDFDocument& document);
-
-    struct MarkedContentInfo {
-        int pageIndex;
-        int markId;
-    };
-
-    // This tag's node ID, which must correspond to the node ID set
-    // on the SkCanvas when content inside this tag is drawn.
-    // The node IDs are arbitrary and are not output to the PDF.
-    int fNodeId;
-
-    // The children of this tag. Some tags like lists and tables require
-    // a particular hierarchical structure, similar to HTML.
-    SkTArray<sk_sp<SkPDFTag>> fChildren;
-
-    // An array consisting of a [page index, mark ID] pair for each piece
-    // of marked content associated with this tag.
-    SkTArray<MarkedContentInfo> fMarkedContent;
+    SkPDFTagTree(const SkPDFTagTree&) = delete;
+    SkPDFTagTree& operator=(const SkPDFTagTree&) = delete;
 };
 
 #endif
diff --git a/src/pdf/SkPDFTypes.cpp b/src/pdf/SkPDFTypes.cpp
index 4d9a71f..76eb58e 100644
--- a/src/pdf/SkPDFTypes.cpp
+++ b/src/pdf/SkPDFTypes.cpp
@@ -9,7 +9,10 @@
 
 #include "SkData.h"
 #include "SkDeflate.h"
+#include "SkExecutor.h"
 #include "SkMakeUnique.h"
+#include "SkPDFDocumentPriv.h"
+#include "SkPDFUnion.h"
 #include "SkPDFUtils.h"
 #include "SkStream.h"
 #include "SkStreamPriv.h"
@@ -31,10 +34,9 @@
         case Type::kStringSkS:
             fSkString.destroy();
             return;
-        case Type::kObjRef:
         case Type::kObject:
             SkASSERT(fObject);
-            fObject->unref();
+            delete fObject;
             return;
         default:
             return;
@@ -64,7 +66,6 @@
         case Type::kStringSkS:
             u.fSkString.init(fSkString.get());
             return u;
-        case Type::kObjRef:
         case Type::kObject:
             SkRef(u.fObject);
             return u;
@@ -195,14 +196,11 @@
         case Type::kStringSkS:
             write_string(stream, fSkString.get().c_str(), fSkString.get().size());
             return;
-        case Type::kObjRef:
-            stream->writeDecAsText(fObject->fIndirectReference.fValue);
-            stream->writeText(" 0 R");  // Generation number is always 0.
-            return;
         case Type::kObject:
             fObject->emitObject(stream);
             return;
         case Type::kRef:
+            SkASSERT(fIntValue >= 0);
             stream->writeDecAsText(fIntValue);
             stream->writeText(" 0 R");  // Generation number is always 0.
             return;
@@ -211,30 +209,6 @@
     }
 }
 
-void SkPDFUnion::addResources(SkPDFObjNumMap* objNumMap) const {
-    switch (fType) {
-        case Type::kInt:
-        case Type::kColorComponent:
-        case Type::kColorComponentF:
-        case Type::kBool:
-        case Type::kScalar:
-        case Type::kName:
-        case Type::kString:
-        case Type::kNameSkS:
-        case Type::kStringSkS:
-        case Type::kRef:
-            return;  // These have no resources.
-        case Type::kObjRef:
-            objNumMap->addObjectRecursively(fObject);
-            return;
-        case Type::kObject:
-            fObject->addResources(objNumMap);
-            return;
-        default:
-            SkDEBUGFAIL("SkPDFUnion::addResources with bad type");
-    }
-}
-
 SkPDFUnion SkPDFUnion::Int(int32_t value) { return SkPDFUnion(Type::kInt, value); }
 
 SkPDFUnion SkPDFUnion::ColorComponent(uint8_t value) {
@@ -272,14 +246,7 @@
 
 SkPDFUnion SkPDFUnion::String(SkString s) { return SkPDFUnion(Type::kStringSkS, std::move(s)); }
 
-SkPDFUnion SkPDFUnion::ObjRef(sk_sp<SkPDFObject> objSp) {
-    SkPDFUnion u(Type::kObjRef);
-    SkASSERT(objSp.get());
-    u.fObject = objSp.release();  // take ownership into union{}
-    return u;
-}
-
-SkPDFUnion SkPDFUnion::Object(sk_sp<SkPDFObject> objSp) {
+SkPDFUnion SkPDFUnion::Object(std::unique_ptr<SkPDFObject> objSp) {
     SkPDFUnion u(Type::kObject);
     SkASSERT(objSp.get());
     u.fObject = objSp.release();  // take ownership into union{}
@@ -296,21 +263,13 @@
 void SkPDFAtom::emitObject(SkWStream* stream) const {
     fValue.emitObject(stream);
 }
-void SkPDFAtom::addResources(SkPDFObjNumMap* map) const {
-    fValue.addResources(map);
-}
 #endif  // 0
 
 ////////////////////////////////////////////////////////////////////////////////
 
-SkPDFArray::SkPDFArray() { SkDEBUGCODE(fDumped = false;) }
+SkPDFArray::SkPDFArray() {}
 
-SkPDFArray::~SkPDFArray() { this->drop(); }
-
-void SkPDFArray::drop() {
-    fValues = std::vector<SkPDFUnion>();
-    SkDEBUGCODE(fDumped = true;)
-}
+SkPDFArray::~SkPDFArray() {}
 
 size_t SkPDFArray::size() const { return fValues.size(); }
 
@@ -319,7 +278,6 @@
 }
 
 void SkPDFArray::emitObject(SkWStream* stream) const {
-    SkASSERT(!fDumped);
     stream->writeText("[");
     for (size_t i = 0; i < fValues.size(); i++) {
         fValues[i].emitObject(stream);
@@ -330,13 +288,6 @@
     stream->writeText("]");
 }
 
-void SkPDFArray::addResources(SkPDFObjNumMap* catalog) const {
-    SkASSERT(!fDumped);
-    for (const SkPDFUnion& value : fValues) {
-        value.addResources(catalog);
-    }
-}
-
 void SkPDFArray::append(SkPDFUnion&& value) {
     fValues.emplace_back(std::move(value));
 }
@@ -373,29 +324,19 @@
     this->append(SkPDFUnion::String(value));
 }
 
-void SkPDFArray::appendObject(sk_sp<SkPDFObject> objSp) {
+void SkPDFArray::appendObject(std::unique_ptr<SkPDFObject>&& objSp) {
     this->append(SkPDFUnion::Object(std::move(objSp)));
 }
 
-void SkPDFArray::appendObjRef(sk_sp<SkPDFObject> objSp) {
-    this->append(SkPDFUnion::ObjRef(std::move(objSp)));
-}
-
 void SkPDFArray::appendRef(SkPDFIndirectReference ref) {
     this->append(SkPDFUnion::Ref(ref));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
-SkPDFDict::~SkPDFDict() { this->drop(); }
-
-void SkPDFDict::drop() {
-    fRecords = std::vector<SkPDFDict::Record>();
-    SkDEBUGCODE(fDumped = true;)
-}
+SkPDFDict::~SkPDFDict() {}
 
 SkPDFDict::SkPDFDict(const char type[]) {
-    SkDEBUGCODE(fDumped = false;)
     if (type) {
         this->insertName("Type", type);
     }
@@ -408,25 +349,17 @@
 }
 
 void SkPDFDict::emitAll(SkWStream* stream) const {
-    SkASSERT(!fDumped);
-    for (size_t i = 0; i < fRecords.size(); i++) {
-        fRecords[i].fKey.emitObject(stream);
+    for (size_t i = 0; i < fRecords.size(); ++i) {
+        const std::pair<SkPDFUnion, SkPDFUnion>& record = fRecords[i];
+        record.first.emitObject(stream);
         stream->writeText(" ");
-        fRecords[i].fValue.emitObject(stream);
+        record.second.emitObject(stream);
         if (i + 1 < fRecords.size()) {
             stream->writeText("\n");
         }
     }
 }
 
-void SkPDFDict::addResources(SkPDFObjNumMap* catalog) const {
-    SkASSERT(!fDumped);
-    for (size_t i = 0; i < fRecords.size(); i++) {
-        fRecords[i].fKey.addResources(catalog);
-        fRecords[i].fValue.addResources(catalog);
-    }
-}
-
 size_t SkPDFDict::size() const { return fRecords.size(); }
 
 void SkPDFDict::reserve(int n) {
@@ -434,36 +367,27 @@
 }
 
 void SkPDFDict::insertRef(const char key[], SkPDFIndirectReference ref) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Ref(ref)});
+    fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Ref(ref));
 }
 
 void SkPDFDict::insertRef(SkString key, SkPDFIndirectReference ref) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(std::move(key)), SkPDFUnion::Ref(ref)});
+    fRecords.emplace_back(SkPDFUnion::Name(std::move(key)), SkPDFUnion::Ref(ref));
 }
 
-void SkPDFDict::insertObjRef(const char key[], sk_sp<SkPDFObject> objSp) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::ObjRef(std::move(objSp))});
+void SkPDFDict::insertObject(const char key[], std::unique_ptr<SkPDFObject>&& objSp) {
+    fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Object(std::move(objSp)));
 }
-
-void SkPDFDict::insertObjRef(SkString key, sk_sp<SkPDFObject> objSp) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(std::move(key)),
-                                 SkPDFUnion::ObjRef(std::move(objSp))});
-}
-
-void SkPDFDict::insertObject(const char key[], sk_sp<SkPDFObject> objSp) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Object(std::move(objSp))});
-}
-void SkPDFDict::insertObject(SkString key, sk_sp<SkPDFObject> objSp) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(std::move(key)),
-                                 SkPDFUnion::Object(std::move(objSp))});
+void SkPDFDict::insertObject(SkString key, std::unique_ptr<SkPDFObject>&& objSp) {
+    fRecords.emplace_back(SkPDFUnion::Name(std::move(key)),
+                          SkPDFUnion::Object(std::move(objSp)));
 }
 
 void SkPDFDict::insertBool(const char key[], bool value) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Bool(value)});
+    fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Bool(value));
 }
 
 void SkPDFDict::insertInt(const char key[], int32_t value) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Int(value)});
+    fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Int(value));
 }
 
 void SkPDFDict::insertInt(const char key[], size_t value) {
@@ -471,173 +395,110 @@
 }
 
 void SkPDFDict::insertColorComponentF(const char key[], SkScalar value) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::ColorComponentF(value)});
+    fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::ColorComponentF(value));
 }
 
 void SkPDFDict::insertScalar(const char key[], SkScalar value) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Scalar(value)});
+    fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Scalar(value));
 }
 
 void SkPDFDict::insertName(const char key[], const char name[]) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Name(name)});
+    fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Name(name));
 }
 
 void SkPDFDict::insertName(const char key[], SkString name) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::Name(std::move(name))});
+    fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::Name(std::move(name)));
 }
 
 void SkPDFDict::insertString(const char key[], const char value[]) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::String(value)});
+    fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::String(value));
 }
 
 void SkPDFDict::insertString(const char key[], SkString value) {
-    fRecords.emplace_back(Record{SkPDFUnion::Name(key), SkPDFUnion::String(std::move(value))});
+    fRecords.emplace_back(SkPDFUnion::Name(key), SkPDFUnion::String(std::move(value)));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
-SkPDFSharedStream::SkPDFSharedStream(std::unique_ptr<SkStreamAsset> data)
-    : fAsset(std::move(data)) {
-    SkASSERT(fAsset);
-}
-
-SkPDFSharedStream::~SkPDFSharedStream() { this->drop(); }
-
-void SkPDFSharedStream::drop() {
-    fAsset = nullptr;
-    fDict.drop();
-}
-
-#ifdef SK_PDF_LESS_COMPRESSION
-void SkPDFSharedStream::emitObject(SkWStream* stream) const {
-    SkASSERT(fAsset);
-    std::unique_ptr<SkStreamAsset> dup(fAsset->duplicate());
-    SkASSERT(dup && dup->hasLength());
-    size_t length = dup->getLength();
-    stream->writeText("<<");
-    fDict.emitAll(stream);
-    stream->writeText("\n");
-    SkPDFUnion::Name("Length").emitObject(stream);
-    stream->writeText(" ");
-    SkPDFUnion::Int(length).emitObject(stream);
-    stream->writeText("\n>>stream\n");
-    SkStreamCopy(stream, dup.get());
-    stream->writeText("\nendstream");
-}
-#else
-void SkPDFSharedStream::emitObject(SkWStream* stream) const {
-    SkASSERT(fAsset);
-    SkDynamicMemoryWStream buffer;
-    SkDeflateWStream deflateWStream(&buffer);
-    // Since emitObject is const, this function doesn't change the dictionary.
-    std::unique_ptr<SkStreamAsset> dup(fAsset->duplicate());  // Cheap copy
-    SkASSERT(dup);
-    SkStreamCopy(&deflateWStream, dup.get());
-    deflateWStream.finalize();
-    size_t length = buffer.bytesWritten();
-    stream->writeText("<<");
-    fDict.emitAll(stream);
-    stream->writeText("\n");
-    SkPDFUnion::Name("Length").emitObject(stream);
-    stream->writeText(" ");
-    SkPDFUnion::Int(length).emitObject(stream);
-    stream->writeText("\n");
-    SkPDFUnion::Name("Filter").emitObject(stream);
-    stream->writeText(" ");
-    SkPDFUnion::Name("FlateDecode").emitObject(stream);
-    stream->writeText(">>");
-    stream->writeText(" stream\n");
-    buffer.writeToAndReset(stream);
-    stream->writeText("\nendstream");
-}
-#endif
-
-void SkPDFSharedStream::addResources(
-        SkPDFObjNumMap* catalog) const {
-    SkASSERT(fAsset);
-    fDict.addResources(catalog);
-}
 
 
-////////////////////////////////////////////////////////////////////////////////
-
-SkPDFStream::SkPDFStream(sk_sp<SkData> data) {
-    this->setData(skstd::make_unique<SkMemoryStream>(std::move(data)));
-}
-
-SkPDFStream::SkPDFStream(std::unique_ptr<SkStreamAsset> stream) {
-    this->setData(std::move(stream));
-}
-
-SkPDFStream::SkPDFStream() {}
-
-SkPDFStream::~SkPDFStream() {}
-
-void SkPDFStream::addResources(SkPDFObjNumMap* catalog) const {
-    SkASSERT(fCompressedData);
-    fDict.addResources(catalog);
-}
-
-void SkPDFStream::drop() {
-    fCompressedData.reset(nullptr);
-    fDict.drop();
-}
-
-void SkPDFStream::emitObject(SkWStream* stream) const {
-    SkASSERT(fCompressedData);
-    fDict.emitObject(stream);
-    // duplicate (a cheap operation) preserves const on fCompressedData.
-    std::unique_ptr<SkStreamAsset> dup(fCompressedData->duplicate());
-    SkASSERT(dup);
-    SkASSERT(dup->hasLength());
-    stream->writeText(" stream\n");
-    stream->writeStream(dup.get(), dup->getLength());
-    stream->writeText("\nendstream");
-}
-
-void SkPDFStream::setData(std::unique_ptr<SkStreamAsset> stream) {
-    SkASSERT(!fCompressedData);  // Only call this function once.
-    SkASSERT(stream);
+static void serialize_stream(const SkPDFDict* origDict,
+                             SkStreamAsset* stream,
+                             bool deflate,
+                             SkPDFDocument* doc,
+                             SkPDFIndirectReference ref) {
     // Code assumes that the stream starts at the beginning.
+    SkASSERT(stream && stream->hasLength());
 
-    #ifdef SK_PDF_LESS_COMPRESSION
-    fCompressedData = std::move(stream);
-    SkASSERT(fCompressedData && fCompressedData->hasLength());
-    fDict.insertInt("Length", fCompressedData->getLength());
-    #else
+    std::unique_ptr<SkStreamAsset> tmp;
+    SkPDFDict dict;
+    static const size_t kMinimumSavings = strlen("/Filter_/FlateDecode_");
+    if (deflate && stream->getLength() > kMinimumSavings) {
+        SkDynamicMemoryWStream compressedData;
+        SkDeflateWStream deflateWStream(&compressedData);
+        SkStreamCopy(&deflateWStream, stream);
+        deflateWStream.finalize();
+        #ifdef SK_PDF_BASE85_BINARY
+        {
+            SkPDFUtils::Base85Encode(compressedData.detachAsStream(), &compressedData);
+            tmp = compressedData.detachAsStream();
+            stream = tmp.get();
+            auto filters = SkPDFMakeArray();
+            filters->appendName("ASCII85Decode");
+            filters->appendName("FlateDecode");
+            dict.insertObject("Filter", std::move(filters));
+        }
+        #else
+        if (stream->getLength() > compressedData.bytesWritten() + kMinimumSavings) {
+            tmp = compressedData.detachAsStream();
+            stream = tmp.get();
+            dict.insertName("Filter", "FlateDecode");
+        } else {
+            SkAssertResult(stream->rewind());
+        }
+        #endif
 
-    SkASSERT(stream->hasLength());
-    SkDynamicMemoryWStream compressedData;
-    SkDeflateWStream deflateWStream(&compressedData);
-    if (stream->getLength() > 0) {
-        SkStreamCopy(&deflateWStream, stream.get());
     }
-    deflateWStream.finalize();
-    size_t compressedLength = compressedData.bytesWritten();
-    size_t originalLength = stream->getLength();
+    dict.insertInt("Length", stream->getLength());
 
-    if (originalLength <= compressedLength + strlen("/Filter_/FlateDecode_")) {
-        SkAssertResult(stream->rewind());
-        fCompressedData = std::move(stream);
-        fDict.insertInt("Length", originalLength);
-        return;
+    SkWStream* dst = doc->beginObject(ref);
+    dst->writeText("<<");
+    if (origDict) {
+        origDict->emitAll(dst);
+        // dst->writeText("\n"); TODO
     }
-    fCompressedData = compressedData.detachAsStream();
-    fDict.insertName("Filter", "FlateDecode");
-    fDict.insertInt("Length", compressedLength);
-    #endif
+    dict.emitAll(dst);
+    dst->writeText(">> stream\n");
+    dst->writeStream(stream, stream->getLength());
+    dst->writeText("\nendstream");
+    doc->endObject();
+}
+
+SkPDFIndirectReference SkPDFStreamOut(std::unique_ptr<SkPDFDict> dict,
+                                      std::unique_ptr<SkStreamAsset> content,
+                                      SkPDFDocument* doc,
+                                      bool deflate) {
+    SkPDFIndirectReference ref = doc->reserveRef();
+    if (SkExecutor* executor = doc->executor()) {
+        SkPDFDict* dictPtr = dict.release();
+        SkStreamAsset* contentPtr = content.release();
+        // Pass ownership of both pointers into a std::function, which should
+        // only be executed once.
+        doc->incrementJobCount();
+        executor->add([dictPtr, contentPtr, deflate, doc, ref]() {
+            serialize_stream(dictPtr, contentPtr, deflate, doc, ref);
+            delete dictPtr;
+            delete contentPtr;
+            doc->signalJobComplete();
+        });
+        return ref;
+    }
+    serialize_stream(dict.get(), content.get(), deflate, doc, ref);
+    return ref;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
-void SkPDFObjNumMap::addObjectRecursively(SkPDFObject* obj) {
-    if (obj && obj->fIndirectReference.fValue == -1) {
-        obj->fIndirectReference.fValue = fNextObjectNumber++;
-        fObjects.emplace_back(sk_ref_sp(obj));
-        obj->addResources(this);
-    }
-}
-
 #ifdef SK_PDF_IMAGE_STATS
 std::atomic<int> gDrawImageCalls(0);
 std::atomic<int> gJpegImageObjects(0);
diff --git a/src/pdf/SkPDFTypes.h b/src/pdf/SkPDFTypes.h
index 935d1b7..79c0fe5 100644
--- a/src/pdf/SkPDFTypes.h
+++ b/src/pdf/SkPDFTypes.h
@@ -13,20 +13,25 @@
 #include "SkTHash.h"
 #include "SkTo.h"
 #include "SkTypes.h"
+#include "SkMakeUnique.h"
 
 #include <new>
 #include <type_traits>
 #include <utility>
 #include <vector>
+#include <memory>
 
 class SkData;
+class SkPDFArray;
 class SkPDFCanon;
+class SkPDFDict;
 class SkPDFDocument;
-class SkPDFObjNumMap;
 class SkPDFObject;
+class SkPDFUnion;
 class SkStreamAsset;
 class SkString;
 class SkWStream;
+struct SkPDFObjectSerializer;
 
 #ifdef SK_PDF_IMAGE_STATS
     #include <atomic>
@@ -34,6 +39,7 @@
 
 struct SkPDFIndirectReference {
     int fValue = -1;
+    explicit operator bool() { return fValue != -1; }
 };
 
 inline static bool operator==(SkPDFIndirectReference u, SkPDFIndirectReference v) {
@@ -51,8 +57,10 @@
     which are common in the PDF format.
 
 */
-class SkPDFObject : public SkRefCnt {
+class SkPDFObject {
 public:
+    SkPDFObject() = default;
+
     /** Subclasses must implement this method to print the object to the
      *  PDF file.
      *  @param catalog  The object catalog to use.
@@ -60,171 +68,17 @@
      */
     virtual void emitObject(SkWStream* stream) const = 0;
 
-    /**
-     *  Adds all transitive dependencies of this object to the
-     *  catalog.  Implementations should respect the catalog's object
-     *  substitution map.
-     */
-    virtual void addResources(SkPDFObjNumMap* catalog) const {}
-
-    /**
-     *  Release all resources associated with this SkPDFObject.  It is
-     *  an error to call emitObject() or addResources() after calling
-     *  drop().
-     */
-    virtual void drop() {}
-
-    virtual ~SkPDFObject() {}
-
-    SkPDFIndirectReference fIndirectReference;
+    virtual ~SkPDFObject() = default;
 
 private:
-    typedef SkRefCnt INHERITED;
+    SkPDFObject(SkPDFObject&&) = delete;
+    SkPDFObject(const SkPDFObject&) = delete;
+    SkPDFObject& operator=(SkPDFObject&&) = delete;
+    SkPDFObject& operator=(const SkPDFObject&) = delete;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 
-template <class T>
-class SkStorageFor {
-public:
-    const T& get() const { return *reinterpret_cast<const T*>(&fStore); }
-    T& get() { return *reinterpret_cast<T*>(&fStore); }
-    // Up to caller to keep track of status.
-    template<class... Args> void init(Args&&... args) {
-        new (&this->get()) T(std::forward<Args>(args)...);
-    }
-    void destroy() { this->get().~T(); }
-private:
-    typename std::aligned_storage<sizeof(T), alignof(T)>::type fStore;
-};
-
-/**
-   A SkPDFUnion is a non-virtualized implementation of the
-   non-compound, non-specialized PDF Object types: Name, String,
-   Number, Boolean.
- */
-class SkPDFUnion {
-public:
-    // Move contstructor and assignemnt operator destroy the argument
-    // and steal their references (if needed).
-    SkPDFUnion(SkPDFUnion&& other);
-    SkPDFUnion& operator=(SkPDFUnion&& other);
-
-    ~SkPDFUnion();
-
-    /** The following nine functions are the standard way of creating
-        SkPDFUnion objects. */
-
-    static SkPDFUnion Int(int32_t);
-
-    static SkPDFUnion Int(size_t v) { return SkPDFUnion::Int(SkToS32(v)); }
-
-    static SkPDFUnion Bool(bool);
-
-    static SkPDFUnion Scalar(SkScalar);
-
-    static SkPDFUnion ColorComponent(uint8_t);
-
-    static SkPDFUnion ColorComponentF(float);
-
-    /** These two functions do NOT take ownership of char*, and do NOT
-        copy the string.  Suitable for passing in static const
-        strings. For example:
-          SkPDFUnion n = SkPDFUnion::Name("Length");
-          SkPDFUnion u = SkPDFUnion::String("Identity"); */
-
-    /** SkPDFUnion::Name(const char*) assumes that the passed string
-        is already a valid name (that is: it has no control or
-        whitespace characters).  This will not copy the name. */
-    static SkPDFUnion Name(const char*);
-
-    /** SkPDFUnion::String will encode the passed string.  This will
-        not copy the name. */
-    static SkPDFUnion String(const char*);
-
-    /** SkPDFUnion::Name(SkString) does not assume that the
-        passed string is already a valid name and it will escape the
-        string. */
-    static SkPDFUnion Name(SkString);
-
-    /** SkPDFUnion::String will encode the passed string. */
-    static SkPDFUnion String(SkString);
-
-    static SkPDFUnion Object(sk_sp<SkPDFObject>);
-    static SkPDFUnion ObjRef(sk_sp<SkPDFObject>);
-
-    static SkPDFUnion Ref(SkPDFIndirectReference);
-
-    /** These two non-virtual methods mirror SkPDFObject's
-        corresponding virtuals. */
-    void emitObject(SkWStream*) const;
-    void addResources(SkPDFObjNumMap*) const;
-
-    bool isName() const;
-
-private:
-    union {
-        int32_t fIntValue;
-        bool fBoolValue;
-        SkScalar fScalarValue;
-        const char* fStaticString;
-        SkStorageFor<SkString> fSkString;
-        SkPDFObject* fObject;
-    };
-    enum class Type : char {
-        /** It is an error to call emitObject() or addResources() on an
-            kDestroyed object. */
-        kDestroyed = 0,
-        kInt,
-        kColorComponent,
-        kColorComponentF,
-        kBool,
-        kScalar,
-        kName,
-        kString,
-        kNameSkS,
-        kStringSkS,
-        kObjRef,
-        kObject,
-        kRef,
-    };
-    Type fType;
-
-    SkPDFUnion(Type);
-    SkPDFUnion(Type, int32_t);
-    SkPDFUnion(Type, bool);
-    SkPDFUnion(Type, SkScalar);
-    SkPDFUnion(Type, SkString);
-    // We do not now need copy constructor and copy assignment, so we
-    // will disable this functionality.
-    SkPDFUnion& operator=(const SkPDFUnion&) = delete;
-    SkPDFUnion(const SkPDFUnion&) = delete;
-};
-static_assert(sizeof(SkString) == sizeof(void*), "SkString_size");
-
-// Exposed for unit testing.
-void SkPDFWriteString(SkWStream* wStream, const char* cin, size_t len);
-
-////////////////////////////////////////////////////////////////////////////////
-
-#if 0  // Enable if needed.
-/** This class is a SkPDFUnion with SkPDFObject virtuals attached.
-    The only use case of this is when a non-compound PDF object is
-    referenced indirectly. */
-class SkPDFAtom final : public SkPDFObject {
-public:
-    void emitObject(SkWStream* stream) final;
-    void addResources(SkPDFObjNumMap* const final;
-    SkPDFAtom(SkPDFUnion&& v) : fValue(std::move(v) {}
-
-private:
-    const SkPDFUnion fValue;
-    typedef SkPDFObject INHERITED;
-};
-#endif  // 0
-
-////////////////////////////////////////////////////////////////////////////////
-
 /** \class SkPDFArray
 
     An array object in a PDF.
@@ -238,8 +92,6 @@
 
     // The SkPDFObject interface.
     void emitObject(SkWStream* stream) const override;
-    void addResources(SkPDFObjNumMap*) const override;
-    void drop() override;
 
     /** The size of the array.
      */
@@ -261,14 +113,12 @@
     void appendName(SkString);
     void appendString(const char[]);
     void appendString(SkString);
-    void appendObject(sk_sp<SkPDFObject>);
-    void appendObjRef(sk_sp<SkPDFObject>);
+    void appendObject(std::unique_ptr<SkPDFObject>&&);
     void appendRef(SkPDFIndirectReference);
 
 private:
     std::vector<SkPDFUnion> fValues;
     void append(SkPDFUnion&& value);
-    SkDEBUGCODE(bool fDumped;)
 };
 
 static inline void SkPDFArray_Append(SkPDFArray* a, int v) { a->appendInt(v); }
@@ -276,14 +126,16 @@
 static inline void SkPDFArray_Append(SkPDFArray* a, SkScalar v) { a->appendScalar(v); }
 
 template <typename T, typename... Args>
-inline void SkPDFArray_Append(SkPDFArray* a, T v, Args... args) {
+static inline void SkPDFArray_Append(SkPDFArray* a, T v, Args... args) {
     SkPDFArray_Append(a, v);
     SkPDFArray_Append(a, args...);
 }
 
+static inline void SkPDFArray_Append(SkPDFArray* a) {}
+
 template <typename... Args>
-inline sk_sp<SkPDFArray> SkPDFMakeArray(Args... args) {
-    auto ret = sk_make_sp<SkPDFArray>();
+static inline std::unique_ptr<SkPDFArray> SkPDFMakeArray(Args... args) {
+    std::unique_ptr<SkPDFArray> ret(new SkPDFArray());
     ret->reserve(sizeof...(Args));
     SkPDFArray_Append(ret.get(), args...);
     return ret;
@@ -293,7 +145,7 @@
 
     A dictionary object in a PDF.
 */
-class SkPDFDict : public SkPDFObject {
+class SkPDFDict final : public SkPDFObject {
 public:
     /** Create a PDF dictionary.
      *  @param type   The value of the Type entry, nullptr for no type.
@@ -304,8 +156,6 @@
 
     // The SkPDFObject interface.
     void emitObject(SkWStream* stream) const override;
-    void addResources(SkPDFObjNumMap*) const override;
-    void drop() override;
 
     /** The size of the dictionary.
      */
@@ -318,10 +168,8 @@
      *  @param key   The text of the key for this dictionary entry.
      *  @param value The value for this dictionary entry.
      */
-    void insertObject(const char key[], sk_sp<SkPDFObject>);
-    void insertObject(SkString, sk_sp<SkPDFObject>);
-    void insertObjRef(const char key[], sk_sp<SkPDFObject>);
-    void insertObjRef(SkString, sk_sp<SkPDFObject>);
+    void insertObject(const char key[], std::unique_ptr<SkPDFObject>&&);
+    void insertObject(SkString, std::unique_ptr<SkPDFObject>&&);
     void insertRef(const char key[], SkPDFIndirectReference);
     void insertRef(SkString, SkPDFIndirectReference);
 
@@ -344,103 +192,23 @@
     void emitAll(SkWStream* stream) const;
 
 private:
-    struct Record {
-        SkPDFUnion fKey;
-        SkPDFUnion fValue;
-    };
-    std::vector<Record> fRecords;
-    SkDEBUGCODE(bool fDumped;)
+    std::vector<std::pair<SkPDFUnion, SkPDFUnion>> fRecords;
 };
 
-/** \class SkPDFSharedStream
+static inline std::unique_ptr<SkPDFDict> SkPDFMakeDict(const char* type = nullptr) {
+    return std::unique_ptr<SkPDFDict>(new SkPDFDict(type));
+}
 
-    This class takes an asset and assumes that it is backed by
-    long-lived shared data (for example, an open file
-    descriptor). That is: no memory savings can be made by holding on
-    to a compressed version instead.
- */
-class SkPDFSharedStream final : public SkPDFObject {
-public:
-    SkPDFSharedStream(std::unique_ptr<SkStreamAsset> data);
-    ~SkPDFSharedStream() override;
-    SkPDFDict* dict() { return &fDict; }
-    void emitObject(SkWStream*) const override;
-    void addResources(SkPDFObjNumMap*) const override;
-    void drop() override;
+#ifdef SK_PDF_LESS_COMPRESSION
+    static constexpr bool kSkPDFDefaultDoDeflate = false;
+#else
+    static constexpr bool kSkPDFDefaultDoDeflate = true;
+#endif
 
-private:
-    std::unique_ptr<SkStreamAsset> fAsset;
-    SkPDFDict fDict;
-    typedef SkPDFObject INHERITED;
-};
-
-/** \class SkPDFStream
-
-    This class takes an asset and assumes that it is the only owner of
-    the asset's data.  It immediately compresses the asset to save
-    memory.
- */
-
-class SkPDFStream final : public SkPDFObject {
-
-public:
-    /** Create a PDF stream. A Length entry is automatically added to the
-     *  stream dictionary.
-     *  @param data   The data part of the stream.
-     *  @param stream The data part of the stream. */
-    explicit SkPDFStream(sk_sp<SkData> data);
-    explicit SkPDFStream(std::unique_ptr<SkStreamAsset> stream);
-    ~SkPDFStream() override;
-
-    SkPDFDict* dict() { return &fDict; }
-
-    // The SkPDFObject interface.
-    void emitObject(SkWStream* stream) const override;
-    void addResources(SkPDFObjNumMap*) const final;
-    void drop() override;
-
-protected:
-    /* Create a PDF stream with no data.  The setData method must be called to
-     * set the data. */
-    SkPDFStream();
-
-    /** Only call this function once. */
-    void setData(std::unique_ptr<SkStreamAsset> stream);
-
-private:
-    std::unique_ptr<SkStreamAsset> fCompressedData;
-    SkPDFDict fDict;
-
-    typedef SkPDFDict INHERITED;
-};
-
-////////////////////////////////////////////////////////////////////////////////
-
-/** \class SkPDFObjNumMap
-
-    The PDF Object Number Map manages object numbers.  It is used to
-    create the PDF cross reference table.
-*/
-class SkPDFObjNumMap : SkNoncopyable {
-public:
-    /** Add the passed object to the catalog, as well as all its dependencies.
-     *  @param obj   The object to add.  If nullptr, this is a noop.
-     */
-    void addObjectRecursively(SkPDFObject* obj);
-
-    /** Get the object number for the passed object.
-     *  @param obj         The object of interest.
-     */
-    int getObjectNumber(SkPDFObject* obj) const {
-        return SkASSERT(obj), obj->fIndirectReference.fValue;
-    }
-    const std::vector<sk_sp<SkPDFObject>>& objects() const { return fObjects; }
-
-private:
-    friend struct SkPDFObjectSerializer;
-    std::vector<sk_sp<SkPDFObject>> fObjects;
-    int fNextObjectNumber = 1;
-};
+SkPDFIndirectReference SkPDFStreamOut(std::unique_ptr<SkPDFDict> dict,
+                                      std::unique_ptr<SkStreamAsset> stream,
+                                      SkPDFDocument* doc,
+                                      bool deflate = kSkPDFDefaultDoDeflate);
 
 ////////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/pdf/SkPDFUnion.h b/src/pdf/SkPDFUnion.h
new file mode 100644
index 0000000..b6536cf
--- /dev/null
+++ b/src/pdf/SkPDFUnion.h
@@ -0,0 +1,128 @@
+// Copyright 2018 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#ifndef SkPDFUnion_DEFINED
+#define SkPDFUnion_DEFINED
+
+#include "SkPDFTypes.h"
+
+template <class T>
+class SkStorageFor {
+public:
+    const T& get() const { return *reinterpret_cast<const T*>(&fStore); }
+    T& get() { return *reinterpret_cast<T*>(&fStore); }
+    // Up to caller to keep track of status.
+    template<class... Args> void init(Args&&... args) {
+        new (&this->get()) T(std::forward<Args>(args)...);
+    }
+    void destroy() { this->get().~T(); }
+private:
+    typename std::aligned_storage<sizeof(T), alignof(T)>::type fStore;
+};
+
+// Exposed for unit testing.
+void SkPDFWriteString(SkWStream* wStream, const char* cin, size_t len);
+
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+   A SkPDFUnion is a non-virtualized implementation of the
+   non-compound, non-specialized PDF Object types: Name, String,
+   Number, Boolean.
+ */
+class SkPDFUnion {
+public:
+    // Move contstructor and assignemnt operator destroy the argument
+    // and steal their references (if needed).
+    SkPDFUnion(SkPDFUnion&& other);
+    SkPDFUnion& operator=(SkPDFUnion&& other);
+
+    ~SkPDFUnion();
+
+    /** The following nine functions are the standard way of creating
+        SkPDFUnion objects. */
+
+    static SkPDFUnion Int(int32_t);
+
+    static SkPDFUnion Int(size_t v) { return SkPDFUnion::Int(SkToS32(v)); }
+
+    static SkPDFUnion Bool(bool);
+
+    static SkPDFUnion Scalar(SkScalar);
+
+    static SkPDFUnion ColorComponent(uint8_t);
+
+    static SkPDFUnion ColorComponentF(float);
+
+    /** These two functions do NOT take ownership of char*, and do NOT
+        copy the string.  Suitable for passing in static const
+        strings. For example:
+          SkPDFUnion n = SkPDFUnion::Name("Length");
+          SkPDFUnion u = SkPDFUnion::String("Identity"); */
+
+    /** SkPDFUnion::Name(const char*) assumes that the passed string
+        is already a valid name (that is: it has no control or
+        whitespace characters).  This will not copy the name. */
+    static SkPDFUnion Name(const char*);
+
+    /** SkPDFUnion::String will encode the passed string.  This will
+        not copy the name. */
+    static SkPDFUnion String(const char*);
+
+    /** SkPDFUnion::Name(SkString) does not assume that the
+        passed string is already a valid name and it will escape the
+        string. */
+    static SkPDFUnion Name(SkString);
+
+    /** SkPDFUnion::String will encode the passed string. */
+    static SkPDFUnion String(SkString);
+
+    static SkPDFUnion Object(std::unique_ptr<SkPDFObject>);
+
+    static SkPDFUnion Ref(SkPDFIndirectReference);
+
+    /** These two non-virtual methods mirror SkPDFObject's
+        corresponding virtuals. */
+    void emitObject(SkWStream*) const;
+
+    bool isName() const;
+
+private:
+    union {
+        int32_t fIntValue;
+        bool fBoolValue;
+        SkScalar fScalarValue;
+        const char* fStaticString;
+        SkStorageFor<SkString> fSkString;
+        SkPDFObject* fObject;
+    };
+    enum class Type : char {
+        /** It is an error to call emitObject() or addResources() on an
+            kDestroyed object. */
+        kDestroyed = 0,
+        kInt,
+        kColorComponent,
+        kColorComponentF,
+        kBool,
+        kScalar,
+        kName,
+        kString,
+        kNameSkS,
+        kStringSkS,
+        kObject,
+        kRef,
+    };
+    Type fType;
+
+    SkPDFUnion(Type);
+    SkPDFUnion(Type, int32_t);
+    SkPDFUnion(Type, bool);
+    SkPDFUnion(Type, SkScalar);
+    SkPDFUnion(Type, SkString);
+    // We do not now need copy constructor and copy assignment, so we
+    // will disable this functionality.
+    SkPDFUnion& operator=(const SkPDFUnion&) = delete;
+    SkPDFUnion(const SkPDFUnion&) = delete;
+};
+static_assert(sizeof(SkString) == sizeof(void*), "SkString_size");
+
+#endif  // SkPDFUnion_DEFINED
diff --git a/src/pdf/SkPDFUtils.cpp b/src/pdf/SkPDFUtils.cpp
index 56e646e..e53accf 100644
--- a/src/pdf/SkPDFUtils.cpp
+++ b/src/pdf/SkPDFUtils.cpp
@@ -44,11 +44,11 @@
     }
 }
 
-sk_sp<SkPDFArray> SkPDFUtils::RectToArray(const SkRect& r) {
+std::unique_ptr<SkPDFArray> SkPDFUtils::RectToArray(const SkRect& r) {
     return SkPDFMakeArray(r.left(), r.top(), r.right(), r.bottom());
 }
 
-sk_sp<SkPDFArray> SkPDFUtils::MatrixToArray(const SkMatrix& matrix) {
+std::unique_ptr<SkPDFArray> SkPDFUtils::MatrixToArray(const SkMatrix& matrix) {
     SkScalar a[6];
     if (!matrix.asAffine(a)) {
         SkMatrix::SetAffineIdentity(a);
@@ -304,7 +304,7 @@
 
 void SkPDFUtils::PopulateTilingPatternDict(SkPDFDict* pattern,
                                            SkRect& bbox,
-                                           sk_sp<SkPDFDict> resources,
+                                           std::unique_ptr<SkPDFDict> resources,
                                            const SkMatrix& matrix) {
     const int kTiling_PatternType = 1;
     const int kColoredTilingPattern_PaintType = 1;
@@ -335,3 +335,40 @@
     }
     return false;
 }
+
+#ifdef SK_PDF_BASE85_BINARY
+void SkPDFUtils::Base85Encode(std::unique_ptr<SkStreamAsset> stream, SkDynamicMemoryWStream* dst) {
+    SkASSERT(dst);
+    SkASSERT(stream);
+    dst->writeText("\n");
+    int column = 0;
+    while (true) {
+        uint8_t src[4] = {0, 0, 0, 0};
+        size_t count = stream->read(src, 4);
+        SkASSERT(count < 5);
+        if (0 == count) {
+            dst->writeText("~>\n");
+            return;
+        }
+        uint32_t v = ((uint32_t)src[0] << 24) | ((uint32_t)src[1] << 16) |
+                     ((uint32_t)src[2] <<  8) | src[3];
+        if (v == 0 && count == 4) {
+            dst->writeText("z");
+            column += 1;
+        } else {
+            char buffer[5];
+            for (int n = 4; n > 0; --n) {
+                buffer[n] = (v % 85) + '!';
+                v /= 85;
+            }
+            buffer[0] = v + '!';
+            dst->write(buffer, count + 1);
+            column += count + 1;
+        }
+        if (column > 74) {
+            dst->writeText("\n");
+            column = 0;
+        }
+    }
+}
+#endif //  SK_PDF_BASE85_BINARY
diff --git a/src/pdf/SkPDFUtils.h b/src/pdf/SkPDFUtils.h
index 8d935e8..0624c9a 100644
--- a/src/pdf/SkPDFUtils.h
+++ b/src/pdf/SkPDFUtils.h
@@ -44,8 +44,8 @@
 
 const char* BlendModeName(SkBlendMode);
 
-sk_sp<SkPDFArray> RectToArray(const SkRect& rect);
-sk_sp<SkPDFArray> MatrixToArray(const SkMatrix& matrix);
+std::unique_ptr<SkPDFArray> RectToArray(const SkRect& rect);
+std::unique_ptr<SkPDFArray> MatrixToArray(const SkMatrix& matrix);
 
 void MoveTo(SkScalar x, SkScalar y, SkWStream* content);
 void AppendLine(SkScalar x, SkScalar y, SkWStream* content);
@@ -121,10 +121,15 @@
 bool InverseTransformBBox(const SkMatrix& matrix, SkRect* bbox);
 void PopulateTilingPatternDict(SkPDFDict* pattern,
                                SkRect& bbox,
-                               sk_sp<SkPDFDict> resources,
+                               std::unique_ptr<SkPDFDict> resources,
                                const SkMatrix& matrix);
 
 bool ToBitmap(const SkImage* img, SkBitmap* dst);
+
+#ifdef SK_PDF_BASE85_BINARY
+void Base85Encode(std::unique_ptr<SkStreamAsset> src, SkDynamicMemoryWStream* dst);
+#endif //  SK_PDF_BASE85_BINARY
+
 }  // namespace SkPDFUtils
 
 #endif
diff --git a/src/pdf/SkUUID.h b/src/pdf/SkUUID.h
new file mode 100644
index 0000000..3d81865
--- /dev/null
+++ b/src/pdf/SkUUID.h
@@ -0,0 +1,18 @@
+// Copyright 2018 Google LLC.
+// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
+#ifndef SkUUID_DEFINED
+#define SkUUID_DEFINED
+
+#include <cstdint>
+#include <cstring>
+
+struct SkUUID {
+    uint8_t fData[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+};
+
+static inline bool operator==(const SkUUID& u, const SkUUID& v) {
+    return 0 == memcmp(u.fData, v.fData, sizeof(u.fData));
+}
+static inline bool operator!=(const SkUUID& u, const SkUUID& v) { return !(u == v); }
+
+#endif  // SkUUID_DEFINED
diff --git a/src/ports/SkFontConfigTypeface.h b/src/ports/SkFontConfigTypeface.h
index 603d77f..395c262 100644
--- a/src/ports/SkFontConfigTypeface.h
+++ b/src/ports/SkFontConfigTypeface.h
@@ -5,6 +5,9 @@
  * found in the LICENSE file.
  */
 
+#ifndef SkFontConfigTypeface_DEFINED
+#define SkFontConfigTypeface_DEFINED
+
 #include "SkFontConfigInterface.h"
 #include "SkFontDescriptor.h"
 #include "SkFontHost_FreeType_common.h"
@@ -78,3 +81,5 @@
 private:
     typedef SkTypeface_FreeType INHERITED;
 };
+
+#endif  // SkFontConfigTypeface_DEFINED
diff --git a/src/ports/SkFontHost_FreeType.cpp b/src/ports/SkFontHost_FreeType.cpp
index c871e94..9f79ed4 100644
--- a/src/ports/SkFontHost_FreeType.cpp
+++ b/src/ports/SkFontHost_FreeType.cpp
@@ -503,7 +503,6 @@
     void generateImage(const SkGlyph& glyph) override;
     bool generatePath(SkGlyphID glyphID, SkPath* path) override;
     void generateFontMetrics(SkFontMetrics*) override;
-    SkUnichar generateGlyphToChar(uint16_t glyph) override;
 
 private:
     using UnrefFTFace = SkFunctionWrapper<void, SkFaceRec, unref_ft_face>;
@@ -1034,22 +1033,6 @@
     return SkToU16(FT_Get_Char_Index( fFace, uni ));
 }
 
-SkUnichar SkScalerContext_FreeType::generateGlyphToChar(uint16_t glyph) {
-    SkAutoMutexAcquire  ac(gFTMutex);
-    // iterate through each cmap entry, looking for matching glyph indices
-    FT_UInt glyphIndex;
-    SkUnichar charCode = FT_Get_First_Char( fFace, &glyphIndex );
-
-    while (glyphIndex != 0) {
-        if (glyphIndex == glyph) {
-            return charCode;
-        }
-        charCode = FT_Get_Next_Char( fFace, charCode, &glyphIndex );
-    }
-
-    return 0;
-}
-
 bool SkScalerContext_FreeType::generateAdvance(SkGlyph* glyph) {
    /* unhinted and light hinted text have linearly scaled advances
     * which are very cheap to compute with some font formats...
diff --git a/src/ports/SkFontHost_mac.cpp b/src/ports/SkFontHost_mac.cpp
index d31916d..aa79033 100644
--- a/src/ports/SkFontHost_mac.cpp
+++ b/src/ports/SkFontHost_mac.cpp
@@ -1334,7 +1334,7 @@
     if ((glyph.fMaskFormat == SkMask::kLCD16_Format) ||
         (glyph.fMaskFormat == SkMask::kA8_Format
          && requestSmooth
-         && smooth_behavior() == SmoothBehavior::subpixel))
+         && smooth_behavior() != SmoothBehavior::none))
     {
         const uint8_t* linear = gLinearCoverageFromCGLCDValue.data();
 
diff --git a/src/ports/SkFontHost_win.cpp b/src/ports/SkFontHost_win.cpp
index 7943597..f75a0c9 100644
--- a/src/ports/SkFontHost_win.cpp
+++ b/src/ports/SkFontHost_win.cpp
@@ -1114,83 +1114,6 @@
     return SkPack888ToRGB16(r, g, b);
 }
 
-// Is this GDI color neither black nor white? If so, we have to keep this
-// image as is, rather than smashing it down to a BW mask.
-//
-// returns int instead of bool, since we don't want/have to pay to convert
-// the zero/non-zero value into a bool
-static int is_not_black_or_white(SkGdiRGB c) {
-    // same as (but faster than)
-    //      c &= 0x00FFFFFF;
-    //      return 0 == c || 0x00FFFFFF == c;
-    return (c + (c & 1)) & 0x00FFFFFF;
-}
-
-static bool is_rgb_really_bw(const SkGdiRGB* src, int width, int height, size_t srcRB) {
-    for (int y = 0; y < height; ++y) {
-        for (int x = 0; x < width; ++x) {
-            if (is_not_black_or_white(src[x])) {
-                return false;
-            }
-        }
-        src = SkTAddOffset<const SkGdiRGB>(src, srcRB);
-    }
-    return true;
-}
-
-// gdi's bitmap is upside-down, so we reverse dst walking in Y
-// whenever we copy it into skia's buffer
-static void rgb_to_bw(const SkGdiRGB* SK_RESTRICT src, size_t srcRB,
-                      const SkGlyph& glyph) {
-    const int width = glyph.fWidth;
-    const size_t dstRB = (width + 7) >> 3;
-    uint8_t* SK_RESTRICT dst = (uint8_t*)((char*)glyph.fImage + (glyph.fHeight - 1) * dstRB);
-
-    int byteCount = width >> 3;
-    int bitCount = width & 7;
-
-    // adjust srcRB to skip the values in our byteCount loop,
-    // since we increment src locally there
-    srcRB -= byteCount * 8 * sizeof(SkGdiRGB);
-
-    for (int y = 0; y < glyph.fHeight; ++y) {
-        if (byteCount > 0) {
-            for (int i = 0; i < byteCount; ++i) {
-                unsigned byte = 0;
-                byte |= src[0] & (1 << 7);
-                byte |= src[1] & (1 << 6);
-                byte |= src[2] & (1 << 5);
-                byte |= src[3] & (1 << 4);
-                byte |= src[4] & (1 << 3);
-                byte |= src[5] & (1 << 2);
-                byte |= src[6] & (1 << 1);
-                byte |= src[7] & (1 << 0);
-                dst[i] = byte;
-                src += 8;
-            }
-        }
-        if (bitCount > 0) {
-            unsigned byte = 0;
-            unsigned mask = 0x80;
-            for (int i = 0; i < bitCount; i++) {
-                byte |= src[i] & mask;
-                mask >>= 1;
-            }
-            dst[byteCount] = byte;
-        }
-        src = SkTAddOffset<const SkGdiRGB>(src, srcRB);
-        dst -= dstRB;
-    }
-#if SK_SHOW_TEXT_BLIT_COVERAGE
-    if (glyph.fWidth > 0 && glyph.fHeight > 0) {
-        uint8_t* first = (uint8_t*)glyph.fImage;
-        uint8_t* last = (uint8_t*)((char*)glyph.fImage + glyph.fHeight * dstRB - 1);
-        *first |= 1 << 7;
-        *last |= bitCount == 0 ? 1 : 1 << (8 - bitCount);
-    }
-#endif
-}
-
 template<bool APPLY_PREBLEND>
 static void rgb_to_a8(const SkGdiRGB* SK_RESTRICT src, size_t srcRB,
                       const SkGlyph& glyph, const uint8_t* table8) {
@@ -1269,7 +1192,6 @@
         }
     }
 
-    int width = glyph.fWidth;
     size_t dstRB = glyph.rowBytes();
     if (isBW) {
         const uint8_t* src = (const uint8_t*)bits;
@@ -1281,7 +1203,7 @@
         }
 #if SK_SHOW_TEXT_BLIT_COVERAGE
             if (glyph.fWidth > 0 && glyph.fHeight > 0) {
-                int bitCount = width & 7;
+                int bitCount = glyph.fWidth & 7;
                 uint8_t* first = (uint8_t*)glyph.fImage;
                 uint8_t* last = (uint8_t*)((char*)glyph.fImage + glyph.fHeight * dstRB - 1);
                 *first |= 1 << 7;
@@ -1299,18 +1221,13 @@
         }
     } else {    // LCD16
         const SkGdiRGB* src = (const SkGdiRGB*)bits;
-        if (is_rgb_really_bw(src, width, glyph.fHeight, srcRB)) {
-            rgb_to_bw(src, srcRB, glyph);
-            ((SkGlyph*)&glyph)->fMaskFormat = SkMask::kBW_Format;
+        SkASSERT(SkMask::kLCD16_Format == glyph.fMaskFormat);
+        if (fPreBlend.isApplicable()) {
+            rgb_to_lcd16<true>(src, srcRB, glyph,
+                               fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
         } else {
-            SkASSERT(SkMask::kLCD16_Format == glyph.fMaskFormat);
-            if (fPreBlend.isApplicable()) {
-                rgb_to_lcd16<true>(src, srcRB, glyph,
-                                   fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
-            } else {
-                rgb_to_lcd16<false>(src, srcRB, glyph,
-                                    fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
-            }
+            rgb_to_lcd16<false>(src, srcRB, glyph,
+                                fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
         }
     }
 }
diff --git a/src/ports/SkFontMgr_android.cpp b/src/ports/SkFontMgr_android.cpp
index b6cb6c4..6518b2a 100644
--- a/src/ports/SkFontMgr_android.cpp
+++ b/src/ports/SkFontMgr_android.cpp
@@ -397,13 +397,7 @@
                 continue;
             }
 
-            SkPaint paint;
-            paint.setTypeface(face);
-            paint.setTextEncoding(kUTF32_SkTextEncoding);
-
-            uint16_t glyphID;
-            paint.textToGlyphs(&character, sizeof(character), &glyphID);
-            if (glyphID != 0) {
+            if (face->unicharToGlyph(character) != 0) {
                 return face;
             }
         }
diff --git a/src/ports/SkFontMgr_fuchsia.cpp b/src/ports/SkFontMgr_fuchsia.cpp
new file mode 100644
index 0000000..187a5a3
--- /dev/null
+++ b/src/ports/SkFontMgr_fuchsia.cpp
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkFontMgr_fuchsia.h"
+
+#include <fuchsia/fonts/cpp/fidl.h>
+#include <lib/zx/vmar.h>
+#include <strings.h>
+#include <memory>
+#include <unordered_map>
+
+#include "third_party/skia/src/core/SkFontDescriptor.h"
+#include "third_party/skia/src/ports/SkFontMgr_custom.h"
+
+#include "SkFontMgr.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+#include "SkTypefaceCache.h"
+
+void UnmapMemory(const void* buffer, uint64_t size) {
+    static_assert(sizeof(void*) == sizeof(uint64_t), "pointers aren't 64-bit");
+    zx::vmar::root_self()->unmap(reinterpret_cast<uintptr_t>(buffer), size);
+}
+
+struct ReleaseSkDataContext {
+    uint64_t fBufferSize;
+    std::function<void()> releaseProc;
+
+    ReleaseSkDataContext(uint64_t bufferSize, const std::function<void()>& releaseProc)
+            : fBufferSize(bufferSize), releaseProc(releaseProc) {}
+};
+
+void ReleaseSkData(const void* buffer, void* context) {
+    auto releaseSkDataContext = reinterpret_cast<ReleaseSkDataContext*>(context);
+    SkASSERT(releaseSkDataContext);
+    UnmapMemory(buffer, releaseSkDataContext->fBufferSize);
+    releaseSkDataContext->releaseProc();
+    delete releaseSkDataContext;
+}
+
+sk_sp<SkData> MakeSkDataFromBuffer(const fuchsia::mem::Buffer& data,
+                                   std::function<void()> release_proc) {
+    uint64_t size = data.size;
+    uintptr_t buffer = 0;
+    zx_status_t status = zx::vmar::root_self()->map(0, data.vmo, 0, size, ZX_VM_PERM_READ, &buffer);
+    if (status != ZX_OK) return nullptr;
+    auto context = new ReleaseSkDataContext(size, release_proc);
+    return SkData::MakeWithProc(reinterpret_cast<void*>(buffer), size, ReleaseSkData, context);
+}
+
+fuchsia::fonts::Slant SkToFuchsiaSlant(SkFontStyle::Slant slant) {
+    switch (slant) {
+        case SkFontStyle::kOblique_Slant:
+            return fuchsia::fonts::Slant::OBLIQUE;
+        case SkFontStyle::kItalic_Slant:
+            return fuchsia::fonts::Slant::ITALIC;
+        case SkFontStyle::kUpright_Slant:
+        default:
+            return fuchsia::fonts::Slant::UPRIGHT;
+    }
+}
+
+SkFontStyle::Slant FuchsiaToSkSlant(fuchsia::fonts::Slant slant) {
+    switch (slant) {
+        case fuchsia::fonts::Slant::OBLIQUE:
+            return SkFontStyle::kOblique_Slant;
+        case fuchsia::fonts::Slant::ITALIC:
+            return SkFontStyle::kItalic_Slant;
+        case fuchsia::fonts::Slant::UPRIGHT:
+        default:
+            return SkFontStyle::kUpright_Slant;
+    }
+}
+
+constexpr struct {
+    const char* fName;
+    fuchsia::fonts::FallbackGroup fFallbackGroup;
+} kFallbackGroupsByName[] = {
+        {"serif", fuchsia::fonts::FallbackGroup::SERIF},
+        {"sans", fuchsia::fonts::FallbackGroup::SANS_SERIF},
+        {"sans-serif", fuchsia::fonts::FallbackGroup::SANS_SERIF},
+        {"mono", fuchsia::fonts::FallbackGroup::MONOSPACE},
+        {"monospace", fuchsia::fonts::FallbackGroup::MONOSPACE},
+        {"cursive", fuchsia::fonts::FallbackGroup::CURSIVE},
+        {"fantasy", fuchsia::fonts::FallbackGroup::FANTASY},
+};
+
+fuchsia::fonts::FallbackGroup GetFallbackGroupByName(const char* name) {
+    if (!name) return fuchsia::fonts::FallbackGroup::NONE;
+    for (auto& group : kFallbackGroupsByName) {
+        if (strcasecmp(group.fName, name) == 0) {
+            return group.fFallbackGroup;
+        }
+    }
+    return fuchsia::fonts::FallbackGroup::NONE;
+}
+
+struct TypefaceId {
+    uint32_t bufferId;
+    uint32_t ttcIndex;
+
+    bool operator==(TypefaceId& other) {
+        return std::tie(bufferId, ttcIndex) == std::tie(other.bufferId, other.ttcIndex);
+    }
+}
+
+constexpr kNullTypefaceId = {0xFFFFFFFF, 0xFFFFFFFF};
+
+class SkTypeface_Fuchsia : public SkTypeface_Stream {
+public:
+    SkTypeface_Fuchsia(std::unique_ptr<SkFontData> fontData, const SkFontStyle& style,
+                       bool isFixedPitch, const SkString familyName, TypefaceId id)
+            : SkTypeface_Stream(std::move(fontData), style, isFixedPitch,
+                                /*sys_font=*/true, familyName)
+            , fId(id) {}
+
+    TypefaceId id() { return fId; }
+
+private:
+    TypefaceId fId;
+};
+
+sk_sp<SkTypeface> CreateTypefaceFromSkStream(std::unique_ptr<SkStreamAsset> stream,
+                                             const SkFontArguments& args, TypefaceId id) {
+    using Scanner = SkTypeface_FreeType::Scanner;
+    Scanner scanner;
+    bool isFixedPitch;
+    SkFontStyle style;
+    SkString name;
+    Scanner::AxisDefinitions axisDefinitions;
+    if (!scanner.scanFont(stream.get(), args.getCollectionIndex(), &name, &style, &isFixedPitch,
+                          &axisDefinitions)) {
+        return nullptr;
+    }
+
+    const SkFontArguments::VariationPosition position = args.getVariationDesignPosition();
+    SkAutoSTMalloc<4, SkFixed> axisValues(axisDefinitions.count());
+    Scanner::computeAxisValues(axisDefinitions, position, axisValues, name);
+
+    auto fontData = std::make_unique<SkFontData>(std::move(stream), args.getCollectionIndex(),
+                                                 axisValues.get(), axisDefinitions.count());
+    return sk_make_sp<SkTypeface_Fuchsia>(std::move(fontData), style, isFixedPitch, name, id);
+}
+
+sk_sp<SkTypeface> CreateTypefaceFromSkData(sk_sp<SkData> data, TypefaceId id) {
+    return CreateTypefaceFromSkStream(std::make_unique<SkMemoryStream>(std::move(data)),
+                                      SkFontArguments().setCollectionIndex(id.ttcIndex), id);
+}
+
+class SkFontMgr_Fuchsia final : public SkFontMgr {
+public:
+    SkFontMgr_Fuchsia(fuchsia::fonts::ProviderSyncPtr provider);
+    ~SkFontMgr_Fuchsia() override;
+
+protected:
+    // SkFontMgr overrides.
+    int onCountFamilies() const override;
+    void onGetFamilyName(int index, SkString* familyName) const override;
+    SkFontStyleSet* onMatchFamily(const char familyName[]) const override;
+    SkFontStyleSet* onCreateStyleSet(int index) const override;
+    SkTypeface* onMatchFamilyStyle(const char familyName[], const SkFontStyle&) const override;
+    SkTypeface* onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle&,
+                                            const char* bcp47[], int bcp47Count,
+                                            SkUnichar character) const override;
+    SkTypeface* onMatchFaceStyle(const SkTypeface*, const SkFontStyle&) const override;
+    sk_sp<SkTypeface> onMakeFromData(sk_sp<SkData>, int ttcIndex) const override;
+    sk_sp<SkTypeface> onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset>,
+                                            int ttcIndex) const override;
+    sk_sp<SkTypeface> onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset>,
+                                           const SkFontArguments&) const override;
+    sk_sp<SkTypeface> onMakeFromFile(const char path[], int ttcIndex) const override;
+    sk_sp<SkTypeface> onLegacyMakeTypeface(const char familyName[], SkFontStyle) const override;
+
+private:
+    friend class SkFontStyleSet_Fuchsia;
+
+    sk_sp<SkTypeface> FetchTypeface(const char familyName[], const SkFontStyle& style,
+                                    const char* bcp47[], int bcp47Count, SkUnichar character,
+                                    bool allow_fallback, bool exact_style_match) const;
+
+    sk_sp<SkData> GetOrCreateSkData(int bufferId, const fuchsia::mem::Buffer& buffer) const;
+    void OnSkDataDeleted(int bufferId) const;
+
+    sk_sp<SkTypeface> GetOrCreateTypeface(TypefaceId id, const fuchsia::mem::Buffer& buffer) const;
+
+    mutable fuchsia::fonts::ProviderSyncPtr fFontProvider;
+
+    mutable SkMutex fCacheMutex;
+
+    // Must be accessed only with fCacheMutex acquired.
+    mutable std::unordered_map<int, SkData*> fBufferCache;
+    mutable SkTypefaceCache fTypefaceCache;
+};
+
+class SkFontStyleSet_Fuchsia : public SkFontStyleSet {
+public:
+    SkFontStyleSet_Fuchsia(sk_sp<SkFontMgr_Fuchsia> font_manager, std::string familyName,
+                 std::vector<SkFontStyle> styles)
+            : fFontManager(font_manager), fFamilyName(familyName), fStyles(styles) {}
+
+    ~SkFontStyleSet_Fuchsia() override = default;
+
+    int count() override { return fStyles.size(); }
+
+    void getStyle(int index, SkFontStyle* style, SkString* styleName) override {
+        SkASSERT(index >= 0 && index < static_cast<int>(fStyles.size()));
+        if (style) *style = fStyles[index];
+
+        // We don't have style names. Return an empty name.
+        if (styleName) styleName->reset();
+    }
+
+    SkTypeface* createTypeface(int index) override {
+        SkASSERT(index >= 0 && index < static_cast<int>(fStyles.size()));
+
+        if (fTypefaces.empty()) fTypefaces.resize(fStyles.size());
+
+        if (!fTypefaces[index]) {
+            fTypefaces[index] = fFontManager->FetchTypeface(
+                    fFamilyName.c_str(), fStyles[index], /*bcp47=*/nullptr,
+                    /*bcp47Count=*/0, /*character=*/0,
+                    /*allow_fallback=*/false, /*exact_style_match=*/true);
+        }
+
+        return SkSafeRef(fTypefaces[index].get());
+    }
+
+    SkTypeface* matchStyle(const SkFontStyle& pattern) override { return matchStyleCSS3(pattern); }
+
+private:
+    sk_sp<SkFontMgr_Fuchsia> fFontManager;
+    std::string fFamilyName;
+    std::vector<SkFontStyle> fStyles;
+    std::vector<sk_sp<SkTypeface>> fTypefaces;
+};
+
+SkFontMgr_Fuchsia::SkFontMgr_Fuchsia(fuchsia::fonts::ProviderSyncPtr provider)
+        : fFontProvider(std::move(provider)) {}
+
+SkFontMgr_Fuchsia::~SkFontMgr_Fuchsia() = default;
+
+int SkFontMgr_Fuchsia::onCountFamilies() const {
+    // Family enumeration is not supported.
+    return 0;
+}
+
+void SkFontMgr_Fuchsia::onGetFamilyName(int index, SkString* familyName) const {
+    // Family enumeration is not supported.
+    familyName->reset();
+}
+
+SkFontStyleSet* SkFontMgr_Fuchsia::onCreateStyleSet(int index) const {
+    // Family enumeration is not supported.
+    return nullptr;
+}
+
+SkFontStyleSet* SkFontMgr_Fuchsia::onMatchFamily(const char familyName[]) const {
+    fuchsia::fonts::FamilyInfoPtr familyInfo;
+    int result = fFontProvider->GetFamilyInfo(familyName, &familyInfo);
+    if (result != ZX_OK || !familyInfo) return nullptr;
+
+    std::vector<SkFontStyle> styles;
+#ifdef USE_STD_FOR_NON_NULLABLE_FIDL_FIELDS
+    for (auto& style : familyInfo->styles) {
+#else
+    for (auto& style : *(familyInfo->styles)) {
+#endif
+        styles.push_back(SkFontStyle(style.weight, style.width, FuchsiaToSkSlant(style.slant)));
+    }
+
+    return new SkFontStyleSet_Fuchsia(sk_ref_sp(this), familyInfo->name, std::move(styles));
+}
+
+SkTypeface* SkFontMgr_Fuchsia::onMatchFamilyStyle(const char familyName[],
+                                                  const SkFontStyle& style) const {
+    sk_sp<SkTypeface> typeface =
+            FetchTypeface(familyName, style, /*bcp47=*/nullptr,
+                          /*bcp47Count=*/0, /*character=*/0,
+                          /*allow_fallback=*/false, /*exact_style_match=*/false);
+    return typeface.release();
+}
+
+SkTypeface* SkFontMgr_Fuchsia::onMatchFamilyStyleCharacter(const char familyName[],
+                                                           const SkFontStyle& style,
+                                                           const char* bcp47[], int bcp47Count,
+                                                           SkUnichar character) const {
+    sk_sp<SkTypeface> typeface =
+            FetchTypeface(familyName, style, bcp47, bcp47Count, character, /*allow_fallback=*/true,
+                          /*exact_style_match=*/false);
+    return typeface.release();
+}
+
+SkTypeface* SkFontMgr_Fuchsia::onMatchFaceStyle(const SkTypeface*, const SkFontStyle&) const {
+    return nullptr;
+}
+
+sk_sp<SkTypeface> SkFontMgr_Fuchsia::onMakeFromData(sk_sp<SkData>, int ttcIndex) const {
+    SkASSERT(false);
+    return nullptr;
+}
+
+sk_sp<SkTypeface> SkFontMgr_Fuchsia::onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset> asset,
+                                                           int ttcIndex) const {
+    return makeFromStream(std::move(asset), SkFontArguments().setCollectionIndex(ttcIndex));
+}
+
+sk_sp<SkTypeface> SkFontMgr_Fuchsia::onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset> asset,
+                                                          const SkFontArguments& args) const {
+    return CreateTypefaceFromSkStream(std::move(asset), args, kNullTypefaceId);
+}
+
+sk_sp<SkTypeface> SkFontMgr_Fuchsia::onMakeFromFile(const char path[], int ttcIndex) const {
+    return makeFromStream(std::make_unique<SkFILEStream>(path), ttcIndex);
+}
+
+sk_sp<SkTypeface> SkFontMgr_Fuchsia::onLegacyMakeTypeface(const char familyName[],
+                                                          SkFontStyle style) const {
+    return sk_sp<SkTypeface>(matchFamilyStyle(familyName, style));
+}
+
+sk_sp<SkTypeface> SkFontMgr_Fuchsia::FetchTypeface(const char familyName[],
+                                                   const SkFontStyle& style, const char* bcp47[],
+                                                   int bcp47Count, SkUnichar character,
+                                                   bool allow_fallback,
+                                                   bool exact_style_match) const {
+    fuchsia::fonts::Request request;
+    request.weight = style.weight();
+    request.width = style.width();
+    request.slant = SkToFuchsiaSlant(style.slant());
+#ifdef USE_STD_FOR_NON_NULLABLE_FIDL_FIELDS
+    request.language.reset(std::vector<std::string>(bcp47, bcp47 + bcp47Count));
+#else
+    request.language.reset(std::vector<fidl::StringPtr>(bcp47, bcp47 + bcp47Count));
+#endif
+    request.character = character;
+    request.fallback_group = GetFallbackGroupByName(familyName);
+
+    // If family name is not specified or it is a generic fallback group name (e.g. "serif") then
+    // enable fallback, otherwise pass the family name as is.
+    if (!familyName || *familyName == '\0' ||
+        request.fallback_group != fuchsia::fonts::FallbackGroup::NONE) {
+        request.family = "";
+        allow_fallback = true;
+    } else {
+        request.family = familyName;
+    }
+
+    request.flags = 0;
+    if (!allow_fallback) request.flags |= fuchsia::fonts::REQUEST_FLAG_NO_FALLBACK;
+    if (exact_style_match) request.flags |= fuchsia::fonts::REQUEST_FLAG_EXACT_MATCH;
+
+    fuchsia::fonts::ResponsePtr response;
+    int result = fFontProvider->GetFont(std::move(request), &response);
+    if (result != ZX_OK) return nullptr;
+
+    // The service may return null response if there is no font matching the request.
+    if (!response) return nullptr;
+
+    return GetOrCreateTypeface(TypefaceId{response->buffer_id, response->font_index},
+                               response->buffer);
+}
+
+sk_sp<SkData> SkFontMgr_Fuchsia::GetOrCreateSkData(int bufferId,
+                                                   const fuchsia::mem::Buffer& buffer) const {
+    fCacheMutex.assertHeld();
+
+    auto iter = fBufferCache.find(bufferId);
+    if (iter != fBufferCache.end()) {
+        return sk_ref_sp(iter->second);
+    }
+    auto font_mgr = sk_ref_sp(this);
+    auto data = MakeSkDataFromBuffer(buffer,
+                                     [font_mgr, bufferId]() { font_mgr->OnSkDataDeleted(bufferId); });
+    if (!data) {
+        return nullptr;
+    }
+    fBufferCache[bufferId] = data.get();
+    return data;
+}
+
+void SkFontMgr_Fuchsia::OnSkDataDeleted(int bufferId) const {
+    SK_UNUSED bool wasFound = fBufferCache.erase(bufferId) != 0;
+    SkASSERT(wasFound);
+}
+
+static bool FindByTypefaceId(SkTypeface* cachedTypeface, void* ctx) {
+    SkTypeface_Fuchsia* cachedFuchsiaTypeface = static_cast<SkTypeface_Fuchsia*>(cachedTypeface);
+    TypefaceId* id = static_cast<TypefaceId*>(ctx);
+
+    return cachedFuchsiaTypeface->id() == *id;
+}
+
+sk_sp<SkTypeface> SkFontMgr_Fuchsia::GetOrCreateTypeface(TypefaceId id,
+                                                         const fuchsia::mem::Buffer& buffer) const {
+    SkAutoMutexAcquire mutexLock(fCacheMutex);
+
+    SkTypeface* cached = fTypefaceCache.findByProcAndRef(FindByTypefaceId, &id);
+    if (cached) return sk_sp<SkTypeface>(cached);
+
+    sk_sp<SkData> data = GetOrCreateSkData(id.bufferId, buffer);
+    if (!data) return nullptr;
+
+    auto result = CreateTypefaceFromSkData(std::move(data), id);
+    fTypefaceCache.add(result.get());
+    return result;
+}
+
+SK_API sk_sp<SkFontMgr> SkFontMgr_New_Fuchsia(fuchsia::fonts::ProviderSyncPtr provider) {
+    return sk_make_sp<SkFontMgr_Fuchsia>(std::move(provider));
+}
diff --git a/src/ports/SkOSFile_ios.h b/src/ports/SkOSFile_ios.h
index d74aa20..c095ba3 100644
--- a/src/ports/SkOSFile_ios.h
+++ b/src/ports/SkOSFile_ios.h
@@ -5,6 +5,9 @@
  * found in the LICENSE file.
  */
 
+#ifndef SkOSFile_ios_DEFINED
+#define SkOSFile_ios_DEFINED
+
 #include "SkString.h"
 
 #ifdef SK_BUILD_FOR_IOS
@@ -39,3 +42,5 @@
     return true;
 }
 #endif
+
+#endif  // SkOSFile_ios_DEFINED
diff --git a/src/ports/SkScalerContext_win_dw.cpp b/src/ports/SkScalerContext_win_dw.cpp
index a4bb208..baa0f67 100644
--- a/src/ports/SkScalerContext_win_dw.cpp
+++ b/src/ports/SkScalerContext_win_dw.cpp
@@ -719,6 +719,7 @@
              "Fallback bounding box could not be determined.");
         if (glyph_check_and_set_bounds(glyph, bbox)) {
             glyph->fForceBW = 1;
+            glyph->fMaskFormat = SkMask::kBW_Format;
         }
     }
     // TODO: handle the case where a request for DWRITE_TEXTURE_ALIASED_1x1
@@ -1122,8 +1123,9 @@
     //Copy the mask into the glyph.
     const uint8_t* src = (const uint8_t*)bits;
     if (DWRITE_RENDERING_MODE_ALIASED == renderingMode) {
+        SkASSERT(SkMask::kBW_Format == glyph.fMaskFormat);
+        SkASSERT(DWRITE_TEXTURE_ALIASED_1x1 == textureType);
         bilevel_to_bw(src, glyph);
-        const_cast<SkGlyph&>(glyph).fMaskFormat = SkMask::kBW_Format;
     } else if (!isLCD(fRec)) {
         if (textureType == DWRITE_TEXTURE_ALIASED_1x1) {
             if (fPreBlend.isApplicable()) {
diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp
index 049dcd9..ebf0c72 100644
--- a/src/shaders/SkImageShader.cpp
+++ b/src/shaders/SkImageShader.cpp
@@ -91,9 +91,7 @@
     // legacy shader impl should be able to handle these matrices
     return true;
 }
-#endif
 
-#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
 SkShaderBase::Context* SkImageShader::onMakeContext(const ContextRec& rec,
                                                     SkArenaAlloc* alloc) const {
     if (fImage->alphaType() == kUnpremul_SkAlphaType) {
@@ -109,6 +107,21 @@
         return nullptr;
     }
 
+    // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer,
+    // so it can't handle bitmaps larger than 65535.
+    //
+    // We back off another bit to 32767 to make small amounts of
+    // intermediate math safe, e.g. in
+    //
+    //     SkFixed fx = ...;
+    //     fx = tile(fx + SK_Fixed1);
+    //
+    // we want to make sure (fx + SK_Fixed1) never overflows.
+    if (fImage-> width() > 32767 ||
+        fImage->height() > 32767) {
+        return nullptr;
+    }
+
     SkMatrix inv;
     if (!this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &inv) ||
         !legacy_shader_can_handle(inv)) {
@@ -131,21 +144,11 @@
     return const_cast<SkImage*>(fImage.get());
 }
 
-static bool bitmap_is_too_big(int w, int h) {
-    // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer, as it
-    // communicates between its matrix-proc and its sampler-proc. Until we can
-    // widen that, we have to reject bitmaps that are larger.
-    //
-    static const int kMaxSize = 65535;
-
-    return w > kMaxSize || h > kMaxSize;
-}
-
 sk_sp<SkShader> SkImageShader::Make(sk_sp<SkImage> image,
                                     TileMode tx, TileMode ty,
                                     const SkMatrix* localMatrix,
                                     bool clampAsIfUnpremul) {
-    if (!image || bitmap_is_too_big(image->width(), image->height())) {
+    if (!image) {
         return sk_make_sp<SkEmptyShader>();
     }
     return sk_sp<SkShader>{ new SkImageShader(image, tx,ty, localMatrix, clampAsIfUnpremul) };
@@ -171,8 +174,7 @@
         case SkShader::TileMode::kMirror_TileMode:
             return GrSamplerState::WrapMode::kMirrorRepeat;
         case SkShader::kDecal_TileMode:
-            // TODO: depending on caps, we should extend WrapMode for decal...
-            return GrSamplerState::WrapMode::kClamp;
+            return GrSamplerState::WrapMode::kClampToBorder;
     }
     SK_ABORT("Unknown tile mode.");
     return GrSamplerState::WrapMode::kClamp;
@@ -189,6 +191,22 @@
     GrSamplerState::WrapMode wrapModes[] = {tile_mode_to_wrap_mode(fTileModeX),
                                             tile_mode_to_wrap_mode(fTileModeY)};
 
+    // If either domainX or domainY are un-ignored, a texture domain effect has to be used to
+    // implement the decal mode (while leaving non-decal axes alone). The wrap mode originally
+    // clamp-to-border is reset to clamp since the hw cannot implement it directly.
+    GrTextureDomain::Mode domainX = GrTextureDomain::kIgnore_Mode;
+    GrTextureDomain::Mode domainY = GrTextureDomain::kIgnore_Mode;
+    if (!args.fContext->contextPriv().caps()->clampToBorderSupport()) {
+        if (wrapModes[0] == GrSamplerState::WrapMode::kClampToBorder) {
+            domainX = GrTextureDomain::kDecal_Mode;
+            wrapModes[0] = GrSamplerState::WrapMode::kClamp;
+        }
+        if (wrapModes[1] == GrSamplerState::WrapMode::kClampToBorder) {
+            domainY = GrTextureDomain::kDecal_Mode;
+            wrapModes[1] = GrSamplerState::WrapMode::kClamp;
+        }
+    }
+
     // Must set wrap and filter on the sampler before requesting a texture. In two places below
     // we check the matrix scale factors to determine how to interpret the filter quality setting.
     // This completely ignores the complexity of the drawVertices case where explicit local coords
@@ -212,9 +230,19 @@
 
     std::unique_ptr<GrFragmentProcessor> inner;
     if (doBicubic) {
-        inner = GrBicubicEffect::Make(std::move(proxy), lmInverse, wrapModes);
+        // domainX and domainY will properly apply the decal effect with the texture domain used in
+        // the bicubic filter if clamp to border was unsupported in hardware
+        inner = GrBicubicEffect::Make(std::move(proxy), lmInverse, wrapModes, domainX, domainY);
     } else {
-        inner = GrSimpleTextureEffect::Make(std::move(proxy), lmInverse, samplerState);
+        if (domainX != GrTextureDomain::kIgnore_Mode || domainY != GrTextureDomain::kIgnore_Mode) {
+            SkRect domain = GrTextureDomain::MakeTexelDomain(
+                    SkIRect::MakeWH(proxy->width(), proxy->height()),
+                    domainX, domainY);
+            inner = GrTextureDomainEffect::Make(std::move(proxy), lmInverse, domain,
+                                                domainX, domainY, samplerState);
+        } else {
+            inner = GrSimpleTextureEffect::Make(std::move(proxy), lmInverse, samplerState);
+        }
     }
     inner = GrColorSpaceXformEffect::Make(std::move(inner), fImage->colorSpace(),
                                           fImage->alphaType(),
diff --git a/src/shaders/SkPerlinNoiseShader.cpp b/src/shaders/SkPerlinNoiseShader.cpp
index b0e3b58..b0d464b 100644
--- a/src/shaders/SkPerlinNoiseShader.cpp
+++ b/src/shaders/SkPerlinNoiseShader.cpp
@@ -768,7 +768,7 @@
             , fNoiseSampler(std::move(noiseProxy))
             , fPaintingData(std::move(paintingData)) {
         this->setTextureSamplerCnt(2);
-        fCoordTransform.reset(matrix);
+        fCoordTransform = GrCoordTransform(matrix);
         this->addCoordTransform(&fCoordTransform);
     }
 
@@ -1191,7 +1191,7 @@
             , fGradientSampler(std::move(gradientProxy))
             , fPaintingData(std::move(paintingData)) {
         this->setTextureSamplerCnt(2);
-        fCoordTransform.reset(matrix);
+        fCoordTransform = GrCoordTransform(matrix);
         this->addCoordTransform(&fCoordTransform);
     }
 
diff --git a/src/shaders/SkPictureShader.cpp b/src/shaders/SkPictureShader.cpp
index 5e0f9be..1601ab9 100644
--- a/src/shaders/SkPictureShader.cpp
+++ b/src/shaders/SkPictureShader.cpp
@@ -18,6 +18,7 @@
 #include "SkPicturePriv.h"
 #include "SkReadBuffer.h"
 #include "SkResourceCache.h"
+#include <atomic>
 
 #if SK_SUPPORT_GPU
 #include "GrCaps.h"
@@ -93,13 +94,14 @@
     }
 };
 
-static int32_t gNextID = 1;
 uint32_t next_id() {
-    int32_t id;
+    static std::atomic<uint32_t> nextID{1};
+
+    uint32_t id;
     do {
-        id = sk_atomic_inc(&gNextID);
+        id = nextID++;
     } while (id == SK_InvalidGenID);
-    return static_cast<uint32_t>(id);
+    return id;
 }
 
 } // namespace
diff --git a/src/shaders/SkShader.cpp b/src/shaders/SkShader.cpp
index f1875ed..b1ac064 100644
--- a/src/shaders/SkShader.cpp
+++ b/src/shaders/SkShader.cpp
@@ -6,7 +6,6 @@
  */
 
 #include "SkArenaAlloc.h"
-#include "SkAtomics.h"
 #include "SkBitmapProcShader.h"
 #include "SkColorShader.h"
 #include "SkColorSpaceXformer.h"
@@ -26,35 +25,13 @@
 #include "GrFragmentProcessor.h"
 #endif
 
-//#define SK_TRACK_SHADER_LIFETIME
-
-#ifdef SK_TRACK_SHADER_LIFETIME
-    static int32_t gShaderCounter;
-#endif
-
-static inline void inc_shader_counter() {
-#ifdef SK_TRACK_SHADER_LIFETIME
-    int32_t prev = sk_atomic_inc(&gShaderCounter);
-    SkDebugf("+++ shader counter %d\n", prev + 1);
-#endif
-}
-static inline void dec_shader_counter() {
-#ifdef SK_TRACK_SHADER_LIFETIME
-    int32_t prev = sk_atomic_dec(&gShaderCounter);
-    SkDebugf("--- shader counter %d\n", prev - 1);
-#endif
-}
-
 SkShaderBase::SkShaderBase(const SkMatrix* localMatrix)
     : fLocalMatrix(localMatrix ? *localMatrix : SkMatrix::I()) {
-    inc_shader_counter();
     // Pre-cache so future calls to fLocalMatrix.getType() are threadsafe.
     (void)fLocalMatrix.getType();
 }
 
-SkShaderBase::~SkShaderBase() {
-    dec_shader_counter();
-}
+SkShaderBase::~SkShaderBase() {}
 
 void SkShaderBase::flatten(SkWriteBuffer& buffer) const {
     this->INHERITED::flatten(buffer);
diff --git a/src/shaders/gradients/Sk4fGradientPriv.h b/src/shaders/gradients/Sk4fGradientPriv.h
index 6755cb4..72f24d3 100644
--- a/src/shaders/gradients/Sk4fGradientPriv.h
+++ b/src/shaders/gradients/Sk4fGradientPriv.h
@@ -17,7 +17,7 @@
 
 // Templates shared by various 4f gradient flavors.
 
-namespace {
+namespace {  // NOLINT(google-build-namespaces)
 
 enum class ApplyPremul { True, False };
 
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 3374630..f9d4098 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -257,6 +257,11 @@
     if (!baseType) {
         return nullptr;
     }
+    if (fKind != Program::kFragmentProcessor_Kind &&
+        (decl.fModifiers.fFlags & Modifiers::kIn_Flag) &&
+        baseType->kind() == Type::Kind::kMatrix_Kind) {
+        fErrors.error(decl.fOffset, "'in' variables may not have matrix type");
+    }
     for (const auto& varDecl : decl.fVars) {
         if (decl.fModifiers.fLayout.fLocation == 0 && decl.fModifiers.fLayout.fIndex == 0 &&
             (decl.fModifiers.fFlags & Modifiers::kOut_Flag) && fKind == Program::kFragment_Kind &&
@@ -1549,37 +1554,6 @@
                                                              std::move(ifFalse)));
 }
 
-// scales the texture coordinates by the texture size for sampling rectangle textures.
-// For float2coordinates, implements the transformation:
-//     texture(sampler, coord) -> texture(sampler, textureSize(sampler) * coord)
-// For float3coordinates, implements the transformation:
-//     texture(sampler, coord) -> texture(sampler, float3textureSize(sampler), 1.0) * coord))
-void IRGenerator::fixRectSampling(std::vector<std::unique_ptr<Expression>>& arguments) {
-    SkASSERT(arguments.size() == 2);
-    SkASSERT(arguments[0]->fType == *fContext.fSampler2DRect_Type);
-    SkASSERT(arguments[0]->fKind == Expression::kVariableReference_Kind);
-    const Variable& sampler = ((VariableReference&) *arguments[0]).fVariable;
-    const Symbol* textureSizeSymbol = (*fSymbolTable)["textureSize"];
-    SkASSERT(textureSizeSymbol->fKind == Symbol::kFunctionDeclaration_Kind);
-    const FunctionDeclaration& textureSize = (FunctionDeclaration&) *textureSizeSymbol;
-    std::vector<std::unique_ptr<Expression>> sizeArguments;
-    sizeArguments.emplace_back(new VariableReference(-1, sampler));
-    std::unique_ptr<Expression> float2ize = call(-1, textureSize, std::move(sizeArguments));
-    const Type& type = arguments[1]->fType;
-    std::unique_ptr<Expression> scale;
-    if (type == *fContext.fFloat2_Type) {
-        scale = std::move(float2ize);
-    } else {
-        SkASSERT(type == *fContext.fFloat3_Type);
-        std::vector<std::unique_ptr<Expression>> float3rguments;
-        float3rguments.push_back(std::move(float2ize));
-        float3rguments.emplace_back(new FloatLiteral(fContext, -1, 1.0));
-        scale.reset(new Constructor(-1, *fContext.fFloat3_Type, std::move(float3rguments)));
-    }
-    arguments[1].reset(new BinaryExpression(-1, std::move(scale), Token::STAR,
-                                            std::move(arguments[1]), type));
-}
-
 std::unique_ptr<Expression> IRGenerator::call(int offset,
                                               const FunctionDeclaration& function,
                                               std::vector<std::unique_ptr<Expression>> arguments) {
@@ -1620,10 +1594,6 @@
                              VariableReference::kPointer_RefKind);
         }
     }
-    if (function.fBuiltin && function.fName == "texture" &&
-        arguments[0]->fType == *fContext.fSampler2DRect_Type) {
-        this->fixRectSampling(arguments);
-    }
     return std::unique_ptr<FunctionCall>(new FunctionCall(offset, *returnType, function,
                                                           std::move(arguments)));
 }
diff --git a/src/sksl/SkSLIRGenerator.h b/src/sksl/SkSLIRGenerator.h
index f456915..0effeb4 100644
--- a/src/sksl/SkSLIRGenerator.h
+++ b/src/sksl/SkSLIRGenerator.h
@@ -167,7 +167,6 @@
     // returns a statement which converts sk_Position from device to normalized coordinates
     std::unique_ptr<Statement> getNormalizeSkPositionCode();
 
-    void fixRectSampling(std::vector<std::unique_ptr<Expression>>& arguments);
     void checkValid(const Expression& expr);
     void setRefKind(const Expression& expr, VariableReference::RefKind kind);
     void getConstantInt(const Expression& value, int64_t* out);
diff --git a/src/sksl/SkSLMetalCodeGenerator.cpp b/src/sksl/SkSLMetalCodeGenerator.cpp
index e4bcb5e..f69efdd 100644
--- a/src/sksl/SkSLMetalCodeGenerator.cpp
+++ b/src/sksl/SkSLMetalCodeGenerator.cpp
@@ -324,7 +324,8 @@
 }
 
 void MetalCodeGenerator::writeFragCoord() {
-    this->write("float4(_fragCoord.x, _anonInterface0.u_skRTHeight - _fragCoord.y, 0.0, 1.0)");
+    this->write("float4(_fragCoord.x, _anonInterface0.u_skRTHeight - _fragCoord.y, 0.0, "
+                "_fragCoord.w)");
 }
 
 void MetalCodeGenerator::writeVariableReference(const VariableReference& ref) {
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.cpp b/src/sksl/SkSLSPIRVCodeGenerator.cpp
index 2878296..9bc134e 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/SkSLSPIRVCodeGenerator.cpp
@@ -1499,8 +1499,11 @@
             chain.push_back(this->writeIntLiteral(index));
             break;
         }
-        default:
-            chain.push_back(this->getLValue(expr, out)->getPointer());
+        default: {
+            SpvId id = this->getLValue(expr, out)->getPointer();
+            SkASSERT(id != 0);
+            chain.push_back(id);
+        }
     }
     return chain;
 }
@@ -1742,7 +1745,7 @@
             fRTHeightFieldIndex = 0;
         }
         SkASSERT(fRTHeightFieldIndex != (SpvId) -1);
-        // write float4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, 0.0, 1.0)
+        // write float4(gl_FragCoord.x, u_skRTHeight - gl_FragCoord.y, 0.0, gl_FragCoord.w)
         SpvId xId = this->nextId();
         this->writeInstruction(SpvOpCompositeExtract, this->getType(*fContext.fFloat_Type), xId,
                                result, 0, out);
@@ -1766,7 +1769,9 @@
         FloatLiteral zero(fContext, -1, 0.0);
         SpvId zeroId = writeFloatLiteral(zero);
         FloatLiteral one(fContext, -1, 1.0);
-        SpvId oneId = writeFloatLiteral(one);
+        SpvId wId = this->nextId();
+        this->writeInstruction(SpvOpCompositeExtract, this->getType(*fContext.fFloat_Type), wId,
+                               result, 3, out);
         SpvId flipped = this->nextId();
         this->writeOpCode(SpvOpCompositeConstruct, 7, out);
         this->writeWord(this->getType(*fContext.fFloat4_Type), out);
@@ -1774,7 +1779,7 @@
         this->writeWord(xId, out);
         this->writeWord(flippedYId, out);
         this->writeWord(zeroId, out);
-        this->writeWord(oneId, out);
+        this->writeWord(wId, out);
         return flipped;
     }
     if (ref.fVariable.fModifiers.fLayout.fBuiltin == SK_CLOCKWISE_BUILTIN &&
@@ -1790,6 +1795,14 @@
 }
 
 SpvId SPIRVCodeGenerator::writeIndexExpression(const IndexExpression& expr, OutputStream& out) {
+    if (expr.fBase->fType.kind() == Type::Kind::kVector_Kind) {
+        SpvId base = this->writeExpression(*expr.fBase, out);
+        SpvId index = this->writeExpression(*expr.fIndex, out);
+        SpvId result = this->nextId();
+        this->writeInstruction(SpvOpVectorExtractDynamic, this->getType(expr.fType), result, base,
+                               index, out);
+        return result;
+    }
     return getLValue(expr, out)->load(out);
 }
 
@@ -2510,12 +2523,12 @@
     fVariableBuffer.reset();
     SpvId result = this->writeFunctionStart(f.fDeclaration, out);
     this->writeLabel(this->nextId(), out);
-    if (f.fDeclaration.fName == "main") {
-        write_stringstream(fGlobalInitializersBuffer, out);
-    }
     StringStream bodyBuffer;
     this->writeBlock((Block&) *f.fBody, bodyBuffer);
     write_stringstream(fVariableBuffer, out);
+    if (f.fDeclaration.fName == "main") {
+        write_stringstream(fGlobalInitializersBuffer, out);
+    }
     write_stringstream(bodyBuffer, out);
     if (fCurrentBlock) {
         if (f.fDeclaration.fReturnType == *fContext.fVoid_Type) {
diff --git a/src/svg/SkSVGDevice.cpp b/src/svg/SkSVGDevice.cpp
index 4ad42bc..e83908b 100644
--- a/src/svg/SkSVGDevice.cpp
+++ b/src/svg/SkSVGDevice.cpp
@@ -17,6 +17,7 @@
 #include "SkColorFilter.h"
 #include "SkData.h"
 #include "SkDraw.h"
+#include "SkFontPriv.h"
 #include "SkImage.h"
 #include "SkImageEncoder.h"
 #include "SkJpegCodec.h"
@@ -240,7 +241,7 @@
 
     void addRectAttributes(const SkRect&);
     void addPathAttributes(const SkPath&);
-    void addTextAttributes(const SkPaint&);
+    void addTextAttributes(const SkFont&);
 
 private:
     Resources addResources(const MxCp&, const SkPaint& paint);
@@ -588,12 +589,12 @@
     this->addAttribute("d", pathData);
 }
 
-void SkSVGDevice::AutoElement::addTextAttributes(const SkPaint& paint) {
-    this->addAttribute("font-size", paint.getTextSize());
+void SkSVGDevice::AutoElement::addTextAttributes(const SkFont& font) {
+    this->addAttribute("font-size", font.getSize());
 
     SkString familyName;
     SkTHashSet<SkString> familySet;
-    sk_sp<SkTypeface> tface = SkPaintPriv::RefTypefaceOrDefault(paint);
+    sk_sp<SkTypeface> tface = SkFontPriv::RefTypefaceOrDefault(font);
 
     SkASSERT(tface);
     SkFontStyle style = tface->fontStyle();
@@ -852,10 +853,9 @@
     SVGTextBuilder(SkPoint origin, const SkGlyphRun& glyphRun)
             : fOrigin(origin)
             , fLastCharWasWhitespace(true) { // start off in whitespace mode to strip all leadingspace
-        const SkPaint& paint = glyphRun.paint();
         auto runSize = glyphRun.runSize();
         SkAutoSTArray<64, SkUnichar> unichars(runSize);
-        paint.glyphsToUnichars(glyphRun.glyphsIDs().data(), runSize, unichars.get());
+        glyphRun.font().glyphsToUnichars(glyphRun.glyphsIDs().data(), runSize, unichars.get());
         auto positions = glyphRun.positions();
         for (size_t i = 0; i < runSize; ++i) {
             this->appendUnichar(unichars[i], positions[i]);
@@ -929,10 +929,10 @@
 
 void SkSVGDevice::drawGlyphRunList(const SkGlyphRunList& glyphRunList)  {
 
-    auto processGlyphRun = [this](SkPoint origin, const SkGlyphRun& glyphRun) {
-        const SkPaint& paint = glyphRun.paint();
-        AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), paint);
-        elem.addTextAttributes(paint);
+    auto processGlyphRun = [this]
+                           (SkPoint origin, const SkGlyphRun& glyphRun, const SkPaint& runPaint) {
+        AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), runPaint);
+        elem.addTextAttributes(glyphRun.font());
 
         SVGTextBuilder builder(origin, glyphRun);
         elem.addAttribute("x", builder.posX());
@@ -941,7 +941,7 @@
     };
 
     for (auto& glyphRun : glyphRunList) {
-        processGlyphRun(glyphRunList.origin(), glyphRun);
+        processGlyphRun(glyphRunList.origin(), glyphRun, glyphRunList.paint());
     }
 }
 
diff --git a/src/utils/SkCallableTraits.h b/src/utils/SkCallableTraits.h
index 9103bf2..7df0124 100644
--- a/src/utils/SkCallableTraits.h
+++ b/src/utils/SkCallableTraits.h
@@ -19,32 +19,31 @@
     };
 };
 
-#define SK_CALLABLE_TRAITS__EMPTY
 #define SK_CALLABLE_TRAITS__COMMA ,
 
-#define SK_CALLABLE_TRAITS__VARARGS(quals) \
-SK_CALLABLE_TRAITS__INSTANCE(quals, SK_CALLABLE_TRAITS__EMPTY) \
+#define SK_CALLABLE_TRAITS__VARARGS(quals, _) \
+SK_CALLABLE_TRAITS__INSTANCE(quals,) \
 SK_CALLABLE_TRAITS__INSTANCE(quals, SK_CALLABLE_TRAITS__COMMA ...)
 
 #ifdef __cpp_noexcept_function_type
-#define SK_CALLABLE_TRAITS__NE_VARARGS(quals) \
-SK_CALLABLE_TRAITS__VARARGS(quals) \
-SK_CALLABLE_TRAITS__VARARGS(quals noexcept)
+#define SK_CALLABLE_TRAITS__NE_VARARGS(quals, _) \
+SK_CALLABLE_TRAITS__VARARGS(quals,) \
+SK_CALLABLE_TRAITS__VARARGS(quals noexcept,)
 #else
-#define SK_CALLABLE_TRAITS__NE_VARARGS(quals) \
-SK_CALLABLE_TRAITS__VARARGS(quals)
+#define SK_CALLABLE_TRAITS__NE_VARARGS(quals, _) \
+SK_CALLABLE_TRAITS__VARARGS(quals,)
 #endif
 
-#define SK_CALLABLE_TRAITS__REF_NE_VARARGS(quals) \
-SK_CALLABLE_TRAITS__NE_VARARGS(quals) \
-SK_CALLABLE_TRAITS__NE_VARARGS(quals &) \
-SK_CALLABLE_TRAITS__NE_VARARGS(quals &&)
+#define SK_CALLABLE_TRAITS__REF_NE_VARARGS(quals, _) \
+SK_CALLABLE_TRAITS__NE_VARARGS(quals,) \
+SK_CALLABLE_TRAITS__NE_VARARGS(quals &,) \
+SK_CALLABLE_TRAITS__NE_VARARGS(quals &&,)
 
 #define SK_CALLABLE_TRAITS__CV_REF_NE_VARARGS() \
-SK_CALLABLE_TRAITS__REF_NE_VARARGS(SK_CALLABLE_TRAITS__EMPTY) \
-SK_CALLABLE_TRAITS__REF_NE_VARARGS(const) \
-SK_CALLABLE_TRAITS__REF_NE_VARARGS(volatile) \
-SK_CALLABLE_TRAITS__REF_NE_VARARGS(const volatile)
+SK_CALLABLE_TRAITS__REF_NE_VARARGS(,) \
+SK_CALLABLE_TRAITS__REF_NE_VARARGS(const,) \
+SK_CALLABLE_TRAITS__REF_NE_VARARGS(volatile,) \
+SK_CALLABLE_TRAITS__REF_NE_VARARGS(const volatile,)
 
 /** Infer the return_type and argument<N> of a callable type T. */
 template <typename T> struct SkCallableTraits : SkCallableTraits<decltype(&T::operator())> {};
@@ -62,7 +61,7 @@
 template <typename R, typename... Args> \
 struct SkCallableTraits<R(*)(Args... varargs) quals> : sk_base_callable_traits<R, Args...> {};
 
-SK_CALLABLE_TRAITS__NE_VARARGS()
+SK_CALLABLE_TRAITS__NE_VARARGS(,)
 #undef SK_CALLABLE_TRAITS__INSTANCE
 
 // pointer to method (..., (const, volatile), (&, &&), noexcept)
@@ -82,6 +81,5 @@
 #undef SK_CALLABLE_TRAITS__NE_VARARGS
 #undef SK_CALLABLE_TRAITS__VARARGS
 #undef SK_CALLABLE_TRAITS__COMMA
-#undef SK_CALLABLE_TRAITS__EMPTY
 
 #endif
diff --git a/src/utils/SkJSON.cpp b/src/utils/SkJSON.cpp
index 7c3dd86..c8a23a2 100644
--- a/src/utils/SkJSON.cpp
+++ b/src/utils/SkJSON.cpp
@@ -740,13 +740,14 @@
             f = f * 10.f + (*p++ - '0'); --exp;
         }
 
-        if (is_numeric(*p)) {
-            SkASSERT(*p == '.' || *p == 'e' || *p == 'E');
-            // We either have malformed input, or an (unsupported) exponent.
+        const auto decimal_scale = pow10(exp);
+        if (is_numeric(*p) || !decimal_scale) {
+            SkASSERT((*p == '.' || *p == 'e' || *p == 'E') || !decimal_scale);
+            // Malformed input, or an (unsupported) exponent, or a collapsed decimal factor.
             return nullptr;
         }
 
-        this->pushFloat(sign * f * pow10(exp));
+        this->pushFloat(sign * f * decimal_scale);
 
         return p;
     }
diff --git a/src/utils/SkLua.cpp b/src/utils/SkLua.cpp
index 4ff6c6e..3852626 100644
--- a/src/utils/SkLua.cpp
+++ b/src/utils/SkLua.cpp
@@ -15,6 +15,7 @@
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
 #include "SkData.h"
+#include "SkFont.h"
 #include "SkFontStyle.h"
 #include "SkGradientShader.h"
 #include "SkImage.h"
@@ -54,6 +55,7 @@
 DEF_MTNAME(SkCanvas)
 DEF_MTNAME(SkColorFilter)
 DEF_MTNAME(DocHolder)
+DEF_MTNAME(SkFont)
 DEF_MTNAME(SkImage)
 DEF_MTNAME(SkImageFilter)
 DEF_MTNAME(SkMatrix)
@@ -727,46 +729,6 @@
     return 0;
 }
 
-static int lpaint_isFakeBoldText(lua_State* L) {
-    lua_pushboolean(L, get_obj<SkPaint>(L, 1)->isFakeBoldText());
-    return 1;
-}
-
-static int lpaint_isLinearText(lua_State* L) {
-    lua_pushboolean(L, get_obj<SkPaint>(L, 1)->isLinearText());
-    return 1;
-}
-
-static int lpaint_isSubpixelText(lua_State* L) {
-    lua_pushboolean(L, get_obj<SkPaint>(L, 1)->isSubpixelText());
-    return 1;
-}
-
-static int lpaint_setSubpixelText(lua_State* L) {
-    get_obj<SkPaint>(L, 1)->setSubpixelText(lua2bool(L, 2));
-    return 1;
-}
-
-static int lpaint_isLCDRenderText(lua_State* L) {
-    lua_pushboolean(L, get_obj<SkPaint>(L, 1)->isLCDRenderText());
-    return 1;
-}
-
-static int lpaint_setLCDRenderText(lua_State* L) {
-    get_obj<SkPaint>(L, 1)->setLCDRenderText(lua2bool(L, 2));
-    return 1;
-}
-
-static int lpaint_isEmbeddedBitmapText(lua_State* L) {
-    lua_pushboolean(L, get_obj<SkPaint>(L, 1)->isEmbeddedBitmapText());
-    return 1;
-}
-
-static int lpaint_isAutohinted(lua_State* L) {
-    lua_pushboolean(L, get_obj<SkPaint>(L, 1)->isAutohinted());
-    return 1;
-}
-
 static int lpaint_getAlpha(lua_State* L) {
     SkLua(L).pushScalar(byte2unit(get_obj<SkPaint>(L, 1)->getAlpha()));
     return 1;
@@ -787,41 +749,6 @@
     return 0;
 }
 
-static int lpaint_getTextSize(lua_State* L) {
-    SkLua(L).pushScalar(get_obj<SkPaint>(L, 1)->getTextSize());
-    return 1;
-}
-
-static int lpaint_getTextScaleX(lua_State* L) {
-    SkLua(L).pushScalar(get_obj<SkPaint>(L, 1)->getTextScaleX());
-    return 1;
-}
-
-static int lpaint_getTextSkewX(lua_State* L) {
-    SkLua(L).pushScalar(get_obj<SkPaint>(L, 1)->getTextSkewX());
-    return 1;
-}
-
-static int lpaint_setTextSize(lua_State* L) {
-    get_obj<SkPaint>(L, 1)->setTextSize(lua2scalar(L, 2));
-    return 0;
-}
-
-static int lpaint_getTypeface(lua_State* L) {
-    push_ref(L, get_obj<SkPaint>(L, 1)->getTypeface());
-    return 1;
-}
-
-static int lpaint_setTypeface(lua_State* L) {
-    get_obj<SkPaint>(L, 1)->setTypeface(sk_ref_sp(get_ref<SkTypeface>(L, 2)));
-    return 0;
-}
-
-static int lpaint_getHinting(lua_State* L) {
-    SkLua(L).pushU32((unsigned)get_obj<SkPaint>(L, 1)->getHinting());
-    return 1;
-}
-
 static int lpaint_getFilterQuality(lua_State* L) {
     SkLua(L).pushU32(get_obj<SkPaint>(L, 1)->getFilterQuality());
     return 1;
@@ -835,12 +762,6 @@
     return 0;
 }
 
-static int lpaint_getFontID(lua_State* L) {
-    SkTypeface* face = get_obj<SkPaint>(L, 1)->getTypeface();
-    SkLua(L).pushU32(SkTypeface::UniqueID(face));
-    return 1;
-}
-
 static int lpaint_getStroke(lua_State* L) {
     lua_pushboolean(L, SkPaint::kStroke_Style == get_obj<SkPaint>(L, 1)->getStyle());
     return 1;
@@ -868,11 +789,6 @@
     return 1;
 }
 
-static int lpaint_getTextEncoding(lua_State* L) {
-    SkLua(L).pushU32((unsigned)get_obj<SkPaint>(L, 1)->getTextEncoding());
-    return 1;
-}
-
 static int lpaint_getStrokeWidth(lua_State* L) {
     SkLua(L).pushScalar(get_obj<SkPaint>(L, 1)->getStrokeWidth());
     return 1;
@@ -888,42 +804,6 @@
     return 1;
 }
 
-static int lpaint_measureText(lua_State* L) {
-    if (lua_isstring(L, 2)) {
-        size_t len;
-        const char* text = lua_tolstring(L, 2, &len);
-        SkLua(L).pushScalar(get_obj<SkPaint>(L, 1)->measureText(text, len));
-        return 1;
-    }
-    return 0;
-}
-
-struct FontMetrics {
-    SkScalar    fTop;       //!< The greatest distance above the baseline for any glyph (will be <= 0)
-    SkScalar    fAscent;    //!< The recommended distance above the baseline (will be <= 0)
-    SkScalar    fDescent;   //!< The recommended distance below the baseline (will be >= 0)
-    SkScalar    fBottom;    //!< The greatest distance below the baseline for any glyph (will be >= 0)
-    SkScalar    fLeading;   //!< The recommended distance to add between lines of text (will be >= 0)
-    SkScalar    fAvgCharWidth;  //!< the average charactor width (>= 0)
-    SkScalar    fXMin;      //!< The minimum bounding box x value for all glyphs
-    SkScalar    fXMax;      //!< The maximum bounding box x value for all glyphs
-    SkScalar    fXHeight;   //!< the height of an 'x' in px, or 0 if no 'x' in face
-};
-
-static int lpaint_getFontMetrics(lua_State* L) {
-    SkFontMetrics fm;
-    SkScalar height = get_obj<SkPaint>(L, 1)->getFontMetrics(&fm);
-
-    lua_newtable(L);
-    setfield_scalar(L, "top", fm.fTop);
-    setfield_scalar(L, "ascent", fm.fAscent);
-    setfield_scalar(L, "descent", fm.fDescent);
-    setfield_scalar(L, "bottom", fm.fBottom);
-    setfield_scalar(L, "leading", fm.fLeading);
-    SkLua(L).pushScalar(height);
-    return 2;
-}
-
 static int lpaint_getEffects(lua_State* L) {
     const SkPaint* paint = get_obj<SkPaint>(L, 1);
 
@@ -1020,36 +900,17 @@
     { "setDither", lpaint_setDither },
     { "getFilterQuality", lpaint_getFilterQuality },
     { "setFilterQuality", lpaint_setFilterQuality },
-    { "isFakeBoldText", lpaint_isFakeBoldText },
-    { "isLinearText", lpaint_isLinearText },
-    { "isSubpixelText", lpaint_isSubpixelText },
-    { "setSubpixelText", lpaint_setSubpixelText },
-    { "isLCDRenderText", lpaint_isLCDRenderText },
-    { "setLCDRenderText", lpaint_setLCDRenderText },
-    { "isEmbeddedBitmapText", lpaint_isEmbeddedBitmapText },
-    { "isAutohinted", lpaint_isAutohinted },
     { "getAlpha", lpaint_getAlpha },
     { "setAlpha", lpaint_setAlpha },
     { "getColor", lpaint_getColor },
     { "setColor", lpaint_setColor },
-    { "getTextSize", lpaint_getTextSize },
-    { "setTextSize", lpaint_setTextSize },
-    { "getTextScaleX", lpaint_getTextScaleX },
-    { "getTextSkewX", lpaint_getTextSkewX },
-    { "getTypeface", lpaint_getTypeface },
-    { "setTypeface", lpaint_setTypeface },
-    { "getHinting", lpaint_getHinting },
-    { "getFontID", lpaint_getFontID },
     { "getStroke", lpaint_getStroke },
     { "setStroke", lpaint_setStroke },
     { "getStrokeCap", lpaint_getStrokeCap },
     { "getStrokeJoin", lpaint_getStrokeJoin },
-    { "getTextEncoding", lpaint_getTextEncoding },
     { "getStrokeWidth", lpaint_getStrokeWidth },
     { "setStrokeWidth", lpaint_setStrokeWidth },
     { "getStrokeMiter", lpaint_getStrokeMiter },
-    { "measureText", lpaint_measureText },
-    { "getFontMetrics", lpaint_getFontMetrics },
     { "getEffects", lpaint_getEffects },
     { "getColorFilter", lpaint_getColorFilter },
     { "setColorFilter", lpaint_setColorFilter },
@@ -1065,6 +926,93 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
+static int lfont_getSize(lua_State* L) {
+    SkLua(L).pushScalar(get_obj<SkFont>(L, 1)->getSize());
+    return 1;
+}
+
+static int lfont_getScaleX(lua_State* L) {
+    SkLua(L).pushScalar(get_obj<SkFont>(L, 1)->getScaleX());
+    return 1;
+}
+
+static int lfont_getSkewX(lua_State* L) {
+    SkLua(L).pushScalar(get_obj<SkFont>(L, 1)->getSkewX());
+    return 1;
+}
+
+static int lfont_setSize(lua_State* L) {
+    get_obj<SkFont>(L, 1)->setSize(lua2scalar(L, 2));
+    return 0;
+}
+
+static int lfont_getTypeface(lua_State* L) {
+    push_ref(L, get_obj<SkFont>(L, 1)->getTypeface());
+    return 1;
+}
+
+static int lfont_setTypeface(lua_State* L) {
+    get_obj<SkFont>(L, 1)->setTypeface(sk_ref_sp(get_ref<SkTypeface>(L, 2)));
+    return 0;
+}
+
+static int lfont_getHinting(lua_State* L) {
+    SkLua(L).pushU32((unsigned)get_obj<SkFont>(L, 1)->getHinting());
+    return 1;
+}
+
+static int lfont_getFontID(lua_State* L) {
+    SkTypeface* face = get_obj<SkFont>(L, 1)->getTypeface();
+    SkLua(L).pushU32(SkTypeface::UniqueID(face));
+    return 1;
+}
+
+static int lfont_measureText(lua_State* L) {
+    if (lua_isstring(L, 2)) {
+        size_t len;
+        const char* text = lua_tolstring(L, 2, &len);
+        SkLua(L).pushScalar(get_obj<SkFont>(L, 1)->measureText(text, len, kUTF8_SkTextEncoding));
+        return 1;
+    }
+    return 0;
+}
+
+static int lfont_getMetrics(lua_State* L) {
+    SkFontMetrics fm;
+    SkScalar height = get_obj<SkFont>(L, 1)->getMetrics(&fm);
+
+    lua_newtable(L);
+    setfield_scalar(L, "top", fm.fTop);
+    setfield_scalar(L, "ascent", fm.fAscent);
+    setfield_scalar(L, "descent", fm.fDescent);
+    setfield_scalar(L, "bottom", fm.fBottom);
+    setfield_scalar(L, "leading", fm.fLeading);
+    SkLua(L).pushScalar(height);
+    return 2;
+}
+
+static int lfont_gc(lua_State* L) {
+    get_obj<SkFont>(L, 1)->~SkFont();
+    return 0;
+}
+
+static const struct luaL_Reg gSkFont_Methods[] = {
+    { "getSize", lfont_getSize },
+    { "setSize", lfont_setSize },
+    { "getScaleX", lfont_getScaleX },
+    { "getSkewX", lfont_getSkewX },
+    { "getTypeface", lfont_getTypeface },
+    { "setTypeface", lfont_setTypeface },
+    { "getHinting", lfont_getHinting },
+    { "getFontID", lfont_getFontID },
+    { "measureText", lfont_measureText },
+    { "getMetrics", lfont_getMetrics },
+    { "__gc", lfont_gc },
+    { nullptr, nullptr }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
 static const char* mode2string(SkShader::TileMode mode) {
     static const char* gNames[] = { "clamp", "repeat", "mirror" };
     SkASSERT((unsigned)mode < SK_ARRAY_COUNT(gNames));
@@ -1923,11 +1871,11 @@
     SkShaper shaper(nullptr);
 
     SkFont font = SkFont::LEGACY_ExtractFromPaint(paint);
-    SkTextBlobBuilder builder;
+    SkTextBlobBuilderLineHandler builder;
     SkPoint end = shaper.shape(&builder, font, text, strlen(text), true,
                                { bounds.left(), bounds.top() }, bounds.width());
 
-    push_ref<SkTextBlob>(L, builder.make());
+    push_ref<SkTextBlob>(L, builder.makeBlob());
     SkLua(L).pushScalar(end.fY);
     return 2;
 }
@@ -2039,6 +1987,7 @@
     REG_CLASS(L, SkCanvas);
     REG_CLASS(L, SkColorFilter);
     REG_CLASS(L, DocHolder);
+    REG_CLASS(L, SkFont);
     REG_CLASS(L, SkImage);
     REG_CLASS(L, SkImageFilter);
     REG_CLASS(L, SkMatrix);
diff --git a/src/utils/SkLuaCanvas.cpp b/src/utils/SkLuaCanvas.cpp
index 7f279ca..486611b 100644
--- a/src/utils/SkLuaCanvas.cpp
+++ b/src/utils/SkLuaCanvas.cpp
@@ -37,11 +37,6 @@
         lua_settop(L, -1);
     }
 
-#ifdef SK_SUPPORT_LEGACY_TEXTENCODINGENUM
-    void pushEncodedText(SkPaint::TextEncoding enc, const void* text, size_t length) {
-        this->pushEncodedText((SkTextEncoding)enc, text, length);
-    }
-#endif
     void pushEncodedText(SkTextEncoding, const void*, size_t);
 
 private:
@@ -105,6 +100,11 @@
     return kNoLayer_SaveLayerStrategy;
 }
 
+bool SkLuaCanvas::onDoSaveBehind(const SkRect*) {
+    // TODO
+    return false;
+}
+
 void SkLuaCanvas::willRestore() {
     AUTO_LUA("restore");
     this->INHERITED::willRestore();
@@ -259,35 +259,6 @@
     }
 }
 
-void SkLuaCanvas::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                             const SkPaint& paint) {
-    AUTO_LUA("drawText");
-    lua.pushEncodedText(paint.getTextEncoding(), text, byteLength);
-    lua.pushPaint(paint, "paint");
-}
-
-void SkLuaCanvas::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                                const SkPaint& paint) {
-    AUTO_LUA("drawPosText");
-    lua.pushEncodedText(paint.getTextEncoding(), text, byteLength);
-    lua.pushPaint(paint, "paint");
-}
-
-void SkLuaCanvas::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                                 SkScalar constY, const SkPaint& paint) {
-    AUTO_LUA("drawPosTextH");
-    lua.pushEncodedText(paint.getTextEncoding(), text, byteLength);
-    lua.pushPaint(paint, "paint");
-}
-
-void SkLuaCanvas::onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                                    const SkRect* cull, const SkPaint& paint) {
-    AUTO_LUA("drawTextRSXform");
-    lua.pushEncodedText(paint.getTextEncoding(), text, byteLength);
-    // TODO: export other params
-    lua.pushPaint(paint, "paint");
-}
-
 void SkLuaCanvas::onDrawTextBlob(const SkTextBlob *blob, SkScalar x, SkScalar y,
                                  const SkPaint &paint) {
     AUTO_LUA("drawTextBlob");
diff --git a/src/utils/SkNWayCanvas.cpp b/src/utils/SkNWayCanvas.cpp
index 81ec208..5e37451 100644
--- a/src/utils/SkNWayCanvas.cpp
+++ b/src/utils/SkNWayCanvas.cpp
@@ -4,7 +4,9 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+
 #include "SkNWayCanvas.h"
+#include "SkCanvasPriv.h"
 
 SkNWayCanvas::SkNWayCanvas(int width, int height) : INHERITED(width, height) {}
 
@@ -45,6 +47,7 @@
         return false;
     }
     SkCanvas* operator->() { return fCanvas; }
+    SkCanvas* get() const { return fCanvas; }
 
 private:
     const SkTDArray<SkCanvas*>& fList;
@@ -72,6 +75,15 @@
     return kNoLayer_SaveLayerStrategy;
 }
 
+bool SkNWayCanvas::onDoSaveBehind(const SkRect* bounds) {
+    Iter iter(fList);
+    while (iter.next()) {
+        SkCanvasPriv::SaveBehind(iter.get(), bounds);
+    }
+    this->INHERITED::onDoSaveBehind(bounds);
+    return false;
+}
+
 void SkNWayCanvas::willRestore() {
     Iter iter(fList);
     while (iter.next()) {
@@ -265,38 +277,6 @@
     }
 }
 
-void SkNWayCanvas::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                              const SkPaint& paint) {
-    Iter iter(fList);
-    while (iter.next()) {
-        iter->drawText(text, byteLength, x, y, paint);
-    }
-}
-
-void SkNWayCanvas::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                                 const SkPaint& paint) {
-    Iter iter(fList);
-    while (iter.next()) {
-        iter->drawPosText(text, byteLength, pos, paint);
-    }
-}
-
-void SkNWayCanvas::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                                  SkScalar constY, const SkPaint& paint) {
-    Iter iter(fList);
-    while (iter.next()) {
-        iter->drawPosTextH(text, byteLength, xpos, constY, paint);
-    }
-}
-
-void SkNWayCanvas::onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                                     const SkRect* cull, const SkPaint& paint) {
-    Iter iter(fList);
-    while (iter.next()) {
-        iter->drawTextRSXform(text, byteLength, xform, cull, paint);
-    }
-}
-
 void SkNWayCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                   const SkPaint &paint) {
     Iter iter(fList);
diff --git a/src/utils/SkPaintFilterCanvas.cpp b/src/utils/SkPaintFilterCanvas.cpp
index b152f44..8f48b44 100644
--- a/src/utils/SkPaintFilterCanvas.cpp
+++ b/src/utils/SkPaintFilterCanvas.cpp
@@ -220,39 +220,6 @@
     }
 }
 
-void SkPaintFilterCanvas::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                                     const SkPaint& paint) {
-    AutoPaintFilter apf(this, kText_Type, paint);
-    if (apf.shouldDraw()) {
-        this->SkNWayCanvas::onDrawText(text, byteLength, x, y, *apf.paint());
-    }
-}
-
-void SkPaintFilterCanvas::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                                        const SkPaint& paint) {
-    AutoPaintFilter apf(this, kText_Type, paint);
-    if (apf.shouldDraw()) {
-        this->SkNWayCanvas::onDrawPosText(text, byteLength, pos, *apf.paint());
-    }
-}
-
-void SkPaintFilterCanvas::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                                         SkScalar constY, const SkPaint& paint) {
-    AutoPaintFilter apf(this, kText_Type, paint);
-    if (apf.shouldDraw()) {
-        this->SkNWayCanvas::onDrawPosTextH(text, byteLength, xpos, constY, *apf.paint());
-    }
-}
-
-void SkPaintFilterCanvas::onDrawTextRSXform(const void* text, size_t byteLength,
-                                            const SkRSXform xform[], const SkRect* cull,
-                                            const SkPaint& paint) {
-    AutoPaintFilter apf(this, kText_Type, paint);
-    if (apf.shouldDraw()) {
-        this->SkNWayCanvas::onDrawTextRSXform(text, byteLength, xform, cull, *apf.paint());
-    }
-}
-
 void SkPaintFilterCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                          const SkPaint& paint) {
     AutoPaintFilter apf(this, kTextBlob_Type, paint);
diff --git a/src/utils/SkTextUtils.cpp b/src/utils/SkTextUtils.cpp
index f33058e..3973497 100644
--- a/src/utils/SkTextUtils.cpp
+++ b/src/utils/SkTextUtils.cpp
@@ -5,49 +5,45 @@
  * found in the LICENSE file.
  */
 
+#include "SkFontPriv.h"
+#include "SkPath.h"
 #include "SkTextUtils.h"
+#include "SkTextBlob.h"
 
-void SkTextUtils::DrawText(SkCanvas* canvas, const void* text, size_t size, SkScalar x, SkScalar y,
-                            const SkPaint& origPaint, Align align) {
-    int count = origPaint.countText(text, size);
-    if (!count) {
-        return;
-    }
-
-    SkPaint paint(origPaint);
-    SkAutoSTArray<32, uint16_t> glyphStorage;
-    const uint16_t* glyphs;
-
-    if ((SkTextEncoding)paint.getTextEncoding() != kGlyphID_SkTextEncoding) {
-        glyphStorage.reset(count);
-        paint.textToGlyphs(text, size, glyphStorage.get());
-        glyphs = glyphStorage.get();
-        paint.setTextEncoding(kGlyphID_SkTextEncoding);
-    } else {
-        glyphs = static_cast<const uint16_t*>(text);
-    }
-
-    SkAutoSTArray<32, SkScalar> widthStorage(count);
-    SkScalar* widths = widthStorage.get();
-    paint.getTextWidths(glyphs, count * sizeof(uint16_t), widths);
-
+void SkTextUtils::Draw(SkCanvas* canvas, const void* text, size_t size, SkTextEncoding encoding,
+                       SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint,
+                       Align align) {
     if (align != kLeft_Align) {
-        SkScalar offset = 0;
-        for (int i = 0; i < count; ++i) {
-            offset += widths[i];
-        }
+        SkScalar width = font.measureText(text, size, encoding);
         if (align == kCenter_Align) {
-            offset *= 0.5f;
+            width *= 0.5f;
         }
-        x -= offset;
+        x -= width;
     }
 
-    // Turn widths into h-positions
-    for (int i = 0; i < count; ++i) {
-        SkScalar w = widths[i];
-        widths[i] = x;
-        x += w;
-    }
-    canvas->drawPosTextH(glyphs, count * sizeof(uint16_t), widths, y, paint);
+    canvas->drawTextBlob(SkTextBlob::MakeFromText(text, size, font, encoding), x, y, paint);
+}
+
+void SkTextUtils::GetPath(const void* text, size_t length, SkTextEncoding encoding,
+                          SkScalar x, SkScalar y, const SkFont& font, SkPath* path) {
+    SkAutoToGlyphs ag(font, text, length, encoding);
+    SkAutoTArray<SkPoint> pos(ag.count());
+    font.getPos(ag.glyphs(), ag.count(), &pos[0], {x, y});
+
+    struct Rec {
+        SkPath* fDst;
+        const SkPoint* fPos;
+    } rec = { path, &pos[0] };
+
+    path->reset();
+    font.getPaths(ag.glyphs(), ag.count(), [](const SkPath* src, const SkMatrix& mx, void* ctx) {
+        Rec* rec = (Rec*)ctx;
+        if (src) {
+            SkMatrix m(mx);
+            m.postTranslate(rec->fPos->fX, rec->fPos->fY);
+            rec->fDst->addPath(*src, m);
+        }
+        rec->fPos += 1;
+    }, &rec);
 }
 
diff --git a/src/utils/mac/SkUniqueCFRef.h b/src/utils/mac/SkUniqueCFRef.h
index 6c90727..7f8ef1c 100644
--- a/src/utils/mac/SkUniqueCFRef.h
+++ b/src/utils/mac/SkUniqueCFRef.h
@@ -17,11 +17,9 @@
 #include <CoreFoundation/CoreFoundation.h>
 #include <memory>
 
-namespace {
 template <typename CFRef> using SkUniqueCFRef =
     std::unique_ptr<skstd::remove_pointer_t<CFRef>,
                     SkFunctionWrapper<void, skstd::remove_pointer_t<CFTypeRef>, CFRelease>>;
-}  // namespace
 
 #endif
 #endif
diff --git a/src/utils/win/SkWGL_win.cpp b/src/utils/win/SkWGL_win.cpp
index 1360a01..1a42c8a 100644
--- a/src/utils/win/SkWGL_win.cpp
+++ b/src/utils/win/SkWGL_win.cpp
@@ -6,7 +6,7 @@
  */
 
 #include "SkTypes.h"
-#if defined(SK_BUILD_FOR_WIN)
+#if defined(SK_BUILD_FOR_WIN) && !defined(_M_ARM64)
 
 #include "SkWGL.h"
 
diff --git a/tests/AndroidCodecTest.cpp b/tests/AndroidCodecTest.cpp
index 3381d4f..49415f8 100644
--- a/tests/AndroidCodecTest.cpp
+++ b/tests/AndroidCodecTest.cpp
@@ -15,7 +15,6 @@
 #include "SkEncodedImageFormat.h"
 #include "SkImageGenerator.h"
 #include "SkImageInfo.h"
-#include "SkMatrix44.h"
 #include "SkPixmapPriv.h"
 #include "SkRefCnt.h"
 #include "SkSize.h"
@@ -157,8 +156,7 @@
         return;
     }
 
-    auto expected = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                          SkColorSpace::kDCIP3_D65_Gamut);
+    auto expected = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
     REPORTER_ASSERT(r, SkColorSpace::Equals(cs.get(), expected.get()));
 }
 
@@ -190,17 +188,15 @@
     REPORTER_ASSERT(r, !cs->isSRGB());
     REPORTER_ASSERT(r, cs->gammaCloseToSRGB());
 
-    SkMatrix44 matrix;
+    skcms_Matrix3x3 matrix;
     cs->toXYZD50(&matrix);
 
-    SkMatrix44 expected;
-    static constexpr float kExpected[] = {
-        0.426254272f,  0.369018555f,  0.168914795f,
-        0.226013184f,  0.685974121f,  0.0880126953f,
-        0.0116729736f, 0.0950927734f, 0.71812439f,
-    };
-    expected.set3x3RowMajorf(kExpected);
-    REPORTER_ASSERT(r, matrix == expected);
+    static constexpr skcms_Matrix3x3 kExpected = {{
+        { 0.426254272f,  0.369018555f,  0.168914795f  },
+        { 0.226013184f,  0.685974121f,  0.0880126953f },
+        { 0.0116729736f, 0.0950927734f, 0.71812439f   },
+    }};
+    REPORTER_ASSERT(r, 0 == memcmp(&matrix, &kExpected, sizeof(skcms_Matrix3x3)));
 }
 
 DEF_TEST(AndroidCodec_orientation, r) {
diff --git a/tests/CanvasTest.cpp b/tests/CanvasTest.cpp
index bf30dd9..8aeb0c4 100644
--- a/tests/CanvasTest.cpp
+++ b/tests/CanvasTest.cpp
@@ -813,8 +813,8 @@
 
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
 DEF_TEST(Canvas_LegacyColorBehavior, r) {
-    sk_sp<SkColorSpace> cs = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                                   SkColorSpace::kAdobeRGB_Gamut);
+    sk_sp<SkColorSpace> cs = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
+                                                   SkNamedGamut::kAdobeRGB);
 
     // Make a Adobe RGB bitmap.
     SkBitmap bitmap;
diff --git a/tests/CodecPartialTest.cpp b/tests/CodecPartialTest.cpp
index 452ba85..84bc03e 100644
--- a/tests/CodecPartialTest.cpp
+++ b/tests/CodecPartialTest.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "CodecPriv.h"
 #include "FakeStreams.h"
 #include "Resources.h"
 #include "SkBitmap.h"
@@ -40,28 +41,25 @@
     return SkCodec::kSuccess == codec->getPixels(info, dst->getPixels(), dst->rowBytes());
 }
 
-static void compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const SkBitmap& bm2) {
+static bool compare_bitmaps(skiatest::Reporter* r, const SkBitmap& bm1, const SkBitmap& bm2) {
     const SkImageInfo& info = bm1.info();
     if (info != bm2.info()) {
         ERRORF(r, "Bitmaps have different image infos!");
-        return;
+        return false;
     }
     const size_t rowBytes = info.minRowBytes();
     for (int i = 0; i < info.height(); i++) {
         if (memcmp(bm1.getAddr(0, i), bm2.getAddr(0, i), rowBytes)) {
             ERRORF(r, "Bitmaps have different pixels, starting on line %i!", i);
-            return;
+            return false;
         }
     }
+
+    return true;
 }
 
-static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
-    sk_sp<SkData> file = GetResourceAsData(name);
-    if (!file) {
-        SkDebugf("missing resource %s\n", name);
-        return;
-    }
-
+static void test_partial(skiatest::Reporter* r, const char* name, const sk_sp<SkData>& file,
+                         size_t minBytes, size_t increment) {
     SkBitmap truth;
     if (!create_truth(file, &truth)) {
         ERRORF(r, "Failed to decode %s\n", name);
@@ -69,15 +67,13 @@
     }
 
     // Now decode part of the file
-    HaltingStream* stream = new HaltingStream(file, SkTMax(file->size() / 2, minBytes));
+    HaltingStream* stream = new HaltingStream(file, minBytes);
 
     // Note that we cheat and hold on to a pointer to stream, though it is owned by
     // partialCodec.
-    std::unique_ptr<SkCodec> partialCodec(SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream)));
+    auto partialCodec = SkCodec::MakeFromStream(std::unique_ptr<SkStream>(stream));
     if (!partialCodec) {
-        // Technically, this could be a small file where half the file is not
-        // enough.
-        ERRORF(r, "Failed to create codec for %s", name);
+        ERRORF(r, "Failed to create codec for %s with %zu bytes", name, minBytes);
         return;
     }
 
@@ -98,12 +94,14 @@
             return;
         }
 
-        // Append some data. The size is arbitrary, but deliberately different from
-        // the buffer size used by SkPngCodec.
-        stream->addNewData(1000);
+        stream->addNewData(increment);
     }
 
     while (true) {
+        // This imitates how Chromium calls getFrameCount before resuming a decode.
+        // Without this line, the test passes. With it, it fails when skia_use_wuffs
+        // is true.
+        partialCodec->getFrameCount();
         const SkCodec::Result result = partialCodec->incrementalDecode();
 
         if (result == SkCodec::kSuccess) {
@@ -117,15 +115,25 @@
             return;
         }
 
-        // Append some data. The size is arbitrary, but deliberately different from
-        // the buffer size used by SkPngCodec.
-        stream->addNewData(1000);
+        stream->addNewData(increment);
     }
 
     // compare to original
     compare_bitmaps(r, truth, incremental);
 }
 
+static void test_partial(skiatest::Reporter* r, const char* name, size_t minBytes = 0) {
+    sk_sp<SkData> file = GetResourceAsData(name);
+    if (!file) {
+        SkDebugf("missing resource %s\n", name);
+        return;
+    }
+
+    // This size is arbitrary, but deliberately different from the buffer size used by SkPngCodec.
+    constexpr size_t kIncrement = 1000;
+    test_partial(r, name, file, SkTMax(file->size() / 2, minBytes), kIncrement);
+}
+
 DEF_TEST(Codec_partial, r) {
 #if 0
     // FIXME (scroggo): SkPngCodec needs to use SkStreamBuffer in order to
@@ -146,6 +154,21 @@
     test_partial(r, "images/color_wheel.gif");
 }
 
+DEF_TEST(Codec_partialWuffs, r) {
+    const char* path = "images/alphabetAnim.gif";
+    auto file = GetResourceAsData(path);
+    if (!file) {
+        ERRORF(r, "missing %s", path);
+    } else {
+        // This is the end of the first frame. SkCodec will treat this as a
+        // single frame gif.
+        file = SkData::MakeSubset(file.get(), 0, 153);
+        // Start with 100 to get a partial decode, then add the rest of the
+        // first frame to decode a full image.
+        test_partial(r, path, file, 100, 53);
+    }
+}
+
 // Verify that when decoding an animated gif byte by byte we report the correct
 // fRequiredFrame as soon as getFrameInfo reports the frame.
 DEF_TEST(Codec_requiredFrame, r) {
@@ -289,7 +312,14 @@
         frameInfo = partialCodec->getFrameInfo();
         REPORTER_ASSERT(r, frameInfo.size() == i + 1);
         REPORTER_ASSERT(r, frameInfo[i].fFullyReceived);
-        compare_bitmaps(r, frames[i], frame);
+        if (!compare_bitmaps(r, frames[i], frame)) {
+            ERRORF(r, "\tfailure was on frame %i", i);
+            SkString name = SkStringPrintf("expected_%i", i);
+            write_bm(name.c_str(), frames[i]);
+
+            name = SkStringPrintf("actual_%i", i);
+            write_bm(name.c_str(), frame);
+        }
     }
 }
 
diff --git a/tests/CodecTest.cpp b/tests/CodecTest.cpp
index 4656d87..8c918a8 100644
--- a/tests/CodecTest.cpp
+++ b/tests/CodecTest.cpp
@@ -26,7 +26,6 @@
 #include "SkMD5.h"
 #include "SkMakeUnique.h"
 #include "SkMalloc.h"
-#include "SkMatrix44.h"
 #include "SkPixmap.h"
 #include "SkPngChunkReader.h"
 #include "SkPngEncoder.h"
@@ -169,9 +168,9 @@
 }
 
 template<typename Codec>
-static void test_codec(skiatest::Reporter* r, Codec* codec, SkBitmap& bm, const SkImageInfo& info,
-        const SkISize& size, SkCodec::Result expectedResult, SkMD5::Digest* digest,
-        const SkMD5::Digest* goodDigest) {
+static void test_codec(skiatest::Reporter* r, const char* path, Codec* codec, SkBitmap& bm,
+        const SkImageInfo& info, const SkISize& size, SkCodec::Result expectedResult,
+        SkMD5::Digest* digest, const SkMD5::Digest* goodDigest) {
 
     REPORTER_ASSERT(r, info.dimensions() == size);
     bm.allocPixels(info);
@@ -195,16 +194,21 @@
             // This will allow comparison even if the image is incomplete.
             bm565.eraseColor(SK_ColorBLACK);
 
-            REPORTER_ASSERT(r, expectedResult == codec->getPixels(info565,
-                    bm565.getPixels(), bm565.rowBytes()));
+            auto actualResult = codec->getPixels(info565, bm565.getPixels(), bm565.rowBytes());
+            if (actualResult == expectedResult) {
+                SkMD5::Digest digest565;
+                md5(bm565, &digest565);
 
-            SkMD5::Digest digest565;
-            md5(bm565, &digest565);
-
-            // A dumb client's request for non-opaque should also succeed.
-            for (auto alpha : { kPremul_SkAlphaType, kUnpremul_SkAlphaType }) {
-                info565 = info565.makeAlphaType(alpha);
-                test_info(r, codec, info565, expectedResult, &digest565);
+                // A request for non-opaque should also succeed.
+                for (auto alpha : { kPremul_SkAlphaType, kUnpremul_SkAlphaType }) {
+                    info565 = info565.makeAlphaType(alpha);
+                    test_info(r, codec, info565, expectedResult, &digest565);
+                }
+            } else {
+                ERRORF(r, "Decoding %s to 565 failed with result \"%s\"\n\t\t\t\texpected:\"%s\"",
+                          path,
+                          SkCodec::ResultToString(actualResult),
+                          SkCodec::ResultToString(expectedResult));
             }
         } else {
             test_info(r, codec, info565, SkCodec::kInvalidConversion, nullptr);
@@ -310,7 +314,7 @@
     SkBitmap bm;
     SkCodec::Result expectedResult =
         supportsIncomplete ? SkCodec::kIncompleteInput : SkCodec::kSuccess;
-    test_codec(r, codec.get(), bm, info, size, expectedResult, &codecDigest, nullptr);
+    test_codec(r, path, codec.get(), bm, info, size, expectedResult, &codecDigest, nullptr);
 
     // Scanline decoding follows.
 
@@ -436,7 +440,7 @@
 
         SkBitmap bm;
         SkMD5::Digest androidCodecDigest;
-        test_codec(r, androidCodec.get(), bm, info, size, expectedResult, &androidCodecDigest,
+        test_codec(r, path, androidCodec.get(), bm, info, size, expectedResult, &androidCodecDigest,
                    &codecDigest);
     }
 
@@ -1020,7 +1024,7 @@
 
     const int dstWidth = subsetWidth / opts.fSampleSize;
     const int dstHeight = subsetHeight / opts.fSampleSize;
-    auto colorSpace = SkColorSpace::MakeRGB(g2Dot2_TransferFn, SkColorSpace::kAdobeRGB_Gamut);
+    auto colorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB);
     SkImageInfo dstInfo = codec->getInfo().makeWH(dstWidth, dstHeight)
                                           .makeColorType(kN32_SkColorType)
                                           .makeColorSpace(colorSpace);
@@ -1586,22 +1590,21 @@
 
     // Test with P3 color space.
     SkDynamicMemoryWStream p3Buf;
-    sk_sp<SkColorSpace> p3 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                                   SkColorSpace::kDCIP3_D65_Gamut);
+    sk_sp<SkColorSpace> p3 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
     pixmap.setColorSpace(p3);
     encode_format(&p3Buf, pixmap, format);
     sk_sp<SkData> p3Data = p3Buf.detachAsData();
     std::unique_ptr<SkCodec> p3Codec(SkCodec::MakeFromData(p3Data));
     REPORTER_ASSERT(r, p3Codec->getInfo().colorSpace()->gammaCloseToSRGB());
-    SkMatrix44 mat0, mat1;
+    skcms_Matrix3x3 mat0, mat1;
     bool success = p3->toXYZD50(&mat0);
     REPORTER_ASSERT(r, success);
     success = p3Codec->getInfo().colorSpace()->toXYZD50(&mat1);
     REPORTER_ASSERT(r, success);
 
-    for (int i = 0; i < 4; i++) {
-        for (int j = 0; j < 4; j++) {
-            REPORTER_ASSERT(r, color_space_almost_equal(mat0.get(i, j), mat1.get(i, j)));
+    for (int i = 0; i < 3; i++) {
+        for (int j = 0; j < 3; j++) {
+            REPORTER_ASSERT(r, color_space_almost_equal(mat0.vals[i][j], mat1.vals[i][j]));
         }
     }
 }
diff --git a/tests/ColorSpaceTest.cpp b/tests/ColorSpaceTest.cpp
index 42986e9..1deb8ee 100644
--- a/tests/ColorSpaceTest.cpp
+++ b/tests/ColorSpaceTest.cpp
@@ -11,7 +11,6 @@
 #include "SkColorSpacePriv.h"
 #include "SkData.h"
 #include "SkImageInfo.h"
-#include "SkMatrix44.h"
 #include "SkRefCnt.h"
 #include "SkStream.h"
 #include "SkTypes.h"
@@ -34,20 +33,13 @@
     REPORTER_ASSERT(r, nullptr != space);
     REPORTER_ASSERT(r, expectedGamma == space->gammaNamed());
 
-    SkMatrix44 mat;
+    skcms_Matrix3x3 mat;
     space->toXYZD50(&mat);
-    const float src[] = {
-        1, 0, 0, 1,
-        0, 1, 0, 1,
-        0, 0, 1, 1,
-    };
     const float* ref[3] = { red, green, blue };
-    float dst[4];
     for (int i = 0; i < 3; ++i) {
-        mat.mapScalars(&src[i*4], dst);
-        REPORTER_ASSERT(r, almost_equal(ref[i][0], dst[0]));
-        REPORTER_ASSERT(r, almost_equal(ref[i][1], dst[1]));
-        REPORTER_ASSERT(r, almost_equal(ref[i][2], dst[2]));
+        REPORTER_ASSERT(r, almost_equal(ref[i][0], mat.vals[0][i]));
+        REPORTER_ASSERT(r, almost_equal(ref[i][1], mat.vals[1][i]));
+        REPORTER_ASSERT(r, almost_equal(ref[i][2], mat.vals[2][i]));
     }
 }
 
@@ -70,12 +62,6 @@
     test_space(r, colorSpace.get(), red, green, blue, expectedGamma);
 }
 
-static constexpr float g_sRGB_XYZ[]{
-    0.4358f, 0.3853f, 0.1430f,    // Rx, Gx, Bx
-    0.2224f, 0.7170f, 0.0606f,    // Ry, Gy, Gz
-    0.0139f, 0.0971f, 0.7139f,    // Rz, Gz, Bz
-};
-
 static constexpr float g_sRGB_R[]{ 0.4358f, 0.2224f, 0.0139f };
 static constexpr float g_sRGB_G[]{ 0.3853f, 0.7170f, 0.0971f };
 static constexpr float g_sRGB_B[]{ 0.1430f, 0.0606f, 0.7139f };
@@ -93,9 +79,9 @@
               kSRGB_SkGammaNamed);
 #endif
 
-    const float red[] = { 0.385117f, 0.716904f, 0.0970612f };
+    const float red[]   = { 0.385117f, 0.716904f, 0.0970612f };
     const float green[] = { 0.143051f, 0.0606079f, 0.713913f };
-    const float blue[] = { 0.436035f, 0.222488f, 0.013916f };
+    const float blue[]  = { 0.436035f, 0.222488f, 0.013916f };
     test_path(r, "images/icc-v2-gbr.jpg", red, green, blue, k2Dot2Curve_SkGammaNamed);
 
     test_path(r, "images/webp-color-profile-crash.webp",
@@ -108,80 +94,6 @@
             red, green, blue, kNonStandard_SkGammaNamed);
 }
 
-DEF_TEST(ColorSpaceSRGBCompare, r) {
-    // Create an sRGB color space by name
-    sk_sp<SkColorSpace> namedColorSpace = SkColorSpace::MakeSRGB();
-
-    // Create an sRGB color space by value
-    SkMatrix44 srgbToxyzD50;
-    srgbToxyzD50.set3x3RowMajorf(g_sRGB_XYZ);
-    sk_sp<SkColorSpace> rgbColorSpace =
-            SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, srgbToxyzD50);
-    REPORTER_ASSERT(r, rgbColorSpace == namedColorSpace);
-
-    SkColorSpaceTransferFn srgbFn;
-    srgbFn.fA = (1.0f / 1.055f);
-    srgbFn.fB = (0.055f / 1.055f);
-    srgbFn.fC = (1.0f / 12.92f);
-    srgbFn.fD = 0.04045f;
-    srgbFn.fE = 0.0f;
-    srgbFn.fF = 0.0f;
-    srgbFn.fG = 2.4f;
-    sk_sp<SkColorSpace> rgbColorSpace2 = SkColorSpace::MakeRGB(srgbFn, srgbToxyzD50);
-    REPORTER_ASSERT(r, rgbColorSpace2 == namedColorSpace);
-
-    // Change a single value from the sRGB matrix
-    srgbToxyzD50.set(2, 2, 0.5f);
-    sk_sp<SkColorSpace> strangeColorSpace =
-            SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, srgbToxyzD50);
-    REPORTER_ASSERT(r, strangeColorSpace != namedColorSpace);
-}
-
-DEF_TEST(ColorSpaceSRGBLinearCompare, r) {
-    // Create the linear sRGB color space by name
-    sk_sp<SkColorSpace> namedColorSpace = SkColorSpace::MakeSRGBLinear();
-
-    // Create the linear sRGB color space via the sRGB color space's makeLinearGamma()
-    auto srgb = SkColorSpace::MakeSRGB();
-    sk_sp<SkColorSpace> viaSrgbColorSpace = srgb->makeLinearGamma();
-    REPORTER_ASSERT(r, namedColorSpace == viaSrgbColorSpace);
-
-    // Create a linear sRGB color space by value
-    SkMatrix44 srgbToxyzD50;
-    srgbToxyzD50.set3x3RowMajorf(g_sRGB_XYZ);
-    sk_sp<SkColorSpace> rgbColorSpace =
-        SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, srgbToxyzD50);
-    REPORTER_ASSERT(r, rgbColorSpace == namedColorSpace);
-
-    SkColorSpaceTransferFn linearExpFn;
-    linearExpFn.fA = 1.0f;
-    linearExpFn.fB = 0.0f;
-    linearExpFn.fC = 0.0f;
-    linearExpFn.fD = 0.0f;
-    linearExpFn.fE = 0.0f;
-    linearExpFn.fF = 0.0f;
-    linearExpFn.fG = 1.0f;
-    sk_sp<SkColorSpace> rgbColorSpace2 = SkColorSpace::MakeRGB(linearExpFn, srgbToxyzD50);
-    REPORTER_ASSERT(r, rgbColorSpace2 == namedColorSpace);
-
-    SkColorSpaceTransferFn linearFn;
-    linearFn.fA = 0.0f;
-    linearFn.fB = 0.0f;
-    linearFn.fC = 1.0f;
-    linearFn.fD = 1.0f;
-    linearFn.fE = 0.0f;
-    linearFn.fF = 0.0f;
-    linearFn.fG = 0.0f;
-    sk_sp<SkColorSpace> rgbColorSpace3 = SkColorSpace::MakeRGB(linearFn, srgbToxyzD50);
-    REPORTER_ASSERT(r, rgbColorSpace3 == namedColorSpace);
-
-    // Change a single value from the sRGB matrix
-    srgbToxyzD50.set(2, 2, 0.5f);
-    sk_sp<SkColorSpace> strangeColorSpace =
-        SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, srgbToxyzD50);
-    REPORTER_ASSERT(r, strangeColorSpace != namedColorSpace);
-}
-
 static void test_serialize(skiatest::Reporter* r, sk_sp<SkColorSpace> space, bool isNamed) {
     sk_sp<SkData> data1 = space->serialize();
 
@@ -219,15 +131,19 @@
     test("icc_profiles/HP_ZR30w.icc");
     test("icc_profiles/HP_Z32x.icc");
 
-    SkColorSpaceTransferFn fn;
-    fn.fA = 1.0f;
-    fn.fB = 0.0f;
-    fn.fC = 1.0f;
-    fn.fD = 0.5f;
-    fn.fE = 0.0f;
-    fn.fF = 0.0f;
-    fn.fG = 1.0f;
-    SkMatrix44 toXYZ(SkMatrix44::kIdentity_Constructor);
+    skcms_TransferFunction fn;
+    fn.a = 1.0f;
+    fn.b = 0.0f;
+    fn.c = 1.0f;
+    fn.d = 0.5f;
+    fn.e = 0.0f;
+    fn.f = 0.0f;
+    fn.g = 1.0f;
+    skcms_Matrix3x3 toXYZ = {{
+        { 1, 0, 0 },
+        { 0, 1, 0 },
+        { 0, 0, 1 },
+    }};
     test_serialize(r, SkColorSpace::MakeRGB(fn, toXYZ), false);
 }
 
@@ -248,15 +164,19 @@
     sk_sp<SkColorSpace> z30 = parse("icc_profiles/HP_ZR30w.icc");
     sk_sp<SkColorSpace> z32 = parse("icc_profiles/HP_Z32x.icc");
 
-    SkColorSpaceTransferFn fn;
-    fn.fA = 1.0f;
-    fn.fB = 0.0f;
-    fn.fC = 1.0f;
-    fn.fD = 0.5f;
-    fn.fE = 0.0f;
-    fn.fF = 0.0f;
-    fn.fG = 1.0f;
-    SkMatrix44 toXYZ(SkMatrix44::kIdentity_Constructor);
+    skcms_TransferFunction fn;
+    fn.a = 1.0f;
+    fn.b = 0.0f;
+    fn.c = 1.0f;
+    fn.d = 0.5f;
+    fn.e = 0.0f;
+    fn.f = 0.0f;
+    fn.g = 1.0f;
+    skcms_Matrix3x3 toXYZ = {{
+        { 1, 0, 0 },
+        { 0, 1, 0 },
+        { 0, 0, 1 },
+    }};
     sk_sp<SkColorSpace> rgb4 = SkColorSpace::MakeRGB(fn, toXYZ);
 
     REPORTER_ASSERT(r, SkColorSpace::Equals(nullptr, nullptr));
@@ -273,28 +193,20 @@
     REPORTER_ASSERT(r, !SkColorSpace::Equals(srgb.get(), rgb4.get()));
 }
 
-static inline bool matrix_almost_equal(const SkMatrix44& a, const SkMatrix44& b) {
-    return almost_equal(a.get(0, 0), b.get(0, 0)) &&
-           almost_equal(a.get(0, 1), b.get(0, 1)) &&
-           almost_equal(a.get(0, 2), b.get(0, 2)) &&
-           almost_equal(a.get(0, 3), b.get(0, 3)) &&
-           almost_equal(a.get(1, 0), b.get(1, 0)) &&
-           almost_equal(a.get(1, 1), b.get(1, 1)) &&
-           almost_equal(a.get(1, 2), b.get(1, 2)) &&
-           almost_equal(a.get(1, 3), b.get(1, 3)) &&
-           almost_equal(a.get(2, 0), b.get(2, 0)) &&
-           almost_equal(a.get(2, 1), b.get(2, 1)) &&
-           almost_equal(a.get(2, 2), b.get(2, 2)) &&
-           almost_equal(a.get(2, 3), b.get(2, 3)) &&
-           almost_equal(a.get(3, 0), b.get(3, 0)) &&
-           almost_equal(a.get(3, 1), b.get(3, 1)) &&
-           almost_equal(a.get(3, 2), b.get(3, 2)) &&
-           almost_equal(a.get(3, 3), b.get(3, 3));
+static inline bool matrix_almost_equal(const skcms_Matrix3x3& a, const skcms_Matrix3x3& b) {
+    for (int r = 0; r < 3; ++r) {
+        for (int c = 0; c < 3; ++c) {
+            if (!almost_equal(a.vals[r][c], b.vals[r][c])) {
+                return false;
+            }
+        }
+    }
+    return true;
 }
 
 static inline void check_primaries(skiatest::Reporter* r, const SkColorSpacePrimaries& primaries,
-                                   const SkMatrix44& reference) {
-    SkMatrix44 toXYZ;
+                                   const skcms_Matrix3x3& reference) {
+    skcms_Matrix3x3 toXYZ;
     bool result = primaries.toXYZD50(&toXYZ);
     REPORTER_ASSERT(r, result);
     REPORTER_ASSERT(r, matrix_almost_equal(toXYZ, reference));
@@ -302,21 +214,16 @@
 
 DEF_TEST(ColorSpace_Primaries, r) {
     // sRGB primaries (D65)
-    SkColorSpacePrimaries srgb;
-    srgb.fRX = 0.64f;
-    srgb.fRY = 0.33f;
-    srgb.fGX = 0.30f;
-    srgb.fGY = 0.60f;
-    srgb.fBX = 0.15f;
-    srgb.fBY = 0.06f;
-    srgb.fWX = 0.3127f;
-    srgb.fWY = 0.3290f;
-    SkMatrix44 srgbToXYZ;
-    bool result = srgb.toXYZD50(&srgbToXYZ);
+    skcms_Matrix3x3 srgbToXYZ;
+    bool result = skcms_PrimariesToXYZD50(
+        0.64f, 0.33f,
+        0.30f, 0.60f,
+        0.15f, 0.06f,
+        0.3127f, 0.3290f,
+        &srgbToXYZ);
     REPORTER_ASSERT(r, result);
 
-    sk_sp<SkColorSpace> space = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                                      srgbToXYZ);
+    sk_sp<SkColorSpace> space = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, srgbToXYZ);
     REPORTER_ASSERT(r, SkColorSpace::MakeSRGB() == space);
 
     // ProPhoto (D50)
@@ -329,10 +236,11 @@
     proPhoto.fBY = 0.0001f;
     proPhoto.fWX = 0.34567f;
     proPhoto.fWY = 0.35850f;
-    SkMatrix44 proToXYZ;
-    proToXYZ.set3x3(0.7976749f, 0.2880402f, 0.0000000f,
-                    0.1351917f, 0.7118741f, 0.0000000f,
-                    0.0313534f, 0.0000857f, 0.8252100f);
+    skcms_Matrix3x3 proToXYZ = {{
+        { 0.7976749f, 0.1351917f, 0.0313534f },
+        { 0.2880402f, 0.7118741f, 0.0000857f },
+        { 0.0000000f, 0.0000000f, 0.8252100f },
+    }};
     check_primaries(r, proPhoto, proToXYZ);
 
     // NTSC (C)
@@ -345,10 +253,11 @@
     ntsc.fBY = 0.08f;
     ntsc.fWX = 0.31006f;
     ntsc.fWY = 0.31616f;
-    SkMatrix44 ntscToXYZ;
-    ntscToXYZ.set3x3(0.6343706f, 0.3109496f, -0.0011817f,
-                     0.1852204f, 0.5915984f, 0.0555518f,
-                     0.1446290f, 0.0974520f, 0.7708399f);
+    skcms_Matrix3x3 ntscToXYZ = {{
+        {  0.6343706f, 0.1852204f, 0.1446290f },
+        {  0.3109496f, 0.5915984f, 0.0974520f },
+        { -0.0011817f, 0.0555518f, 0.7708399f }
+    }};
     check_primaries(r, ntsc, ntscToXYZ);
 
     // DCI P3 (D65)
@@ -361,9 +270,8 @@
     p3.fBY = 0.060f;
     p3.fWX = 0.3127f;
     p3.fWY = 0.3290f;
-    space = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                  SkColorSpace::kDCIP3_D65_Gamut);
-    SkMatrix44 reference;
+    space = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
+    skcms_Matrix3x3 reference;
     SkAssertResult(space->toXYZD50(&reference));
     check_primaries(r, p3, reference);
 
@@ -377,8 +285,7 @@
     rec2020.fBY = 0.046f;
     rec2020.fWX = 0.3127f;
     rec2020.fWY = 0.3290f;
-    space = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                  SkColorSpace::kRec2020_Gamut);
+    space = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kRec2020);
     SkAssertResult(space->toXYZD50(&reference));
     check_primaries(r, rec2020, reference);
 }
@@ -386,18 +293,16 @@
 DEF_TEST(ColorSpace_MatrixHash, r) {
     sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB();
 
-    SkColorSpaceTransferFn fn;
-    fn.fA = 1.0f;
-    fn.fB = 0.0f;
-    fn.fC = 0.0f;
-    fn.fD = 0.0f;
-    fn.fE = 0.0f;
-    fn.fF = 0.0f;
-    fn.fG = 3.0f;
+    skcms_TransferFunction fn;
+    fn.a = 1.0f;
+    fn.b = 0.0f;
+    fn.c = 0.0f;
+    fn.d = 0.0f;
+    fn.e = 0.0f;
+    fn.f = 0.0f;
+    fn.g = 3.0f;
 
-    SkMatrix44 srgbMat;
-    srgbMat.set3x3RowMajorf(gSRGB_toXYZD50);
-    sk_sp<SkColorSpace> strange = SkColorSpace::MakeRGB(fn, srgbMat);
+    sk_sp<SkColorSpace> strange = SkColorSpace::MakeRGB(fn, SkNamedGamut::kSRGB);
 
     REPORTER_ASSERT(r, srgb->toXYZD50Hash() == strange->toXYZD50Hash());
 }
@@ -405,15 +310,15 @@
 DEF_TEST(ColorSpace_IsSRGB, r) {
     sk_sp<SkColorSpace> srgb0 = SkColorSpace::MakeSRGB();
 
-    SkColorSpaceTransferFn fn;
-    fn.fA = 1.0f;
-    fn.fB = 0.0f;
-    fn.fC = 0.0f;
-    fn.fD = 0.0f;
-    fn.fE = 0.0f;
-    fn.fF = 0.0f;
-    fn.fG = 2.2f;
-    sk_sp<SkColorSpace> twoDotTwo = SkColorSpace::MakeRGB(fn, SkColorSpace::kSRGB_Gamut);
+    skcms_TransferFunction fn;
+    fn.a = 1.0f;
+    fn.b = 0.0f;
+    fn.c = 0.0f;
+    fn.d = 0.0f;
+    fn.e = 0.0f;
+    fn.f = 0.0f;
+    fn.g = 2.2f;
+    sk_sp<SkColorSpace> twoDotTwo = SkColorSpace::MakeRGB(fn, SkNamedGamut::kSRGB);
 
     REPORTER_ASSERT(r, srgb0->isSRGB());
     REPORTER_ASSERT(r, !twoDotTwo->isSRGB());
diff --git a/tests/DeferredDisplayListTest.cpp b/tests/DeferredDisplayListTest.cpp
index 12793d6..8e49446 100644
--- a/tests/DeferredDisplayListTest.cpp
+++ b/tests/DeferredDisplayListTest.cpp
@@ -23,6 +23,7 @@
 #include "SkCanvas.h"
 #include "SkColorSpace.h"
 #include "SkDeferredDisplayList.h"
+#include "SkDeferredDisplayListPriv.h"
 #include "SkDeferredDisplayListRecorder.h"
 #include "SkGpuDevice.h"
 #include "SkImage.h"
@@ -295,8 +296,7 @@
         case 4:
             // This just needs to be a colorSpace different from that returned by MakeSRGB().
             // In this case we just change the gamut.
-            fColorSpace = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                                SkColorSpace::kAdobeRGB_Gamut);
+            fColorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kAdobeRGB);
             break;
         case kSampleCount:
             fSampleCount = 4;
@@ -803,6 +803,61 @@
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// Ensure that reusing a single DDLRecorder to create multiple DDLs works cleanly
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(DDLMultipleDDLs, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+
+    SkImageInfo ii = SkImageInfo::MakeN32Premul(32, 32);
+    sk_sp<SkSurface> s = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, ii);
+
+    SkBitmap bitmap;
+    bitmap.allocPixels(ii);
+
+    SkSurfaceCharacterization characterization;
+    SkAssertResult(s->characterize(&characterization));
+
+    SkDeferredDisplayListRecorder recorder(characterization);
+
+    SkCanvas* canvas1 = recorder.getCanvas();
+
+    canvas1->clear(SK_ColorRED);
+
+    canvas1->save();
+    canvas1->clipRect(SkRect::MakeXYWH(8, 8, 16, 16));
+
+    std::unique_ptr<SkDeferredDisplayList> ddl1 = recorder.detach();
+
+    SkCanvas* canvas2 = recorder.getCanvas();
+
+    SkPaint p;
+    p.setColor(SK_ColorGREEN);
+    canvas2->drawRect(SkRect::MakeWH(32, 32), p);
+
+    std::unique_ptr<SkDeferredDisplayList> ddl2 = recorder.detach();
+
+    REPORTER_ASSERT(reporter, ddl1->priv().lazyProxyData());
+    REPORTER_ASSERT(reporter, ddl2->priv().lazyProxyData());
+
+    // The lazy proxy data being different ensures that the SkSurface, SkCanvas and backing-
+    // lazy proxy are all different between the two DDLs
+    REPORTER_ASSERT(reporter, ddl1->priv().lazyProxyData() != ddl2->priv().lazyProxyData());
+
+    s->draw(ddl1.get());
+    s->draw(ddl2.get());
+
+    // Make sure the clipRect from DDL1 didn't percolate into DDL2
+    s->readPixels(ii, bitmap.getPixels(), bitmap.rowBytes(), 0, 0);
+    for (int y = 0; y < 32; ++y) {
+        for (int x = 0; x < 32; ++x) {
+            REPORTER_ASSERT(reporter, bitmap.getColor(x, y) == SK_ColorGREEN);
+            if (bitmap.getColor(x, y) != SK_ColorGREEN) {
+                return; // we only really need to report the error once
+            }
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // Check that the texture-specific flags (i.e., for external & rectangle textures) work
 // for promise images. As such, this is a GL-only test.
 DEF_GPUTEST_FOR_GL_RENDERING_CONTEXTS(DDLTextureFlagsTest, reporter, ctxInfo) {
diff --git a/tests/DrawBitmapRectTest.cpp b/tests/DrawBitmapRectTest.cpp
index 43132e9..b8fed49 100644
--- a/tests/DrawBitmapRectTest.cpp
+++ b/tests/DrawBitmapRectTest.cpp
@@ -105,28 +105,8 @@
     REPORTER_ASSERT(reporter, SkTreatAsSprite(mat, size, aaPaint));
 }
 
-static void assert_ifDrawnTo(skiatest::Reporter* reporter,
-                             const SkBitmap& bm, bool shouldBeDrawn) {
-    for (int y = 0; y < bm.height(); ++y) {
-        for (int x = 0; x < bm.width(); ++x) {
-            if (shouldBeDrawn) {
-                if (SK_ColorTRANSPARENT == *bm.getAddr32(x, y)) {
-                    REPORTER_ASSERT(reporter, false);
-                    return;
-                }
-            } else {
-                // should not be drawn
-                if (SK_ColorTRANSPARENT != *bm.getAddr32(x, y)) {
-                    REPORTER_ASSERT(reporter, false);
-                    return;
-                }
-            }
-        }
-    }
-}
-
 static void test_wacky_bitmapshader(skiatest::Reporter* reporter,
-                                    int width, int height, bool shouldBeDrawn) {
+                                    int width, int height) {
     SkBitmap dev;
     dev.allocN32Pixels(0x56F, 0x4f6);
     dev.eraseColor(SK_ColorTRANSPARENT);  // necessary, so we know if we draw to it
@@ -147,7 +127,8 @@
     if (bm.tryAllocN32Pixels(width, height)) {
         bm.eraseColor(SK_ColorRED);
     } else {
-        shouldBeDrawn = false;
+        SkASSERT(false);
+        return;
     }
 
     matrix.setAll(0.0078740157f,
@@ -164,9 +145,19 @@
     SkRect r = SkRect::MakeXYWH(681, 239, 695, 253);
     c.drawRect(r, paint);
 
-    assert_ifDrawnTo(reporter, dev, shouldBeDrawn);
+    for (int y = 0; y < dev.height(); ++y) {
+        for (int x = 0; x < dev.width(); ++x) {
+            if (SK_ColorTRANSPARENT == *dev.getAddr32(x, y)) {
+                REPORTER_ASSERT(reporter, false);
+                return;
+            }
+        }
+    }
 }
 
+// ATTENTION  We should always draw each of these sizes safely now.  ATTENTION
+// ATTENTION  I'm leaving this next /*comment*/ for posterity.       ATTENTION
+
 /*
  *  Original bug was asserting that the matrix-proc had generated a (Y) value
  *  that was out of range. This led (in the release build) to the sampler-proc
@@ -189,17 +180,15 @@
     static const struct {
         int fWidth;
         int fHeight;
-        bool fExpectedToDraw;
     } gTests[] = {
-        { 0x1b294, 0x7f,  false },   // crbug 118018 (width exceeds 64K)
-        { 0xFFFF, 0x7f,    true },   // should draw, test max width
-        { 0x7f, 0xFFFF,    true },   // should draw, test max height
+        { 0x1b294, 0x7f},   // crbug 118018 (width exceeds 64K)... should draw safely now.
+        { 0xFFFF, 0x7f },   // should draw, test max width
+        { 0x7f, 0xFFFF },   // should draw, test max height
     };
 
     for (size_t i = 0; i < SK_ARRAY_COUNT(gTests); ++i) {
         test_wacky_bitmapshader(reporter,
-                                gTests[i].fWidth, gTests[i].fHeight,
-                                gTests[i].fExpectedToDraw);
+                                gTests[i].fWidth, gTests[i].fHeight);
     }
 }
 
diff --git a/tests/DrawOpAtlasTest.cpp b/tests/DrawOpAtlasTest.cpp
index 10bc51d..cbae1a3 100644
--- a/tests/DrawOpAtlasTest.cpp
+++ b/tests/DrawOpAtlasTest.cpp
@@ -143,7 +143,7 @@
                                                 format,
                                                 kAlpha_8_GrPixelConfig,
                                                 kAtlasSize, kAtlasSize,
-                                                kNumPlots, kNumPlots,
+                                                kAtlasSize/kNumPlots, kAtlasSize/kNumPlots,
                                                 GrDrawOpAtlas::AllowMultitexturing::kYes,
                                                 EvictionFunc, nullptr);
     check(reporter, atlas.get(), 0, 4, 0);
@@ -199,14 +199,14 @@
 
     SkPaint paint;
     paint.setColor(SK_ColorRED);
-    paint.setLCDRenderText(false);
-    paint.setAntiAlias(false);
-    paint.setSubpixelText(false);
+
+    SkFont font;
+    font.setEdging(SkFont::Edging::kAlias);
 
     const char* text = "a";
 
     std::unique_ptr<GrDrawOp> op = textContext->createOp_TestingOnly(
-            context, textContext, rtc.get(), paint, SkMatrix::I(), text, 16, 16);
+            context, textContext, rtc.get(), paint, font, SkMatrix::I(), text, 16, 16);
     op->finalize(*context->contextPriv().caps(), nullptr);
 
     TestingUploadTarget uploadTarget;
@@ -233,11 +233,66 @@
     opMemoryPool->release(std::move(op));
 }
 
+void test_atlas_config(skiatest::Reporter* reporter, int maxTextureSize, size_t maxBytes,
+                       GrMaskFormat maskFormat, SkISize expectedDimensions,
+                       SkISize expectedPlotDimensions) {
+    GrDrawOpAtlasConfig config(maxTextureSize, maxBytes);
+    REPORTER_ASSERT(reporter, config.atlasDimensions(maskFormat) == expectedDimensions);
+    REPORTER_ASSERT(reporter, config.plotDimensions(maskFormat) == expectedPlotDimensions);
+}
+
 DEF_GPUTEST(GrDrawOpAtlasConfig_Basic, reporter, options) {
-    REPORTER_ASSERT(reporter, GrDrawOpAtlasConfig::PlotsPerLongDimensionForARGB(   1) == 1);
-    REPORTER_ASSERT(reporter, GrDrawOpAtlasConfig::PlotsPerLongDimensionForARGB( 256) == 1);
-    REPORTER_ASSERT(reporter, GrDrawOpAtlasConfig::PlotsPerLongDimensionForARGB( 512) == 2);
-    REPORTER_ASSERT(reporter, GrDrawOpAtlasConfig::PlotsPerLongDimensionForARGB(1024) == 4);
-    REPORTER_ASSERT(reporter, GrDrawOpAtlasConfig::PlotsPerLongDimensionForARGB(2048) == 8);
-    REPORTER_ASSERT(reporter, GrDrawOpAtlasConfig::PlotsPerLongDimensionForARGB(4096) == 8);
+    // 1/4 MB
+    test_atlas_config(reporter, 65536, 256 * 1024, kARGB_GrMaskFormat,
+                      { 256, 256 }, { 256, 256 });
+    test_atlas_config(reporter, 65536, 256 * 1024, kA8_GrMaskFormat,
+                      { 512, 512 }, { 256, 256 });
+    // 1/2 MB
+    test_atlas_config(reporter, 65536, 512 * 1024, kARGB_GrMaskFormat,
+                      { 512, 256 }, { 256, 256 });
+    test_atlas_config(reporter, 65536, 512 * 1024, kA8_GrMaskFormat,
+                      { 1024, 512 }, { 256, 256 });
+    // 1 MB
+    test_atlas_config(reporter, 65536, 1024 * 1024, kARGB_GrMaskFormat,
+                      { 512, 512 }, { 256, 256 });
+    test_atlas_config(reporter, 65536, 1024 * 1024, kA8_GrMaskFormat,
+                      { 1024, 1024 }, { 256, 256 });
+    // 2 MB
+    test_atlas_config(reporter, 65536, 2 * 1024 * 1024, kARGB_GrMaskFormat,
+                      { 1024, 512 }, { 256, 256 });
+    test_atlas_config(reporter, 65536, 2 * 1024 * 1024, kA8_GrMaskFormat,
+                      { 2048, 1024 }, { 512, 256 });
+    // 4 MB
+    test_atlas_config(reporter, 65536, 4 * 1024 * 1024, kARGB_GrMaskFormat,
+                      { 1024, 1024 }, { 256, 256 });
+    test_atlas_config(reporter, 65536, 4 * 1024 * 1024, kA8_GrMaskFormat,
+                      { 2048, 2048 }, { 512, 512 });
+    // 8 MB
+    test_atlas_config(reporter, 65536, 8 * 1024 * 1024, kARGB_GrMaskFormat,
+                      { 2048, 1024 }, { 256, 256 });
+    test_atlas_config(reporter, 65536, 8 * 1024 * 1024, kA8_GrMaskFormat,
+                      { 2048, 2048 }, { 512, 512 });
+    // 16 MB (should be same as 8 MB)
+    test_atlas_config(reporter, 65536, 16 * 1024 * 1024, kARGB_GrMaskFormat,
+                      { 2048, 1024 }, { 256, 256 });
+    test_atlas_config(reporter, 65536, 16 * 1024 * 1024, kA8_GrMaskFormat,
+                      { 2048, 2048 }, { 512, 512 });
+
+    // 4MB, restricted texture size
+    test_atlas_config(reporter, 1024, 8 * 1024 * 1024, kARGB_GrMaskFormat,
+                      { 1024, 1024 }, { 256, 256 });
+    test_atlas_config(reporter, 1024, 8 * 1024 * 1024, kA8_GrMaskFormat,
+                      { 1024, 1024 }, { 256, 256 });
+
+    // 3 MB (should be same as 2 MB)
+    test_atlas_config(reporter, 65536, 3 * 1024 * 1024, kARGB_GrMaskFormat,
+                      { 1024, 512 }, { 256, 256 });
+    test_atlas_config(reporter, 65536, 3 * 1024 * 1024, kA8_GrMaskFormat,
+                      { 2048, 1024 }, { 512, 256 });
+
+    // minimum size
+    test_atlas_config(reporter, 65536, 0, kARGB_GrMaskFormat,
+                      { 256, 256 }, { 256, 256 });
+    test_atlas_config(reporter, 65536, 0, kA8_GrMaskFormat,
+                      { 512, 512 }, { 256, 256 });
 }
diff --git a/tests/DrawTextTest.cpp b/tests/DrawTextTest.cpp
index ac13140..4ae5c09 100644
--- a/tests/DrawTextTest.cpp
+++ b/tests/DrawTextTest.cpp
@@ -21,6 +21,7 @@
 #include "Test.h"
 
 #include <cmath>
+#include <SkFont.h>
 
 static const SkColor bgColor = SK_ColorWHITE;
 
@@ -64,55 +65,6 @@
     return true;
 }
 
-DEF_TEST(DrawText, reporter) {
-    SkPaint paint;
-    paint.setColor(SK_ColorGRAY);
-    paint.setTextSize(SkIntToScalar(20));
-
-    SkIRect drawTextRect = SkIRect::MakeWH(64, 64);
-    SkBitmap drawTextBitmap;
-    create(&drawTextBitmap, drawTextRect);
-    SkCanvas drawTextCanvas(drawTextBitmap);
-
-    SkIRect drawPosTextRect = SkIRect::MakeWH(64, 64);
-    SkBitmap drawPosTextBitmap;
-    create(&drawPosTextBitmap, drawPosTextRect);
-    SkCanvas drawPosTextCanvas(drawPosTextBitmap);
-
-    // Two test cases "A" for the normal path through the code, and " " to check the
-    // early return path.
-    const char* cases[] = {"A", " "};
-    for (auto c : cases) {
-        for (float offsetY = 0.0f; offsetY < 1.0f; offsetY += (1.0f / 16.0f)) {
-            for (float offsetX = 0.0f; offsetX < 1.0f; offsetX += (1.0f / 16.0f)) {
-                SkPoint point = SkPoint::Make(25.0f + offsetX,
-                                              25.0f + offsetY);
-
-                for (unsigned int flags = 0; flags < (1 << 3); ++flags) {
-                    static const unsigned int antiAliasFlag = 1;
-                    static const unsigned int subpixelFlag = 1 << 1;
-                    static const unsigned int lcdFlag = 1 << 2;
-
-                    paint.setAntiAlias(SkToBool(flags & antiAliasFlag));
-                    paint.setSubpixelText(SkToBool(flags & subpixelFlag));
-                    paint.setLCDRenderText(SkToBool(flags & lcdFlag));
-
-                    // Test: drawText and drawPosText draw the same.
-                    drawBG(&drawTextCanvas);
-                    drawTextCanvas.drawText(c, 1, point.fX, point.fY, paint);
-
-                    drawBG(&drawPosTextCanvas);
-                    drawPosTextCanvas.drawPosText(c, 1, &point, paint);
-
-                    REPORTER_ASSERT(reporter,
-                                    compare(drawTextBitmap, drawTextRect,
-                                            drawPosTextBitmap, drawPosTextRect));
-                }
-            }
-        }
-    }
-}
-
 /** Test that drawing glyphs with empty paths is different from drawing glyphs without paths. */
 DEF_TEST(DrawText_dashout, reporter) {
     SkIRect size = SkIRect::MakeWH(64, 64);
@@ -167,12 +119,12 @@
     SkScalar oddballs[] = { 0.0f, (float)INFINITY, (float)NAN, 34359738368.0f };
 
     for (auto x : oddballs) {
-        canvas->drawString("a", +x, 0.0f, SkPaint());
-        canvas->drawString("a", -x, 0.0f, SkPaint());
+        canvas->drawString("a", +x, 0.0f, SkFont(), SkPaint());
+        canvas->drawString("a", -x, 0.0f, SkFont(), SkPaint());
     }
     for (auto y : oddballs) {
-        canvas->drawString("a", 0.0f, +y, SkPaint());
-        canvas->drawString("a", 0.0f, -y, SkPaint());
+        canvas->drawString("a", 0.0f, +y, SkFont(), SkPaint());
+        canvas->drawString("a", 0.0f, -y, SkFont(), SkPaint());
     }
 }
 
@@ -182,9 +134,8 @@
     auto surface = SkSurface::MakeRasterN32Premul(100,100);
     auto canvas = surface->getCanvas();
 
-    SkPaint paint;
-    paint.setAntiAlias(true);
-    paint.setLCDRenderText(true);
+    SkFont font;
+    font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
 
     struct {
         SkScalar textSize;
@@ -205,11 +156,11 @@
     };
 
     for (const auto& testCase : testCases) {
-        paint.setTextSize(testCase.textSize);
+        font.setSize(testCase.textSize);
         const SkScalar(&m)[9] = testCase.matrix;
         SkMatrix mat;
         mat.setAll(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]);
         canvas->setMatrix(mat);
-        canvas->drawString("Hamburgefons", 10, 10, paint);
+        canvas->drawString("Hamburgefons", 10, 10, font, SkPaint());
     }
 }
diff --git a/tests/FlattenDrawableTest.cpp b/tests/FlattenDrawableTest.cpp
index 835f9a0..525a8a9 100644
--- a/tests/FlattenDrawableTest.cpp
+++ b/tests/FlattenDrawableTest.cpp
@@ -7,6 +7,7 @@
 
 #include "SkCanvas.h"
 #include "SkDrawable.h"
+#include "SkFont.h"
 #include "SkPictureRecorder.h"
 #include "SkReadBuffer.h"
 #include "SkRect.h"
@@ -258,7 +259,7 @@
     canvas->drawPaint(paint);
     SkPaint textPaint;
     textPaint.setColor(SK_ColorBLUE);
-    canvas->drawString("TEXT", 467.0f, 100.0f, textPaint);
+    canvas->drawString("TEXT", 467.0f, 100.0f, SkFont(), textPaint);
 
     // Draw some drawables as well
     sk_sp<SkDrawable> drawable(new IntDrawable(1, 2, 3, 4));
diff --git a/tests/FontHostStreamTest.cpp b/tests/FontHostStreamTest.cpp
index df54f36..343a68c 100644
--- a/tests/FontHostStreamTest.cpp
+++ b/tests/FontHostStreamTest.cpp
@@ -8,6 +8,7 @@
 #include "SkBitmap.h"
 #include "SkCanvas.h"
 #include "SkColor.h"
+#include "SkFont.h"
 #include "SkFontDescriptor.h"
 #include "SkGraphics.h"
 #include "SkPaint.h"
@@ -65,9 +66,8 @@
     {
         SkPaint paint;
         paint.setColor(SK_ColorGRAY);
-        paint.setTextSize(SkIntToScalar(30));
 
-        paint.setTypeface(SkTypeface::MakeFromName("Georgia", SkFontStyle()));
+        SkFont font(SkTypeface::MakeFromName("Georgia", SkFontStyle()), 30);
 
         SkIRect origRect = SkIRect::MakeWH(64, 64);
         SkBitmap origBitmap;
@@ -83,7 +83,7 @@
 
         // Test: origTypeface and streamTypeface from orig data draw the same
         drawBG(&origCanvas);
-        origCanvas.drawString("A", point.fX, point.fY, paint);
+        origCanvas.drawSimpleText("A", 1, kUTF8_SkTextEncoding, point.fX, point.fY, font, paint);
 
         sk_sp<SkTypeface> typeface = SkPaintPriv::RefTypefaceOrDefault(paint);
         int ttcIndex;
@@ -103,7 +103,7 @@
 
         paint.setTypeface(streamTypeface);
         drawBG(&streamCanvas);
-        streamCanvas.drawPosText("A", 1, &point, paint);
+        streamCanvas.drawSimpleText("A", 1, kUTF8_SkTextEncoding, point.fX, point.fY, font, paint);
 
         REPORTER_ASSERT(reporter,
                         compare(origBitmap, origRect, streamBitmap, streamRect));
diff --git a/tests/FontHostTest.cpp b/tests/FontHostTest.cpp
index e4da0a0..2316760 100644
--- a/tests/FontHostTest.cpp
+++ b/tests/FontHostTest.cpp
@@ -8,6 +8,7 @@
 #include "Resources.h"
 #include "SkAutoMalloc.h"
 #include "SkEndian.h"
+#include "SkFont.h"
 #include "SkFontStream.h"
 #include "SkOSFile.h"
 #include "SkPaint.h"
@@ -87,17 +88,17 @@
 };
 
 // Test that SkPaint::textToGlyphs agrees with SkTypeface::charsToGlyphs.
-static void test_charsToGlyphs(skiatest::Reporter* reporter, const sk_sp<SkTypeface>& face) {
+static void test_charsToGlyphs(skiatest::Reporter* reporter, sk_sp<SkTypeface> face) {
     uint16_t paintGlyphIds[256];
     uint16_t faceGlyphIds[256];
 
     for (size_t testIndex = 0; testIndex < SK_ARRAY_COUNT(charsToGlyphs_TestData); ++testIndex) {
         CharsToGlyphs_TestData& test = charsToGlyphs_TestData[testIndex];
+        SkTextEncoding encoding = static_cast<SkTextEncoding>(test.typefaceEncoding);
 
-        SkPaint paint;
-        paint.setTypeface(face);
-        paint.setTextEncoding((SkTextEncoding)test.typefaceEncoding);
-        paint.textToGlyphs(test.chars, test.charsByteLength, paintGlyphIds);
+        SkFont font(face);
+        font.textToGlyphs(test.chars, test.charsByteLength, encoding,
+                          paintGlyphIds, SK_ARRAY_COUNT(paintGlyphIds));
 
         face->charsToGlyphs(test.chars, test.typefaceEncoding, faceGlyphIds, test.charCount);
 
@@ -154,20 +155,17 @@
     }
 }
 
+// Exercise this rare cmap format (platform 3, encoding 0)
 static void test_symbolfont(skiatest::Reporter* reporter) {
-    SkUnichar c = 0xf021;
-    uint16_t g;
-    SkPaint paint;
-    paint.setTypeface(MakeResourceAsTypeface("fonts/SpiderSymbol.ttf"));
-    paint.setTextEncoding(kUTF32_SkTextEncoding);
-    paint.textToGlyphs(&c, 4, &g);
-
-    if (!paint.getTypeface()) {
+    auto tf = MakeResourceAsTypeface("fonts/SpiderSymbol.ttf");
+    if (tf) {
+        SkUnichar c = 0xf021;
+        uint16_t g = SkFont(tf).unicharToGlyph(c);
+        REPORTER_ASSERT(reporter, g == 3);
+    } else {
+        // not all platforms support data fonts, so we just note that failure
         SkDebugf("Skipping FontHostTest::test_symbolfont\n");
-        return;
     }
-
-    REPORTER_ASSERT(reporter, g == 3);
 }
 
 static void test_tables(skiatest::Reporter* reporter, const sk_sp<SkTypeface>& face) {
@@ -274,29 +272,29 @@
         { SK_Scalar1/2, -SK_Scalar1/4 },
     };
 
-    SkPaint paint;
+    SkFont font;
     char txt[] = "long.text.with.lots.of.dots.";
 
     for (size_t i = 0; i < SK_ARRAY_COUNT(faces); i++) {
-        paint.setTypeface(SkTypeface::MakeFromName(faces[i], SkFontStyle()));
+        font.setTypeface(SkTypeface::MakeFromName(faces[i], SkFontStyle()));
 
         for (size_t j = 0; j  < SK_ARRAY_COUNT(settings); j++) {
-            paint.setHinting(settings[j].hinting);
-            paint.setLinearText((settings[j].flags & SkPaint::kLinearText_Flag) != 0);
-            paint.setSubpixelText((settings[j].flags & SkPaint::kSubpixelText_Flag) != 0);
+            font.setHinting(settings[j].hinting);
+            font.setLinearMetrics((settings[j].flags & SkPaint::kLinearText_Flag) != 0);
+            font.setSubpixel((settings[j].flags & SkPaint::kSubpixelText_Flag) != 0);
 
             for (size_t k = 0; k < SK_ARRAY_COUNT(gScaleRec); ++k) {
-                paint.setTextScaleX(gScaleRec[k].fScaleX);
-                paint.setTextSkewX(gScaleRec[k].fSkewX);
+                font.setScaleX(gScaleRec[k].fScaleX);
+                font.setSkewX(gScaleRec[k].fSkewX);
 
                 SkRect bounds;
 
                 // For no hinting and light hinting this should take the
                 // optimized generateAdvance path.
-                SkScalar width1 = paint.measureText(txt, strlen(txt));
+                SkScalar width1 = font.measureText(txt, strlen(txt), kUTF8_SkTextEncoding);
 
                 // Requesting the bounds forces a generateMetrics call.
-                SkScalar width2 = paint.measureText(txt, strlen(txt), &bounds);
+                SkScalar width2 = font.measureText(txt, strlen(txt), kUTF8_SkTextEncoding, &bounds);
 
                 // SkDebugf("Font: %s, generateAdvance: %f, generateMetrics: %f\n",
                 //    faces[i], SkScalarToFloat(width1), SkScalarToFloat(width2));
diff --git a/tests/FontMgrTest.cpp b/tests/FontMgrTest.cpp
index 502c1ca..47dd9db 100644
--- a/tests/FontMgrTest.cpp
+++ b/tests/FontMgrTest.cpp
@@ -20,7 +20,7 @@
 static void test_font(skiatest::Reporter* reporter) {
     SkFont font(nullptr, 24);
 
-    REPORTER_ASSERT(reporter, font.getTypeface());
+    REPORTER_ASSERT(reporter, nullptr == font.getTypeface());
     REPORTER_ASSERT(reporter, 24 == font.getSize());
     REPORTER_ASSERT(reporter, 1 == font.getScaleX());
     REPORTER_ASSERT(reporter, 0 == font.getSkewX());
@@ -46,11 +46,6 @@
     REPORTER_ASSERT(reporter, font.getTypeface() == newFont.getTypeface());
     REPORTER_ASSERT(reporter, 36 == newFont.getSize());   // double check we haven't changed
     REPORTER_ASSERT(reporter, 24 == font.getSize());   // double check we haven't changed
-
-    SkPaint paint;
-    paint.setTextSize(18);
-    font = SkFont::LEGACY_ExtractFromPaint(paint);
-    REPORTER_ASSERT(reporter, font.getSize() == paint.getTextSize());
 }
 
 /*
diff --git a/tests/FontObjTest.cpp b/tests/FontObjTest.cpp
index 8085e07..86044b4 100644
--- a/tests/FontObjTest.cpp
+++ b/tests/FontObjTest.cpp
@@ -34,6 +34,7 @@
     REPORTER_ASSERT(reporter, paint.getHinting() == p.getHinting());
 }
 
+#ifdef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
 static void test_fontmetrics(skiatest::Reporter* reporter,
                              const SkPaint& paint, const SkFont& font) {
     SkFontMetrics fm0, fm1;
@@ -50,6 +51,7 @@
     CMP(fLeading);
 #undef CMP
 }
+#endif
 
 static void test_cachedfont(skiatest::Reporter* reporter) {
     static const char* const faces[] = {
@@ -67,7 +69,9 @@
     };
 
     SkPaint paint;
+#ifdef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
     char txt[] = "long .text .with .lots .of.dots.";
+#endif
 
     unsigned mask = SkPaint::kAntiAlias_Flag            |
                     SkPaint::kFakeBoldText_Flag         |
@@ -77,6 +81,7 @@
                     SkPaint::kEmbeddedBitmapText_Flag   |
                     SkPaint::kAutoHinting_Flag;
 
+    paint.setStrokeWidth(2);
     for (size_t i = 0; i < SK_ARRAY_COUNT(faces); i++) {
         paint.setTypeface(SkTypeface::MakeFromName(faces[i], SkFontStyle()));
         for (unsigned flags = 0; flags <= 0xFFF; ++flags) {
@@ -89,20 +94,25 @@
                 for (size_t k = 0; k < SK_ARRAY_COUNT(gScaleRec); ++k) {
                     paint.setTextScaleX(gScaleRec[k].fScaleX);
                     paint.setTextSkewX(gScaleRec[k].fSkewX);
+                    for (auto style : { SkPaint::kFill_Style, SkPaint::kStroke_Style}) {
+                        paint.setStyle(style);
 
-                    const SkFont font(SkFont::LEGACY_ExtractFromPaint(paint));
+                        const SkFont font(SkFont::LEGACY_ExtractFromPaint(paint));
 
-                    test_cachedfont(reporter, paint, font);
-                    test_fontmetrics(reporter, paint, font);
+                        test_cachedfont(reporter, paint, font);
+#ifdef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
+                        test_fontmetrics(reporter, paint, font);
 
-                    SkRect pbounds, fbounds;
+                        SkRect pbounds, fbounds;
 
-                    // Requesting the bounds forces a generateMetrics call.
-                    SkScalar pwidth = paint.measureText(txt, strlen(txt), &pbounds);
-                    SkScalar fwidth = font.measureText(txt, strlen(txt), kUTF8_SkTextEncoding,
-                                                      &fbounds);
-                    REPORTER_ASSERT(reporter, pwidth == fwidth);
-                    REPORTER_ASSERT(reporter, pbounds == fbounds);
+                        // Requesting the bounds forces a generateMetrics call.
+                        SkScalar pwidth = paint.measureText(txt, strlen(txt), &pbounds);
+                        SkScalar fwidth = font.measureText(txt, strlen(txt), kUTF8_SkTextEncoding,
+                                                          &fbounds, &paint);
+                        REPORTER_ASSERT(reporter, pwidth == fwidth);
+                        REPORTER_ASSERT(reporter, pbounds == fbounds);
+#endif
+                    }
                 }
             }
         }
diff --git a/tests/GlyphRunTest.cpp b/tests/GlyphRunTest.cpp
index da368df..0405647 100644
--- a/tests/GlyphRunTest.cpp
+++ b/tests/GlyphRunTest.cpp
@@ -41,17 +41,7 @@
     }
 }
 
-DEF_TEST(GlyphRunBasic, reporter) {
-    SkGlyphID glyphs[] = {100, 3, 240, 3, 234, 111, 3, 4, 10, 11};
-    uint16_t count = SK_ARRAY_COUNT(glyphs);
-
-    SkPaint paint;
-    paint.setTextEncoding(kGlyphID_SkTextEncoding);
-
-    SkGlyphRunBuilder builder;
-    builder.drawText(paint, glyphs, count, SkPoint::Make(0, 0));
-}
-
+#if 0   // should we revitalize this by consing up a device for drawTextBlob() ?
 DEF_TEST(GlyphRunBlob, reporter) {
     constexpr uint16_t count = 5;
     constexpr int runCount = 2;
@@ -101,3 +91,4 @@
         runIndex += 1;
     }
 }
+#endif
diff --git a/tests/GrCCPRTest.cpp b/tests/GrCCPRTest.cpp
index a400985..7bff2b3 100644
--- a/tests/GrCCPRTest.cpp
+++ b/tests/GrCCPRTest.cpp
@@ -18,11 +18,13 @@
 #include "GrRenderTargetContextPriv.h"
 #include "GrShape.h"
 #include "GrTexture.h"
+#include "SkExchange.h"
 #include "SkMatrix.h"
 #include "SkPathPriv.h"
 #include "SkRect.h"
 #include "sk_tool_utils.h"
 #include "ccpr/GrCoverageCountingPathRenderer.h"
+#include "ccpr/GrCCPathCache.h"
 #include "mock/GrMockTypes.h"
 
 #include <cmath>
@@ -56,7 +58,7 @@
 
 class CCPRPathDrawer {
 public:
-    CCPRPathDrawer(GrContext* ctx, skiatest::Reporter* reporter, bool doStroke)
+    CCPRPathDrawer(sk_sp<GrContext> ctx, skiatest::Reporter* reporter, bool doStroke)
             : fCtx(ctx)
             , fCCPR(fCtx->contextPriv().drawingManager()->getCoverageCountingPathRenderer())
             , fRTC(fCtx->contextPriv().makeDeferredRenderTargetContext(
@@ -72,13 +74,19 @@
         }
     }
 
-    GrContext* ctx() const { return fCtx; }
+    GrContext* ctx() const { return fCtx.get(); }
     GrCoverageCountingPathRenderer* ccpr() const { return fCCPR; }
 
     bool valid() const { return fCCPR && fRTC; }
     void clear() const { fRTC->clear(nullptr, SK_PMColor4fTRANSPARENT,
                                      GrRenderTargetContext::CanClearFullscreen::kYes); }
-    void abandonGrContext() { fCtx = nullptr; fCCPR = nullptr; fRTC = nullptr; }
+    void destroyGrContext() {
+        SkASSERT(fRTC->unique());
+        SkASSERT(fCtx->unique());
+        fRTC.reset();
+        fCCPR = nullptr;
+        fCtx.reset();
+    }
 
     void drawPath(const SkPath& path, const SkMatrix& matrix = SkMatrix::I()) const {
         SkASSERT(this->valid());
@@ -102,7 +110,7 @@
         }
 
         fCCPR->testingOnly_drawPathDirectly({
-                fCtx, std::move(paint), &GrUserStencilSettings::kUnused, fRTC.get(), &noClip,
+                fCtx.get(), std::move(paint), &GrUserStencilSettings::kUnused, fRTC.get(), &noClip,
                 &clipBounds, &matrix, &shape, GrAAType::kCoverage, false});
     }
 
@@ -122,7 +130,7 @@
     }
 
 private:
-    GrContext* fCtx;
+    sk_sp<GrContext> fCtx;
     GrCoverageCountingPathRenderer* fCCPR;
     sk_sp<GrRenderTargetContext> fRTC;
     const bool fDoStroke;
@@ -150,17 +158,17 @@
 
         this->customizeOptions(&mockOptions, &ctxOptions);
 
-        fMockContext = GrContext::MakeMock(&mockOptions, ctxOptions);
-        if (!fMockContext) {
+        sk_sp<GrContext> mockContext = GrContext::MakeMock(&mockOptions, ctxOptions);
+        if (!mockContext) {
             ERRORF(reporter, "could not create mock context");
             return;
         }
-        if (!fMockContext->unique()) {
+        if (!mockContext->unique()) {
             ERRORF(reporter, "mock context is not unique");
             return;
         }
 
-        CCPRPathDrawer ccpr(fMockContext.get(), reporter, doStroke);
+        CCPRPathDrawer ccpr(skstd::exchange(mockContext, nullptr), reporter, doStroke);
         if (!ccpr.valid()) {
             return;
         }
@@ -176,7 +184,6 @@
     virtual void customizeOptions(GrMockOptions*, GrContextOptions*) {}
     virtual void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) = 0;
 
-    sk_sp<GrContext> fMockContext;
     SkPath fPath;
 };
 
@@ -187,7 +194,7 @@
         test.run(reporter, true); \
     }
 
-class GrCCPRTest_cleanup : public CCPRTest {
+class CCPR_cleanup : public CCPRTest {
     void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
         REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
 
@@ -212,22 +219,22 @@
             ccpr.drawPath(fPath);
             ccpr.clipFullscreenRect(fPath);
         }
-        ccpr.abandonGrContext();
         REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath));
-        fMockContext.reset();
+
+        ccpr.destroyGrContext();
         REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
     }
 };
-DEF_CCPR_TEST(GrCCPRTest_cleanup)
+DEF_CCPR_TEST(CCPR_cleanup)
 
-class GrCCPRTest_cleanupWithTexAllocFail : public GrCCPRTest_cleanup {
+class CCPR_cleanupWithTexAllocFail : public CCPR_cleanup {
     void customizeOptions(GrMockOptions* mockOptions, GrContextOptions*) override {
         mockOptions->fFailTextureAllocations = true;
     }
 };
-DEF_CCPR_TEST(GrCCPRTest_cleanupWithTexAllocFail)
+DEF_CCPR_TEST(CCPR_cleanupWithTexAllocFail)
 
-class GrCCPRTest_unregisterCulledOps : public CCPRTest {
+class CCPR_unregisterCulledOps : public CCPRTest {
     void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
         REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
 
@@ -243,13 +250,12 @@
         REPORTER_ASSERT(reporter, !SkPathPriv::TestingOnly_unique(fPath));
         ccpr.clear(); // Clear should delete the CCPR DrawPathsOp.
         REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
-        ccpr.abandonGrContext();
-        fMockContext.reset(); // Should not crash (DrawPathsOp should have unregistered itself).
+        ccpr.destroyGrContext(); // Should not crash (DrawPathsOp should have unregistered itself).
     }
 };
-DEF_CCPR_TEST(GrCCPRTest_unregisterCulledOps)
+DEF_CCPR_TEST(CCPR_unregisterCulledOps)
 
-class GrCCPRTest_parseEmptyPath : public CCPRTest {
+class CCPR_parseEmptyPath : public CCPRTest {
     void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
         REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
 
@@ -283,119 +289,552 @@
         ccpr.flush();
     }
 };
-DEF_CCPR_TEST(GrCCPRTest_parseEmptyPath)
+DEF_CCPR_TEST(CCPR_parseEmptyPath)
 
-// This test exercises CCPR's cache capabilities by drawing many paths with two different
-// transformation matrices. We then vary the matrices independently by whole and partial pixels,
-// and verify the caching behaved as expected.
-class GrCCPRTest_cache : public CCPRTest {
+static int get_mock_texture_id(const GrTexture* texture) {
+    const GrBackendTexture& backingTexture = texture->getBackendTexture();
+    SkASSERT(GrBackendApi::kMock == backingTexture.backend());
+
+    if (!backingTexture.isValid()) {
+        return 0;
+    }
+
+    GrMockTextureInfo info;
+    backingTexture.getMockTextureInfo(&info);
+    return info.fID;
+}
+
+// Base class for cache path unit tests.
+class CCPRCacheTest : public CCPRTest {
+protected:
+    // Registers as an onFlush callback in order to snag the CCPR per-flush resources and note the
+    // texture IDs.
+    class RecordLastMockAtlasIDs : public GrOnFlushCallbackObject {
+    public:
+        RecordLastMockAtlasIDs(sk_sp<GrCoverageCountingPathRenderer> ccpr) : fCCPR(ccpr) {}
+
+        int lastCopyAtlasID() const { return fLastCopyAtlasID; }
+        int lastRenderedAtlasID() const { return fLastRenderedAtlasID; }
+
+        void preFlush(GrOnFlushResourceProvider*, const uint32_t* opListIDs, int numOpListIDs,
+                      SkTArray<sk_sp<GrRenderTargetContext>>* out) override {
+            fLastRenderedAtlasID = fLastCopyAtlasID = 0;
+
+            const GrCCPerFlushResources* resources = fCCPR->testingOnly_getCurrentFlushResources();
+            if (!resources) {
+                return;
+            }
+
+            if (const GrTexture* tex = resources->testingOnly_frontCopyAtlasTexture()) {
+                fLastCopyAtlasID = get_mock_texture_id(tex);
+            }
+            if (const GrTexture* tex = resources->testingOnly_frontRenderedAtlasTexture()) {
+                fLastRenderedAtlasID = get_mock_texture_id(tex);
+            }
+        }
+
+        void postFlush(GrDeferredUploadToken, const uint32_t*, int) override {}
+
+    private:
+        sk_sp<GrCoverageCountingPathRenderer> fCCPR;
+        int fLastCopyAtlasID = 0;
+        int fLastRenderedAtlasID = 0;
+    };
+
+    CCPRCacheTest() {
+        static constexpr int primes[11] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
+
+        SkRandom rand;
+        for (size_t i = 0; i < SK_ARRAY_COUNT(fPaths); ++i) {
+            int numPts = rand.nextRangeU(GrShape::kMaxKeyFromDataVerbCnt + 1,
+                                         GrShape::kMaxKeyFromDataVerbCnt * 2);
+            int step;
+            do {
+                step = primes[rand.nextU() % SK_ARRAY_COUNT(primes)];
+            } while (step == numPts);
+            fPaths[i] = sk_tool_utils::make_star(SkRect::MakeLTRB(0,0,1,1), numPts, step);
+        }
+    }
+
+    void drawPathsAndFlush(CCPRPathDrawer& ccpr, const SkMatrix& m) {
+        this->drawPathsAndFlush(ccpr, &m, 1);
+    }
+    void drawPathsAndFlush(CCPRPathDrawer& ccpr, const SkMatrix* matrices, int numMatrices) {
+        // Draw all the paths.
+        for (size_t i = 0; i < SK_ARRAY_COUNT(fPaths); ++i) {
+            ccpr.drawPath(fPaths[i], matrices[i % numMatrices]);
+        }
+        // Re-draw a few paths, to test the case where a cache entry is hit more than once in a
+        // single flush.
+        SkRandom rand;
+        int duplicateIndices[10];
+        for (size_t i = 0; i < SK_ARRAY_COUNT(duplicateIndices); ++i) {
+            duplicateIndices[i] = rand.nextULessThan(SK_ARRAY_COUNT(fPaths));
+        }
+        for (size_t i = 0; i < SK_ARRAY_COUNT(duplicateIndices); ++i) {
+            for (size_t j = 0; j <= i; ++j) {
+                int idx = duplicateIndices[j];
+                ccpr.drawPath(fPaths[idx], matrices[idx % numMatrices]);
+            }
+        }
+        ccpr.flush();
+    }
+
+private:
     void customizeOptions(GrMockOptions*, GrContextOptions* ctxOptions) override {
         ctxOptions->fAllowPathMaskCaching = true;
     }
 
-    void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
-        static constexpr int kPathSize = 20;
-        SkRandom rand;
+    void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) final {
+        RecordLastMockAtlasIDs atlasIDRecorder(sk_ref_sp(ccpr.ccpr()));
+        ccpr.ctx()->contextPriv().addOnFlushCallbackObject(&atlasIDRecorder);
 
-        SkPath paths[300];
-        int primes[11] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
-        for (size_t i = 0; i < SK_ARRAY_COUNT(paths); ++i) {
-            int numPts = rand.nextRangeU(GrShape::kMaxKeyFromDataVerbCnt + 1,
-                                         GrShape::kMaxKeyFromDataVerbCnt * 2);
-            paths[i] = sk_tool_utils::make_star(SkRect::MakeIWH(kPathSize, kPathSize), numPts,
-                                                primes[rand.nextU() % SK_ARRAY_COUNT(primes)]);
+        this->onRun(reporter, ccpr, atlasIDRecorder);
+
+        ccpr.ctx()->contextPriv().testingOnly_flushAndRemoveOnFlushCallbackObject(&atlasIDRecorder);
+    }
+
+    virtual void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
+                       const RecordLastMockAtlasIDs&) = 0;
+
+protected:
+    SkPath fPaths[350];
+};
+
+// Ensures ccpr always reuses the same atlas texture in the animation use case.
+class CCPR_cache_animationAtlasReuse : public CCPRCacheTest {
+    void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
+               const RecordLastMockAtlasIDs& atlasIDRecorder) override {
+        SkMatrix m = SkMatrix::MakeTrans(kCanvasSize/2, kCanvasSize/2);
+        m.preScale(80, 80);
+        m.preTranslate(-.5,-.5);
+        this->drawPathsAndFlush(ccpr, m);
+
+        REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+        REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
+        const int atlasID = atlasIDRecorder.lastRenderedAtlasID();
+
+        // Ensures we always reuse the same atlas texture in the animation use case.
+        for (int i = 0; i < 12; ++i) {
+            // 59 is prime, so we will hit every integer modulo 360 before repeating.
+            m.preRotate(59, .5, .5);
+
+            // Go twice. Paths have to get drawn twice with the same matrix before we cache their
+            // atlas. This makes sure that on the subsequent draw, after an atlas has been cached
+            // and is then invalidated since the matrix will change, that the same underlying
+            // texture object is still reused for the next atlas.
+            for (int j = 0; j < 2; ++j) {
+                this->drawPathsAndFlush(ccpr, m);
+                // Nothing should be copied to an 8-bit atlas after just two draws.
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+                REPORTER_ASSERT(reporter, atlasIDRecorder.lastRenderedAtlasID() == atlasID);
+            }
         }
 
+        // Do the last draw again. (On draw 3 they should get copied to an 8-bit atlas.)
+        this->drawPathsAndFlush(ccpr, m);
+        REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
+        REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+
+        // Now double-check that everything continues to hit the cache as expected when the matrix
+        // doesn't change.
+        for (int i = 0; i < 10; ++i) {
+            this->drawPathsAndFlush(ccpr, m);
+            REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+            REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+        }
+    }
+};
+DEF_CCPR_TEST(CCPR_cache_animationAtlasReuse)
+
+class CCPR_cache_recycleEntries : public CCPRCacheTest {
+    void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
+               const RecordLastMockAtlasIDs& atlasIDRecorder) override {
+        SkMatrix m = SkMatrix::MakeTrans(kCanvasSize/2, kCanvasSize/2);
+        m.preScale(80, 80);
+        m.preTranslate(-.5,-.5);
+
+        auto cache = ccpr.ccpr()->testingOnly_getPathCache();
+        REPORTER_ASSERT(reporter, cache);
+
+        const auto& lru = cache->testingOnly_getLRU();
+
+        SkTArray<const void*> expectedPtrs;
+
+        // Ensures we always reuse the same atlas texture in the animation use case.
+        for (int i = 0; i < 5; ++i) {
+            // 59 is prime, so we will hit every integer modulo 360 before repeating.
+            m.preRotate(59, .5, .5);
+
+            // Go twice. Paths have to get drawn twice with the same matrix before we cache their
+            // atlas.
+            for (int j = 0; j < 2; ++j) {
+                this->drawPathsAndFlush(ccpr, m);
+                // Nothing should be copied to an 8-bit atlas after just two draws.
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+                REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
+            }
+
+            int idx = 0;
+            for (const GrCCPathCacheEntry* entry : lru) {
+                if (0 == i) {
+                    expectedPtrs.push_back(entry);
+                } else {
+                    // The same pointer should have been recycled for the new matrix.
+                    REPORTER_ASSERT(reporter, entry == expectedPtrs[idx]);
+                }
+                ++idx;
+            }
+        }
+    }
+};
+DEF_CCPR_TEST(CCPR_cache_recycleEntries)
+
+// Ensures mostly-visible paths get their full mask cached.
+class CCPR_cache_mostlyVisible : public CCPRCacheTest {
+    void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
+               const RecordLastMockAtlasIDs& atlasIDRecorder) override {
+        SkMatrix matrices[3] = {
+            SkMatrix::MakeScale(kCanvasSize/2, kCanvasSize/2), // Fully visible.
+            SkMatrix::MakeScale(kCanvasSize * 1.25, kCanvasSize * 1.25), // Mostly visible.
+            SkMatrix::MakeScale(kCanvasSize * 1.5, kCanvasSize * 1.5), // Mostly NOT visible.
+        };
+
+        for (int i = 0; i < 10; ++i) {
+            this->drawPathsAndFlush(ccpr, matrices, 3);
+            if (2 == i) {
+                // The mostly-visible paths should still get cached.
+                REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
+            } else {
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+            }
+            // Ensure mostly NOT-visible paths never get cached.
+            REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
+        }
+
+        // Clear the path cache.
+        this->drawPathsAndFlush(ccpr, SkMatrix::I());
+
+        // Now only draw the fully/mostly visible ones.
+        for (int i = 0; i < 2; ++i) {
+            this->drawPathsAndFlush(ccpr, matrices, 2);
+            REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+            REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
+        }
+
+        // On draw 3 they should get copied to an 8-bit atlas.
+        this->drawPathsAndFlush(ccpr, matrices, 2);
+        REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
+        REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+
+        for (int i = 0; i < 10; ++i) {
+            this->drawPathsAndFlush(ccpr, matrices, 2);
+            REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+            REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+        }
+
+        // Draw a different part of the path to ensure the full mask was cached.
+        matrices[1].postTranslate(SkScalarFloorToInt(kCanvasSize * -.25f),
+                                  SkScalarFloorToInt(kCanvasSize * -.25f));
+        for (int i = 0; i < 10; ++i) {
+            this->drawPathsAndFlush(ccpr, matrices, 2);
+            REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+            REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+        }
+    }
+};
+DEF_CCPR_TEST(CCPR_cache_mostlyVisible)
+
+// Ensures GrContext::performDeferredCleanup works.
+class CCPR_cache_deferredCleanup : public CCPRCacheTest {
+    void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
+               const RecordLastMockAtlasIDs& atlasIDRecorder) override {
+        SkMatrix m = SkMatrix::MakeScale(20, 20);
+        int lastRenderedAtlasID = 0;
+
+        for (int i = 0; i < 5; ++i) {
+            this->drawPathsAndFlush(ccpr, m);
+            REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+            REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
+            int renderedAtlasID = atlasIDRecorder.lastRenderedAtlasID();
+            REPORTER_ASSERT(reporter, renderedAtlasID != lastRenderedAtlasID);
+            lastRenderedAtlasID = renderedAtlasID;
+
+            this->drawPathsAndFlush(ccpr, m);
+            REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+            REPORTER_ASSERT(reporter, lastRenderedAtlasID == atlasIDRecorder.lastRenderedAtlasID());
+
+            // On draw 3 they should get copied to an 8-bit atlas.
+            this->drawPathsAndFlush(ccpr, m);
+            REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
+            REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+
+            for (int i = 0; i < 10; ++i) {
+                this->drawPathsAndFlush(ccpr, m);
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+            }
+
+            ccpr.ctx()->performDeferredCleanup(std::chrono::milliseconds(0));
+        }
+    }
+};
+DEF_CCPR_TEST(CCPR_cache_deferredCleanup)
+
+// Verifies the cache/hash table internals.
+class CCPR_cache_hashTable : public CCPRCacheTest {
+    void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
+               const RecordLastMockAtlasIDs& atlasIDRecorder) override {
+        using CoverageType = GrCCAtlas::CoverageType;
+        SkMatrix m = SkMatrix::MakeScale(20, 20);
+
+        for (int i = 0; i < 5; ++i) {
+            this->drawPathsAndFlush(ccpr, m);
+            if (2 == i) {
+                REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
+            } else {
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+            }
+            if (i < 2) {
+                REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
+            } else {
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+            }
+
+            auto cache = ccpr.ccpr()->testingOnly_getPathCache();
+            REPORTER_ASSERT(reporter, cache);
+
+            const auto& hash = cache->testingOnly_getHashTable();
+            const auto& lru = cache->testingOnly_getLRU();
+            int count = 0;
+            for (GrCCPathCacheEntry* entry : lru) {
+                auto* node = hash.find(entry->cacheKey());
+                REPORTER_ASSERT(reporter, node);
+                REPORTER_ASSERT(reporter, node->entry() == entry);
+                REPORTER_ASSERT(reporter, 0 == entry->testingOnly_peekOnFlushRefCnt());
+                REPORTER_ASSERT(reporter, entry->unique());
+                if (0 == i) {
+                    REPORTER_ASSERT(reporter, !entry->cachedAtlas());
+                } else {
+                    const GrCCCachedAtlas* cachedAtlas = entry->cachedAtlas();
+                    REPORTER_ASSERT(reporter, cachedAtlas);
+                    if (1 == i) {
+                        REPORTER_ASSERT(reporter, CoverageType::kFP16_CoverageCount
+                                                          == cachedAtlas->coverageType());
+                    } else {
+                        REPORTER_ASSERT(reporter, CoverageType::kA8_LiteralCoverage
+                                                          == cachedAtlas->coverageType());
+                    }
+                    REPORTER_ASSERT(reporter, cachedAtlas->textureKey().isValid());
+                    // The actual proxy should not be held past the end of a flush.
+                    REPORTER_ASSERT(reporter, !cachedAtlas->getOnFlushProxy());
+                    REPORTER_ASSERT(reporter, 0 == cachedAtlas->testingOnly_peekOnFlushRefCnt());
+                }
+                ++count;
+            }
+            REPORTER_ASSERT(reporter, hash.count() == count);
+        }
+    }
+};
+DEF_CCPR_TEST(CCPR_cache_hashTable)
+
+// Ensures paths get cached even when using a sporadic flushing pattern and drawing out of order
+// (a la Chrome tiles).
+class CCPR_cache_multiFlush : public CCPRCacheTest {
+    void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
+               const RecordLastMockAtlasIDs& atlasIDRecorder) override {
+        static constexpr int kNumPaths = SK_ARRAY_COUNT(fPaths);
+        static constexpr int kBigPrimes[] = {
+                9323, 11059, 22993, 38749, 45127, 53147, 64853, 77969, 83269, 99989};
+
+        SkRandom rand;
+        SkMatrix m = SkMatrix::I();
+
+        for (size_t i = 0; i < SK_ARRAY_COUNT(kBigPrimes); ++i) {
+            int prime = kBigPrimes[i];
+            int endPathIdx = (int)rand.nextULessThan(kNumPaths);
+            int pathIdx = endPathIdx;
+            int nextFlush = rand.nextRangeU(1, 47);
+            for (int j = 0; j < kNumPaths; ++j) {
+                pathIdx = (pathIdx + prime) % kNumPaths;
+                int repeat = rand.nextRangeU(1, 3);
+                for (int k = 0; k < repeat; ++k) {
+                    ccpr.drawPath(fPaths[pathIdx], m);
+                }
+                if (nextFlush == j) {
+                    ccpr.flush();
+                    // The paths are small enough that we should never copy to an A8 atlas.
+                    REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+                    if (i < 2) {
+                        REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
+                    } else {
+                        REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+                    }
+                    nextFlush = SkTMin(j + (int)rand.nextRangeU(1, 29), kNumPaths - 1);
+                }
+            }
+            SkASSERT(endPathIdx == pathIdx % kNumPaths);
+        }
+    }
+};
+DEF_CCPR_TEST(CCPR_cache_multiFlush)
+
+// Ensures a path drawn over mutiple tiles gets cached.
+class CCPR_cache_multiTileCache : public CCPRCacheTest {
+    void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
+               const RecordLastMockAtlasIDs& atlasIDRecorder) override {
+        // Make sure a path drawn over 9 tiles gets cached (1 tile out of 9 is >10% visibility).
+        const SkMatrix m0 = SkMatrix::MakeScale(kCanvasSize*3, kCanvasSize*3);
+        const SkPath p0 = fPaths[0];
+        for (int i = 0; i < 9; ++i) {
+            static constexpr int kRowOrder[9] = {0,1,1,0,2,2,2,1,0};
+            static constexpr int kColumnOrder[9] = {0,0,1,1,0,1,2,2,2};
+
+            SkMatrix tileM = m0;
+            tileM.postTranslate(-kCanvasSize * kColumnOrder[i], -kCanvasSize * kRowOrder[i]);
+            ccpr.drawPath(p0, tileM);
+            ccpr.flush();
+            if (i < 5) {
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+                REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
+            } else if (5 == i) {
+                REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+            } else {
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+            }
+        }
+
+        // Now make sure paths don't get cached when visibility is <10% for every draw (12 tiles).
+        const SkMatrix m1 = SkMatrix::MakeScale(kCanvasSize*4, kCanvasSize*3);
+        const SkPath p1 = fPaths[1];
+        for (int row = 0; row < 3; ++row) {
+            for (int col = 0; col < 4; ++col) {
+                SkMatrix tileM = m1;
+                tileM.postTranslate(-kCanvasSize * col, -kCanvasSize * row);
+                ccpr.drawPath(p1, tileM);
+                ccpr.flush();
+                REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+                REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
+            }
+        }
+
+        // Double-check the cache is still intact.
+        ccpr.drawPath(p0, m0);
+        ccpr.flush();
+        REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+        REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+
+        ccpr.drawPath(p1, m1);
+        ccpr.flush();
+        REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+        REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastRenderedAtlasID());
+    }
+};
+DEF_CCPR_TEST(CCPR_cache_multiTileCache)
+
+// This test exercises CCPR's cache capabilities by drawing many paths with two different
+// transformation matrices. We then vary the matrices independently by whole and partial pixels,
+// and verify the caching behaved as expected.
+class CCPR_cache_partialInvalidate : public CCPRCacheTest {
+    void customizeOptions(GrMockOptions*, GrContextOptions* ctxOptions) override {
+        ctxOptions->fAllowPathMaskCaching = true;
+    }
+
+    static constexpr int kPathSize = 4;
+
+    void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr,
+               const RecordLastMockAtlasIDs& atlasIDRecorder) override {
         SkMatrix matrices[2] = {
             SkMatrix::MakeTrans(5, 5),
             SkMatrix::MakeTrans(kCanvasSize - kPathSize - 5, kCanvasSize - kPathSize - 5)
         };
+        matrices[0].preScale(kPathSize, kPathSize);
+        matrices[1].preScale(kPathSize, kPathSize);
 
-        int firstAtlasID = -1;
+        int firstAtlasID = 0;
 
-        for (int iterIdx = 0; iterIdx < 10; ++iterIdx) {
-            static constexpr int kNumHitsBeforeStash = 2;
-            static const GrUniqueKey gInvalidUniqueKey;
-
-            // Draw all the paths then flush. Repeat until a new stash occurs.
-            const GrUniqueKey* stashedAtlasKey = &gInvalidUniqueKey;
-            for (int j = 0; j < kNumHitsBeforeStash; ++j) {
-                // Nothing should be stashed until its hit count reaches kNumHitsBeforeStash.
-                REPORTER_ASSERT(reporter, !stashedAtlasKey->isValid());
-
-                for (size_t i = 0; i < SK_ARRAY_COUNT(paths); ++i) {
-                    ccpr.drawPath(paths[i], matrices[i % 2]);
-                }
-                ccpr.flush();
-
-                stashedAtlasKey = &ccpr.ccpr()->testingOnly_getStashedAtlasKey();
-            }
-
-            // Figure out the mock backend ID of the atlas texture stashed away by CCPR.
-            GrMockTextureInfo stashedAtlasInfo;
-            stashedAtlasInfo.fID = -1;
-            if (stashedAtlasKey->isValid()) {
-                GrResourceProvider* rp = ccpr.ctx()->contextPriv().resourceProvider();
-                sk_sp<GrSurface> stashedAtlas = rp->findByUniqueKey<GrSurface>(*stashedAtlasKey);
-                REPORTER_ASSERT(reporter, stashedAtlas);
-                if (stashedAtlas) {
-                    const auto& backendTexture = stashedAtlas->asTexture()->getBackendTexture();
-                    backendTexture.getMockTextureInfo(&stashedAtlasInfo);
-                }
-            }
+        for (int iterIdx = 0; iterIdx < 4*3*2; ++iterIdx) {
+            this->drawPathsAndFlush(ccpr, matrices, 2);
 
             if (0 == iterIdx) {
                 // First iteration: just note the ID of the stashed atlas and continue.
-                REPORTER_ASSERT(reporter, stashedAtlasKey->isValid());
-                firstAtlasID = stashedAtlasInfo.fID;
+                firstAtlasID = atlasIDRecorder.lastRenderedAtlasID();
+                REPORTER_ASSERT(reporter, 0 != firstAtlasID);
                 continue;
             }
 
-            switch (iterIdx % 3) {
-                case 1:
-                    // This draw should have gotten 100% cache hits; we only did integer translates
-                    // last time (or none if it was the first flush). Therefore, no atlas should
-                    // have been stashed away.
-                    REPORTER_ASSERT(reporter, !stashedAtlasKey->isValid());
+            int testIdx = (iterIdx/2) % 3;
+            int repetitionIdx = iterIdx % 2;
+            switch (testIdx) {
+                case 0:
+                    if (0 == repetitionIdx) {
+                        // This is the big test. New paths were drawn twice last round. On hit 2
+                        // (last time), 'firstAtlasID' was cached as a 16-bit atlas. Now, on hit 3,
+                        // these paths should be copied out of 'firstAtlasID', and into an A8 atlas.
+                        // THEN: we should recycle 'firstAtlasID' and reuse that same texture to
+                        // render the new masks.
+                        REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
+                        REPORTER_ASSERT(reporter,
+                                        atlasIDRecorder.lastRenderedAtlasID() == firstAtlasID);
+                    } else {
+                        REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+                        // This is hit 2 for the new masks. Next time they will be copied to an A8
+                        // atlas.
+                        REPORTER_ASSERT(reporter,
+                                        atlasIDRecorder.lastRenderedAtlasID() == firstAtlasID);
+                    }
 
-                    // Invalidate even path masks.
-                    matrices[0].preTranslate(1.6f, 1.4f);
+                    if (1 == repetitionIdx) {
+                        // Integer translates: all path masks stay valid.
+                        matrices[0].preTranslate(-1, -1);
+                        matrices[1].preTranslate(1, 1);
+                    }
+                    break;
+
+                case 1:
+                    if (0 == repetitionIdx) {
+                        // New paths were drawn twice last round. The third hit (now) they should be
+                        // copied to an A8 atlas.
+                        REPORTER_ASSERT(reporter, 0 != atlasIDRecorder.lastCopyAtlasID());
+                    } else {
+                        REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
+                    }
+
+                    // This draw should have gotten 100% cache hits; we only did integer translates
+                    // last time (or none if it was the first flush). Therefore, everything should
+                    // have been cached.
+                    REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastRenderedAtlasID());
+
+                    if (1 == repetitionIdx) {
+                        // Invalidate even path masks.
+                        matrices[0].preTranslate(1.6f, 1.4f);
+                    }
                     break;
 
                 case 2:
-                    // Even path masks were invalidated last iteration by a subpixel translate. They
-                    // should have been re-rendered this time and stashed away in the CCPR atlas.
-                    REPORTER_ASSERT(reporter, stashedAtlasKey->isValid());
+                    // No new masks to copy from last time; it had 100% cache hits.
+                    REPORTER_ASSERT(reporter, 0 == atlasIDRecorder.lastCopyAtlasID());
 
-                    // 'firstAtlasID' should be kept as a scratch texture in the resource cache.
-                    REPORTER_ASSERT(reporter, stashedAtlasInfo.fID == firstAtlasID);
+                    // Even path masks were invalidated last iteration by a subpixel translate.
+                    // They should have been re-rendered this time in the original 'firstAtlasID'
+                    // texture.
+                    REPORTER_ASSERT(reporter,
+                                    atlasIDRecorder.lastRenderedAtlasID() == firstAtlasID);
 
-                    // Invalidate odd path masks.
-                    matrices[1].preTranslate(-1.4f, -1.6f);
-                    break;
-
-                case 0:
-                    // Odd path masks were invalidated last iteration by a subpixel translate. They
-                    // should have been re-rendered this time and stashed away in the CCPR atlas.
-                    REPORTER_ASSERT(reporter, stashedAtlasKey->isValid());
-
-                    // 'firstAtlasID' is the same texture that got stashed away last time (assuming
-                    // no assertion failures). So if it also got stashed this time, it means we
-                    // first copied the even paths out of it, then recycled the exact same texture
-                    // to render the odd paths. This is the expected behavior.
-                    REPORTER_ASSERT(reporter, stashedAtlasInfo.fID == firstAtlasID);
-
-                    // Integer translates: all path masks stay valid.
-                    matrices[0].preTranslate(-1, -1);
-                    matrices[1].preTranslate(1, 1);
+                    if (1 == repetitionIdx) {
+                        // Invalidate odd path masks.
+                        matrices[1].preTranslate(-1.4f, -1.6f);
+                    }
                     break;
             }
         }
     }
 };
-DEF_CCPR_TEST(GrCCPRTest_cache)
+DEF_CCPR_TEST(CCPR_cache_partialInvalidate)
 
-class GrCCPRTest_unrefPerOpListPathsBeforeOps : public CCPRTest {
+class CCPR_unrefPerOpListPathsBeforeOps : public CCPRTest {
     void onRun(skiatest::Reporter* reporter, CCPRPathDrawer& ccpr) override {
         REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
         for (int i = 0; i < 10000; ++i) {
@@ -413,7 +852,7 @@
         REPORTER_ASSERT(reporter, SkPathPriv::TestingOnly_unique(fPath));
     }
 };
-DEF_CCPR_TEST(GrCCPRTest_unrefPerOpListPathsBeforeOps)
+DEF_CCPR_TEST(CCPR_unrefPerOpListPathsBeforeOps)
 
 class CCPRRenderingTest {
 public:
@@ -421,7 +860,7 @@
         if (!ctx->contextPriv().drawingManager()->getCoverageCountingPathRenderer()) {
             return; // CCPR is not enabled on this GPU.
         }
-        CCPRPathDrawer ccpr(ctx, reporter, doStroke);
+        CCPRPathDrawer ccpr(sk_ref_sp(ctx), reporter, doStroke);
         if (!ccpr.valid()) {
             return;
         }
@@ -441,7 +880,7 @@
         test.run(reporter, ctxInfo.grContext(), true); \
     }
 
-class GrCCPRTest_busyPath : public CCPRRenderingTest {
+class CCPR_busyPath : public CCPRRenderingTest {
     void onRun(skiatest::Reporter* reporter, const CCPRPathDrawer& ccpr) const override {
         static constexpr int kNumBusyVerbs = 1 << 17;
         ccpr.clear();
@@ -459,4 +898,4 @@
                       // your platform's GrGLCaps.
     }
 };
-DEF_CCPR_RENDERING_TEST(GrCCPRTest_busyPath)
+DEF_CCPR_RENDERING_TEST(CCPR_busyPath)
diff --git a/tests/GrOpListFlushTest.cpp b/tests/GrOpListFlushTest.cpp
new file mode 100644
index 0000000..7e1f7ad
--- /dev/null
+++ b/tests/GrOpListFlushTest.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrContext.h"
+#include "GrContextPriv.h"
+#include "GrGpu.h"
+#include "SkCanvas.h"
+#include "SkSurface.h"
+#include "Test.h"
+
+static bool check_read(skiatest::Reporter* reporter, const SkBitmap& bitmap) {
+    bool result = true;
+    for (int x = 0; x < 1000 && result; ++x) {
+        const uint32_t srcPixel = *bitmap.getAddr32(x, 0);
+        if (srcPixel != SK_ColorGREEN) {
+            ERRORF(reporter, "Expected color of Green, but got 0x%08x, at pixel (%d, 0).",
+                   x, srcPixel);
+            result = false;
+        }
+    }
+    return result;
+}
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(GrOpListFlushCount, reporter, ctxInfo) {
+    GrContext* context = ctxInfo.grContext();
+    GrGpu* gpu = context->contextPriv().getGpu();
+
+    SkImageInfo imageInfo = SkImageInfo::Make(1000, 1, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+
+    sk_sp<SkSurface> surface1 = SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, imageInfo);
+    if (!surface1) {
+        return;
+    }
+    sk_sp<SkSurface> surface2 = SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, imageInfo);
+    if (!surface2) {
+        return;
+    }
+
+    SkCanvas* canvas1 = surface1->getCanvas();
+    SkCanvas* canvas2 = surface2->getCanvas();
+
+    canvas1->clear(SK_ColorRED);
+    canvas2->clear(SK_ColorRED);
+
+    SkIRect srcRect = SkIRect::MakeWH(1, 1);
+    SkRect dstRect = SkRect::MakeWH(1, 1);
+    SkPaint paint;
+    paint.setColor(SK_ColorGREEN);
+    canvas1->drawRect(dstRect, paint);
+
+    for (int i = 0; i < 1000; ++i) {
+        srcRect.fLeft = i;
+        srcRect.fRight = srcRect.fLeft + 1;
+
+        sk_sp<SkImage> image = surface1->makeImageSnapshot();
+        canvas2->drawImageRect(image.get(), srcRect, dstRect, nullptr);
+        if (i != 999) {
+            dstRect.fLeft = i+1;
+            dstRect.fRight = dstRect.fLeft + 1;
+            image = surface2->makeImageSnapshot();
+            canvas1->drawImageRect(image.get(), srcRect, dstRect, nullptr);
+        }
+    }
+    context->flush();
+
+    // In total we make 2000 oplists. Our current limit on max oplists between flushes is 100, so we
+    // should do 20 flushes while executing oplists. Additionaly we always do 1 flush at the end of
+    // executing all oplists. So in total we should see 21 flushes here.
+    REPORTER_ASSERT(reporter, gpu->stats()->numFinishFlushes() == 21);
+
+    SkBitmap readbackBitmap;
+    readbackBitmap.allocN32Pixels(1000, 1);
+    REPORTER_ASSERT(reporter, surface1->readPixels(readbackBitmap, 0, 0));
+    REPORTER_ASSERT(reporter, check_read(reporter, readbackBitmap));
+
+    REPORTER_ASSERT(reporter, surface2->readPixels(readbackBitmap, 0, 0));
+    REPORTER_ASSERT(reporter, check_read(reporter, readbackBitmap));
+}
diff --git a/tests/GrPorterDuffTest.cpp b/tests/GrPorterDuffTest.cpp
index 2bbfa4c..f94e274 100644
--- a/tests/GrPorterDuffTest.cpp
+++ b/tests/GrPorterDuffTest.cpp
@@ -1068,8 +1068,8 @@
 
     GrXferProcessor::DstProxy fakeDstProxy;
     {
-        sk_sp<GrTextureProxy> proxy =
-                proxyProvider->wrapBackendTexture(backendTex, kTopLeft_GrSurfaceOrigin);
+        sk_sp<GrTextureProxy> proxy = proxyProvider->wrapBackendTexture(
+                backendTex, kTopLeft_GrSurfaceOrigin, kBorrow_GrWrapOwnership, kRead_GrIOType);
         fakeDstProxy.setProxy(std::move(proxy));
     }
 
diff --git a/tests/GrQuadListTest.cpp b/tests/GrQuadListTest.cpp
new file mode 100644
index 0000000..fd88dde
--- /dev/null
+++ b/tests/GrQuadListTest.cpp
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2019 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Test.h"
+
+#include "GrQuad.h"
+
+#define ASSERT(cond) REPORTER_ASSERT(r, cond)
+#define ASSERTF(cond, ...) REPORTER_ASSERT(r, cond, __VA_ARGS__)
+#define TEST(name) DEF_TEST(GrQuadList##name, r)
+
+struct TestData {
+    int fItem1;
+    float fItem2;
+};
+
+// Simple factories to make placeholder quads used in the tests. The 2D quads
+// will have the kRect quad type.
+static GrQuad make_2d_quad() {
+    return GrQuad(SkRect::MakeLTRB(1.f, 2.f, 3.f, 4.f));
+}
+static bool is_2d_quad(const GrPerspQuad& quad) {
+    return quad.x(0) == 1.f && quad.x(1) == 1.f && quad.x(2) == 3.f && quad.x(3) == 3.f &&
+           quad.y(0) == 2.f && quad.y(1) == 4.f && quad.y(2) == 2.f && quad.y(3) == 4.f &&
+           quad.w(0) == 1.f && quad.w(1) == 1.f && quad.w(2) == 1.f && quad.w(3) == 1.f;
+}
+
+static GrPerspQuad make_2d_persp_quad() {
+    return GrPerspQuad(SkRect::MakeLTRB(5.f, 6.f, 7.f, 8.f), SkMatrix::I());
+}
+static bool is_2d_persp_quad(const GrPerspQuad& quad) {
+    return quad.x(0) == 5.f && quad.x(1) == 5.f && quad.x(2) == 7.f && quad.x(3) == 7.f &&
+           quad.y(0) == 6.f && quad.y(1) == 8.f && quad.y(2) == 6.f && quad.y(3) == 8.f &&
+           quad.w(0) == 1.f && quad.w(1) == 1.f && quad.w(2) == 1.f && quad.w(3) == 1.f;
+}
+
+static GrPerspQuad make_3d_persp_quad() {
+    // This perspective matrix leaves x and y unmodified, and sets w to the persp2 value
+    SkMatrix p = SkMatrix::I();
+    p[SkMatrix::kMPersp2] = 13.f;
+    SkASSERT(p.hasPerspective()); // Sanity check
+    return GrPerspQuad(SkRect::MakeLTRB(9.f, 10.f, 11.f, 12.f), p);
+}
+static bool is_3d_persp_quad(const GrPerspQuad& quad) {
+    return quad.x(0) == 9.f && quad.x(1) == 9.f && quad.x(2) == 11.f && quad.x(3) == 11.f &&
+           quad.y(0) == 10.f && quad.y(1) == 12.f && quad.y(2) == 10.f && quad.y(3) == 12.f &&
+           quad.w(0) == 13.f && quad.w(1) == 13.f && quad.w(2) == 13.f && quad.w(3) == 13.f;
+}
+
+TEST(Add2D) {
+    GrQuadList list2D;
+    // Add a plain quad, a 2D persp quad, and then a 3D persp quad, then read back and make sure
+    // the coordinates make sense (including that the type was lifted to perspective).
+    list2D.push_back(make_2d_quad(), GrQuadType::kRect);
+    list2D.push_back(make_2d_persp_quad(), GrQuadType::kRect);
+
+    // Check 2D state of the list
+    ASSERTF(list2D.count() == 2, "Unexpected count: %d", list2D.count());
+    ASSERTF(list2D.quadType() == GrQuadType::kRect, "Unexpected quad type: %d",
+            (uint32_t) list2D.quadType());
+    ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0");
+    ASSERTF(is_2d_persp_quad(list2D[1]), "Incorrect quad at i=1");
+
+    // Force the 2D quads to be updated to store ws by adding a perspective quad
+    list2D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
+    ASSERTF(list2D.quadType() == GrQuadType::kPerspective,
+            "Expected 2D list to be upgraded to perspective");
+
+    // Re-check full state of list after type upgrade
+    ASSERTF(list2D.count() == 3, "Unexpected count: %d", list2D.count());
+    ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0 after upgrade");
+    ASSERTF(is_2d_persp_quad(list2D[1]), "Incorrect quad at i=1 after upgrade");
+    ASSERTF(is_3d_persp_quad(list2D[2]), "Incorrect quad at i=2");
+}
+
+TEST(Add3D) {
+    // Now make a list that starts with a 3D persp quad, then has conventional quads added to it
+    // and make sure its state is correct
+    GrQuadList list3D;
+    list3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
+    list3D.push_back(make_2d_persp_quad(), GrQuadType::kRect);
+    list3D.push_back(make_2d_quad(), GrQuadType::kRect);
+
+    ASSERTF(list3D.count() == 3, "Unexpected count: %d", list3D.count());
+    ASSERTF(is_3d_persp_quad(list3D[0]), "Incorrect quad at i=0");
+    ASSERTF(is_2d_persp_quad(list3D[1]), "Incorrect quad at i=1");
+    ASSERTF(is_2d_quad(list3D[2]), "Incorrect quad at i=2");
+}
+
+TEST(AddWithMetadata2D) {
+    // As above, but also make sure that the metadata is saved and read properly
+    GrTQuadList<TestData> list2D;
+    // Add a plain quad, a 2D persp quad, and then a 3D persp quad, then read back and make sure
+    // the coordinates make sense (including that the type was lifted to perspective).
+    list2D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
+    list2D.push_back(make_2d_persp_quad(), GrQuadType::kRect, {2, 2.f});
+
+    // Check 2D state of the list
+    ASSERTF(list2D.count() == 2, "Unexpected count: %d", list2D.count());
+    ASSERTF(list2D.quadType() == GrQuadType::kRect, "Unexpected quad type: %d",
+            (uint32_t) list2D.quadType());
+    ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0");
+    ASSERTF(list2D.metadata(0).fItem1 == 1 && list2D.metadata(0).fItem2 == 1.f,
+            "Incorrect metadata at i=0");
+    ASSERTF(is_2d_persp_quad(list2D[1]), "Incorrect quad at i=1");
+    ASSERTF(list2D.metadata(1).fItem1 == 2 && list2D.metadata(1).fItem2 == 2.f,
+            "Incorrect metadata at i=1");
+
+    // Force the 2D quads to be updated to store ws by adding a perspective quad
+    list2D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {3, 3.f});
+    ASSERTF(list2D.quadType() == GrQuadType::kPerspective,
+            "Expected 2D list to be upgraded to perspective");
+
+    // Re-check full state of list after type upgrade
+    ASSERTF(list2D.count() == 3, "Unexpected count: %d", list2D.count());
+    ASSERTF(is_2d_quad(list2D[0]), "Incorrect quad at i=0 after upgrade");
+    ASSERTF(list2D.metadata(0).fItem1 == 1 && list2D.metadata(0).fItem2 == 1.f,
+            "Incorrect metadata at i=0");
+    ASSERTF(is_2d_persp_quad(list2D[1]), "Incorrect quad at i=1 after upgrade");
+    ASSERTF(list2D.metadata(1).fItem1 == 2 && list2D.metadata(1).fItem2 == 2.f,
+            "Incorrect metadata at i=1");
+    ASSERTF(is_3d_persp_quad(list2D[2]), "Incorrect quad at i=2");
+    ASSERTF(list2D.metadata(2).fItem1 == 3 && list2D.metadata(2).fItem2 == 3.f,
+            "Incorrect metadata at i=2");
+}
+
+TEST(AddWithMetadata3D) {
+    // Now make a list that starts with a 3D persp quad, then has conventional quads added to it
+    // and make sure its state is correct
+    GrTQuadList<TestData> list3D;
+    list3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {3, 3.f});
+    list3D.push_back(make_2d_persp_quad(), GrQuadType::kRect, {2, 2.f});
+    list3D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
+
+    ASSERTF(list3D.count() == 3, "Unexpected count: %d", list3D.count());
+    ASSERTF(is_3d_persp_quad(list3D[0]), "Incorrect quad at i=0");
+    ASSERTF(list3D.metadata(0).fItem1 == 3 && list3D.metadata(0).fItem2 == 3.f,
+            "Incorrect metadata at i=0");
+    ASSERTF(is_2d_persp_quad(list3D[1]), "Incorrect quad at i=1");
+    ASSERTF(list3D.metadata(1).fItem1 == 2 && list3D.metadata(1).fItem2 == 2.f,
+            "Incorrect metadata at i=1");
+    ASSERTF(is_2d_quad(list3D[2]), "Incorrect quad at i=2");
+    ASSERTF(list3D.metadata(2).fItem1 == 1 && list3D.metadata(2).fItem2 == 1.f,
+            "Incorrect metadata at i=2");
+}
+
+TEST(Concat2DWith2D) {
+    GrQuadList a2D;
+    a2D.push_back(make_2d_quad(), GrQuadType::kRect);
+    GrQuadList b2D;
+    b2D.push_back(make_2d_persp_quad(), GrQuadType::kRect);
+
+    a2D.concat(b2D);
+
+    ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
+    ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
+    ASSERTF(is_2d_persp_quad(a2D[1]), "Incorrect quad at i=1");
+}
+
+TEST(Concat2DWith3D) {
+    GrQuadList a2D;
+    a2D.push_back(make_2d_quad(), GrQuadType::kRect);
+    GrQuadList b3D;
+    b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
+
+    a2D.concat(b3D);
+
+    ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
+    ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
+    ASSERTF(is_3d_persp_quad(a2D[1]), "Incorrect quad at i=1");
+}
+
+TEST(Concat3DWith2D) {
+    GrQuadList a3D;
+    a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
+    GrQuadList b2D;
+    b2D.push_back(make_2d_quad(), GrQuadType::kRect);
+
+    a3D.concat(b2D);
+
+    ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
+    ASSERTF(is_3d_persp_quad(a3D[0]), "Incorrect quad at i=0");
+    ASSERTF(is_2d_quad(a3D[1]), "Incorrect quad at i=1");
+}
+
+TEST(Concat3DWith3D) {
+    GrQuadList a3D;
+    a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
+    GrQuadList b3D;
+    b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective);
+
+    a3D.concat(b3D);
+
+    ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
+    ASSERTF(is_3d_persp_quad(a3D[0]), "Incorrect quad at i=0");
+    ASSERTF(is_3d_persp_quad(a3D[1]), "Incorrect quad at i=1");
+}
+
+TEST(Concat2DWith2DMetadata) {
+    GrTQuadList<TestData> a2D;
+    a2D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
+    GrTQuadList<TestData> b2D;
+    b2D.push_back(make_2d_persp_quad(), GrQuadType::kRect, {2, 2.f});
+
+    a2D.concat(b2D);
+
+    ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
+    ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
+    ASSERTF(a2D.metadata(0).fItem1 == 1 && a2D.metadata(0).fItem2 == 1.f,
+            "Incorrect metadata at i=0");
+    ASSERTF(is_2d_persp_quad(a2D[1]), "Incorrect quad at i=1");
+    ASSERTF(a2D.metadata(1).fItem1 == 2 && a2D.metadata(1).fItem2 == 2.f,
+            "Incorrect metadata at i=1");
+}
+
+TEST(Concat2DWith3DMetadata) {
+    GrTQuadList<TestData> a2D;
+    a2D.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
+    GrTQuadList<TestData> b3D;
+    b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {2, 2.f});
+
+    a2D.concat(b3D);
+
+    ASSERTF(a2D.count() == 2, "Unexpected count: %d", a2D.count());
+    ASSERTF(is_2d_quad(a2D[0]), "Incorrect quad at i=0");
+    ASSERTF(a2D.metadata(0).fItem1 == 1 && a2D.metadata(0).fItem2 == 1.f,
+            "Incorrect metadata at i=0");
+    ASSERTF(is_3d_persp_quad(a2D[1]), "Incorrect quad at i=1");
+    ASSERTF(a2D.metadata(1).fItem1 == 2 && a2D.metadata(1).fItem2 == 2.f,
+            "Incorrect metadata at i=1");
+}
+
+TEST(Concat3DWith2DMetadata) {
+    GrTQuadList<TestData> a3D;
+    a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {1, 1.f});
+    GrTQuadList<TestData> b2D;
+    b2D.push_back(make_2d_quad(), GrQuadType::kRect, {2, 2.f});
+
+    a3D.concat(b2D);
+
+    ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
+    ASSERTF(is_3d_persp_quad(a3D[0]), "Incorrect quad at i=0");
+    ASSERTF(a3D.metadata(0).fItem1 == 1 && a3D.metadata(0).fItem2 == 1.f,
+            "Incorrect metadata at i=0");
+    ASSERTF(is_2d_quad(a3D[1]), "Incorrect quad at i=1");
+    ASSERTF(a3D.metadata(1).fItem1 == 2 && a3D.metadata(1).fItem2 == 2.f,
+            "Incorrect metadata at i=1");
+}
+
+TEST(Concat3DWith3DMetadata) {
+    GrTQuadList<TestData> a3D;
+    a3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {1, 1.f});
+    GrTQuadList<TestData> b3D;
+    b3D.push_back(make_3d_persp_quad(), GrQuadType::kPerspective, {2, 2.f});
+
+    a3D.concat(b3D);
+
+    ASSERTF(a3D.count() == 2, "Unexpected count: %d", a3D.count());
+    ASSERTF(is_3d_persp_quad(a3D[0]), "Incorrect quad at i=0");
+    ASSERTF(a3D.metadata(0).fItem1 == 1 && a3D.metadata(0).fItem2 == 1.f,
+            "Incorrect metadata at i=0");
+    ASSERTF(is_3d_persp_quad(a3D[1]), "Incorrect quad at i=1");
+    ASSERTF(a3D.metadata(1).fItem1 == 2 && a3D.metadata(1).fItem2 == 2.f,
+            "Incorrect metadata at i=1");
+}
+
+TEST(WriteMetadata) {
+    GrTQuadList<TestData> list;
+    list.push_back(make_2d_quad(), GrQuadType::kRect, {1, 1.f});
+    ASSERTF(list.metadata(0).fItem1 == 1 && list.metadata(0).fItem2 == 1.f,
+            "Incorrect metadata at i=0"); // Sanity check
+
+    // Rewrite metadata within the list and read back
+    list.metadata(0).fItem1 = 2;
+    list.metadata(0).fItem2 = 2.f;
+    ASSERTF(list.metadata(0).fItem1 == 2 && list.metadata(0).fItem2 == 2.f,
+            "Incorrect metadata at i=0 after edit");
+}
diff --git a/tests/GrSurfaceTest.cpp b/tests/GrSurfaceTest.cpp
index d3cc2f4..15d6441 100644
--- a/tests/GrSurfaceTest.cpp
+++ b/tests/GrSurfaceTest.cpp
@@ -5,8 +5,8 @@
  * found in the LICENSE file.
  */
 
-#include "SkTypes.h"
-
+#include <set>
+#include "GrClip.h"
 #include "GrContext.h"
 #include "GrContextPriv.h"
 #include "GrGpu.h"
@@ -14,7 +14,10 @@
 #include "GrRenderTarget.h"
 #include "GrResourceProvider.h"
 #include "GrTexture.h"
+#include "GrTexturePriv.h"
+#include "SkAutoPixmapStorage.h"
 #include "SkMipMap.h"
+#include "SkSurface.h"
 #include "Test.h"
 
 // Tests that GrSurface::asTexture(), GrSurface::asRenderTarget(), and static upcasting of texture
@@ -85,6 +88,7 @@
         kRGBA_4444_GrPixelConfig,
         kRGBA_8888_GrPixelConfig,
         kRGB_888_GrPixelConfig,
+        kRG_88_GrPixelConfig,
         kBGRA_8888_GrPixelConfig,
         kSRGBA_8888_GrPixelConfig,
         kSBGRA_8888_GrPixelConfig,
@@ -236,3 +240,291 @@
         }
     }
 }
+
+DEF_GPUTEST_FOR_RENDERING_CONTEXTS(ReadOnlyTexture, reporter, context_info) {
+    auto fillPixels = [](const SkPixmap* p, const std::function<uint32_t(int x, int y)>& f) {
+        for (int y = 0; y < p->height(); ++y) {
+            for (int x = 0; x < p->width(); ++x) {
+                *p->writable_addr32(x, y) = f(x, y);
+            }
+        }
+    };
+
+    auto comparePixels = [](const SkPixmap& p1, const SkPixmap& p2, skiatest::Reporter* reporter) {
+        SkASSERT(p1.info() == p2.info());
+        for (int y = 0; y < p1.height(); ++y) {
+            for (int x = 0; x < p1.width(); ++x) {
+                REPORTER_ASSERT(reporter, p1.getColor(x, y) == p2.getColor(x, y));
+                if (p1.getColor(x, y) != p2.getColor(x, y)) {
+                    return;
+                }
+            }
+        }
+    };
+
+    static constexpr int kSize = 100;
+    SkAutoPixmapStorage pixels;
+    pixels.alloc(SkImageInfo::Make(kSize, kSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType));
+    fillPixels(&pixels,
+               [](int x, int y) { return (0xFFU << 24) | (x << 16) | (y << 8) | uint8_t(x * y); });
+
+    GrContext* context = context_info.grContext();
+    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
+
+    // We test both kRW in addition to kRead mostly to ensure that the calls are structured such
+    // that they'd succeed if the texture wasn't kRead. We want to be sure we're failing with
+    // kRead for the right reason.
+    for (auto ioType : {kRead_GrIOType, kRW_GrIOType}) {
+        auto backendTex = context->contextPriv().getGpu()->createTestingOnlyBackendTexture(
+                pixels.addr(), kSize, kSize, kRGBA_8888_SkColorType, true, GrMipMapped::kNo);
+        auto proxy = proxyProvider->wrapBackendTexture(backendTex, kTopLeft_GrSurfaceOrigin,
+                                                       kBorrow_GrWrapOwnership, ioType);
+        auto surfContext = context->contextPriv().makeWrappedSurfaceContext(proxy);
+
+        // Read pixels should work with a read-only texture.
+        SkAutoPixmapStorage read;
+        read.alloc(pixels.info());
+        auto readResult = surfContext->readPixels(pixels.info(), read.writable_addr(), 0, 0, 0);
+        REPORTER_ASSERT(reporter, readResult);
+        if (readResult) {
+            comparePixels(pixels, read, reporter);
+        }
+
+        // Write pixels should not work with a read-only texture.
+        SkAutoPixmapStorage write;
+        write.alloc(pixels.info());
+        fillPixels(&write, [&pixels](int x, int y) { return ~*pixels.addr32(); });
+        auto writeResult = surfContext->writePixels(pixels.info(), pixels.addr(), 0, 0, 0);
+        REPORTER_ASSERT(reporter, writeResult == (ioType == kRW_GrIOType));
+        // Try the low level write.
+        context->flush();
+        auto gpuWriteResult = context->contextPriv().getGpu()->writePixels(
+                proxy->peekTexture(), 0, 0, kSize, kSize, GrColorType::kRGBA_8888, write.addr32(),
+                0);
+        REPORTER_ASSERT(reporter, gpuWriteResult == (ioType == kRW_GrIOType));
+
+        // Copies should not work with a read-only texture
+        auto copySrc = proxyProvider->createTextureProxy(
+                SkImage::MakeFromRaster(write, nullptr, nullptr), kNone_GrSurfaceFlags, 1,
+                SkBudgeted::kYes, SkBackingFit::kExact);
+        REPORTER_ASSERT(reporter, copySrc);
+        auto copyResult = surfContext->copy(copySrc.get());
+        REPORTER_ASSERT(reporter, copyResult == (ioType == kRW_GrIOType));
+        // Try the low level copy.
+        context->flush();
+        auto gpuCopyResult = context->contextPriv().getGpu()->copySurface(
+                proxy->peekTexture(), kTopLeft_GrSurfaceOrigin, copySrc->peekTexture(),
+                kTopLeft_GrSurfaceOrigin, SkIRect::MakeWH(kSize, kSize), {0, 0});
+        REPORTER_ASSERT(reporter, gpuCopyResult == (ioType == kRW_GrIOType));
+
+        // Mip regen should not work with a read only texture.
+        if (context->contextPriv().caps()->mipMapSupport()) {
+            backendTex = context->contextPriv().getGpu()->createTestingOnlyBackendTexture(
+                    nullptr, kSize, kSize, kRGBA_8888_SkColorType, true, GrMipMapped::kYes);
+            proxy = proxyProvider->wrapBackendTexture(backendTex, kTopLeft_GrSurfaceOrigin,
+                                                      kBorrow_GrWrapOwnership, ioType);
+            context->flush();
+            proxy->peekTexture()->texturePriv().markMipMapsDirty();  // avoids assert in GrGpu.
+            auto regenResult =
+                    context->contextPriv().getGpu()->regenerateMipMapLevels(proxy->peekTexture());
+            REPORTER_ASSERT(reporter, regenResult == (ioType == kRW_GrIOType));
+        }
+    }
+}
+
+DEF_GPUTEST(TextureIdleProcTest, reporter, options) {
+    GrContext* context;
+    static const int kS = 10;
+
+    // Helper to delete a backend texture in a GrTexture's release proc.
+    static const auto installBackendTextureReleaseProc = [](GrTexture* texture) {
+        auto backendTexture = texture->getBackendTexture();
+        auto context = texture->getContext();
+        struct ReleaseContext {
+            GrContext* fContext;
+            GrBackendTexture fBackendTexture;
+        };
+        auto release = [](void* rc) {
+            auto releaseContext = static_cast<ReleaseContext*>(rc);
+            if (!releaseContext->fContext->abandoned()) {
+                if (auto gpu = releaseContext->fContext->contextPriv().getGpu()) {
+                    gpu->deleteTestingOnlyBackendTexture(releaseContext->fBackendTexture);
+                }
+            }
+            delete releaseContext;
+        };
+        texture->setRelease(sk_make_sp<GrReleaseProcHelper>(
+                release, new ReleaseContext{context, backendTexture}));
+    };
+
+    // Various ways of making textures.
+    auto makeWrapped = [](GrContext* context) {
+        auto backendTexture = context->contextPriv().getGpu()->createTestingOnlyBackendTexture(
+                nullptr, kS, kS, GrColorType::kRGBA_8888, false, GrMipMapped::kNo);
+        auto texture = context->contextPriv().resourceProvider()->wrapBackendTexture(
+                backendTexture, kBorrow_GrWrapOwnership, kRW_GrIOType);
+        installBackendTextureReleaseProc(texture.get());
+        return texture;
+    };
+
+    auto makeWrappedRenderable = [](GrContext* context) {
+        auto backendTexture = context->contextPriv().getGpu()->createTestingOnlyBackendTexture(
+                nullptr, kS, kS, GrColorType::kRGBA_8888, true, GrMipMapped::kNo);
+        auto texture = context->contextPriv().resourceProvider()->wrapRenderableBackendTexture(
+                backendTexture, 1, kBorrow_GrWrapOwnership);
+        installBackendTextureReleaseProc(texture.get());
+        return texture;
+    };
+
+    auto makeNormal = [](GrContext* context) {
+        GrSurfaceDesc desc;
+        desc.fConfig = kRGBA_8888_GrPixelConfig;
+        desc.fWidth = desc.fHeight = kS;
+        return context->contextPriv().resourceProvider()->createTexture(desc, SkBudgeted::kNo);
+    };
+
+    auto makeRenderable = [](GrContext* context) {
+        GrSurfaceDesc desc;
+        desc.fFlags = kRenderTarget_GrSurfaceFlag;
+        desc.fConfig = kRGBA_8888_GrPixelConfig;
+        desc.fWidth = desc.fHeight = kS;
+        return context->contextPriv().resourceProvider()->createTexture(desc, SkBudgeted::kNo);
+    };
+
+    std::function<sk_sp<GrTexture>(GrContext*)> makers[] = {makeWrapped, makeWrappedRenderable,
+                                                            makeNormal, makeRenderable};
+
+    // Add a unique key, or not.
+    auto addKey = [](GrTexture* texture) {
+        static uint32_t gN = 0;
+        static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+        GrUniqueKey key;
+        GrUniqueKey::Builder builder(&key, kDomain, 1);
+        builder[0] = gN++;
+        builder.finish();
+        texture->resourcePriv().setUniqueKey(key);
+    };
+    auto dontAddKey = [](GrTexture* texture) {};
+    std::function<void(GrTexture*)> keyAdders[] = {addKey, dontAddKey};
+
+    for (const auto& m : makers) {
+        for (const auto& keyAdder : keyAdders) {
+            for (int type = 0; type < sk_gpu_test::GrContextFactory::kContextTypeCnt; ++type) {
+                sk_gpu_test::GrContextFactory factory;
+                auto contextType = static_cast<sk_gpu_test::GrContextFactory::ContextType>(type);
+                context = factory.get(contextType);
+                if (!context) {
+                    continue;
+                }
+
+                // The callback we add simply adds an integer to a set.
+                std::set<int> idleIDs;
+                struct Context {
+                    std::set<int>* fIdleIDs;
+                    int fNum;
+                };
+                auto proc = [](void* context) {
+                    static_cast<Context*>(context)->fIdleIDs->insert(
+                            static_cast<Context*>(context)->fNum);
+                    delete static_cast<Context*>(context);
+                };
+
+                // Makes a texture, possibly adds a key, and sets the callback.
+                auto make = [&m, &keyAdder, &proc, &idleIDs](GrContext* context, int num) {
+                    sk_sp<GrTexture> texture = m(context);
+                    texture->setIdleProc(proc, new Context{&idleIDs, num});
+                    keyAdder(texture.get());
+                    return texture;
+                };
+
+                auto texture = make(context, 1);
+                REPORTER_ASSERT(reporter, idleIDs.find(1) == idleIDs.end());
+                bool isRT = SkToBool(texture->asRenderTarget());
+                auto backendFormat = texture->backendFormat();
+                texture.reset();
+                REPORTER_ASSERT(reporter, idleIDs.find(1) != idleIDs.end());
+
+                texture = make(context, 2);
+                SkImageInfo info =
+                        SkImageInfo::Make(kS, kS, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+                auto rt = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, info, 0, nullptr);
+                auto rtc = rt->getCanvas()->internal_private_accessTopLayerRenderTargetContext();
+                auto singleUseLazyCB = [&texture](GrResourceProvider* rp) {
+                    return rp ? std::move(texture) : nullptr;
+                };
+                GrSurfaceDesc desc;
+                desc.fWidth = desc.fHeight = kS;
+                desc.fConfig = kRGBA_8888_GrPixelConfig;
+                if (isRT) {
+                    desc.fFlags = kRenderTarget_GrSurfaceFlag;
+                }
+                SkBudgeted budgeted(texture->resourcePriv().isBudgeted());
+                auto proxy = context->contextPriv().proxyProvider()->createLazyProxy(
+                        singleUseLazyCB, backendFormat, desc,
+                        GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
+                        GrInternalSurfaceFlags ::kNone, SkBackingFit::kExact, budgeted,
+                        GrSurfaceProxy::LazyInstantiationType::kSingleUse);
+                rtc->drawTexture(GrNoClip(), proxy, GrSamplerState::Filter::kNearest, SkPMColor4f(),
+                                 SkRect::MakeWH(kS, kS), SkRect::MakeWH(kS, kS),
+                                 GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
+                                 SkMatrix::I(), nullptr);
+                // We still have the proxy, which should remain instantiated, thereby keeping the
+                // texture not purgeable.
+                REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
+                context->flush();
+                REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
+                context->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
+                REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
+
+                // This time we move the proxy into the draw.
+                rtc->drawTexture(GrNoClip(), std::move(proxy), GrSamplerState::Filter::kNearest,
+                                 SkPMColor4f(), SkRect::MakeWH(kS, kS), SkRect::MakeWH(kS, kS),
+                                 GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
+                                 SkMatrix::I(), nullptr);
+                REPORTER_ASSERT(reporter, idleIDs.find(2) == idleIDs.end());
+                context->flush();
+                context->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
+                // Now that the draw is fully consumed by the GPU, the texture should be idle.
+                REPORTER_ASSERT(reporter, idleIDs.find(2) != idleIDs.end());
+
+                // Make a proxy that should deinstantiate even if we keep a ref on it.
+                auto deinstantiateLazyCB = [&make, &context](GrResourceProvider* rp) {
+                    return rp ? make(context, 3) : nullptr;
+                };
+                proxy = context->contextPriv().proxyProvider()->createLazyProxy(
+                        deinstantiateLazyCB, backendFormat, desc,
+                        GrSurfaceOrigin::kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
+                        GrInternalSurfaceFlags ::kNone, SkBackingFit::kExact, budgeted,
+                        GrSurfaceProxy::LazyInstantiationType::kDeinstantiate);
+                rtc->drawTexture(GrNoClip(), std::move(proxy), GrSamplerState::Filter::kNearest,
+                                 SkPMColor4f(), SkRect::MakeWH(kS, kS), SkRect::MakeWH(kS, kS),
+                                 GrQuadAAFlags::kNone, SkCanvas::kFast_SrcRectConstraint,
+                                 SkMatrix::I(), nullptr);
+                // At this point the proxy shouldn't even be instantiated, there is no texture with
+                // id 3.
+                REPORTER_ASSERT(reporter, idleIDs.find(3) == idleIDs.end());
+                context->flush();
+                context->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
+                // Now that the draw is fully consumed, we should have deinstantiated the proxy and
+                // the texture it made should be idle.
+                REPORTER_ASSERT(reporter, idleIDs.find(3) != idleIDs.end());
+
+                // Make sure we make the call during various shutdown scenarios.
+                texture = make(context, 4);
+                context->abandonContext();
+                REPORTER_ASSERT(reporter, idleIDs.find(4) != idleIDs.end());
+                factory.destroyContexts();
+                context = factory.get(contextType);
+
+                texture = make(context, 5);
+                factory.destroyContexts();
+                REPORTER_ASSERT(reporter, idleIDs.find(5) != idleIDs.end());
+                context = factory.get(contextType);
+
+                texture = make(context, 6);
+                factory.releaseResourcesAndAbandonContexts();
+                REPORTER_ASSERT(reporter, idleIDs.find(6) != idleIDs.end());
+            }
+        }
+    }
+}
diff --git a/tests/GrTestingBackendTextureUploadTest.cpp b/tests/GrTestingBackendTextureUploadTest.cpp
index 979d05e..9ff0e50 100644
--- a/tests/GrTestingBackendTextureUploadTest.cpp
+++ b/tests/GrTestingBackendTextureUploadTest.cpp
@@ -50,9 +50,8 @@
         wrappedTex = gpu->wrapRenderableBackendTexture(backendTex, 1,
                                                        GrWrapOwnership::kAdopt_GrWrapOwnership);
     } else {
-        wrappedTex = gpu->wrapBackendTexture(backendTex,
-                                             GrWrapOwnership::kAdopt_GrWrapOwnership,
-                                             false);
+        wrappedTex = gpu->wrapBackendTexture(backendTex, GrWrapOwnership::kAdopt_GrWrapOwnership,
+                                             kRead_GrIOType, false);
     }
     REPORTER_ASSERT(reporter, wrappedTex);
 
diff --git a/tests/ICCTest.cpp b/tests/ICCTest.cpp
index f82bfa4..efe39a4 100644
--- a/tests/ICCTest.cpp
+++ b/tests/ICCTest.cpp
@@ -15,56 +15,13 @@
 
 #include "../third_party/skcms/skcms.h"
 
-DEF_TEST(WriteICCProfile, r) {
-    auto adobeRGB = SkColorSpace::MakeRGB(g2Dot2_TransferFn, SkColorSpace::kAdobeRGB_Gamut);
-
-    struct {
-        SkColorSpaceTransferFn fn;
-        const float*           toXYZD50;
-        const char*            desc;
-        sk_sp<SkColorSpace>    want;
-    } tests[] = {
-        {g2Dot2_TransferFn, gAdobeRGB_toXYZD50, "AdobeRGB", adobeRGB},
-        { gSRGB_TransferFn,     gSRGB_toXYZD50,     "sRGB", SkColorSpace::MakeSRGB()},
-    };
-
-    for (auto test : tests) {
-        sk_sp<SkData> profile = SkWriteICCProfile(test.fn, test.toXYZD50);
-        REPORTER_ASSERT(r, profile);
-
-        skcms_ICCProfile parsed;
-        REPORTER_ASSERT(r, skcms_Parse(profile->data(), profile->size(), &parsed));
-
-        sk_sp<SkColorSpace> got = SkColorSpace::Make(parsed);
-        REPORTER_ASSERT(r, got);
-        REPORTER_ASSERT(r, SkColorSpace::Equals(got.get(), test.want.get()));
-
-        skcms_ICCTag desc;
-        REPORTER_ASSERT(r, skcms_GetTagBySignature(&parsed,
-                                                   SkSetFourByteTag('d','e','s','c'),
-                                                   &desc));
-
-        // Rather than really carefully break down the 'desc' tag,
-        // just check our expected description is somewhere in there (as big-endian UTF-16).
-        uint8_t big_endian_utf16[16];
-        for (size_t i = 0; i < strlen(test.desc); i++) {
-            big_endian_utf16[2*i+0] = 0;
-            big_endian_utf16[2*i+1] = test.desc[i];
-        }
-
-        SkString haystack((const char*)desc.buf, desc.size),
-                 needle  ((const char*)big_endian_utf16, 2*strlen(test.desc));
-        REPORTER_ASSERT(r, haystack.contains(needle.c_str()));
-    }
-}
-
 DEF_TEST(AdobeRGB, r) {
     if (sk_sp<SkData> profile = GetResourceAsData("icc_profiles/AdobeRGB1998.icc")) {
         skcms_ICCProfile parsed;
         REPORTER_ASSERT(r, skcms_Parse(profile->data(), profile->size(), &parsed));
 
         auto got  = SkColorSpace::Make(parsed);
-        auto want = SkColorSpace::MakeRGB(g2Dot2_TransferFn, SkColorSpace::kAdobeRGB_Gamut);
+        auto want = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB);
         REPORTER_ASSERT(r, SkColorSpace::Equals(got.get(), want.get()));
     }
 }
diff --git a/tests/ImageFilterTest.cpp b/tests/ImageFilterTest.cpp
index 82cf29d..ecf8847 100644
--- a/tests/ImageFilterTest.cpp
+++ b/tests/ImageFilterTest.cpp
@@ -724,9 +724,8 @@
     const int tileSize = 8;
 
     SkPaint textPaint;
-    sk_tool_utils::set_portable_typeface(&textPaint);
-    textPaint.setTextSize(SkIntToScalar(height));
     textPaint.setColor(SK_ColorWHITE);
+    SkFont font(sk_tool_utils::create_portable_typeface(), height);
 
     const char* text = "ABC";
     const SkScalar yPos = SkIntToScalar(height);
@@ -734,15 +733,13 @@
     for (int scale = 1; scale <= 2; ++scale) {
         for (int i = 0; i < filters.count(); ++i) {
             SkPaint combinedPaint;
-            sk_tool_utils::set_portable_typeface(&combinedPaint);
-            combinedPaint.setTextSize(SkIntToScalar(height));
             combinedPaint.setColor(SK_ColorWHITE);
             combinedPaint.setImageFilter(sk_ref_sp(filters.getFilter(i)));
 
             untiledCanvas.clear(SK_ColorTRANSPARENT);
             untiledCanvas.save();
             untiledCanvas.scale(SkIntToScalar(scale), SkIntToScalar(scale));
-            untiledCanvas.drawString(text, 0, yPos, combinedPaint);
+            untiledCanvas.drawString(text, 0, yPos, font, combinedPaint);
             untiledCanvas.restore();
             untiledCanvas.flush();
 
@@ -756,11 +753,11 @@
                         const SkRect layerBounds = SkRect::MakeWH(width, height);
                         tiledCanvas.saveLayer(&layerBounds, &combinedPaint);
                             tiledCanvas.scale(SkIntToScalar(scale), SkIntToScalar(scale));
-                            tiledCanvas.drawString(text, 0, yPos, textPaint);
+                            tiledCanvas.drawString(text, 0, yPos, font, textPaint);
                         tiledCanvas.restore();
                     } else {
                         tiledCanvas.scale(SkIntToScalar(scale), SkIntToScalar(scale));
-                        tiledCanvas.drawString(text, 0, yPos, combinedPaint);
+                        tiledCanvas.drawString(text, 0, yPos, font, combinedPaint);
                     }
 
                     tiledCanvas.restore();
diff --git a/tests/ImageTest.cpp b/tests/ImageTest.cpp
index 9b78821..1ad49dd 100644
--- a/tests/ImageTest.cpp
+++ b/tests/ImageTest.cpp
@@ -1168,13 +1168,13 @@
     REPORTER_ASSERT(r, srgb.get() == image->colorSpace());
 
     image = GetResourceAsImage("images/webp-color-profile-lossy.webp");
-    SkColorSpaceTransferFn fn;
+    skcms_TransferFunction fn;
     bool success = image->colorSpace()->isNumericalTransferFn(&fn);
     REPORTER_ASSERT(r, success);
-    REPORTER_ASSERT(r, color_space_almost_equal(1.8f, fn.fG));
+    REPORTER_ASSERT(r, color_space_almost_equal(1.8f, fn.g));
 
-    sk_sp<SkColorSpace> rec2020 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                                        SkColorSpace::kRec2020_Gamut);
+    sk_sp<SkColorSpace> rec2020 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB,
+                                                        SkNamedGamut::kRec2020);
     image = create_picture_image(rec2020);
     REPORTER_ASSERT(r, SkColorSpace::Equals(rec2020.get(), image->colorSpace()));
 
@@ -1195,11 +1195,10 @@
 }
 
 DEF_TEST(Image_makeColorSpace, r) {
-    sk_sp<SkColorSpace> p3 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                                   SkColorSpace::kDCIP3_D65_Gamut);
-    SkColorSpaceTransferFn fn;
-    fn.fA = 1.f; fn.fB = 0.f; fn.fC = 0.f; fn.fD = 0.f; fn.fE = 0.f; fn.fF = 0.f; fn.fG = 1.8f;
-    sk_sp<SkColorSpace> adobeGamut = SkColorSpace::MakeRGB(fn, SkColorSpace::kAdobeRGB_Gamut);
+    sk_sp<SkColorSpace> p3 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
+    skcms_TransferFunction fn;
+    fn.a = 1.f; fn.b = 0.f; fn.c = 0.f; fn.d = 0.f; fn.e = 0.f; fn.f = 0.f; fn.g = 1.8f;
+    sk_sp<SkColorSpace> adobeGamut = SkColorSpace::MakeRGB(fn, SkNamedGamut::kAdobeRGB);
 
     SkBitmap srgbBitmap;
     srgbBitmap.allocPixels(SkImageInfo::MakeS32(1, 1, kOpaque_SkAlphaType));
diff --git a/tests/JSONTest.cpp b/tests/JSONTest.cpp
index 4b1b906..f5b71bb 100644
--- a/tests/JSONTest.cpp
+++ b/tests/JSONTest.cpp
@@ -414,3 +414,43 @@
                                                             "]"
                                             "}");
 }
+
+DEF_TEST(JSON_ParseNumber, reporter) {
+    static constexpr struct {
+        const char* string;
+        SkScalar    value,
+                    tolerance;
+    } gTests[] = {
+        { "0", 0, 0 },
+        { "1", 1, 0 },
+
+        { "00000000", 0, 0 },
+        { "00000001", 1, 0 },
+
+        { "0.001", 0.001f, 0 },
+        { "1.001", 1.001f, 0 },
+
+        { "0.000001"   ,    0.000001f, 0 },
+        { "1.000001"   ,    1.000001f, 0 },
+        { "1000.000001", 1000.000001f, 0 },
+
+        { "0.0000000001"   ,    0.0000000001f, 0 },
+        { "1.0000000001"   ,    1.0000000001f, 0 },
+        { "1000.0000000001", 1000.0000000001f, 0 },
+
+        { "20.001111814444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444473",
+          20.001f, 0.001f },
+    };
+
+    for (const auto& test : gTests) {
+        const auto json = SkStringPrintf("{ \"key\": %s }", test.string);
+        const DOM dom(json.c_str(), json.size());
+        const ObjectValue* jroot = dom.root();
+
+        REPORTER_ASSERT(reporter, jroot);
+
+        const NumberValue* jnumber = (*jroot)["key"];
+        REPORTER_ASSERT(reporter, jnumber);
+        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(**jnumber, test.value, test.tolerance));
+    }
+}
diff --git a/tests/LListTest.cpp b/tests/LListTest.cpp
index e28cad9..f11e325 100644
--- a/tests/LListTest.cpp
+++ b/tests/LListTest.cpp
@@ -120,26 +120,31 @@
     SkTInternalLList<ListElement> listA, listB;
     listA.concat(std::move(listB));
     check_list(listA, reporter, true, 0, false, false, false, false, elements);
+    // NOLINTNEXTLINE(bugprone-use-after-move)
     check_list(listB, reporter, true, 0, false, false, false, false, elements);
 
     listB.addToTail(&elements[0]);
     listA.concat(std::move(listB));
     check_list(listA, reporter, false, 1, true, false, false, false, elements);
+    // NOLINTNEXTLINE(bugprone-use-after-move)
     check_list(listB, reporter, true, 0, false, false, false, false, elements);
 
     listB.addToTail(&elements[1]);
     listA.concat(std::move(listB));
     check_list(listA, reporter, false, 2, true, true, false, false, elements);
+    // NOLINTNEXTLINE(bugprone-use-after-move)
     check_list(listB, reporter, true, 0, false, false, false, false, elements);
 
     listA.concat(std::move(listB));
     check_list(listA, reporter, false, 2, true, true, false, false, elements);
+    // NOLINTNEXTLINE(bugprone-use-after-move)
     check_list(listB, reporter, true, 0, false, false, false, false, elements);
 
     listB.addToTail(&elements[2]);
     listB.addToTail(&elements[3]);
     listA.concat(std::move(listB));
     check_list(listA, reporter, false, 4, true, true, true, true, elements);
+    // NOLINTNEXTLINE(bugprone-use-after-move)
     check_list(listB, reporter, true, 0, false, false, false, false, elements);
 
     cur = iter.init(listA, Iter::kHead_IterStart);
diff --git a/tests/LazyProxyTest.cpp b/tests/LazyProxyTest.cpp
index 8b106f0..eb2520d 100644
--- a/tests/LazyProxyTest.cpp
+++ b/tests/LazyProxyTest.cpp
@@ -247,7 +247,7 @@
     for (bool doInstantiate : {true, false}) {
         for (auto lazyType : {LazyInstantiationType::kSingleUse,
                               LazyInstantiationType::kMultipleUse,
-                              LazyInstantiationType::kUninstantiate}) {
+                              LazyInstantiationType::kDeinstantiate}) {
             int testCount = 0;
             int* testCountPtr = &testCount;
             sk_sp<GrTextureProxy> proxy = proxyProvider->createLazyProxy(
@@ -390,14 +390,14 @@
     }
 }
 
-class LazyUninstantiateTestOp : public GrDrawOp {
+class LazyDeinstantiateTestOp : public GrDrawOp {
 public:
     DEFINE_OP_CLASS_ID
 
     static std::unique_ptr<GrDrawOp> Make(GrContext* context, sk_sp<GrTextureProxy> proxy) {
         GrOpMemoryPool* pool = context->contextPriv().opMemoryPool();
 
-        return pool->allocate<LazyUninstantiateTestOp>(std::move(proxy));
+        return pool->allocate<LazyDeinstantiateTestOp>(std::move(proxy));
     }
 
     void visitProxies(const VisitProxyFunc& func, VisitorType) const override {
@@ -407,13 +407,12 @@
 private:
     friend class GrOpMemoryPool; // for ctor
 
-    LazyUninstantiateTestOp(sk_sp<GrTextureProxy> proxy)
-            : INHERITED(ClassID())
-            , fLazyProxy(std::move(proxy)) {
+    LazyDeinstantiateTestOp(sk_sp<GrTextureProxy> proxy)
+            : INHERITED(ClassID()), fLazyProxy(std::move(proxy)) {
         this->setBounds(SkRect::MakeIWH(kSize, kSize), HasAABloat::kNo, IsZeroArea::kNo);
     }
 
-    const char* name() const override { return "LazyUninstantiateTestOp"; }
+    const char* name() const override { return "LazyDeinstantiateTestOp"; }
     FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
     RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*) override {
         return RequiresDstTexture::kNo;
@@ -426,13 +425,11 @@
     typedef GrDrawOp INHERITED;
 };
 
-static void UninstantiateReleaseProc(void* releaseValue) {
-    (*static_cast<int*>(releaseValue))++;
-}
+static void DeinstantiateReleaseProc(void* releaseValue) { (*static_cast<int*>(releaseValue))++; }
 
-// Test that lazy proxies with the Uninstantiate LazyCallbackType are uninstantiated and released as
+// Test that lazy proxies with the Deinstantiate LazyCallbackType are deinstantiated and released as
 // expected.
-DEF_GPUTEST(LazyProxyUninstantiateTest, reporter, /* options */) {
+DEF_GPUTEST(LazyProxyDeinstantiateTest, reporter, /* options */) {
     GrMockOptions mockOptions;
     sk_sp<GrContext> ctx = GrContext::MakeMock(&mockOptions, GrContextOptions());
     GrProxyProvider* proxyProvider = ctx->contextPriv().proxyProvider();
@@ -442,7 +439,7 @@
                 ctx->contextPriv().caps()->getBackendFormatFromColorType(kRGBA_8888_SkColorType);
 
     using LazyType = GrSurfaceProxy::LazyInstantiationType;
-    for (auto lazyType : {LazyType::kSingleUse, LazyType::kMultipleUse, LazyType::kUninstantiate}) {
+    for (auto lazyType : {LazyType::kSingleUse, LazyType::kMultipleUse, LazyType::kDeinstantiate}) {
         sk_sp<GrRenderTargetContext> rtc = ctx->contextPriv().makeDeferredRenderTargetContext(
                 format, SkBackingFit::kExact, 100, 100,
                 kRGBA_8888_GrPixelConfig, nullptr);
@@ -469,25 +466,26 @@
                         return sk_sp<GrTexture>();
                     }
 
-                    sk_sp<GrTexture> texture = rp->wrapBackendTexture(backendTex);
+                    sk_sp<GrTexture> texture = rp->wrapBackendTexture(
+                            backendTex, kBorrow_GrWrapOwnership, kRead_GrIOType);
                     if (!texture) {
                         return sk_sp<GrTexture>();
                     }
                     (*instantiatePtr)++;
-                    texture->setRelease(UninstantiateReleaseProc, releasePtr);
+                    texture->setRelease(DeinstantiateReleaseProc, releasePtr);
                     return texture;
                 },
                 format, desc, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo,
-                GrInternalSurfaceFlags::kNone, SkBackingFit::kExact, SkBudgeted::kNo, lazyType);
+                GrInternalSurfaceFlags::kReadOnly, SkBackingFit::kExact, SkBudgeted::kNo, lazyType);
 
         REPORTER_ASSERT(reporter, lazyProxy.get());
 
-        rtc->priv().testingOnly_addDrawOp(LazyUninstantiateTestOp::Make(ctx.get(), lazyProxy));
+        rtc->priv().testingOnly_addDrawOp(LazyDeinstantiateTestOp::Make(ctx.get(), lazyProxy));
 
         ctx->flush();
 
         REPORTER_ASSERT(reporter, 1 == instantiateTestValue);
-        if (LazyType::kUninstantiate == lazyType) {
+        if (LazyType::kDeinstantiate == lazyType) {
             REPORTER_ASSERT(reporter, 1 == releaseTestValue);
         } else {
             REPORTER_ASSERT(reporter, 0 == releaseTestValue);
@@ -495,12 +493,12 @@
 
         // This should cause the uninstantiate proxies to be instantiated again but have no effect
         // on the others
-        rtc->priv().testingOnly_addDrawOp(LazyUninstantiateTestOp::Make(ctx.get(), lazyProxy));
+        rtc->priv().testingOnly_addDrawOp(LazyDeinstantiateTestOp::Make(ctx.get(), lazyProxy));
         // Add a second op to make sure we only instantiate once.
-        rtc->priv().testingOnly_addDrawOp(LazyUninstantiateTestOp::Make(ctx.get(), lazyProxy));
+        rtc->priv().testingOnly_addDrawOp(LazyDeinstantiateTestOp::Make(ctx.get(), lazyProxy));
         ctx->flush();
 
-        if (LazyType::kUninstantiate == lazyType) {
+        if (LazyType::kDeinstantiate == lazyType) {
             REPORTER_ASSERT(reporter, 2 == instantiateTestValue);
             REPORTER_ASSERT(reporter, 2 == releaseTestValue);
         } else {
@@ -509,7 +507,7 @@
         }
 
         lazyProxy.reset();
-        if (LazyType::kUninstantiate == lazyType) {
+        if (LazyType::kDeinstantiate == lazyType) {
             REPORTER_ASSERT(reporter, 2 == releaseTestValue);
         } else {
             REPORTER_ASSERT(reporter, 1 == releaseTestValue);
diff --git a/tests/NonlinearBlendingTest.cpp b/tests/NonlinearBlendingTest.cpp
index e91dda8..768d964 100644
--- a/tests/NonlinearBlendingTest.cpp
+++ b/tests/NonlinearBlendingTest.cpp
@@ -12,8 +12,7 @@
 
 DEF_TEST(SkColorSpaceXformSteps_vs_skcms, r) {
     auto srgb = SkColorSpace::MakeSRGB();
-    auto dp3  = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                      SkColorSpace::kDCIP3_D65_Gamut);
+    auto dp3  = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
 
     skcms_ICCProfile srgb_profile;
     srgb->toProfile(&srgb_profile);
diff --git a/tests/PDFDocumentTest.cpp b/tests/PDFDocumentTest.cpp
index b4941dc..72fc13c 100644
--- a/tests/PDFDocumentTest.cpp
+++ b/tests/PDFDocumentTest.cpp
@@ -8,6 +8,7 @@
 
 #include "Resources.h"
 #include "SkCanvas.h"
+#include "SkExecutor.h"
 #include "SkOSFile.h"
 #include "SkOSPath.h"
 #include "SkPDFDocument.h"
@@ -134,7 +135,7 @@
     canvas->translate(20.0f, 10.0f);
     canvas->rotate(30.0f);
     const char text[] = "HELLO";
-    canvas->drawString(text, 0, 0, SkPaint());
+    canvas->drawString(text, 0, 0, SkFont(), SkPaint());
 }
 
 static bool contains(const uint8_t* result, size_t size, const char expectation[]) {
@@ -239,3 +240,19 @@
                 SkColorSetARGB(0xFF, 0x00, (uint8_t)(255.0f * i / (n - 1)), 0x00));
     }
 }
+
+// Test to make sure that jobs launched by PDF backend don't cause a segfault
+// after calling abort().
+DEF_TEST(SkPDF_abort_jobs, rep) {
+    SkBitmap b;
+    b.allocN32Pixels(612, 792);
+    b.eraseColor(0x4F9643A0);
+    SkPDF::Metadata metadata;
+    std::unique_ptr<SkExecutor> executor = SkExecutor::MakeFIFOThreadPool();
+    metadata.fExecutor = executor.get();
+    SkNullWStream dst;
+    sk_sp<SkDocument> doc = SkPDF::MakeDocument(&dst, metadata);
+    doc->beginPage(612, 792)->drawBitmap(b, 0, 0);
+    doc->abort();
+}
+
diff --git a/tests/PDFJpegEmbedTest.cpp b/tests/PDFJpegEmbedTest.cpp
index ccac4f3..e1ae59b 100644
--- a/tests/PDFJpegEmbedTest.cpp
+++ b/tests/PDFJpegEmbedTest.cpp
@@ -71,7 +71,9 @@
     sk_sp<SkData> pdfData = pdf.detachAsData();
     SkASSERT(pdfData);
 
+    #ifndef SK_PDF_BASE85_BINARY
     REPORTER_ASSERT(r, is_subset_of(mandrillData.get(), pdfData.get()));
+    #endif
 
     // This JPEG uses a nonstandard colorspace - it can not be
     // embedded into the PDF directly.
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index 91e64e0..143ada2 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -23,9 +23,11 @@
 #include "SkPDFCanon.h"
 #include "SkPDFDevice.h"
 #include "SkPDFDocument.h"
+#include "SkPDFDocumentPriv.h"
 #include "SkPDFFont.h"
 #include "SkPDFTypes.h"
 #include "SkPDFUtils.h"
+#include "SkPDFUnion.h"
 #include "SkReadBuffer.h"
 #include "SkScalar.h"
 #include "SkSpecialImage.h"
@@ -77,84 +79,6 @@
     assert_eq(reporter, result, string);
 }
 
-static void TestPDFStream(skiatest::Reporter* reporter) {
-    char streamBytes[] = "Test\nFoo\tBar";
-    auto streamData = skstd::make_unique<SkMemoryStream>(
-            streamBytes, strlen(streamBytes), true);
-    auto stream = sk_make_sp<SkPDFStream>(std::move(streamData));
-    assert_emit_eq(reporter,
-                   *stream,
-                   "<</Length 12>> stream\nTest\nFoo\tBar\nendstream");
-    stream->dict()->insertInt("Attribute", 42);
-    assert_emit_eq(reporter,
-                   *stream,
-                   "<</Length 12\n/Attribute 42>> stream\n"
-                   "Test\nFoo\tBar\nendstream");
-
-    {
-        char streamBytes2[] = "This is a longer string, so that compression "
-                              "can do something with it. With shorter strings, "
-                              "the short circuit logic cuts in and we end up "
-                              "with an uncompressed string.";
-        auto stream = sk_make_sp<SkPDFStream>(
-                SkData::MakeWithCopy(streamBytes2, strlen(streamBytes2)));
-
-        SkDynamicMemoryWStream compressedByteStream;
-        SkDeflateWStream deflateWStream(&compressedByteStream);
-        deflateWStream.write(streamBytes2, strlen(streamBytes2));
-        deflateWStream.finalize();
-
-        SkDynamicMemoryWStream expected;
-        expected.writeText("<</Filter /FlateDecode\n/Length 116>> stream\n");
-        compressedByteStream.writeToStream(&expected);
-        compressedByteStream.reset();
-        expected.writeText("\nendstream");
-        sk_sp<SkData> expectedResultData2(expected.detachAsData());
-        SkString result = emit_to_string(*stream);
-        #ifndef SK_PDF_LESS_COMPRESSION
-        assert_eql(reporter,
-                   result,
-                   (const char*)expectedResultData2->data(),
-                   expectedResultData2->size());
-        #endif
-    }
-}
-
-static void TestObjectNumberMap(skiatest::Reporter* reporter) {
-    SkPDFObjNumMap objNumMap;
-    sk_sp<SkPDFArray> a1(new SkPDFArray);
-    sk_sp<SkPDFArray> a2(new SkPDFArray);
-    sk_sp<SkPDFArray> a3(new SkPDFArray);
-
-    objNumMap.addObjectRecursively(a1.get());
-    objNumMap.addObjectRecursively(a2.get());
-    objNumMap.addObjectRecursively(a3.get());
-
-    // The objects should be numbered in the order they are added,
-    // starting with 1.
-    REPORTER_ASSERT(reporter, objNumMap.getObjectNumber(a1.get()) == 1);
-    REPORTER_ASSERT(reporter, objNumMap.getObjectNumber(a2.get()) == 2);
-    REPORTER_ASSERT(reporter, objNumMap.getObjectNumber(a3.get()) == 3);
-    // Assert that repeated calls to get the object number return
-    // consistent result.
-    REPORTER_ASSERT(reporter, objNumMap.getObjectNumber(a1.get()) == 1);
-}
-
-static void TestObjectRef(skiatest::Reporter* reporter) {
-    sk_sp<SkPDFArray> a1(new SkPDFArray);
-    sk_sp<SkPDFArray> a2(new SkPDFArray);
-    a2->appendObjRef(a1);
-
-    SkPDFObjNumMap catalog;
-    catalog.addObjectRecursively(a1.get());
-    REPORTER_ASSERT(reporter, catalog.getObjectNumber(a1.get()) == 1);
-
-    SkString result = emit_to_string(*a2);
-    // If appendObjRef misbehaves, then the result would
-    // be [[]], not [1 0 R].
-    assert_eq(reporter, result, "[1 0 R]");
-}
-
 // This test used to assert without the fix submitted for
 // http://code.google.com/p/skia/issues/detail?id=1083.
 // SKP files might have invalid glyph ids. This test ensures they are ignored,
@@ -163,11 +87,9 @@
     SkDynamicMemoryWStream outStream;
     sk_sp<SkDocument> doc(SkPDF::MakeDocument(&outStream));
     SkCanvas* canvas = doc->beginPage(100.0f, 100.0f);
-    SkPaint paint;
-    paint.setTextEncoding(kGlyphID_SkTextEncoding);
 
     uint16_t glyphID = 65000;
-    canvas->drawText(&glyphID, 2, 0, 0, paint);
+    canvas->drawSimpleText(&glyphID, 2, kGlyphID_SkTextEncoding, 0, 0, SkFont(), SkPaint());
 
     doc->close();
 }
@@ -226,7 +148,7 @@
 }
 
 static void TestPDFArray(skiatest::Reporter* reporter) {
-    sk_sp<SkPDFArray> array(new SkPDFArray);
+    std::unique_ptr<SkPDFArray> array(new SkPDFArray);
     assert_emit_eq(reporter, *array, "[]");
 
     array->appendInt(42);
@@ -256,28 +178,16 @@
                    "[42 .5 0 true /ThisName /AnotherName (This String) "
                    "(Another String)]");
 
-    sk_sp<SkPDFArray> innerArray(new SkPDFArray);
+    std::unique_ptr<SkPDFArray> innerArray(new SkPDFArray);
     innerArray->appendInt(-1);
     array->appendObject(std::move(innerArray));
     assert_emit_eq(reporter, *array,
                    "[42 .5 0 true /ThisName /AnotherName (This String) "
                    "(Another String) [-1]]");
-
-    sk_sp<SkPDFArray> referencedArray(new SkPDFArray);
-    SkPDFObjNumMap catalog;
-    catalog.addObjectRecursively(referencedArray.get());
-    REPORTER_ASSERT(reporter, catalog.getObjectNumber(
-                            referencedArray.get()) == 1);
-    array->appendObjRef(std::move(referencedArray));
-
-    SkString result = emit_to_string(*array);
-    assert_eq(reporter, result,
-              "[42 .5 0 true /ThisName /AnotherName (This String) "
-              "(Another String) [-1] 1 0 R]");
 }
 
 static void TestPDFDict(skiatest::Reporter* reporter) {
-    sk_sp<SkPDFDict> dict(new SkPDFDict);
+    std::unique_ptr<SkPDFDict> dict(new SkPDFDict);
     assert_emit_eq(reporter, *dict, "<<>>");
 
     dict->insertInt("n1", SkToSizeT(42));
@@ -292,7 +202,7 @@
     dict->insertScalar("n2", SK_ScalarHalf);
 
     SkString n3("n3");
-    sk_sp<SkPDFArray> innerArray(new SkPDFArray);
+    std::unique_ptr<SkPDFArray> innerArray(new SkPDFArray);
     innerArray->appendInt(-100);
     dict->insertObject(n3, std::move(innerArray));
     assert_emit_eq(reporter, *dict, "<</n1 42\n/n2 .5\n/n3 [-100]>>");
@@ -326,24 +236,12 @@
 
     dict.reset(new SkPDFDict("DType"));
     assert_emit_eq(reporter, *dict, "<</Type /DType>>");
-
-    sk_sp<SkPDFArray> referencedArray(new SkPDFArray);
-    SkPDFObjNumMap catalog;
-    catalog.addObjectRecursively(referencedArray.get());
-    REPORTER_ASSERT(reporter, catalog.getObjectNumber(
-                            referencedArray.get()) == 1);
-    dict->insertObjRef("n1", std::move(referencedArray));
-    SkString result = emit_to_string(*dict);
-    assert_eq(reporter, result, "<</Type /DType\n/n1 1 0 R>>");
 }
 
 DEF_TEST(SkPDF_Primitives, reporter) {
     TestPDFUnion(reporter);
     TestPDFArray(reporter);
     TestPDFDict(reporter);
-    TestPDFStream(reporter);
-    TestObjectNumberMap(reporter);
-    TestObjectRef(reporter);
     test_issue1083();
 }
 
@@ -486,9 +384,9 @@
 }
 
 static SkGlyphRun make_run(size_t len, const SkGlyphID* glyphs, SkPoint* pos,
-                           SkPaint paint, const uint32_t* clusters,
+                           const SkFont& font, const uint32_t* clusters,
                            size_t utf8TextByteLength, const char* utf8Text) {
-    return SkGlyphRun(paint, SkRunFont{paint},
+    return SkGlyphRun(font,
                       SkSpan<const SkPoint>{pos, len},
                       SkSpan<const SkGlyphID>{glyphs, len},
                       SkSpan<const char>{utf8Text, utf8TextByteLength},
@@ -496,8 +394,7 @@
 }
 
 DEF_TEST(SkPDF_Clusterator, reporter) {
-    SkPaint paint;
-    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+    SkFont font;
     {
         constexpr unsigned len = 11;
         const uint32_t clusters[len] = { 3, 2, 2, 1, 0, 4, 4, 7, 6, 6, 5 };
@@ -505,7 +402,7 @@
         SkPoint pos[len] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},
                                   {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}};
         const char text[] = "abcdefgh";
-        SkGlyphRun run = make_run(len, glyphs, pos, paint, clusters, strlen(text), text);
+        SkGlyphRun run = make_run(len, glyphs, pos, font, clusters, strlen(text), text);
         SkClusterator clusterator(run);
         SkClusterator::Cluster expectations[] = {
             {&text[3], 1, 0, 1},
@@ -528,7 +425,7 @@
         const SkGlyphID glyphs[len] = { 43, 167, 79, 79, 82, };
         SkPoint pos[len] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}};
         const char text[] = "Ha\xCC\x8A" "llo";
-        SkGlyphRun run = make_run(len, glyphs, pos, paint, clusters, strlen(text), text);
+        SkGlyphRun run = make_run(len, glyphs, pos, font, clusters, strlen(text), text);
         SkClusterator clusterator(run);
         SkClusterator::Cluster expectations[] = {
             {&text[0], 1, 0, 1},
diff --git a/tests/PaintBreakTextTest.cpp b/tests/PaintBreakTextTest.cpp
index 8754b49..38370e3 100644
--- a/tests/PaintBreakTextTest.cpp
+++ b/tests/PaintBreakTextTest.cpp
@@ -7,35 +7,19 @@
 
 #include "SkAutoMalloc.h"
 #include "SkFont.h"
-#include "SkPaint.h"
 #include "Test.h"
 
-static size_t break_text(const SkPaint& paint, const void* text, size_t length, SkScalar maxw,
-                         SkScalar* measuredw, skiatest::Reporter* reporter) {
-    size_t n = paint.breakText(text, length, maxw, measuredw);
-    SkScalar measuredw2;
-    size_t n2 = SkFont::LEGACY_ExtractFromPaint(paint).breakText(text, length,
-                                     (SkTextEncoding)paint.getTextEncoding(), maxw, &measuredw2);
-    REPORTER_ASSERT(reporter, n == n2);
-    if (measuredw) {
-        REPORTER_ASSERT(reporter, *measuredw == measuredw2);
-    }
-    return n;
-}
-
-static void test_monotonic(skiatest::Reporter* reporter,
-                           const SkPaint& paint,
-                           const char* msg) {
+static void test_monotonic(skiatest::Reporter* reporter, const SkFont& font, const char* msg) {
     const char* text = "sdfkljAKLDFJKEWkldfjlk#$%&sdfs.dsj";
     const size_t length = strlen(text);
-    const SkScalar width = paint.measureText(text, length);
+    const SkScalar width = font.measureText(text, length, kUTF8_SkTextEncoding);
 
     SkScalar mm = 0;
     size_t nn = 0;
     const SkScalar step = SkMaxScalar(width / 10, SK_Scalar1);
     for (SkScalar w = 0; w <= width; w += step) {
         SkScalar m;
-        const size_t n = break_text(paint, text, length, w, &m, reporter);
+        const size_t n = font.breakText(text, length, kUTF8_SkTextEncoding, w, &m);
 
         REPORTER_ASSERT(reporter, n <= length, msg);
         REPORTER_ASSERT(reporter, m <= width, msg);
@@ -56,44 +40,41 @@
     }
 }
 
-static void test_eq_measure_text(skiatest::Reporter* reporter,
-                                 const SkPaint& paint,
+static void test_eq_measure_text(skiatest::Reporter* reporter, const SkFont& font,
                                  const char* msg) {
     const char* text = "The ultimate measure of a man is not where he stands in moments of comfort "
         "and convenience, but where he stands at times of challenge and controversy.";
     const size_t length = strlen(text);
-    const SkScalar width = paint.measureText(text, length);
+    const SkScalar width = font.measureText(text, length, kUTF8_SkTextEncoding);
 
     SkScalar mm;
-    const size_t length2 = break_text(paint, text, length, width, &mm, reporter);
+    const size_t length2 = font.breakText(text, length, kUTF8_SkTextEncoding, width, &mm);
     REPORTER_ASSERT(reporter, length2 == length, msg);
     REPORTER_ASSERT(reporter, mm == width, msg);
 }
 
-static void test_long_text(skiatest::Reporter* reporter,
-                           const SkPaint& paint,
-                           const char* msg) {
+static void test_long_text(skiatest::Reporter* reporter, const SkFont& font, const char* msg) {
     static const int kSize = 16 * 1024;
     SkAutoMalloc block(kSize);
     memset(block.get(), 'a', kSize - 1);
     char* text = static_cast<char*>(block.get());
     text[kSize - 1] = '\0';
-    const SkScalar width = paint.measureText(text, kSize);
+    const SkScalar width = font.measureText(text, kSize, kUTF8_SkTextEncoding);
 
     SkScalar mm;
-    const size_t length = break_text(paint, text, kSize, width, &mm, reporter);
+    const size_t length = font.breakText(text, kSize, kUTF8_SkTextEncoding, width, &mm);
     REPORTER_ASSERT(reporter, length == kSize, msg);
     REPORTER_ASSERT(reporter, mm == width, msg);
 }
 
 DEF_TEST(PaintBreakText, reporter) {
-    SkPaint paint;
-    test_monotonic(reporter, paint, "default");
-    test_eq_measure_text(reporter, paint, "default");
-    test_long_text(reporter, paint, "default");
-    paint.setTextSize(SkIntToScalar(1 << 17));
-    test_monotonic(reporter, paint, "huge text size");
-    test_eq_measure_text(reporter, paint, "huge text size");
-    paint.setTextSize(0);
-    test_monotonic(reporter, paint, "zero text size");
+    SkFont font;
+    test_monotonic(reporter, font, "default");
+    test_eq_measure_text(reporter, font, "default");
+    test_long_text(reporter, font, "default");
+    font.setSize(SkIntToScalar(1 << 17));
+    test_monotonic(reporter, font, "huge text size");
+    test_eq_measure_text(reporter, font, "huge text size");
+    font.setSize(0);
+    test_monotonic(reporter, font, "zero text size");
 }
diff --git a/tests/PaintTest.cpp b/tests/PaintTest.cpp
index e33f91d..d4fabde 100644
--- a/tests/PaintTest.cpp
+++ b/tests/PaintTest.cpp
@@ -47,12 +47,6 @@
     return count * sizeof(SkUnichar);
 }
 
-static SkTypeface::Encoding paint2encoding(const SkPaint& paint) {
-    SkTextEncoding enc = (SkTextEncoding)paint.getTextEncoding();
-    SkASSERT(kGlyphID_SkTextEncoding != enc);
-    return (SkTypeface::Encoding)enc;
-}
-
 static int find_first_zero(const uint16_t glyphs[], int count) {
     for (int i = 0; i < count; ++i) {
         if (0 == glyphs[i]) {
@@ -82,9 +76,9 @@
     };
 
     SkRandom rand;
-    SkPaint paint;
-    paint.setTypeface(SkTypeface::MakeDefault());
-    SkTypeface* face = paint.getTypeface();
+    SkFont font;
+    font.setTypeface(SkTypeface::MakeDefault());
+    SkTypeface* face = font.getTypeface();
 
     for (int i = 0; i < 1000; ++i) {
         // generate some random text
@@ -95,15 +89,14 @@
         src[rand.nextU() & 63] = rand.nextU() & 0xFFF;
 
         for (size_t k = 0; k < SK_ARRAY_COUNT(gRec); ++k) {
-            paint.setTextEncoding(gRec[k].fEncoding);
-
             size_t len = gRec[k].fSeedTextProc(src, dst, NGLYPHS);
 
             uint16_t    glyphs0[NGLYPHS], glyphs1[NGLYPHS];
 
-            bool contains = paint.containsText(dst, len);
-            int nglyphs = paint.textToGlyphs(dst, len, glyphs0);
-            int first = face->charsToGlyphs(dst, paint2encoding(paint), glyphs1, NGLYPHS);
+            bool contains = font.containsText(dst, len, gRec[k].fEncoding);
+            int nglyphs = font.textToGlyphs(dst, len, gRec[k].fEncoding, glyphs0, NGLYPHS);
+            int first = face->charsToGlyphs(dst, (SkTypeface::Encoding)gRec[k].fEncoding,
+                                            glyphs1, NGLYPHS);
             int index = find_first_zero(glyphs1, NGLYPHS);
 
             REPORTER_ASSERT(reporter, NGLYPHS == nglyphs);
@@ -209,12 +202,6 @@
         kMedium_SkFilterQuality,
         kHigh_SkFilterQuality,
     };
-    const SkFontHinting hinting[] = {
-        kNo_SkFontHinting,
-        kSlight_SkFontHinting,
-        kNormal_SkFontHinting,
-        kFull_SkFontHinting,
-    };
     const SkPaint::Cap caps[] = {
         SkPaint::kButt_Cap,
         SkPaint::kRound_Cap,
@@ -225,12 +212,6 @@
         SkPaint::kRound_Join,
         SkPaint::kBevel_Join,
     };
-    const SkTextEncoding encodings[] = {
-        kUTF8_SkTextEncoding,
-        kUTF16_SkTextEncoding,
-        kUTF32_SkTextEncoding,
-        kGlyphID_SkTextEncoding,
-    };
     const SkPaint::Style styles[] = {
         SkPaint::kFill_Style,
         SkPaint::kStroke_Style,
@@ -239,16 +220,16 @@
 
 #define FOR_SETUP(index, array, setter)                                 \
     for (size_t index = 0; index < SK_ARRAY_COUNT(array); ++index) {    \
-        paint.setter(array[index]);                                     \
+        paint.setter(array[index]);
 
     SkPaint paint;
-    paint.setFlags(0x1234);
+    paint.setAntiAlias(true);
+
+    // we don't serialize hinting or encoding -- soon to be removed from paint
 
     FOR_SETUP(i, levels, setFilterQuality)
-    FOR_SETUP(j, hinting, setHinting)
     FOR_SETUP(l, caps, setStrokeCap)
     FOR_SETUP(m, joins, setStrokeJoin)
-    FOR_SETUP(n, encodings, setTextEncoding)
     FOR_SETUP(p, styles, setStyle)
 
     SkBinaryWriteBuffer writer;
@@ -262,7 +243,7 @@
     SkPaintPriv::Unflatten(&paint2, reader);
     REPORTER_ASSERT(reporter, paint2 == paint);
 
-    }}}}}}
+    }}}}
 #undef FOR_SETUP
 
 }
@@ -270,14 +251,14 @@
 // found and fixed for android: not initializing rect for string's of length 0
 DEF_TEST(Paint_regression_measureText, reporter) {
 
-    SkPaint paint;
-    paint.setTextSize(12.0f);
+    SkFont font;
+    font.setSize(12.0f);
 
     SkRect r;
     r.setLTRB(SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN);
 
     // test that the rect was reset
-    paint.measureText("", 0, &r);
+    font.measureText("", 0, kUTF8_SkTextEncoding, &r);
     REPORTER_ASSERT(reporter, r.isEmpty());
 }
 
@@ -304,8 +285,6 @@
 
     // No matter the encoding, these must always hold.
     ASSERT(other.getColor()      == paint.getColor());
-    ASSERT(other.getTextScaleX() == paint.getTextScaleX());
-    ASSERT(other.getTextSize()   == paint.getTextSize());
     ASSERT(other.getLooper()     == paint.getLooper());
     ASSERT(other.getBlendMode()  == paint.getBlendMode());
 }
@@ -362,6 +341,7 @@
     REPORTER_ASSERT(r, !paint.nothingToDraw());
 }
 
+#ifdef SK_SUPPORT_LEGACY_PAINT_TEXTMEASURE
 DEF_TEST(Paint_getwidths, r) {
     SkPaint paint;
     const char text[] = "Hamburgefons!@#!#23425,./;'[]";
@@ -396,6 +376,7 @@
         }
     }
 }
+#endif
 
 DEF_TEST(Font_getpos, r) {
     SkFont font;
diff --git a/tests/PathOpsAngleTest.cpp b/tests/PathOpsAngleTest.cpp
index 7f4c04a..cd99915 100644
--- a/tests/PathOpsAngleTest.cpp
+++ b/tests/PathOpsAngleTest.cpp
@@ -197,7 +197,7 @@
     }
 
     static int AllOnOneSide(SkOpAngle& lh, SkOpAngle& rh) {
-        return lh.allOnOneSide(&rh);
+        return lh.lineOnOneSide(&rh, false);
     }
 
     static int ConvexHullOverlaps(SkOpAngle& lh, SkOpAngle& rh) {
diff --git a/tests/PathOpsOpTest.cpp b/tests/PathOpsOpTest.cpp
index b664fa5..070f802 100644
--- a/tests/PathOpsOpTest.cpp
+++ b/tests/PathOpsOpTest.cpp
@@ -9079,6 +9079,30 @@
     testPathOp(reporter, path1, path2, kIntersect_SkPathOp, filename);
 }
 
+static void bug8380(skiatest::Reporter* reporter, const char* filename) {
+SkPath path, path2;
+path.setFillType(SkPath::kEvenOdd_FillType);
+path.moveTo(SkBits2Float(0xa6800000), SkBits2Float(0x43b0f22d));  // -8.88178e-16f, 353.892f
+path.lineTo(SkBits2Float(0x42fc0000), SkBits2Float(0x4116566d));  // 126, 9.3961f
+path.cubicTo(SkBits2Float(0x42fb439d), SkBits2Float(0x4114bbc7), SkBits2Float(0x42fa3ed7), SkBits2Float(0x411565bd), SkBits2Float(0x42f934d2), SkBits2Float(0x4116131e));  // 125.632f, 9.29584f, 125.123f, 9.33734f, 124.603f, 9.37967f
+path.cubicTo(SkBits2Float(0x42f84915), SkBits2Float(0x4116acc3), SkBits2Float(0x42f75939), SkBits2Float(0x41174918), SkBits2Float(0x42f693f8), SkBits2Float(0x4116566d));  // 124.143f, 9.41718f, 123.674f, 9.45535f, 123.289f, 9.3961f
+path.lineTo(SkBits2Float(0x42ec3cee), SkBits2Float(0x410127bb));  // 118.119f, 8.0722f
+path.lineTo(SkBits2Float(0x4102c0ec), SkBits2Float(0x42d06d0e));  // 8.1721f, 104.213f
+path.lineTo(SkBits2Float(0xa6000000), SkBits2Float(0x4381a63d));  // -4.44089e-16f, 259.299f
+path.lineTo(SkBits2Float(0x00000000), SkBits2Float(0x43b0f22d));  // 0, 353.892f
+path.lineTo(SkBits2Float(0xa6800000), SkBits2Float(0x43b0f22d));  // -8.88178e-16f, 353.892f
+path.close();
+path2.setFillType(SkPath::kEvenOdd_FillType);
+path2.moveTo(SkBits2Float(0x4102c0ec), SkBits2Float(0x42d06d0e));  // 8.1721f, 104.213f
+path2.lineTo(SkBits2Float(0xc0ba5a1d), SkBits2Float(0x43b8e831));  // -5.8235f, 369.814f
+path2.lineTo(SkBits2Float(0x42fc0000), SkBits2Float(0x411656d6));  // 126, 9.3962f
+path2.cubicTo(SkBits2Float(0x42fa9cac), SkBits2Float(0x41134fdf), SkBits2Float(0x42f837cf), SkBits2Float(0x41185aee), SkBits2Float(0x42f693f8), SkBits2Float(0x411656d6));  // 125.306f, 9.207f, 124.109f, 9.5222f, 123.289f, 9.3962f
+path2.lineTo(SkBits2Float(0x42ec3cee), SkBits2Float(0x410127bb));  // 118.119f, 8.0722f
+path2.lineTo(SkBits2Float(0x4102c0ec), SkBits2Float(0x42d06d0e));  // 8.1721f, 104.213f
+path2.close();
+    testPathOp(reporter, path, path2, kIntersect_SkPathOp, filename);
+}
+
 static void (*skipTest)(skiatest::Reporter* , const char* filename) = 0;
 static void (*firstTest)(skiatest::Reporter* , const char* filename) = 0;
 static void (*stopTest)(skiatest::Reporter* , const char* filename) = 0;
@@ -9086,6 +9110,7 @@
 #define TEST(name) { name, #name }
 
 static struct TestDesc tests[] = {
+    TEST(bug8380),
     TEST(crbug_526025),
     TEST(bug8228),
     TEST(op_4),
diff --git a/tests/PathRendererCacheTests.cpp b/tests/PathRendererCacheTests.cpp
index ae81b69..4b7f969 100644
--- a/tests/PathRendererCacheTests.cpp
+++ b/tests/PathRendererCacheTests.cpp
@@ -138,9 +138,9 @@
         return new GrSoftwarePathRenderer(ctx->contextPriv().proxyProvider(), true);
     };
 
-    // Software path renderer creates a mask texture, but also renders with a non-AA rect, which
-    // refs the quad index buffer.
-    const int kExpectedResources = 2;
+    // Software path renderer creates a mask texture and renders with a non-AA rect, but the flush
+    // only contains a single quad so GrFillRectOp doesn't need to use the shared index buffer.
+    const int kExpectedResources = 1;
 
     test_path(reporter, create_concave_path, createPR, kExpectedResources, GrAAType::kCoverage);
 
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index ec60206..0668098 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -7,6 +7,7 @@
 
 #include "SkAutoMalloc.h"
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkGeometry.h"
 #include "SkNullCanvas.h"
 #include "SkPaint.h"
@@ -27,6 +28,7 @@
 
 #include <cmath>
 #include <utility>
+#include <vector>
 
 static void set_radii(SkVector radii[4], int index, float rad) {
     sk_bzero(radii, sizeof(SkVector) * 4);
@@ -1319,6 +1321,23 @@
     SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
     SkPath::Convexity c = copy.getConvexity();
     REPORTER_ASSERT(reporter, c == expected);
+#ifndef SK_LEGACY_PATH_CONVEXITY
+    // test points-by-array interface
+    SkPath::Iter iter(path, true);
+    int initialMoves = 0;
+    SkPoint pts[4];
+    while (SkPath::kMove_Verb == iter.next(pts, false, false)) {
+        ++initialMoves;
+    }
+    if (initialMoves > 0) {
+        std::vector<SkPoint> points;
+        points.resize(path.getPoints(nullptr, 0));
+        (void) path.getPoints(&points.front(), points.size());
+        int skip = initialMoves - 1;
+        bool isConvex = SkPathPriv::IsConvex(&points.front() + skip, points.size() - skip);
+        REPORTER_ASSERT(reporter, isConvex == (SkPath::kConvex_Convexity == expected));
+    }
+#endif
 }
 
 static void test_path_crbug389050(skiatest::Reporter* reporter) {
@@ -1329,8 +1348,16 @@
     tinyConvexPolygon.lineTo(600.134891f, 800.137724f);
     tinyConvexPolygon.close();
     tinyConvexPolygon.getConvexity();
-    check_convexity(reporter, tinyConvexPolygon, SkPath::kConvex_Convexity);
+    check_convexity(reporter, tinyConvexPolygon, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
+#if SK_TREAT_COLINEAR_DIAGONAL_POINTS_AS_CONCAVE
+    // colinear diagonal points cause convexicator to give up, so CheapComputeFirstDirection
+    // makes its best guess
     check_direction(reporter, tinyConvexPolygon, SkPathPriv::kCW_FirstDirection);
+#else
+    // lines are close enough to straight that polygon collapses to line that does not
+    // enclose area, so has unknown first direction
+    check_direction(reporter, tinyConvexPolygon, SkPathPriv::kUnknown_FirstDirection);
+#endif
 
     SkPath  platTriangle;
     platTriangle.moveTo(0, 0);
@@ -1458,7 +1485,7 @@
     SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
     stroke.setStrokeStyle(2 * SK_Scalar1);
     stroke.applyToPath(&strokedSin, strokedSin);
-    check_convexity(reporter, strokedSin, SkPath::kConcave_Convexity);
+    check_convexity(reporter, strokedSin, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
     check_direction(reporter, strokedSin, kDontCheckDir);
 
     // http://crbug.com/412640
@@ -1487,6 +1514,29 @@
     check_convexity(reporter, badFirstVector, SkPath::kConcave_Convexity);
 }
 
+static void test_convexity_doubleback(skiatest::Reporter* reporter) {
+    SkPath doubleback;
+    doubleback.lineTo(1, 1);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+    doubleback.lineTo(2, 2);
+    check_convexity(reporter, doubleback, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
+    doubleback.reset();
+    doubleback.lineTo(1, 0);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+    doubleback.lineTo(2, 0);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+    doubleback.lineTo(1, 0);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+    doubleback.reset();
+    doubleback.quadTo(1, 1, 2, 2);
+    check_convexity(reporter, doubleback, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
+    doubleback.reset();
+    doubleback.quadTo(1, 0, 2, 0);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+    doubleback.quadTo(1, 0, 0, 0);
+    check_convexity(reporter, doubleback, SkPath::kConvex_Convexity);
+}
+
 static void check_convex_bounds(skiatest::Reporter* reporter, const SkPath& p,
                                 const SkRect& bounds) {
     REPORTER_ASSERT(reporter, p.isConvex());
@@ -1541,8 +1591,8 @@
     REPORTER_ASSERT(reporter, SkPathPriv::CheapIsFirstDirection(path, SkPathPriv::kCW_FirstDirection));
 
     path.reset();
-    path.quadTo(100, 100, 50, 50); // This is a convex path from GM:convexpaths
-    check_convexity(reporter, path, SkPath::kConvex_Convexity);
+    path.quadTo(100, 100, 50, 50); // This from GM:convexpaths
+    check_convexity(reporter, path, SkPath::COLINEAR_DIAGONAL_CONVEXITY);
 
     static const struct {
         const char*                 fPathStr;
@@ -1560,7 +1610,7 @@
     };
 
     for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
-        SkPath path;
+        path.reset();
         setFromString(&path, gRec[i].fPathStr);
         check_convexity(reporter, path, gRec[i].fExpectedConvexity);
         check_direction(reporter, path, gRec[i].fExpectedDirection);
@@ -1594,62 +1644,102 @@
 
     const size_t nonFinitePtsCount = sizeof(nonFinitePts) / sizeof(nonFinitePts[0]);
 
-    static const SkPoint finitePts[] = {
+    static const SkPoint axisAlignedPts[] = {
         { SK_ScalarMax, 0 },
         { 0, SK_ScalarMax },
-        { SK_ScalarMax, SK_ScalarMax },
         { SK_ScalarMin, 0 },
         { 0, SK_ScalarMin },
-        { SK_ScalarMin, SK_ScalarMin },
     };
 
-    const size_t finitePtsCount = sizeof(finitePts) / sizeof(finitePts[0]);
+    const size_t axisAlignedPtsCount = sizeof(axisAlignedPts) / sizeof(axisAlignedPts[0]);
 
-    for (int index = 0; index < (int) (13 * nonFinitePtsCount * finitePtsCount); ++index) {
+    for (int index = 0; index < (int) (13 * nonFinitePtsCount * axisAlignedPtsCount); ++index) {
         int i = (int) (index % nonFinitePtsCount);
-        int f = (int) (index % finitePtsCount);
-        int g = (int) ((f + 1) % finitePtsCount);
+        int f = (int) (index % axisAlignedPtsCount);
+        int g = (int) ((f + 1) % axisAlignedPtsCount);
         path.reset();
         switch (index % 13) {
             case 0: path.lineTo(nonFinitePts[i]); break;
             case 1: path.quadTo(nonFinitePts[i], nonFinitePts[i]); break;
-            case 2: path.quadTo(nonFinitePts[i], finitePts[f]); break;
-            case 3: path.quadTo(finitePts[f], nonFinitePts[i]); break;
-            case 4: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[f]); break;
-            case 5: path.cubicTo(finitePts[f], nonFinitePts[i], finitePts[f]); break;
-            case 6: path.cubicTo(finitePts[f], finitePts[f], nonFinitePts[i]); break;
-            case 7: path.cubicTo(nonFinitePts[i], nonFinitePts[i], finitePts[f]); break;
-            case 8: path.cubicTo(nonFinitePts[i], finitePts[f], nonFinitePts[i]); break;
-            case 9: path.cubicTo(finitePts[f], nonFinitePts[i], nonFinitePts[i]); break;
+            case 2: path.quadTo(nonFinitePts[i], axisAlignedPts[f]); break;
+            case 3: path.quadTo(axisAlignedPts[f], nonFinitePts[i]); break;
+            case 4: path.cubicTo(nonFinitePts[i], axisAlignedPts[f], axisAlignedPts[f]); break;
+            case 5: path.cubicTo(axisAlignedPts[f], nonFinitePts[i], axisAlignedPts[f]); break;
+            case 6: path.cubicTo(axisAlignedPts[f], axisAlignedPts[f], nonFinitePts[i]); break;
+            case 7: path.cubicTo(nonFinitePts[i], nonFinitePts[i], axisAlignedPts[f]); break;
+            case 8: path.cubicTo(nonFinitePts[i], axisAlignedPts[f], nonFinitePts[i]); break;
+            case 9: path.cubicTo(axisAlignedPts[f], nonFinitePts[i], nonFinitePts[i]); break;
             case 10: path.cubicTo(nonFinitePts[i], nonFinitePts[i], nonFinitePts[i]); break;
-            case 11: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[g]); break;
+            case 11: path.cubicTo(nonFinitePts[i], axisAlignedPts[f], axisAlignedPts[g]); break;
             case 12: path.moveTo(nonFinitePts[i]); break;
         }
         check_convexity(reporter, path, SkPath::kUnknown_Convexity);
     }
 
-    for (int index = 0; index < (int) (11 * finitePtsCount); ++index) {
-        int f = (int) (index % finitePtsCount);
-        int g = (int) ((f + 1) % finitePtsCount);
+    for (int index = 0; index < (int) (11 * axisAlignedPtsCount); ++index) {
+        int f = (int) (index % axisAlignedPtsCount);
+        int g = (int) ((f + 1) % axisAlignedPtsCount);
         path.reset();
         int curveSelect = index % 11;
         switch (curveSelect) {
-            case 0: path.moveTo(finitePts[f]); break;
-            case 1: path.lineTo(finitePts[f]); break;
-            case 2: path.quadTo(finitePts[f], finitePts[f]); break;
-            case 3: path.quadTo(finitePts[f], finitePts[g]); break;
-            case 4: path.quadTo(finitePts[g], finitePts[f]); break;
-            case 5: path.cubicTo(finitePts[f], finitePts[f], finitePts[f]); break;
-            case 6: path.cubicTo(finitePts[f], finitePts[f], finitePts[g]); break;
-            case 7: path.cubicTo(finitePts[f], finitePts[g], finitePts[f]); break;
-            case 8: path.cubicTo(finitePts[f], finitePts[g], finitePts[g]); break;
-            case 9: path.cubicTo(finitePts[g], finitePts[f], finitePts[f]); break;
-            case 10: path.cubicTo(finitePts[g], finitePts[f], finitePts[g]); break;
+            case 0: path.moveTo(axisAlignedPts[f]); break;
+            case 1: path.lineTo(axisAlignedPts[f]); break;
+            case 2: path.quadTo(axisAlignedPts[f], axisAlignedPts[f]); break;
+            case 3: path.quadTo(axisAlignedPts[f], axisAlignedPts[g]); break;
+            case 4: path.quadTo(axisAlignedPts[g], axisAlignedPts[f]); break;
+            case 5: path.cubicTo(axisAlignedPts[f], axisAlignedPts[f], axisAlignedPts[f]); break;
+            case 6: path.cubicTo(axisAlignedPts[f], axisAlignedPts[f], axisAlignedPts[g]); break;
+            case 7: path.cubicTo(axisAlignedPts[f], axisAlignedPts[g], axisAlignedPts[f]); break;
+            case 8: path.cubicTo(axisAlignedPts[f], axisAlignedPts[g], axisAlignedPts[g]); break;
+            case 9: path.cubicTo(axisAlignedPts[g], axisAlignedPts[f], axisAlignedPts[f]); break;
+            case 10: path.cubicTo(axisAlignedPts[g], axisAlignedPts[f], axisAlignedPts[g]); break;
         }
-        check_convexity(reporter, path, curveSelect == 0 ? SkPath::kConvex_Convexity
-                : SkPath::kUnknown_Convexity);
+        if (curveSelect == 0 || curveSelect == 1 || curveSelect == 2 || curveSelect == 5) {
+            check_convexity(reporter, path, SkPath::kConvex_Convexity);
+        } else {
+            SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
+            SkPath::Convexity c = copy.getConvexity();
+            REPORTER_ASSERT(reporter, SkPath::kUnknown_Convexity == c
+                    || SkPath::kConcave_Convexity == c);
+        }
     }
 
+    static const SkPoint diagonalPts[] = {
+        { SK_ScalarMax, SK_ScalarMax },
+        { SK_ScalarMin, SK_ScalarMin },
+    };
+
+    const size_t diagonalPtsCount = sizeof(diagonalPts) / sizeof(diagonalPts[0]);
+
+    for (int index = 0; index < (int) (7 * diagonalPtsCount); ++index) {
+        int f = (int) (index % diagonalPtsCount);
+        int g = (int) ((f + 1) % diagonalPtsCount);
+        path.reset();
+        int curveSelect = index % 11;
+        switch (curveSelect) {
+            case 0: path.moveTo(diagonalPts[f]); break;
+            case 1: path.lineTo(diagonalPts[f]); break;
+            case 2: path.quadTo(diagonalPts[f], diagonalPts[f]); break;
+            case 3: path.quadTo(axisAlignedPts[f], diagonalPts[g]); break;
+            case 4: path.quadTo(diagonalPts[g], axisAlignedPts[f]); break;
+            case 5: path.cubicTo(diagonalPts[f], diagonalPts[f], diagonalPts[f]); break;
+            case 6: path.cubicTo(diagonalPts[f], diagonalPts[f], axisAlignedPts[g]); break;
+            case 7: path.cubicTo(diagonalPts[f], axisAlignedPts[g], diagonalPts[f]); break;
+            case 8: path.cubicTo(axisAlignedPts[f], diagonalPts[g], diagonalPts[g]); break;
+            case 9: path.cubicTo(diagonalPts[g], diagonalPts[f], axisAlignedPts[f]); break;
+            case 10: path.cubicTo(diagonalPts[g], axisAlignedPts[f], diagonalPts[g]); break;
+        }
+        if (curveSelect == 0) {
+            check_convexity(reporter, path, SkPath::kConvex_Convexity);
+        } else {
+            SkPath copy(path); // we make a copy so that we don't cache the result on the passed in path.
+            SkPath::Convexity c = copy.getConvexity();
+            REPORTER_ASSERT(reporter, SkPath::kUnknown_Convexity == c
+                    || SkPath::kConcave_Convexity == c);
+        }
+    }
+
+
     path.reset();
     path.moveTo(SkBits2Float(0xbe9171db), SkBits2Float(0xbd7eeb5d));  // -0.284072f, -0.0622362f
     path.lineTo(SkBits2Float(0xbe9171db), SkBits2Float(0xbd7eea38));  // -0.284072f, -0.0622351f
@@ -3523,7 +3613,7 @@
     REPORTER_ASSERT(reporter, path->isConvex());
     REPORTER_ASSERT(reporter, SkPathPriv::CheapIsFirstDirection(*path, SkPathPriv::AsFirstDirection(dir)));
     path->setConvexity(SkPath::kUnknown_Convexity);
-    REPORTER_ASSERT(reporter, path->getConvexity() == SkPath::kUnknown_Convexity);
+    REPORTER_ASSERT(reporter, path->getConvexity() == SkPath::kConvex_Convexity);
     path->reset();
 }
 
@@ -3619,10 +3709,13 @@
     REPORTER_ASSERT(reporter, p == ccwOval);
     p.reset();
     p.addArc(oval, 1, 180);
-    REPORTER_ASSERT(reporter, p.isConvex());
+    // diagonal colinear points make arc convex
+    // TODO: one way to keep it concave would be to introduce interpolated on curve points
+    // between control points and computing the on curve point at scan conversion time
+    REPORTER_ASSERT(reporter, p.getConvexity() == SkPath::COLINEAR_DIAGONAL_CONVEXITY);
     REPORTER_ASSERT(reporter, SkPathPriv::CheapIsFirstDirection(p, SkPathPriv::kCW_FirstDirection));
     p.setConvexity(SkPath::kUnknown_Convexity);
-    REPORTER_ASSERT(reporter, p.isConvex());
+    REPORTER_ASSERT(reporter, p.getConvexity() == SkPath::COLINEAR_DIAGONAL_CONVEXITY);
 }
 
 static inline SkScalar oval_start_index_to_angle(unsigned start) {
@@ -4623,6 +4716,7 @@
     test_direction(reporter);
     test_convexity(reporter);
     test_convexity2(reporter);
+    test_convexity_doubleback(reporter);
     test_conservativelyContains(reporter);
     test_close(reporter);
     test_segment_masks(reporter);
@@ -5189,7 +5283,6 @@
     REPORTER_ASSERT(r, p.getBounds() == SkRect::MakeLTRB(0,0, 30,10));  // was {0,0, 20,61}
 
     REPORTER_ASSERT(r, p.isValid());
-    REPORTER_ASSERT(r, p.pathRefIsValid());
 }
 
 DEF_TEST(Path_increserve_handle_neg_crbug_883666, r) {
@@ -5303,3 +5396,29 @@
     survive(&path, x, false, r, [](const SkPath& p) { return true; });
 }
 
+DEF_TEST(path_last_move_to_index, r) {
+    // Make sure that copyPath is safe after the call to path.offset().
+    // Previously, we would leave its fLastMoveToIndex alone after the copy, but now we should
+    // set it to path's value inside SkPath::transform()
+
+    const char text[] = "hello";
+    constexpr size_t len = sizeof(text) - 1;
+    SkGlyphID glyphs[len];
+
+    SkFont font;
+    font.textToGlyphs(text, len, kUTF8_SkTextEncoding, glyphs, len);
+
+    SkPath copyPath;
+    SkFont().getPaths(glyphs, len, [](const SkPath* src, const SkMatrix& mx, void* ctx) {
+        if (src) {
+            ((SkPath*)ctx)->addPath(*src, mx);
+        }
+    }, &copyPath);
+
+    SkScalar radii[] = { 80, 100, 0, 0, 40, 60, 0, 0 };
+    SkPath path;
+    path.addRoundRect({10, 10, 110, 110}, radii);
+    path.offset(0, 5, &(copyPath));                     // <== change buffer copyPath.fPathRef->fPoints but not reset copyPath.fLastMoveToIndex lead to out of bound
+
+    copyPath.rConicTo(1, 1, 3, 3, 0.707107f);
+}
diff --git a/tests/PictureTest.cpp b/tests/PictureTest.cpp
index d7ff8a4..255e910 100644
--- a/tests/PictureTest.cpp
+++ b/tests/PictureTest.cpp
@@ -108,6 +108,7 @@
         : INHERITED(width, height)
         , fSaveCount(0)
         , fSaveLayerCount(0)
+        , fSaveBehindCount(0)
         , fRestoreCount(0){
     }
 
@@ -116,6 +117,11 @@
         return this->INHERITED::getSaveLayerStrategy(rec);
     }
 
+    bool onDoSaveBehind(const SkRect* subset) override {
+        ++fSaveBehindCount;
+        return this->INHERITED::onDoSaveBehind(subset);
+    }
+
     void willSave() override {
         ++fSaveCount;
         this->INHERITED::willSave();
@@ -128,11 +134,13 @@
 
     unsigned int getSaveCount() const { return fSaveCount; }
     unsigned int getSaveLayerCount() const { return fSaveLayerCount; }
+    unsigned int getSaveBehindCount() const { return fSaveBehindCount; }
     unsigned int getRestoreCount() const { return fRestoreCount; }
 
 private:
     unsigned int fSaveCount;
     unsigned int fSaveLayerCount;
+    unsigned int fSaveBehindCount;
     unsigned int fRestoreCount;
 
     typedef SkCanvas INHERITED;
@@ -550,9 +558,8 @@
 static void test_typeface(skiatest::Reporter* reporter) {
     SkPictureRecorder recorder;
     SkCanvas* canvas = recorder.beginRecording(10, 10);
-    SkPaint paint;
-    paint.setTypeface(SkTypeface::MakeFromName("Arial", SkFontStyle::Italic()));
-    canvas->drawString("Q", 0, 10, paint);
+    SkFont font(SkTypeface::MakeFromName("Arial", SkFontStyle::Italic()));
+    canvas->drawString("Q", 0, 10, font, SkPaint());
     sk_sp<SkPicture> picture(recorder.finishRecordingAsPicture());
     SkDynamicMemoryWStream stream;
     picture->serialize(&stream);
diff --git a/tests/ProcessorTest.cpp b/tests/ProcessorTest.cpp
index e4aab42..3335015 100644
--- a/tests/ProcessorTest.cpp
+++ b/tests/ProcessorTest.cpp
@@ -19,10 +19,11 @@
 #include "GrResourceProvider.h"
 #include "glsl/GrGLSLFragmentProcessor.h"
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "ops/GrFillRectOp.h"
 #include "ops/GrMeshDrawOp.h"
-#include "ops/GrRectOpFactory.h"
 #include "TestUtils.h"
 
+#include <atomic>
 #include <random>
 
 namespace {
@@ -84,9 +85,8 @@
     const char* name() const override { return "test"; }
 
     void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
-        // We don't really care about reusing these.
-        static int32_t gKey = 0;
-        b->add32(sk_atomic_inc(&gKey));
+        static std::atomic<int32_t> nextKey{0};
+        b->add32(nextKey++);
     }
 
     std::unique_ptr<GrFragmentProcessor> clone() const override {
@@ -259,9 +259,8 @@
     paint.addColorFragmentProcessor(std::move(fp));
     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
 
-    auto op = GrRectOpFactory::MakeNonAAFill(context, std::move(paint), SkMatrix::I(),
-                                             SkRect::MakeWH(rtc->width(), rtc->height()),
-                                             GrAAType::kNone);
+    auto op = GrFillRectOp::Make(context, std::move(paint), GrAAType::kNone, SkMatrix::I(),
+                                 SkRect::MakeWH(rtc->width(), rtc->height()));
     rtc->addDrawOp(GrNoClip(), std::move(op));
 }
 
@@ -519,7 +518,7 @@
             // violating the optimizations, it's reasonable to expect it to violate requirements on
             // a large number of pixels in the image. Sporadic pixel violations are more indicative
             // of device errors and represents a separate problem.
-#if defined(SK_SKQP_GLOBAL_ERROR_TOLERANCE)
+#if defined(SK_BUILD_FOR_SKQP)
             static constexpr int kMaxAcceptableFailedPixels = 0; // Strict when running as SKQP
 #else
             static constexpr int kMaxAcceptableFailedPixels = 2 * kRenderSize; // ~0.7% of the image
diff --git a/tests/PromiseImageTest.cpp b/tests/PromiseImageTest.cpp
index 2a43458..216d656 100644
--- a/tests/PromiseImageTest.cpp
+++ b/tests/PromiseImageTest.cpp
@@ -10,7 +10,6 @@
 #include "GrBackendSurface.h"
 #include "GrContextPriv.h"
 #include "GrGpu.h"
-#include "SkDeferredDisplayListRecorder.h"
 #include "SkImage_Gpu.h"
 
 using namespace sk_gpu_test;
@@ -104,7 +103,7 @@
                 nullptr, kWidth, kHeight, GrColorType::kRGBA_8888, true, GrMipMapped::kNo);
         REPORTER_ASSERT(reporter, backendTex.isValid());
 
-        GrBackendFormat backendFormat = gpu->caps()->createFormatFromBackendTexture(backendTex);
+        GrBackendFormat backendFormat = backendTex.getBackendFormat();
         REPORTER_ASSERT(reporter, backendFormat.isValid());
 
         PromiseTextureChecker promiseChecker(backendTex);
diff --git a/tests/ProxyTest.cpp b/tests/ProxyTest.cpp
index 9a5a0d1..a52ed8e 100644
--- a/tests/ProxyTest.cpp
+++ b/tests/ProxyTest.cpp
@@ -317,7 +317,7 @@
                                                                  false, GrMipMapped::kNo);
 
                     sk_sp<GrSurfaceProxy> sProxy = proxyProvider->wrapBackendTexture(
-                            backendTex, origin, kBorrow_GrWrapOwnership, nullptr, nullptr);
+                            backendTex, origin, kBorrow_GrWrapOwnership, kRead_GrIOType);
                     if (!sProxy) {
                         gpu->deleteTestingOnlyBackendTexture(backendTex);
                         continue;
diff --git a/tests/RecordDrawTest.cpp b/tests/RecordDrawTest.cpp
index de9d233..4902338 100644
--- a/tests/RecordDrawTest.cpp
+++ b/tests/RecordDrawTest.cpp
@@ -150,31 +150,6 @@
 }
 #endif
 
-// A regression test for crbug.com/409110.
-DEF_TEST(RecordDraw_TextBounds, r) {
-    SkRecord record;
-    SkRecorder recorder(&record, W, H);
-
-    // Two Chinese characters in UTF-8.
-    const char text[] = { '\xe6', '\xbc', '\xa2', '\xe5', '\xad', '\x97' };
-    const size_t bytes = SK_ARRAY_COUNT(text);
-
-    const SkScalar xpos[] = { 10, 20 };
-    recorder.drawPosTextH(text, bytes, xpos, 30, SkPaint());
-
-    const SkPoint pos[] = { {40, 50}, {60, 70} };
-    recorder.drawPosText(text, bytes, pos, SkPaint());
-
-    SkAutoTMalloc<SkRect> bounds(record.count());
-    SkRecordFillBounds(SkRect::MakeWH(SkIntToScalar(W), SkIntToScalar(H)), record, bounds);
-
-    // We can make these next assertions confidently because SkRecordFillBounds
-    // builds its bounds by overestimating font metrics in a platform-independent way.
-    // If that changes, these tests will need to be more flexible.
-    REPORTER_ASSERT(r, sloppy_rect_eq(bounds[0], SkRect::MakeLTRB(0,  0, 140, 60)));
-    REPORTER_ASSERT(r, sloppy_rect_eq(bounds[1], SkRect::MakeLTRB(0, 20, 180, 100)));
-}
-
 // Base test to ensure start/stop range is respected
 DEF_TEST(RecordDraw_PartialStartStop, r) {
     static const int kWidth = 10, kHeight = 10;
diff --git a/tests/RectangleTextureTest.cpp b/tests/RectangleTextureTest.cpp
index 21a9e19..dc1d3b7 100644
--- a/tests/RectangleTextureTest.cpp
+++ b/tests/RectangleTextureTest.cpp
@@ -135,7 +135,8 @@
             }
         }
 
-        sk_sp<GrTextureProxy> rectProxy = proxyProvider->wrapBackendTexture(rectangleTex, origin);
+        sk_sp<GrTextureProxy> rectProxy = proxyProvider->wrapBackendTexture(
+                rectangleTex, origin, kBorrow_GrWrapOwnership, kRW_GrIOType);
 
         if (!rectProxy) {
             ERRORF(reporter, "Error creating proxy for rectangle texture.");
diff --git a/tests/RefCntTest.cpp b/tests/RefCntTest.cpp
index 2b6091c..c79e87f 100644
--- a/tests/RefCntTest.cpp
+++ b/tests/RefCntTest.cpp
@@ -234,7 +234,7 @@
     check(reporter, 0, 0, 1, 0);
     paint.set(std::move(baz));
     check(reporter, 0, 0, 1, 0);
-    REPORTER_ASSERT(reporter, !baz);
+    REPORTER_ASSERT(reporter, !baz);  // NOLINT(bugprone-use-after-move)
     paint.set(nullptr);
     check(reporter, 0, 1, 1, 1);
 
diff --git a/tests/ResourceAllocatorTest.cpp b/tests/ResourceAllocatorTest.cpp
index 58af84e..ab24b00 100644
--- a/tests/ResourceAllocatorTest.cpp
+++ b/tests/ResourceAllocatorTest.cpp
@@ -10,6 +10,7 @@
 #include "Test.h"
 
 #include "GrContextPriv.h"
+#include "GrDeinstantiateProxyTracker.h"
 #include "GrGpu.h"
 #include "GrProxyProvider.h"
 #include "GrResourceAllocator.h"
@@ -17,7 +18,6 @@
 #include "GrSurfaceProxyPriv.h"
 #include "GrTexture.h"
 #include "GrTextureProxy.h"
-#include "GrUninstantiateProxyTracker.h"
 
 #include "SkSurface.h"
 
@@ -69,7 +69,8 @@
         return nullptr;
     }
 
-    auto tmp = proxyProvider->wrapBackendTexture(*backendTex, p.fOrigin);
+    auto tmp = proxyProvider->wrapBackendTexture(*backendTex, p.fOrigin, kBorrow_GrWrapOwnership,
+                                                 kRead_GrIOType);
     if (!tmp) {
         return nullptr;
     }
@@ -89,8 +90,8 @@
 // assigned different GrSurfaces.
 static void overlap_test(skiatest::Reporter* reporter, GrResourceProvider* resourceProvider,
                          GrSurfaceProxy* p1, GrSurfaceProxy* p2, bool expectedResult) {
-    GrUninstantiateProxyTracker uninstantiateTracker;
-    GrResourceAllocator alloc(resourceProvider, &uninstantiateTracker);
+    GrDeinstantiateProxyTracker deinstantiateTracker;
+    GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker);
 
     alloc.addInterval(p1, 0, 4);
     alloc.addInterval(p2, 1, 2);
@@ -112,8 +113,8 @@
 static void non_overlap_test(skiatest::Reporter* reporter, GrResourceProvider* resourceProvider,
                              GrSurfaceProxy* p1, GrSurfaceProxy* p2,
                              bool expectedResult) {
-    GrUninstantiateProxyTracker uninstantiateTracker;
-    GrResourceAllocator alloc(resourceProvider, &uninstantiateTracker);
+    GrDeinstantiateProxyTracker deinstantiateTracker;
+    GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker);
 
     alloc.addInterval(p1, 0, 2);
     alloc.addInterval(p2, 3, 5);
@@ -310,12 +311,9 @@
         }
     };
     const GrBackendFormat format = caps->getBackendFormatFromColorType(p.fColorType);
-    auto lazyType = deinstantiate ? GrSurfaceProxy::LazyInstantiationType ::kUninstantiate
+    auto lazyType = deinstantiate ? GrSurfaceProxy::LazyInstantiationType ::kDeinstantiate
                                   : GrSurfaceProxy::LazyInstantiationType ::kSingleUse;
     GrInternalSurfaceFlags flags = GrInternalSurfaceFlags::kNone;
-    if (p.fIsRT && caps->maxWindowRectangles() > 0) {
-        flags = GrInternalSurfaceFlags::kWindowRectsSupport;
-    }
     return proxyProvider->createLazyProxy(callback, format, desc, p.fOrigin, GrMipMapped::kNo,
                                           flags, p.fFit, SkBudgeted::kNo, lazyType);
 }
@@ -342,9 +340,9 @@
         auto p2 = make_lazy(proxyProvider, caps, rtParams, true);
         auto p3 = make_lazy(proxyProvider, caps, rtParams, false);
 
-        GrUninstantiateProxyTracker uninstantiateTracker;
+        GrDeinstantiateProxyTracker deinstantiateTracker;
         {
-            GrResourceAllocator alloc(resourceProvider, &uninstantiateTracker);
+            GrResourceAllocator alloc(resourceProvider, &deinstantiateTracker);
             alloc.addInterval(p0.get(), 0, 1);
             alloc.addInterval(p1.get(), 0, 1);
             alloc.addInterval(p2.get(), 0, 1);
@@ -354,7 +352,7 @@
             GrResourceAllocator::AssignError error;
             alloc.assign(&startIndex, &stopIndex, &error);
         }
-        uninstantiateTracker.uninstantiateAllProxies();
+        deinstantiateTracker.deinstantiateAllProxies();
         REPORTER_ASSERT(reporter, !p0->isInstantiated());
         REPORTER_ASSERT(reporter, p1->isInstantiated());
         REPORTER_ASSERT(reporter, !p2->isInstantiated());
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index 03ba4d1..1748125 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -223,10 +223,10 @@
     context->resetContext();
 
     sk_sp<GrTexture> borrowed(resourceProvider->wrapBackendTexture(
-            backendTextures[0], kBorrow_GrWrapOwnership));
+            backendTextures[0], kBorrow_GrWrapOwnership, kRead_GrIOType));
 
     sk_sp<GrTexture> adopted(resourceProvider->wrapBackendTexture(
-            backendTextures[1], kAdopt_GrWrapOwnership));
+            backendTextures[1], kAdopt_GrWrapOwnership, kRead_GrIOType));
 
     REPORTER_ASSERT(reporter, borrowed != nullptr && adopted != nullptr);
     if (!borrowed || !adopted) {
diff --git a/tests/SVGDeviceTest.cpp b/tests/SVGDeviceTest.cpp
index 92070ab..0a02f7c 100644
--- a/tests/SVGDeviceTest.cpp
+++ b/tests/SVGDeviceTest.cpp
@@ -114,12 +114,14 @@
 
     SkDOM dom;
     SkPaint paint;
+    SkFont font;
     SkPoint offset = SkPoint::Make(10, 20);
 
     {
         SkXMLParserWriter writer(dom.beginParsing());
         std::unique_ptr<SkCanvas> svgCanvas = SkSVGCanvas::Make(SkRect::MakeWH(100, 100), &writer);
-        svgCanvas->drawText(txt, len, offset.x(), offset.y(), paint);
+        svgCanvas->drawSimpleText(txt, len, kUTF8_SkTextEncoding, offset.x(), offset.y(),
+                                  font, paint);
     }
     check_text_node(reporter, dom, dom.finishParsing(), offset, 2, expected);
 
@@ -131,7 +133,8 @@
 
         SkXMLParserWriter writer(dom.beginParsing());
         std::unique_ptr<SkCanvas> svgCanvas = SkSVGCanvas::Make(SkRect::MakeWH(100, 100), &writer);
-        svgCanvas->drawPosTextH(txt, len, xpos, offset.y(), paint);
+        auto blob = SkTextBlob::MakeFromPosTextH(txt, len, &xpos[0], offset.y(), font);
+        svgCanvas->drawTextBlob(blob, 0, 0, paint);
     }
     check_text_node(reporter, dom, dom.finishParsing(), offset, 2, expected);
 
@@ -142,8 +145,8 @@
         }
 
         SkXMLParserWriter writer(dom.beginParsing());
-        std::unique_ptr<SkCanvas> svgCanvas = SkSVGCanvas::Make(SkRect::MakeWH(100, 100), &writer);
-        svgCanvas->drawPosText(txt, len, pos, paint);
+        auto blob = SkTextBlob::MakeFromPosTextH(txt, len, &pos[0], font);
+        svgCanvas->drawTextBlob(blob, 0, 0, paint);
     }
     check_text_node(reporter, dom, dom.finishParsing(), offset, 2, expected);
 }
diff --git a/tests/SerializationTest.cpp b/tests/SerializationTest.cpp
index 7d4df08..f13329a 100644
--- a/tests/SerializationTest.cpp
+++ b/tests/SerializationTest.cpp
@@ -441,8 +441,10 @@
     paint.setColor(SK_ColorRED);
     canvas->drawCircle(SkIntToScalar(kBitmapSize/2), SkIntToScalar(kBitmapSize/2), SkIntToScalar(kBitmapSize/3), paint);
     paint.setColor(SK_ColorBLACK);
-    paint.setTextSize(SkIntToScalar(kBitmapSize/3));
-    canvas->drawString("Picture", SkIntToScalar(kBitmapSize/2), SkIntToScalar(kBitmapSize/4), paint);
+
+    SkFont font;
+    font.setSize(kBitmapSize/3);
+    canvas->drawString("Picture", SkIntToScalar(kBitmapSize/2), SkIntToScalar(kBitmapSize/4), font, paint);
 }
 
 DEF_TEST(Serialization, reporter) {
diff --git a/tests/SkColorSpaceXformStepsTest.cpp b/tests/SkColorSpaceXformStepsTest.cpp
index f0ae414c..6b15385 100644
--- a/tests/SkColorSpaceXformStepsTest.cpp
+++ b/tests/SkColorSpaceXformStepsTest.cpp
@@ -11,8 +11,8 @@
 
 DEF_TEST(SkColorSpaceXformSteps, r) {
     auto srgb   = SkColorSpace::MakeSRGB(),
-         adobe  = SkColorSpace::MakeRGB(g2Dot2_TransferFn, SkColorSpace::kAdobeRGB_Gamut),
-         srgb22 = SkColorSpace::MakeRGB(g2Dot2_TransferFn, SkColorSpace::    kSRGB_Gamut),
+         adobe  = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB),
+         srgb22 = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kSRGB),
          srgb1  = srgb ->makeLinearGamma(),
          adobe1 = adobe->makeLinearGamma();
 
diff --git a/tests/SkLiteDLTest.cpp b/tests/SkLiteDLTest.cpp
index 657fd03..e64e5aa 100644
--- a/tests/SkLiteDLTest.cpp
+++ b/tests/SkLiteDLTest.cpp
@@ -63,28 +63,3 @@
     canvas.flush();
     REPORTER_ASSERT(r, !dl.empty());
 }
-
-// skia:7133 regression test.
-// At one point we recorded text before the transforms, which makes it easy for
-// the recording buffer to not be suitably aligned for the transforms.
-DEF_TEST(SkLiteRecorder_RSXformAlignment, r) {
-    SkLiteDL dl;
-    SkLiteRecorder canvas;
-    canvas.reset(&dl, {0,0,100,100});
-
-    SkPaint paint;
-    paint.setTextEncoding(kUTF8_SkTextEncoding);
-
-    // These values don't really matter... we just need 5 valid transforms.
-    SkRSXform xforms[] = {
-        {1,0, 1,1},
-        {1,0, 2,2},
-        {1,0, 3,3},
-        {1,0, 4,4},
-        {1,0, 5,5},
-    };
-    canvas.drawTextRSXform("hello", 5, xforms, nullptr, paint);
-
-    // We're just checking that this recorded our draw without SkASSERTing in Debug builds.
-    REPORTER_ASSERT(r, !dl.empty());
-}
diff --git a/tests/SkNxTest.cpp b/tests/SkNxTest.cpp
index 7afedfe..f210974 100644
--- a/tests/SkNxTest.cpp
+++ b/tests/SkNxTest.cpp
@@ -185,8 +185,8 @@
         int exact = (a*b+127)/255;
 
         // Duplicate a and b 16x each.
-        auto av = Sk4px::DupAlpha(a),
-             bv = Sk4px::DupAlpha(b);
+        Sk4px av = Sk16b(a),
+              bv = Sk16b(b);
 
         // This way should always be exactly correct.
         int correct = (av * bv).div255()[0];
@@ -202,22 +202,6 @@
     }
 }
 
-DEF_TEST(Sk4px_widening, r) {
-    SkPMColor colors[] = {
-        SkPreMultiplyColor(0xff00ff00),
-        SkPreMultiplyColor(0x40008000),
-        SkPreMultiplyColor(0x7f020406),
-        SkPreMultiplyColor(0x00000000),
-    };
-    auto packed = Sk4px::Load4(colors);
-
-    auto wideLo = packed.widenLo(),
-         wideHi = packed.widenHi(),
-         wideLoHi    = packed.widenLoHi(),
-         wideLoHiAlt = wideLo + wideHi;
-    REPORTER_ASSERT(r, 0 == memcmp(&wideLoHi, &wideLoHiAlt, sizeof(wideLoHi)));
-}
-
 DEF_TEST(SkNx_abs, r) {
     auto fs = Sk4f(0.0f, -0.0f, 2.0f, -4.0f).abs();
     REPORTER_ASSERT(r, fs[0] == 0.0f);
diff --git a/tests/SkRemoteGlyphCacheTest.cpp b/tests/SkRemoteGlyphCacheTest.cpp
index 5127224..6081acf 100644
--- a/tests/SkRemoteGlyphCacheTest.cpp
+++ b/tests/SkRemoteGlyphCacheTest.cpp
@@ -750,8 +750,8 @@
     REPORTER_ASSERT(reporter, clientTf);
 
     SkFont font;
+    font.setEdging(SkFont::Edging::kAntiAlias);
     SkPaint paint;
-    paint.setAntiAlias(true);
     paint.setColor(SK_ColorRED);
 
     auto lostGlyphID = SkPackedGlyphID(1, SK_FixedHalf, SK_FixedHalf);
@@ -766,7 +766,7 @@
         SkScalerContextRec rec;
         SkScalerContextEffects effects;
         SkScalerContextFlags flags = SkScalerContextFlags::kFakeGammaAndBoostContrast;
-        paint.setTypeface(serverTf);
+        font.setTypeface(serverTf);
         SkScalerContext::MakeRecAndEffects(
                 font, paint, SkSurfacePropsCopyOrDefault(nullptr), flags,
                 SkMatrix::I(), &rec, &effects, false);
@@ -786,7 +786,7 @@
         SkScalerContextRec rec;
         SkScalerContextEffects effects;
         SkScalerContextFlags flags = SkScalerContextFlags::kFakeGammaAndBoostContrast;
-        paint.setTypeface(clientTf);
+        font.setTypeface(clientTf);
         SkScalerContext::MakeRecAndEffects(
                 font, paint, SkSurfacePropsCopyOrDefault(nullptr), flags,
                 SkMatrix::I(), &rec, &effects, false);
@@ -806,9 +806,9 @@
         SkAutoDescriptor ad;
         SkScalerContextEffects effects;
         SkScalerContextFlags flags = SkScalerContextFlags::kFakeGammaAndBoostContrast;
-        paint.setTypeface(serverTf);
+        font.setTypeface(serverTf);
         auto* cacheState = server.getOrCreateCache(
-                paint, SkSurfacePropsCopyOrDefault(nullptr),
+                paint, font, SkSurfacePropsCopyOrDefault(nullptr),
                 SkMatrix::I(), flags, &effects);
         cacheState->addGlyph(lostGlyphID, false);
 
@@ -825,7 +825,7 @@
         SkScalerContextRec rec;
         SkScalerContextEffects effects;
         SkScalerContextFlags flags = SkScalerContextFlags::kFakeGammaAndBoostContrast;
-        paint.setTypeface(clientTf);
+        font.setTypeface(clientTf);
         SkScalerContext::MakeRecAndEffects(
                 font, paint, SkSurfaceProps(0, kUnknown_SkPixelGeometry), flags,
                 SkMatrix::I(), &rec, &effects, false);
diff --git a/tests/SkSLGLSLTest.cpp b/tests/SkSLGLSLTest.cpp
index 8d12dcc..64e1406 100644
--- a/tests/SkSLGLSLTest.cpp
+++ b/tests/SkSLGLSLTest.cpp
@@ -1562,7 +1562,7 @@
          "out vec4 sk_FragColor;\n"
          "uniform sampler2DRect test;\n"
          "void main() {\n"
-         "    sk_FragColor = texture(test, textureSize(test) * vec2(0.5));\n"
+         "    sk_FragColor = texture(test, vec2(0.5));\n"
          "}\n");
     test(r,
          "uniform sampler2DRect test;"
@@ -1574,7 +1574,7 @@
          "out vec4 sk_FragColor;\n"
          "uniform sampler2DRect test;\n"
          "void main() {\n"
-         "    sk_FragColor = texture(test, vec3(textureSize(test), 1.0) * vec3(0.5));\n"
+         "    sk_FragColor = texture(test, vec3(0.5));\n"
          "}\n");
 }
 
diff --git a/tests/SurfaceSemaphoreTest.cpp b/tests/SurfaceSemaphoreTest.cpp
index b5e1982..66237fd 100644
--- a/tests/SurfaceSemaphoreTest.cpp
+++ b/tests/SurfaceSemaphoreTest.cpp
@@ -20,6 +20,7 @@
 #include "gl/GrGLUtil.h"
 
 #ifdef SK_VULKAN
+#include "vk/GrVkCommandPool.h"
 #include "vk/GrVkGpu.h"
 #include "vk/GrVkTypes.h"
 #include "vk/GrVkUtil.h"
@@ -245,14 +246,14 @@
         const GrVkInterface* interface = gpu->vkInterface();
         VkDevice device = gpu->device();
         VkQueue queue = gpu->queue();
-        VkCommandPool cmdPool = gpu->cmdPool();
+        GrVkCommandPool* cmdPool = gpu->cmdPool();
         VkCommandBuffer cmdBuffer;
 
         // Create Command Buffer
         const VkCommandBufferAllocateInfo cmdInfo = {
             VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,   // sType
             nullptr,                                          // pNext
-            cmdPool,                                          // commandPool
+            cmdPool->vkCommandPool(),                         // commandPool
             VK_COMMAND_BUFFER_LEVEL_PRIMARY,                  // level
             1                                                 // bufferCount
         };
diff --git a/tests/SurfaceTest.cpp b/tests/SurfaceTest.cpp
index 87dc561..42fc8a1 100644
--- a/tests/SurfaceTest.cpp
+++ b/tests/SurfaceTest.cpp
@@ -409,19 +409,6 @@
     testRRect.setRectXY(testRect, SK_Scalar1, SK_Scalar1);
 
     SkString testText("Hello World");
-    const SkPoint testPoints2[] = {
-        { SkIntToScalar(0), SkIntToScalar(1) },
-        { SkIntToScalar(1), SkIntToScalar(1) },
-        { SkIntToScalar(2), SkIntToScalar(1) },
-        { SkIntToScalar(3), SkIntToScalar(1) },
-        { SkIntToScalar(4), SkIntToScalar(1) },
-        { SkIntToScalar(5), SkIntToScalar(1) },
-        { SkIntToScalar(6), SkIntToScalar(1) },
-        { SkIntToScalar(7), SkIntToScalar(1) },
-        { SkIntToScalar(8), SkIntToScalar(1) },
-        { SkIntToScalar(9), SkIntToScalar(1) },
-        { SkIntToScalar(10), SkIntToScalar(1) },
-    };
 
 #define EXPECT_COPY_ON_WRITE(command)                               \
     {                                                               \
@@ -444,9 +431,7 @@
     EXPECT_COPY_ON_WRITE(drawBitmap(testBitmap, 0, 0))
     EXPECT_COPY_ON_WRITE(drawBitmapRect(testBitmap, testRect, nullptr))
     EXPECT_COPY_ON_WRITE(drawBitmapNine(testBitmap, testIRect, testRect, nullptr))
-    EXPECT_COPY_ON_WRITE(drawString(testText, 0, 1, testPaint))
-    EXPECT_COPY_ON_WRITE(drawPosText(testText.c_str(), testText.size(), testPoints2, \
-        testPaint))
+    EXPECT_COPY_ON_WRITE(drawString(testText, 0, 1, SkFont(), testPaint))
 }
 DEF_TEST(SurfaceCopyOnWrite, reporter) {
     test_copy_on_write(reporter, create_surface().get());
@@ -672,8 +657,29 @@
     GrContext* context, int sampleCnt, uint32_t color, GrBackendTexture* outTexture) {
     GrGpu* gpu = context->contextPriv().getGpu();
 
+    // On Pixel and Pixel2XL's with Adreno 530 and 540s, setting width and height to 10s reliably
+    // triggers what appears to be a driver race condition where the 10x10 surface from the
+    // OverdrawSurface_gpu test is reused(?) for this surface created by the SurfacePartialDraw_gpu
+    // test.
+    //
+    // Immediately after creation of this surface, readback shows the correct initial solid color.
+    // However, sometime before content is rendered into the upper half of the surface, the driver
+    // presumably cleans up the OverdrawSurface_gpu's memory which corrupts this color buffer. The
+    // top half of the surface is fine after the partially-covering rectangle is drawn, but the
+    // untouched bottom half contains random pixel values that trigger asserts in the
+    // SurfacePartialDraw_gpu test for no longer matching the initial color. Running the
+    // SurfacePartialDraw_gpu test without the OverdrawSurface_gpu test completes successfully.
+    //
+    // Requesting a much larger backend texture size seems to prevent it from reusing the same
+    // memory and avoids the issue.
+#if defined(SK_BUILD_FOR_SKQP)
     const int kWidth = 10;
     const int kHeight = 10;
+#else
+    const int kWidth = 100;
+    const int kHeight = 100;
+#endif
+
     std::unique_ptr<uint32_t[]> pixels(new uint32_t[kWidth * kHeight]);
     sk_memset32(pixels.get(), color, kWidth * kHeight);
 
diff --git a/tests/TableColorFilterTest.cpp b/tests/TableColorFilterTest.cpp
index 857aa7e..bb5df60 100644
--- a/tests/TableColorFilterTest.cpp
+++ b/tests/TableColorFilterTest.cpp
@@ -17,8 +17,7 @@
 
 DEF_TEST(TableColorFilter, r) {
     // Using a wide source gamut will make saturated colors go well out of range of sRGB.
-    auto rec2020 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                         SkColorSpace::kRec2020_Gamut);
+    auto rec2020 = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kRec2020);
     sk_sp<SkColorFilter> to_srgb = SkToSRGBColorFilter::Make(rec2020);
 
     // Any table will work fine here.  An identity table makes testing easy.
diff --git a/tests/TemplatesTest.cpp b/tests/TemplatesTest.cpp
index 9d5ca77..88268a0 100644
--- a/tests/TemplatesTest.cpp
+++ b/tests/TemplatesTest.cpp
@@ -137,7 +137,7 @@
     REPORTER_ASSERT(r, foo.get());
 
     foo = std::move(foo);
-    REPORTER_ASSERT(r, foo.get());
+    REPORTER_ASSERT(r, foo.get());  // NOLINT(bugprone-use-after-move)
 
 #if defined(__clang__)
     #pragma clang diagnostic pop
diff --git a/tests/TessellatingPathRendererTests.cpp b/tests/TessellatingPathRendererTests.cpp
index db8665d..58e0d5d 100644
--- a/tests/TessellatingPathRendererTests.cpp
+++ b/tests/TessellatingPathRendererTests.cpp
@@ -340,14 +340,6 @@
     return path;
 }
 
-// A quad which becomes NaN when interpolated.
-static SkPath create_path_22() {
-    SkPath path;
-    path.moveTo(-5.71889e+13f, 1.36759e+09f);
-    path.quadTo(2.45472e+19f, -3.12406e+15f, -2.19589e+18f, 2.79462e+14f);
-    return path;
-}
-
 // A path which contains out-of-range colinear intersections.
 static SkPath create_path_23() {
     SkPath path;
@@ -713,7 +705,6 @@
     test_path(ctx, rtc.get(), create_path_19());
     test_path(ctx, rtc.get(), create_path_20(), SkMatrix(), GrAAType::kCoverage);
     test_path(ctx, rtc.get(), create_path_21(), SkMatrix(), GrAAType::kCoverage);
-    test_path(ctx, rtc.get(), create_path_22());
     test_path(ctx, rtc.get(), create_path_23());
     test_path(ctx, rtc.get(), create_path_24());
     test_path(ctx, rtc.get(), create_path_25(), SkMatrix(), GrAAType::kCoverage);
diff --git a/tests/TextBlobTest.cpp b/tests/TextBlobTest.cpp
index 09e4bb1..89f1460 100644
--- a/tests/TextBlobTest.cpp
+++ b/tests/TextBlobTest.cpp
@@ -210,12 +210,7 @@
 
         SkTextBlobRunIterator it(blob.get());
         while (!it.done()) {
-            SkPaint paint;
-            it.applyFontToPaint(&paint);    // need iter for fonts
-            SkFont iterFont = SkFont::LEGACY_ExtractFromPaint(paint);
-
-            REPORTER_ASSERT(reporter, iterFont == font);
-
+            REPORTER_ASSERT(reporter, it.font() == font);
             it.next();
         }
 
@@ -320,17 +315,16 @@
 
 DEF_TEST(TextBlob_extended, reporter) {
     SkTextBlobBuilder textBlobBuilder;
-    SkPaint paint;
+    SkFont font;
     const char text1[] = "Foo";
     const char text2[] = "Bar";
 
-    int glyphCount = paint.textToGlyphs(text1, strlen(text1), nullptr);
+    int glyphCount = font.countText(text1, strlen(text1), kUTF8_SkTextEncoding);
     SkAutoTMalloc<uint16_t> glyphs(glyphCount);
-    (void)paint.textToGlyphs(text1, strlen(text1), glyphs.get());
-    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+    (void)font.textToGlyphs(text1, strlen(text1), kUTF8_SkTextEncoding, glyphs.get(), glyphCount);
 
     auto run = SkTextBlobBuilderPriv::AllocRunText(&textBlobBuilder,
-            paint, glyphCount, 0, 0, SkToInt(strlen(text2)), SkString(), nullptr);
+            font, glyphCount, 0, 0, SkToInt(strlen(text2)), SkString(), nullptr);
     memcpy(run.glyphs, glyphs.get(), sizeof(uint16_t) * glyphCount);
     memcpy(run.utf8text, text2, strlen(text2));
     for (int i = 0; i < glyphCount; ++i) {
@@ -416,8 +410,8 @@
         sk_sp<SkTypeface> tf = SkTypeface::MakeDefault();
 
         SkTextBlobBuilder builder;
-        add_run(&builder, "Hello", 10, 20, nullptr);    // we don't flatten this in the paint
-        add_run(&builder, "World", 10, 40, tf);         // we will flatten this in the paint
+        add_run(&builder, "Hello", 10, 20, nullptr);    // don't flatten a typeface
+        add_run(&builder, "World", 10, 40, tf);         // do flatten this typeface
         return builder.make();
     }();
 
@@ -426,7 +420,7 @@
     serializeProcs.fTypefaceProc = &SerializeTypeface;
     serializeProcs.fTypefaceCtx = (void*) &array;
     sk_sp<SkData> data = blob0->serialize(serializeProcs);
-    REPORTER_ASSERT(reporter, array.count() == 2);
+    REPORTER_ASSERT(reporter, array.count() == 1);
     SkDeserialProcs deserializeProcs;
     deserializeProcs.fTypefaceProc = &DeserializeTypeface;
     deserializeProcs.fTypefaceCtx = (void*) &array;
diff --git a/tests/TextureProxyTest.cpp b/tests/TextureProxyTest.cpp
index 03ded09..5260a0e 100644
--- a/tests/TextureProxyTest.cpp
+++ b/tests/TextureProxyTest.cpp
@@ -112,7 +112,8 @@
     GrBackendTexture backendTex = (*backingSurface)->getBackendTexture();
     backendTex.setPixelConfig(desc.fConfig);
 
-    return proxyProvider->wrapBackendTexture(backendTex, kBottomLeft_GrSurfaceOrigin);
+    return proxyProvider->wrapBackendTexture(backendTex, kBottomLeft_GrSurfaceOrigin,
+                                             kBorrow_GrWrapOwnership, kRead_GrIOType);
 }
 
 
diff --git a/tests/ToSRGBColorFilter.cpp b/tests/ToSRGBColorFilter.cpp
index 01c5673..519ffb8 100644
--- a/tests/ToSRGBColorFilter.cpp
+++ b/tests/ToSRGBColorFilter.cpp
@@ -22,8 +22,7 @@
     REPORTER_ASSERT(r, nullptr == SkToSRGBColorFilter::Make(nullptr));
 
     // Here's a realistic conversion.
-    auto dci_p3 = SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma,
-                                        SkColorSpace::kDCIP3_D65_Gamut);
+    auto dci_p3 = SkColorSpace::MakeRGB(SkNamedTransferFn::kLinear, SkNamedGamut::kDCIP3);
     REPORTER_ASSERT(r, nullptr != SkToSRGBColorFilter::Make(dci_p3));
 
 }
diff --git a/tests/TraceMemoryDumpTest.cpp b/tests/TraceMemoryDumpTest.cpp
index 32eab62..c58e915 100644
--- a/tests/TraceMemoryDumpTest.cpp
+++ b/tests/TraceMemoryDumpTest.cpp
@@ -128,7 +128,7 @@
     idDesc.fOwnership = GrBackendObjectOwnership::kBorrowed;
 
     auto texture = GrGLTexture::MakeWrapped(gpu, desc, GrMipMapsStatus::kNotAllocated, idDesc,
-                                            false);
+                                            kRead_GrIOType, false);
 
     ValidateMemoryDumps(reporter, context, texture->gpuMemorySize(), false /* isOwned */);
 }
diff --git a/tests/TransferPixelsTest.cpp b/tests/TransferPixelsTest.cpp
index 537b5fe..5dd360d 100644
--- a/tests/TransferPixelsTest.cpp
+++ b/tests/TransferPixelsTest.cpp
@@ -68,7 +68,12 @@
     // set up the data
     const int kTextureWidth = 16;
     const int kTextureHeight = 16;
+#ifdef SK_BUILD_FOR_IOS
+    // UNPACK_ROW_LENGTH is broken on iOS so rowBytes needs to match data width
+    const int kBufferWidth = GrBackendApi::kOpenGL == context->contextPriv().getBackend() ? 16 : 20;
+#else
     const int kBufferWidth = 20;
+#endif
     const int kBufferHeight = 16;
     size_t rowBytes = kBufferWidth * sizeof(GrColor);
     SkAutoTMalloc<GrColor> srcBuffer(kBufferWidth*kBufferHeight);
@@ -137,7 +142,12 @@
 
         //////////////////////////
         // transfer partial data
-
+#ifdef SK_BUILD_FOR_IOS
+        // UNPACK_ROW_LENGTH is broken on iOS so we can't do partial transfers
+        if (GrBackendApi::kOpenGL == context->contextPriv().getBackend()) {
+            continue;
+        }
+#endif
         const int kLeft = 2;
         const int kTop = 10;
         const int kWidth = 10;
diff --git a/tests/UnicodeTest.cpp b/tests/UnicodeTest.cpp
index 62174e9..ee39e57 100644
--- a/tests/UnicodeTest.cpp
+++ b/tests/UnicodeTest.cpp
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkFont.h"
 #include "SkPaint.h"
 #include "SkUTF.h"
 #include "Test.h"
@@ -29,16 +30,11 @@
     uint16_t glyphs16[sizeof(text8)];
     uint16_t glyphs32[sizeof(text8)];
 
-    SkPaint paint;
+    SkFont font;
 
-    paint.setTextEncoding(kUTF8_SkTextEncoding);
-    int count8  = paint.textToGlyphs(text8,  len8,  glyphs8);
-
-    paint.setTextEncoding(kUTF16_SkTextEncoding);
-    int count16 = paint.textToGlyphs(text16, len16, glyphs16);
-
-    paint.setTextEncoding(kUTF32_SkTextEncoding);
-    int count32 = paint.textToGlyphs(text32, len32, glyphs32);
+    int count8  = font.textToGlyphs(text8,  len8,  kUTF8_SkTextEncoding,  glyphs8,  SK_ARRAY_COUNT(glyphs8));
+    int count16 = font.textToGlyphs(text16, len16, kUTF16_SkTextEncoding, glyphs16, SK_ARRAY_COUNT(glyphs16));
+    int count32 = font.textToGlyphs(text32, len32, kUTF32_SkTextEncoding, glyphs32, SK_ARRAY_COUNT(glyphs32));
 
     REPORTER_ASSERT(reporter, (int)len8 == count8);
     REPORTER_ASSERT(reporter, (int)len8 == count16);
@@ -47,3 +43,24 @@
     REPORTER_ASSERT(reporter, !memcmp(glyphs8, glyphs16, count8 * sizeof(uint16_t)));
     REPORTER_ASSERT(reporter, !memcmp(glyphs8, glyphs32, count8 * sizeof(uint16_t)));
 }
+
+#include "SkFont.h"
+#include "SkFontPriv.h"
+
+DEF_TEST(glyphs_to_unichars, reporter) {
+    SkFont font;
+
+    const int N = 52;
+    SkUnichar uni[N];
+    for (int i = 0; i < 26; ++i) {
+        uni[i +  0] = i + 'A';
+        uni[i + 26] = i + 'a';
+    }
+    uint16_t glyphs[N];
+    font.textToGlyphs(uni, sizeof(uni), kUTF32_SkTextEncoding, glyphs, N);
+
+    SkUnichar uni2[N];
+    SkFontPriv::GlyphsToUnichars(font, glyphs, N, uni2);
+    REPORTER_ASSERT(reporter, memcmp(uni, uni2, sizeof(uni)) == 0);
+}
+
diff --git a/tests/VkDrawableTest.cpp b/tests/VkDrawableTest.cpp
index d9aa8b6..3ff2607 100644
--- a/tests/VkDrawableTest.cpp
+++ b/tests/VkDrawableTest.cpp
@@ -22,6 +22,7 @@
 #include "vk/GrVkGpu.h"
 #include "vk/GrVkInterface.h"
 #include "vk/GrVkMemory.h"
+#include "vk/GrVkSecondaryCBDrawContext.h"
 #include "vk/GrVkUtil.h"
 
 using sk_gpu_test::GrContextFactory;
@@ -30,22 +31,23 @@
 
 class TestDrawable : public SkDrawable {
 public:
-    TestDrawable(const GrVkInterface* interface, int32_t width, int32_t height)
+    TestDrawable(const GrVkInterface* interface, GrContext* context, int32_t width, int32_t height)
             : INHERITED()
             , fInterface(interface)
+            , fContext(context)
             , fWidth(width)
             , fHeight(height) {}
 
     ~TestDrawable() override {}
 
-    class DrawHandler : public GpuDrawHandler {
+    class DrawHandlerBasic : public GpuDrawHandler {
     public:
-        DrawHandler(const GrVkInterface* interface, int32_t width, int32_t height)
+        DrawHandlerBasic(const GrVkInterface* interface, int32_t width, int32_t height)
             : INHERITED()
             , fInterface(interface)
             , fWidth(width)
             , fHeight(height) {}
-        ~DrawHandler() override {}
+        ~DrawHandlerBasic() override {}
 
         void draw(const GrBackendDrawableInfo& info) override {
             GrVkDrawableInfo vkInfo;
@@ -67,7 +69,7 @@
 
             VkClearAttachment attachment;
             attachment.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-            attachment.colorAttachment = vkInfo.fImageAttachmentIndex;
+            attachment.colorAttachment = vkInfo.fColorAttachmentIndex;
             attachment.clearValue.color = vkColor;
 
             GR_VK_CALL(fInterface, CmdClearAttachments(vkInfo.fSecondaryCommandBuffer,
@@ -86,13 +88,100 @@
         typedef GpuDrawHandler INHERITED;
     };
 
+    typedef void (*DrawProc)(TestDrawable*, const GrVkDrawableInfo&);
+    typedef void (*SubmitProc)(TestDrawable*);
+
+    // Exercises the exporting of a secondary command buffer from one GrContext and then importing
+    // it into a second GrContext. We then draw to the secondary command buffer from the second
+    // GrContext.
+    class DrawHandlerImport : public GpuDrawHandler {
+    public:
+        DrawHandlerImport(TestDrawable* td, DrawProc drawProc, SubmitProc submitProc)
+            : INHERITED()
+            , fTestDrawable(td)
+            , fDrawProc(drawProc)
+            , fSubmitProc(submitProc) {}
+        ~DrawHandlerImport() override {
+            fSubmitProc(fTestDrawable);
+        }
+
+        void draw(const GrBackendDrawableInfo& info) override {
+            GrVkDrawableInfo vkInfo;
+            SkAssertResult(info.getVkDrawableInfo(&vkInfo));
+
+            fDrawProc(fTestDrawable, vkInfo);
+        }
+    private:
+        TestDrawable* fTestDrawable;
+        DrawProc      fDrawProc;
+        SubmitProc    fSubmitProc;
+
+        typedef GpuDrawHandler INHERITED;
+    };
+
+    // Helper function to test drawing to a secondary command buffer that we imported into the
+    // GrContext using a GrVkSecondaryCBDrawContext.
+    static void ImportDraw(TestDrawable* td, const GrVkDrawableInfo& info) {
+        SkImageInfo imageInfo = SkImageInfo::Make(td->fWidth, td->fHeight, kRGBA_8888_SkColorType,
+                                                  kPremul_SkAlphaType);
+
+        td->fDrawContext = GrVkSecondaryCBDrawContext::Make(td->fContext, imageInfo, info, nullptr);
+        if (!td->fDrawContext) {
+            return;
+        }
+
+        SkCanvas* canvas = td->fDrawContext->getCanvas();
+        SkIRect rect = SkIRect::MakeXYWH(td->fWidth/2, 0, td->fWidth/4, td->fHeight);
+        SkPaint paint;
+        paint.setColor(SK_ColorRED);
+        canvas->drawIRect(rect, paint);
+
+        // Draw to an offscreen target so that we end up with a mix of "real" secondary command
+        // buffers and the imported secondary command buffer.
+        sk_sp<SkSurface> surf = SkSurface::MakeRenderTarget(td->fContext, SkBudgeted::kYes,
+                                                            imageInfo);
+        surf->getCanvas()->clear(SK_ColorRED);
+
+        SkRect dstRect = SkRect::MakeXYWH(3*td->fWidth/4, 0, td->fWidth/4, td->fHeight);
+        SkIRect srcRect = SkIRect::MakeWH(td->fWidth/4, td->fHeight);
+        canvas->drawImageRect(surf->makeImageSnapshot(), srcRect, dstRect, &paint);
+
+        td->fDrawContext->flush();
+    }
+
+    // Helper function to test waiting for the imported secondary command buffer to be submitted on
+    // its original context and then cleaning up the GrVkSecondaryCBDrawContext from this GrContext.
+    static void ImportSubmitted(TestDrawable* td) {
+        // Typical use case here would be to create a fence that we submit to the gpu and then wait
+        // on before releasing the GrVkSecondaryCBDrawContext resources. To simulate that for this
+        // test (and since we are running single threaded anyways), we will just force a sync of
+        // the gpu and cpu here.
+        td->fContext->contextPriv().getGpu()->testingOnly_flushGpuAndSync();
+
+        td->fDrawContext->releaseResources();
+        // We release the GrContext here manually to test that we waited long enough before
+        // releasing the GrVkSecondaryCBDrawContext. This simulates when a client is able to delete
+        // the GrContext it used to imported the secondary command buffer. If we had released the
+        // GrContext's resources earlier (before waiting on the gpu above), we would get vulkan
+        // validation layer errors saying we freed some vulkan objects while they were still in use
+        // on the GPU.
+        td->fContext->releaseResourcesAndAbandonContext();
+    }
+
+
     std::unique_ptr<GpuDrawHandler> onSnapGpuDrawHandler(GrBackendApi backendApi,
-                                                         const SkMatrix& matrix) override {
+                                                         const SkMatrix& matrix,
+                                                         const SkIRect& clipBounds) override {
         if (backendApi != GrBackendApi::kVulkan) {
             return nullptr;
         }
-        std::unique_ptr<DrawHandler> draw(new DrawHandler(fInterface, fWidth, fHeight));
-        return std::move(draw);
+        std::unique_ptr<GpuDrawHandler> draw;
+        if (fContext) {
+            draw.reset(new DrawHandlerImport(this, ImportDraw, ImportSubmitted));
+        } else {
+            draw.reset(new DrawHandlerBasic(fInterface, fWidth, fHeight));
+        }
+        return draw;
     }
 
     SkRect onGetBounds() override {
@@ -105,13 +194,15 @@
 
 private:
     const GrVkInterface* fInterface;
-    int32_t fWidth;
-    int32_t fHeight;
+    GrContext*           fContext;
+    sk_sp<GrVkSecondaryCBDrawContext> fDrawContext;
+    int32_t              fWidth;
+    int32_t              fHeight;
 
     typedef SkDrawable INHERITED;
 };
 
-void draw_drawable_test(skiatest::Reporter* reporter, GrContext* context) {
+void draw_drawable_test(skiatest::Reporter* reporter, GrContext* context, GrContext* childContext) {
     GrVkGpu* gpu = static_cast<GrVkGpu*>(context->contextPriv().getGpu());
 
     const SkImageInfo ii = SkImageInfo::Make(DEV_W, DEV_H, kRGBA_8888_SkColorType,
@@ -121,7 +212,7 @@
     SkCanvas* canvas = surface->getCanvas();
     canvas->clear(SK_ColorBLUE);
 
-    sk_sp<TestDrawable> drawable(new TestDrawable(gpu->vkInterface(), DEV_W, DEV_H));
+    sk_sp<TestDrawable> drawable(new TestDrawable(gpu->vkInterface(), childContext, DEV_W, DEV_H));
     canvas->drawDrawable(drawable.get());
 
     SkPaint paint;
@@ -137,8 +228,8 @@
     const uint32_t* canvasPixels = static_cast<const uint32_t*>(bitmap.getPixels());
     bool failureFound = false;
     SkPMColor expectedPixel;
-    for (int cy = 0; cy < DEV_H || failureFound; ++cy) {
-        for (int cx = 0; cx < DEV_W || failureFound; ++cx) {
+    for (int cy = 0; cy < DEV_H && !failureFound; ++cy) {
+        for (int cx = 0; cx < DEV_W && !failureFound; ++cx) {
             SkPMColor canvasPixel = canvasPixels[cy * DEV_W + cx];
             if (cy < DEV_H / 2) {
                 if (cx < DEV_W / 2) {
@@ -158,9 +249,32 @@
     }
 }
 
-
 DEF_GPUTEST_FOR_VULKAN_CONTEXT(VkDrawableTest, reporter, ctxInfo) {
-    draw_drawable_test(reporter, ctxInfo.grContext());
+    draw_drawable_test(reporter, ctxInfo.grContext(), nullptr);
+}
+
+DEF_GPUTEST(VkDrawableImportTest, reporter, options) {
+    for (int typeInt = 0; typeInt < sk_gpu_test::GrContextFactory::kContextTypeCnt; ++typeInt) {
+        sk_gpu_test::GrContextFactory::ContextType contextType =
+                (sk_gpu_test::GrContextFactory::ContextType) typeInt;
+        if (contextType != sk_gpu_test::GrContextFactory::kVulkan_ContextType) {
+            continue;
+        }
+        sk_gpu_test::GrContextFactory factory(options);
+        sk_gpu_test::ContextInfo ctxInfo = factory.getContextInfo(
+                contextType, sk_gpu_test::GrContextFactory::ContextOverrides::kDisableNVPR);
+        skiatest::ReporterContext ctx(
+                   reporter, SkString(sk_gpu_test::GrContextFactory::ContextTypeName(contextType)));
+        if (ctxInfo.grContext()) {
+            sk_gpu_test::ContextInfo child =
+                    factory.getSharedContextInfo(ctxInfo.grContext(), 0);
+            if (!child.grContext()) {
+                continue;
+            }
+
+            draw_drawable_test(reporter, ctxInfo.grContext(), child.grContext());
+        }
+    }
 }
 
 #endif
diff --git a/tests/VkMakeCopyPipelineTest.cpp b/tests/VkMakeCopyPipelineTest.cpp
index f5a19d5..cee90ee 100644
--- a/tests/VkMakeCopyPipelineTest.cpp
+++ b/tests/VkMakeCopyPipelineTest.cpp
@@ -64,9 +64,11 @@
             "}";
 
         SkSL::Program::Settings settings;
+        SkSL::String spirv;
         SkSL::Program::Inputs inputs;
         if (!GrCompileVkShaderModule(gpu, vertShaderText, VK_SHADER_STAGE_VERTEX_BIT,
-                                     &fVertShaderModule, &fShaderStageInfo[0], settings, &inputs)) {
+                                     &fVertShaderModule, &fShaderStageInfo[0], settings,
+                                     &spirv, &inputs)) {
             this->destroyResources(gpu);
             REPORTER_ASSERT(reporter, false);
             return;
@@ -74,7 +76,8 @@
         SkASSERT(inputs.isEmpty());
 
         if (!GrCompileVkShaderModule(gpu, fragShaderText, VK_SHADER_STAGE_FRAGMENT_BIT,
-                                     &fFragShaderModule, &fShaderStageInfo[1], settings, &inputs)) {
+                                     &fFragShaderModule, &fShaderStageInfo[1], settings,
+                                     &spirv, &inputs)) {
             this->destroyResources(gpu);
             REPORTER_ASSERT(reporter, false);
             return;
diff --git a/tests/VkWrapTests.cpp b/tests/VkWrapTests.cpp
index 1553334..7c846ad 100644
--- a/tests/VkWrapTests.cpp
+++ b/tests/VkWrapTests.cpp
@@ -42,7 +42,8 @@
     GrVkImageInfo imageInfo;
     SkAssertResult(origBackendTex.getVkImageInfo(&imageInfo));
 
-    sk_sp<GrTexture> tex = gpu->wrapBackendTexture(origBackendTex, kBorrow_GrWrapOwnership, false);
+    sk_sp<GrTexture> tex =
+            gpu->wrapBackendTexture(origBackendTex, kBorrow_GrWrapOwnership, kRead_GrIOType, false);
     REPORTER_ASSERT(reporter, tex);
 
     // image is null
@@ -51,9 +52,9 @@
         backendCopy.fImage = VK_NULL_HANDLE;
         GrBackendTexture backendTex = GrBackendTexture(kW, kH, backendCopy);
         backendTex.setPixelConfig(kPixelConfig);
-        tex = gpu->wrapBackendTexture(backendTex, kBorrow_GrWrapOwnership, false);
+        tex = gpu->wrapBackendTexture(backendTex, kBorrow_GrWrapOwnership, kRead_GrIOType, false);
         REPORTER_ASSERT(reporter, !tex);
-        tex = gpu->wrapBackendTexture(backendTex, kAdopt_GrWrapOwnership, false);
+        tex = gpu->wrapBackendTexture(backendTex, kAdopt_GrWrapOwnership, kRead_GrIOType, false);
         REPORTER_ASSERT(reporter, !tex);
     }
 
@@ -63,9 +64,9 @@
         backendCopy.fAlloc = GrVkAlloc();
         GrBackendTexture backendTex = GrBackendTexture(kW, kH, backendCopy);
         backendTex.setPixelConfig(kPixelConfig);
-        tex = gpu->wrapBackendTexture(backendTex, kBorrow_GrWrapOwnership, false);
+        tex = gpu->wrapBackendTexture(backendTex, kBorrow_GrWrapOwnership, kRead_GrIOType, false);
         REPORTER_ASSERT(reporter, !tex);
-        tex = gpu->wrapBackendTexture(backendTex, kAdopt_GrWrapOwnership, false);
+        tex = gpu->wrapBackendTexture(backendTex, kAdopt_GrWrapOwnership, kRead_GrIOType, false);
         REPORTER_ASSERT(reporter, !tex);
     }
 
@@ -74,7 +75,7 @@
         GrVkImageInfo backendCopy = imageInfo;
         GrBackendTexture backendTex = GrBackendTexture(kW, kH, backendCopy);
         backendTex.setPixelConfig(kPixelConfig);
-        tex = gpu->wrapBackendTexture(backendTex, kAdopt_GrWrapOwnership, false);
+        tex = gpu->wrapBackendTexture(backendTex, kAdopt_GrWrapOwnership, kRead_GrIOType, false);
 
         REPORTER_ASSERT(reporter, tex);
     }
diff --git a/tests/skia_test.cpp b/tests/skia_test.cpp
index 99f9609..5d3169b 100644
--- a/tests/skia_test.cpp
+++ b/tests/skia_test.cpp
@@ -6,10 +6,11 @@
  */
 
 #include "CrashHandler.h"
+#include "GrContext.h"
+#include "GrContextFactory.h"
 #include "OverwriteLine.h"
 #include "PathOpsDebug.h"
 #include "Resources.h"
-#include "SkAtomics.h"
 #include "SkCommonFlags.h"
 #include "SkGraphics.h"
 #include "SkOSFile.h"
@@ -19,9 +20,7 @@
 #include "SkTemplates.h"
 #include "SkTime.h"
 #include "Test.h"
-
-#include "GrContext.h"
-#include "GrContextFactory.h"
+#include <atomic>
 
 using namespace skiatest;
 using namespace sk_gpu_test;
@@ -50,10 +49,8 @@
                  bool success,
                  SkMSec elapsed,
                  int testCount) {
-        const int done = 1 + sk_atomic_inc(&fDone);
-        for (int i = 0; i < testCount; ++i) {
-            sk_atomic_inc(&fTestCount);
-        }
+        const int done = ++fDone;
+        fTestCount += testCount;
         if (!success) {
             SkDebugf("\n---- %s FAILED", testName);
         }
@@ -68,15 +65,15 @@
                  testName);
     }
 
-    void reportFailure() { sk_atomic_inc(&fFailCount); }
+    void reportFailure() { fFailCount++; }
 
     int32_t testCount() { return fTestCount; }
     int32_t failCount() { return fFailCount; }
 
 private:
-    int32_t fDone;  // atomic
-    int32_t fTestCount;  // atomic
-    int32_t fFailCount;  // atomic
+    std::atomic<int32_t> fDone;
+    std::atomic<int32_t> fTestCount;
+    std::atomic<int32_t> fFailCount;
     const int fTotal;
 };
 
diff --git a/third_party/angle2/BUILD.gn b/third_party/angle2/BUILD.gn
index 8de5558..981acda 100644
--- a/third_party/angle2/BUILD.gn
+++ b/third_party/angle2/BUILD.gn
@@ -89,7 +89,8 @@
               angle_translator_essl_sources + angle_translator_glsl_sources +
               angle_translator_hlsl_sources + libangle_sources +
               libangle_common_sources + libangle_image_util_sources +
-              libglesv2_sources + libangle_gl_sources,
+              libglesv2_sources + libangle_gl_sources +
+              angle_system_utils_sources,
           ".",
           angle_root)
   if (is_win) {
@@ -101,21 +102,18 @@
       "Xi",
       "Xext",
     ]
-    sources +=
-        rebase_path(libangle_gl_glx_sources + libangle_common_linux_sources,
-                    ".",
-                    angle_root) +
-        [ "$angle_root/src/third_party/libXNVCtrl/NVCtrl.c" ]
+    sources += rebase_path(libangle_gl_glx_sources, ".", angle_root) +
+               [ "$angle_root/src/third_party/libXNVCtrl/NVCtrl.c" ]
   } else if (is_win) {
     defines += [
       # TODO: ANGLE_PRELOADED_D3DCOMPILER_MODULE_NAMES
     ]
-    sources += rebase_path(
-            libangle_gl_wgl_sources + libangle_d3d_shared_sources +
-                libangle_d3d9_sources + libangle_d3d11_sources +
-                libangle_d3d11_win32_sources + libangle_common_win_sources,
-            ".",
-            angle_root)
+    sources +=
+        rebase_path(libangle_gl_wgl_sources + libangle_d3d_shared_sources +
+                        libangle_d3d9_sources + libangle_d3d11_sources +
+                        libangle_d3d11_win32_sources,
+                    ".",
+                    angle_root)
     libs += [
       "d3d9.lib",
       "dxguid.lib",
diff --git a/third_party/icu/BUILD.gn b/third_party/icu/BUILD.gn
index 7233fcc..0b68772 100644
--- a/third_party/icu/BUILD.gn
+++ b/third_party/icu/BUILD.gn
@@ -15,9 +15,36 @@
     defines = [ "U_USING_ICU_NAMESPACE=0" ]
   }
 } else {
+  data_assembly = "$target_gen_dir/icudtl_dat.S"
+  data_dir = "../externals/icu/"
+  if (is_android) {
+    data_dir += "android"
+  } else if (is_ios) {
+    data_dir += "ios"
+  } else {
+    data_dir += "common"
+  }
+  action("make_data_assembly") {
+    script = "../externals/icu/scripts/make_data_assembly.py"
+    inputs = [
+      "$data_dir/icudtl.dat",
+    ]
+    outputs = [
+      "$data_assembly",
+    ]
+    args = [
+      rebase_path(inputs[0], root_build_dir),
+      rebase_path(data_assembly, root_build_dir),
+    ]
+    if (is_mac || is_ios) {
+      args += [ "--mac" ]
+    }
+  }
+
   third_party("icu") {
     public_include_dirs = [
       "../externals/icu/source/common",
+      "../externals/icu/source/i18n",
       ".",
     ]
     public_defines = [ "U_USING_ICU_NAMESPACE=0" ]
@@ -27,188 +54,698 @@
       "U_COMMON_IMPLEMENTATION",
       "U_STATIC_IMPLEMENTATION",
       "U_ENABLE_DYLOAD=0",
+      "U_I18N_IMPLEMENTATION",
     ]
     sources = [
       "../externals/icu/source/common/appendable.cpp",
       "../externals/icu/source/common/bmpset.cpp",
+      "../externals/icu/source/common/bmpset.h",
       "../externals/icu/source/common/brkeng.cpp",
+      "../externals/icu/source/common/brkeng.h",
       "../externals/icu/source/common/brkiter.cpp",
+      "../externals/icu/source/common/bytesinkutil.cpp",
+      "../externals/icu/source/common/bytesinkutil.h",
       "../externals/icu/source/common/bytestream.cpp",
       "../externals/icu/source/common/bytestrie.cpp",
       "../externals/icu/source/common/bytestriebuilder.cpp",
       "../externals/icu/source/common/bytestrieiterator.cpp",
       "../externals/icu/source/common/caniter.cpp",
+      "../externals/icu/source/common/characterproperties.cpp",
       "../externals/icu/source/common/chariter.cpp",
       "../externals/icu/source/common/charstr.cpp",
-      "../externals/icu/source/common/cmemory.c",
-      "../externals/icu/source/common/cstring.c",
-      "../externals/icu/source/common/cwchar.c",
+      "../externals/icu/source/common/charstr.h",
+      "../externals/icu/source/common/cmemory.cpp",
+      "../externals/icu/source/common/cmemory.h",
+      "../externals/icu/source/common/cpputils.h",
+      "../externals/icu/source/common/cstr.cpp",
+      "../externals/icu/source/common/cstr.h",
+      "../externals/icu/source/common/cstring.cpp",
+      "../externals/icu/source/common/cstring.h",
+      "../externals/icu/source/common/cwchar.cpp",
+      "../externals/icu/source/common/cwchar.h",
       "../externals/icu/source/common/dictbe.cpp",
+      "../externals/icu/source/common/dictbe.h",
       "../externals/icu/source/common/dictionarydata.cpp",
+      "../externals/icu/source/common/dictionarydata.h",
       "../externals/icu/source/common/dtintrv.cpp",
+      "../externals/icu/source/common/edits.cpp",
       "../externals/icu/source/common/errorcode.cpp",
       "../externals/icu/source/common/filteredbrk.cpp",
       "../externals/icu/source/common/filterednormalizer2.cpp",
-      "../externals/icu/source/common/icudataver.c",
+      "../externals/icu/source/common/hash.h",
+      "../externals/icu/source/common/icudataver.cpp",
       "../externals/icu/source/common/icuplug.cpp",
-      "../externals/icu/source/common/listformatter.cpp",
+      "../externals/icu/source/common/icuplugimp.h",
       "../externals/icu/source/common/loadednormalizer2impl.cpp",
+      "../externals/icu/source/common/localsvc.h",
       "../externals/icu/source/common/locavailable.cpp",
       "../externals/icu/source/common/locbased.cpp",
+      "../externals/icu/source/common/locbased.h",
       "../externals/icu/source/common/locdispnames.cpp",
+      "../externals/icu/source/common/locdspnm.cpp",
       "../externals/icu/source/common/locid.cpp",
       "../externals/icu/source/common/loclikely.cpp",
-      "../externals/icu/source/common/locmap.c",
+      "../externals/icu/source/common/locmap.cpp",
+      "../externals/icu/source/common/locmap.h",
       "../externals/icu/source/common/locresdata.cpp",
       "../externals/icu/source/common/locutil.cpp",
+      "../externals/icu/source/common/locutil.h",
+      "../externals/icu/source/common/messageimpl.h",
       "../externals/icu/source/common/messagepattern.cpp",
+      "../externals/icu/source/common/msvcres.h",
+      "../externals/icu/source/common/mutex.h",
+      "../externals/icu/source/common/norm2_nfc_data.h",
+      "../externals/icu/source/common/norm2allmodes.h",
       "../externals/icu/source/common/normalizer2.cpp",
       "../externals/icu/source/common/normalizer2impl.cpp",
+      "../externals/icu/source/common/normalizer2impl.h",
       "../externals/icu/source/common/normlzr.cpp",
       "../externals/icu/source/common/parsepos.cpp",
       "../externals/icu/source/common/patternprops.cpp",
+      "../externals/icu/source/common/patternprops.h",
       "../externals/icu/source/common/pluralmap.cpp",
+      "../externals/icu/source/common/pluralmap.h",
       "../externals/icu/source/common/propname.cpp",
-      "../externals/icu/source/common/propsvec.c",
+      "../externals/icu/source/common/propname.h",
+      "../externals/icu/source/common/propname_data.h",
+      "../externals/icu/source/common/propsvec.cpp",
+      "../externals/icu/source/common/propsvec.h",
       "../externals/icu/source/common/punycode.cpp",
+      "../externals/icu/source/common/punycode.h",
       "../externals/icu/source/common/putil.cpp",
+      "../externals/icu/source/common/putilimp.h",
       "../externals/icu/source/common/rbbi.cpp",
+      "../externals/icu/source/common/rbbi_cache.cpp",
+      "../externals/icu/source/common/rbbi_cache.h",
       "../externals/icu/source/common/rbbidata.cpp",
+      "../externals/icu/source/common/rbbidata.h",
       "../externals/icu/source/common/rbbinode.cpp",
+      "../externals/icu/source/common/rbbinode.h",
       "../externals/icu/source/common/rbbirb.cpp",
+      "../externals/icu/source/common/rbbirb.h",
+      "../externals/icu/source/common/rbbirpt.h",
       "../externals/icu/source/common/rbbiscan.cpp",
+      "../externals/icu/source/common/rbbiscan.h",
       "../externals/icu/source/common/rbbisetb.cpp",
+      "../externals/icu/source/common/rbbisetb.h",
       "../externals/icu/source/common/rbbistbl.cpp",
       "../externals/icu/source/common/rbbitblb.cpp",
+      "../externals/icu/source/common/rbbitblb.h",
       "../externals/icu/source/common/resbund.cpp",
       "../externals/icu/source/common/resbund_cnv.cpp",
       "../externals/icu/source/common/resource.cpp",
+      "../externals/icu/source/common/resource.h",
       "../externals/icu/source/common/ruleiter.cpp",
+      "../externals/icu/source/common/ruleiter.h",
       "../externals/icu/source/common/schriter.cpp",
       "../externals/icu/source/common/serv.cpp",
+      "../externals/icu/source/common/serv.h",
       "../externals/icu/source/common/servlk.cpp",
       "../externals/icu/source/common/servlkf.cpp",
+      "../externals/icu/source/common/servloc.h",
       "../externals/icu/source/common/servls.cpp",
       "../externals/icu/source/common/servnotf.cpp",
+      "../externals/icu/source/common/servnotf.h",
       "../externals/icu/source/common/servrbf.cpp",
       "../externals/icu/source/common/servslkf.cpp",
       "../externals/icu/source/common/sharedobject.cpp",
-      "../externals/icu/source/common/simplepatternformatter.cpp",
+      "../externals/icu/source/common/sharedobject.h",
+      "../externals/icu/source/common/simpleformatter.cpp",
+      "../externals/icu/source/common/sprpimpl.h",
+      "../externals/icu/source/common/static_unicode_sets.cpp",
+      "../externals/icu/source/common/static_unicode_sets.h",
       "../externals/icu/source/common/stringpiece.cpp",
       "../externals/icu/source/common/stringtriebuilder.cpp",
-      "../externals/icu/source/common/uarrsort.c",
-      "../externals/icu/source/common/ubidi.c",
-      "../externals/icu/source/common/ubidi_props.c",
-      "../externals/icu/source/common/ubidiln.c",
-      "../externals/icu/source/common/ubidiwrt.c",
+      "../externals/icu/source/common/uarrsort.cpp",
+      "../externals/icu/source/common/uarrsort.h",
+      "../externals/icu/source/common/uassert.h",
+      "../externals/icu/source/common/ubidi.cpp",
+      "../externals/icu/source/common/ubidi_props.cpp",
+      "../externals/icu/source/common/ubidi_props.h",
+      "../externals/icu/source/common/ubidi_props_data.h",
+      "../externals/icu/source/common/ubidiimp.h",
+      "../externals/icu/source/common/ubidiln.cpp",
+      "../externals/icu/source/common/ubiditransform.cpp",
+      "../externals/icu/source/common/ubidiwrt.cpp",
       "../externals/icu/source/common/ubrk.cpp",
+      "../externals/icu/source/common/ubrkimpl.h",
       "../externals/icu/source/common/ucase.cpp",
+      "../externals/icu/source/common/ucase.h",
+      "../externals/icu/source/common/ucase_props_data.h",
       "../externals/icu/source/common/ucasemap.cpp",
+      "../externals/icu/source/common/ucasemap_imp.h",
       "../externals/icu/source/common/ucasemap_titlecase_brkiter.cpp",
-      "../externals/icu/source/common/ucat.c",
-      "../externals/icu/source/common/uchar.c",
+      "../externals/icu/source/common/ucat.cpp",
+      "../externals/icu/source/common/uchar.cpp",
+      "../externals/icu/source/common/uchar_props_data.h",
       "../externals/icu/source/common/ucharstrie.cpp",
       "../externals/icu/source/common/ucharstriebuilder.cpp",
       "../externals/icu/source/common/ucharstrieiterator.cpp",
       "../externals/icu/source/common/uchriter.cpp",
+      "../externals/icu/source/common/ucln.h",
       "../externals/icu/source/common/ucln_cmn.cpp",
-      "../externals/icu/source/common/ucmndata.c",
-      "../externals/icu/source/common/ucnv.c",
+      "../externals/icu/source/common/ucln_cmn.h",
+      "../externals/icu/source/common/ucln_imp.h",
+      "../externals/icu/source/common/ucmndata.cpp",
+      "../externals/icu/source/common/ucmndata.h",
+      "../externals/icu/source/common/ucnv.cpp",
       "../externals/icu/source/common/ucnv2022.cpp",
       "../externals/icu/source/common/ucnv_bld.cpp",
-      "../externals/icu/source/common/ucnv_cb.c",
-      "../externals/icu/source/common/ucnv_cnv.c",
-      "../externals/icu/source/common/ucnv_ct.c",
-      "../externals/icu/source/common/ucnv_err.c",
+      "../externals/icu/source/common/ucnv_bld.h",
+      "../externals/icu/source/common/ucnv_cb.cpp",
+      "../externals/icu/source/common/ucnv_cnv.cpp",
+      "../externals/icu/source/common/ucnv_cnv.h",
+      "../externals/icu/source/common/ucnv_ct.cpp",
+      "../externals/icu/source/common/ucnv_err.cpp",
       "../externals/icu/source/common/ucnv_ext.cpp",
+      "../externals/icu/source/common/ucnv_ext.h",
+      "../externals/icu/source/common/ucnv_imp.h",
       "../externals/icu/source/common/ucnv_io.cpp",
-      "../externals/icu/source/common/ucnv_lmb.c",
-      "../externals/icu/source/common/ucnv_set.c",
-      "../externals/icu/source/common/ucnv_u16.c",
-      "../externals/icu/source/common/ucnv_u32.c",
-      "../externals/icu/source/common/ucnv_u7.c",
-      "../externals/icu/source/common/ucnv_u8.c",
+      "../externals/icu/source/common/ucnv_io.h",
+      "../externals/icu/source/common/ucnv_lmb.cpp",
+      "../externals/icu/source/common/ucnv_set.cpp",
+      "../externals/icu/source/common/ucnv_u16.cpp",
+      "../externals/icu/source/common/ucnv_u32.cpp",
+      "../externals/icu/source/common/ucnv_u7.cpp",
+      "../externals/icu/source/common/ucnv_u8.cpp",
       "../externals/icu/source/common/ucnvbocu.cpp",
-      "../externals/icu/source/common/ucnvdisp.c",
-      "../externals/icu/source/common/ucnvhz.c",
-      "../externals/icu/source/common/ucnvisci.c",
-      "../externals/icu/source/common/ucnvlat1.c",
+      "../externals/icu/source/common/ucnvdisp.cpp",
+      "../externals/icu/source/common/ucnvhz.cpp",
+      "../externals/icu/source/common/ucnvisci.cpp",
+      "../externals/icu/source/common/ucnvlat1.cpp",
       "../externals/icu/source/common/ucnvmbcs.cpp",
-      "../externals/icu/source/common/ucnvscsu.c",
+      "../externals/icu/source/common/ucnvmbcs.h",
+      "../externals/icu/source/common/ucnvscsu.cpp",
       "../externals/icu/source/common/ucnvsel.cpp",
+      "../externals/icu/source/common/ucol_data.h",
       "../externals/icu/source/common/ucol_swp.cpp",
+      "../externals/icu/source/common/ucol_swp.h",
+      "../externals/icu/source/common/ucptrie.cpp",
+      "../externals/icu/source/common/ucptrie_impl.h",
+      "../externals/icu/source/common/ucurr.cpp",
+      "../externals/icu/source/common/ucurrimp.h",
       "../externals/icu/source/common/udata.cpp",
-      "../externals/icu/source/common/udatamem.c",
-      "../externals/icu/source/common/udataswp.c",
-      "../externals/icu/source/common/uenum.c",
-      "../externals/icu/source/common/uhash.c",
+      "../externals/icu/source/common/udatamem.cpp",
+      "../externals/icu/source/common/udatamem.h",
+      "../externals/icu/source/common/udataswp.cpp",
+      "../externals/icu/source/common/udataswp.h",
+      "../externals/icu/source/common/uelement.h",
+      "../externals/icu/source/common/uenum.cpp",
+      "../externals/icu/source/common/uenumimp.h",
+      "../externals/icu/source/common/uhash.cpp",
+      "../externals/icu/source/common/uhash.h",
       "../externals/icu/source/common/uhash_us.cpp",
       "../externals/icu/source/common/uidna.cpp",
       "../externals/icu/source/common/uinit.cpp",
-      "../externals/icu/source/common/uinvchar.c",
+      "../externals/icu/source/common/uinvchar.cpp",
+      "../externals/icu/source/common/uinvchar.h",
       "../externals/icu/source/common/uiter.cpp",
-      "../externals/icu/source/common/ulist.c",
-      "../externals/icu/source/common/ulistformatter.cpp",
+      "../externals/icu/source/common/ulayout_props_data.h",
+      "../externals/icu/source/common/ulist.cpp",
+      "../externals/icu/source/common/ulist.h",
       "../externals/icu/source/common/uloc.cpp",
       "../externals/icu/source/common/uloc_keytype.cpp",
-      "../externals/icu/source/common/uloc_tag.c",
-      "../externals/icu/source/common/umapfile.c",
-      "../externals/icu/source/common/umath.c",
+      "../externals/icu/source/common/uloc_tag.cpp",
+      "../externals/icu/source/common/ulocimp.h",
+      "../externals/icu/source/common/umapfile.cpp",
+      "../externals/icu/source/common/umapfile.h",
+      "../externals/icu/source/common/umath.cpp",
+      "../externals/icu/source/common/umutablecptrie.cpp",
       "../externals/icu/source/common/umutex.cpp",
+      "../externals/icu/source/common/umutex.h",
       "../externals/icu/source/common/unames.cpp",
       "../externals/icu/source/common/unifiedcache.cpp",
+      "../externals/icu/source/common/unifiedcache.h",
       "../externals/icu/source/common/unifilt.cpp",
       "../externals/icu/source/common/unifunct.cpp",
       "../externals/icu/source/common/uniset.cpp",
       "../externals/icu/source/common/uniset_closure.cpp",
       "../externals/icu/source/common/uniset_props.cpp",
       "../externals/icu/source/common/unisetspan.cpp",
+      "../externals/icu/source/common/unisetspan.h",
       "../externals/icu/source/common/unistr.cpp",
       "../externals/icu/source/common/unistr_case.cpp",
       "../externals/icu/source/common/unistr_case_locale.cpp",
       "../externals/icu/source/common/unistr_cnv.cpp",
       "../externals/icu/source/common/unistr_props.cpp",
       "../externals/icu/source/common/unistr_titlecase_brkiter.cpp",
+      "../externals/icu/source/common/unistrappender.h",
       "../externals/icu/source/common/unorm.cpp",
       "../externals/icu/source/common/unormcmp.cpp",
+      "../externals/icu/source/common/unormimp.h",
       "../externals/icu/source/common/uobject.cpp",
+      "../externals/icu/source/common/uposixdefs.h",
       "../externals/icu/source/common/uprops.cpp",
-      "../externals/icu/source/common/ures_cnv.c",
+      "../externals/icu/source/common/uprops.h",
+      "../externals/icu/source/common/ures_cnv.cpp",
       "../externals/icu/source/common/uresbund.cpp",
       "../externals/icu/source/common/uresdata.cpp",
-      "../externals/icu/source/common/uresource.cpp",
-      "../externals/icu/source/common/usc_impl.c",
-      "../externals/icu/source/common/uscript.c",
+      "../externals/icu/source/common/uresdata.h",
+      "../externals/icu/source/common/uresimp.h",
+      "../externals/icu/source/common/ureslocs.h",
+      "../externals/icu/source/common/usc_impl.cpp",
+      "../externals/icu/source/common/usc_impl.h",
+      "../externals/icu/source/common/uscript.cpp",
       "../externals/icu/source/common/uscript_props.cpp",
       "../externals/icu/source/common/uset.cpp",
+      "../externals/icu/source/common/uset_imp.h",
       "../externals/icu/source/common/uset_props.cpp",
       "../externals/icu/source/common/usetiter.cpp",
       "../externals/icu/source/common/ushape.cpp",
       "../externals/icu/source/common/usprep.cpp",
       "../externals/icu/source/common/ustack.cpp",
       "../externals/icu/source/common/ustr_cnv.cpp",
+      "../externals/icu/source/common/ustr_cnv.h",
+      "../externals/icu/source/common/ustr_imp.h",
       "../externals/icu/source/common/ustr_titlecase_brkiter.cpp",
       "../externals/icu/source/common/ustr_wcs.cpp",
       "../externals/icu/source/common/ustrcase.cpp",
       "../externals/icu/source/common/ustrcase_locale.cpp",
       "../externals/icu/source/common/ustrenum.cpp",
-      "../externals/icu/source/common/ustrfmt.c",
+      "../externals/icu/source/common/ustrenum.h",
+      "../externals/icu/source/common/ustrfmt.cpp",
+      "../externals/icu/source/common/ustrfmt.h",
       "../externals/icu/source/common/ustring.cpp",
       "../externals/icu/source/common/ustrtrns.cpp",
       "../externals/icu/source/common/utext.cpp",
-      "../externals/icu/source/common/utf_impl.c",
+      "../externals/icu/source/common/utf_impl.cpp",
       "../externals/icu/source/common/util.cpp",
+      "../externals/icu/source/common/util.h",
       "../externals/icu/source/common/util_props.cpp",
-      "../externals/icu/source/common/utrace.c",
+      "../externals/icu/source/common/utrace.cpp",
+      "../externals/icu/source/common/utracimp.h",
       "../externals/icu/source/common/utrie.cpp",
+      "../externals/icu/source/common/utrie.h",
       "../externals/icu/source/common/utrie2.cpp",
+      "../externals/icu/source/common/utrie2.h",
       "../externals/icu/source/common/utrie2_builder.cpp",
+      "../externals/icu/source/common/utrie2_impl.h",
+      "../externals/icu/source/common/utrie_swap.cpp",
       "../externals/icu/source/common/uts46.cpp",
-      "../externals/icu/source/common/utypes.c",
+      "../externals/icu/source/common/utypeinfo.h",
+      "../externals/icu/source/common/utypes.cpp",
       "../externals/icu/source/common/uvector.cpp",
+      "../externals/icu/source/common/uvector.h",
       "../externals/icu/source/common/uvectr32.cpp",
+      "../externals/icu/source/common/uvectr32.h",
       "../externals/icu/source/common/uvectr64.cpp",
-      "../externals/icu/source/common/wintz.c",
+      "../externals/icu/source/common/uvectr64.h",
+      "../externals/icu/source/common/wintz.cpp",
+      "../externals/icu/source/common/wintz.h",
+      "../externals/icu/source/i18n/alphaindex.cpp",
+      "../externals/icu/source/i18n/anytrans.cpp",
+      "../externals/icu/source/i18n/anytrans.h",
+      "../externals/icu/source/i18n/astro.cpp",
+      "../externals/icu/source/i18n/astro.h",
+      "../externals/icu/source/i18n/basictz.cpp",
+      "../externals/icu/source/i18n/bocsu.cpp",
+      "../externals/icu/source/i18n/bocsu.h",
+      "../externals/icu/source/i18n/brktrans.cpp",
+      "../externals/icu/source/i18n/brktrans.h",
+      "../externals/icu/source/i18n/buddhcal.cpp",
+      "../externals/icu/source/i18n/buddhcal.h",
+      "../externals/icu/source/i18n/calendar.cpp",
+      "../externals/icu/source/i18n/casetrn.cpp",
+      "../externals/icu/source/i18n/casetrn.h",
+      "../externals/icu/source/i18n/cecal.cpp",
+      "../externals/icu/source/i18n/cecal.h",
+      "../externals/icu/source/i18n/chnsecal.cpp",
+      "../externals/icu/source/i18n/chnsecal.h",
+      "../externals/icu/source/i18n/choicfmt.cpp",
+      "../externals/icu/source/i18n/coleitr.cpp",
+      "../externals/icu/source/i18n/coll.cpp",
+      "../externals/icu/source/i18n/collation.cpp",
+      "../externals/icu/source/i18n/collation.h",
+      "../externals/icu/source/i18n/collationbuilder.cpp",
+      "../externals/icu/source/i18n/collationbuilder.h",
+      "../externals/icu/source/i18n/collationcompare.cpp",
+      "../externals/icu/source/i18n/collationcompare.h",
+      "../externals/icu/source/i18n/collationdata.cpp",
+      "../externals/icu/source/i18n/collationdata.h",
+      "../externals/icu/source/i18n/collationdatabuilder.cpp",
+      "../externals/icu/source/i18n/collationdatabuilder.h",
+      "../externals/icu/source/i18n/collationdatareader.cpp",
+      "../externals/icu/source/i18n/collationdatareader.h",
+      "../externals/icu/source/i18n/collationdatawriter.cpp",
+      "../externals/icu/source/i18n/collationdatawriter.h",
+      "../externals/icu/source/i18n/collationfastlatin.cpp",
+      "../externals/icu/source/i18n/collationfastlatin.h",
+      "../externals/icu/source/i18n/collationfastlatinbuilder.cpp",
+      "../externals/icu/source/i18n/collationfastlatinbuilder.h",
+      "../externals/icu/source/i18n/collationfcd.cpp",
+      "../externals/icu/source/i18n/collationfcd.h",
+      "../externals/icu/source/i18n/collationiterator.cpp",
+      "../externals/icu/source/i18n/collationiterator.h",
+      "../externals/icu/source/i18n/collationkeys.cpp",
+      "../externals/icu/source/i18n/collationkeys.h",
+      "../externals/icu/source/i18n/collationroot.cpp",
+      "../externals/icu/source/i18n/collationroot.h",
+      "../externals/icu/source/i18n/collationrootelements.cpp",
+      "../externals/icu/source/i18n/collationrootelements.h",
+      "../externals/icu/source/i18n/collationruleparser.cpp",
+      "../externals/icu/source/i18n/collationruleparser.h",
+      "../externals/icu/source/i18n/collationsets.cpp",
+      "../externals/icu/source/i18n/collationsets.h",
+      "../externals/icu/source/i18n/collationsettings.cpp",
+      "../externals/icu/source/i18n/collationsettings.h",
+      "../externals/icu/source/i18n/collationtailoring.cpp",
+      "../externals/icu/source/i18n/collationtailoring.h",
+      "../externals/icu/source/i18n/collationweights.cpp",
+      "../externals/icu/source/i18n/collationweights.h",
+      "../externals/icu/source/i18n/collunsafe.h",
+      "../externals/icu/source/i18n/compactdecimalformat.cpp",
+      "../externals/icu/source/i18n/coptccal.cpp",
+      "../externals/icu/source/i18n/coptccal.h",
+      "../externals/icu/source/i18n/cpdtrans.cpp",
+      "../externals/icu/source/i18n/cpdtrans.h",
+      "../externals/icu/source/i18n/csdetect.cpp",
+      "../externals/icu/source/i18n/csdetect.h",
+      "../externals/icu/source/i18n/csmatch.cpp",
+      "../externals/icu/source/i18n/csmatch.h",
+      "../externals/icu/source/i18n/csr2022.cpp",
+      "../externals/icu/source/i18n/csr2022.h",
+      "../externals/icu/source/i18n/csrecog.cpp",
+      "../externals/icu/source/i18n/csrecog.h",
+      "../externals/icu/source/i18n/csrmbcs.cpp",
+      "../externals/icu/source/i18n/csrmbcs.h",
+      "../externals/icu/source/i18n/csrsbcs.cpp",
+      "../externals/icu/source/i18n/csrsbcs.h",
+      "../externals/icu/source/i18n/csrucode.cpp",
+      "../externals/icu/source/i18n/csrucode.h",
+      "../externals/icu/source/i18n/csrutf8.cpp",
+      "../externals/icu/source/i18n/csrutf8.h",
+      "../externals/icu/source/i18n/curramt.cpp",
+      "../externals/icu/source/i18n/currfmt.cpp",
+      "../externals/icu/source/i18n/currfmt.h",
+      "../externals/icu/source/i18n/currpinf.cpp",
+      "../externals/icu/source/i18n/currunit.cpp",
+      "../externals/icu/source/i18n/dangical.cpp",
+      "../externals/icu/source/i18n/dangical.h",
+      "../externals/icu/source/i18n/datefmt.cpp",
+      "../externals/icu/source/i18n/dayperiodrules.cpp",
+      "../externals/icu/source/i18n/dayperiodrules.h",
+      "../externals/icu/source/i18n/dcfmtsym.cpp",
+      "../externals/icu/source/i18n/decContext.cpp",
+      "../externals/icu/source/i18n/decContext.h",
+      "../externals/icu/source/i18n/decNumber.cpp",
+      "../externals/icu/source/i18n/decNumber.h",
+      "../externals/icu/source/i18n/decNumberLocal.h",
+      "../externals/icu/source/i18n/decimfmt.cpp",
+      "../externals/icu/source/i18n/double-conversion-bignum-dtoa.cpp",
+      "../externals/icu/source/i18n/double-conversion-bignum-dtoa.h",
+      "../externals/icu/source/i18n/double-conversion-bignum.cpp",
+      "../externals/icu/source/i18n/double-conversion-bignum.h",
+      "../externals/icu/source/i18n/double-conversion-cached-powers.cpp",
+      "../externals/icu/source/i18n/double-conversion-cached-powers.h",
+      "../externals/icu/source/i18n/double-conversion-diy-fp.cpp",
+      "../externals/icu/source/i18n/double-conversion-diy-fp.h",
+      "../externals/icu/source/i18n/double-conversion-fast-dtoa.cpp",
+      "../externals/icu/source/i18n/double-conversion-fast-dtoa.h",
+      "../externals/icu/source/i18n/double-conversion-ieee.h",
+      "../externals/icu/source/i18n/double-conversion-strtod.cpp",
+      "../externals/icu/source/i18n/double-conversion-strtod.h",
+      "../externals/icu/source/i18n/double-conversion-utils.h",
+      "../externals/icu/source/i18n/double-conversion.cpp",
+      "../externals/icu/source/i18n/double-conversion.h",
+      "../externals/icu/source/i18n/dt_impl.h",
+      "../externals/icu/source/i18n/dtfmtsym.cpp",
+      "../externals/icu/source/i18n/dtitv_impl.h",
+      "../externals/icu/source/i18n/dtitvfmt.cpp",
+      "../externals/icu/source/i18n/dtitvinf.cpp",
+      "../externals/icu/source/i18n/dtptngen.cpp",
+      "../externals/icu/source/i18n/dtptngen_impl.h",
+      "../externals/icu/source/i18n/dtrule.cpp",
+      "../externals/icu/source/i18n/erarules.cpp",
+      "../externals/icu/source/i18n/erarules.h",
+      "../externals/icu/source/i18n/esctrn.cpp",
+      "../externals/icu/source/i18n/esctrn.h",
+      "../externals/icu/source/i18n/ethpccal.cpp",
+      "../externals/icu/source/i18n/ethpccal.h",
+      "../externals/icu/source/i18n/fmtable.cpp",
+      "../externals/icu/source/i18n/fmtable_cnv.cpp",
+      "../externals/icu/source/i18n/fmtableimp.h",
+      "../externals/icu/source/i18n/format.cpp",
+      "../externals/icu/source/i18n/fphdlimp.cpp",
+      "../externals/icu/source/i18n/fphdlimp.h",
+      "../externals/icu/source/i18n/fpositer.cpp",
+      "../externals/icu/source/i18n/funcrepl.cpp",
+      "../externals/icu/source/i18n/funcrepl.h",
+      "../externals/icu/source/i18n/gender.cpp",
+      "../externals/icu/source/i18n/gregocal.cpp",
+      "../externals/icu/source/i18n/gregoimp.cpp",
+      "../externals/icu/source/i18n/gregoimp.h",
+      "../externals/icu/source/i18n/hebrwcal.cpp",
+      "../externals/icu/source/i18n/hebrwcal.h",
+      "../externals/icu/source/i18n/indiancal.cpp",
+      "../externals/icu/source/i18n/indiancal.h",
+      "../externals/icu/source/i18n/inputext.cpp",
+      "../externals/icu/source/i18n/inputext.h",
+      "../externals/icu/source/i18n/islamcal.cpp",
+      "../externals/icu/source/i18n/islamcal.h",
+      "../externals/icu/source/i18n/japancal.cpp",
+      "../externals/icu/source/i18n/japancal.h",
+      "../externals/icu/source/i18n/listformatter.cpp",
+      "../externals/icu/source/i18n/measfmt.cpp",
+      "../externals/icu/source/i18n/measunit.cpp",
+      "../externals/icu/source/i18n/measure.cpp",
+      "../externals/icu/source/i18n/msgfmt.cpp",
+      "../externals/icu/source/i18n/msgfmt_impl.h",
+      "../externals/icu/source/i18n/name2uni.cpp",
+      "../externals/icu/source/i18n/name2uni.h",
+      "../externals/icu/source/i18n/nfrlist.h",
+      "../externals/icu/source/i18n/nfrs.cpp",
+      "../externals/icu/source/i18n/nfrs.h",
+      "../externals/icu/source/i18n/nfrule.cpp",
+      "../externals/icu/source/i18n/nfrule.h",
+      "../externals/icu/source/i18n/nfsubs.cpp",
+      "../externals/icu/source/i18n/nfsubs.h",
+      "../externals/icu/source/i18n/nortrans.cpp",
+      "../externals/icu/source/i18n/nortrans.h",
+      "../externals/icu/source/i18n/nounit.cpp",
+      "../externals/icu/source/i18n/nultrans.cpp",
+      "../externals/icu/source/i18n/nultrans.h",
+      "../externals/icu/source/i18n/number_affixutils.cpp",
+      "../externals/icu/source/i18n/number_affixutils.h",
+      "../externals/icu/source/i18n/number_asformat.cpp",
+      "../externals/icu/source/i18n/number_asformat.h",
+      "../externals/icu/source/i18n/number_capi.cpp",
+      "../externals/icu/source/i18n/number_compact.cpp",
+      "../externals/icu/source/i18n/number_compact.h",
+      "../externals/icu/source/i18n/number_currencysymbols.cpp",
+      "../externals/icu/source/i18n/number_currencysymbols.h",
+      "../externals/icu/source/i18n/number_decimalquantity.cpp",
+      "../externals/icu/source/i18n/number_decimalquantity.h",
+      "../externals/icu/source/i18n/number_decimfmtprops.cpp",
+      "../externals/icu/source/i18n/number_decimfmtprops.h",
+      "../externals/icu/source/i18n/number_decnum.h",
+      "../externals/icu/source/i18n/number_fluent.cpp",
+      "../externals/icu/source/i18n/number_formatimpl.cpp",
+      "../externals/icu/source/i18n/number_formatimpl.h",
+      "../externals/icu/source/i18n/number_grouping.cpp",
+      "../externals/icu/source/i18n/number_integerwidth.cpp",
+      "../externals/icu/source/i18n/number_longnames.cpp",
+      "../externals/icu/source/i18n/number_longnames.h",
+      "../externals/icu/source/i18n/number_mapper.cpp",
+      "../externals/icu/source/i18n/number_mapper.h",
+      "../externals/icu/source/i18n/number_microprops.h",
+      "../externals/icu/source/i18n/number_modifiers.cpp",
+      "../externals/icu/source/i18n/number_modifiers.h",
+      "../externals/icu/source/i18n/number_multiplier.cpp",
+      "../externals/icu/source/i18n/number_multiplier.h",
+      "../externals/icu/source/i18n/number_notation.cpp",
+      "../externals/icu/source/i18n/number_padding.cpp",
+      "../externals/icu/source/i18n/number_patternmodifier.cpp",
+      "../externals/icu/source/i18n/number_patternmodifier.h",
+      "../externals/icu/source/i18n/number_patternstring.cpp",
+      "../externals/icu/source/i18n/number_patternstring.h",
+      "../externals/icu/source/i18n/number_rounding.cpp",
+      "../externals/icu/source/i18n/number_roundingutils.h",
+      "../externals/icu/source/i18n/number_scientific.cpp",
+      "../externals/icu/source/i18n/number_scientific.h",
+      "../externals/icu/source/i18n/number_skeletons.cpp",
+      "../externals/icu/source/i18n/number_skeletons.h",
+      "../externals/icu/source/i18n/number_stringbuilder.cpp",
+      "../externals/icu/source/i18n/number_stringbuilder.h",
+      "../externals/icu/source/i18n/number_types.h",
+      "../externals/icu/source/i18n/number_utils.cpp",
+      "../externals/icu/source/i18n/number_utils.h",
+      "../externals/icu/source/i18n/number_utypes.h",
+      "../externals/icu/source/i18n/numfmt.cpp",
+      "../externals/icu/source/i18n/numparse_affixes.cpp",
+      "../externals/icu/source/i18n/numparse_affixes.h",
+      "../externals/icu/source/i18n/numparse_compositions.cpp",
+      "../externals/icu/source/i18n/numparse_compositions.h",
+      "../externals/icu/source/i18n/numparse_currency.cpp",
+      "../externals/icu/source/i18n/numparse_currency.h",
+      "../externals/icu/source/i18n/numparse_decimal.cpp",
+      "../externals/icu/source/i18n/numparse_decimal.h",
+      "../externals/icu/source/i18n/numparse_impl.cpp",
+      "../externals/icu/source/i18n/numparse_impl.h",
+      "../externals/icu/source/i18n/numparse_parsednumber.cpp",
+      "../externals/icu/source/i18n/numparse_scientific.cpp",
+      "../externals/icu/source/i18n/numparse_scientific.h",
+      "../externals/icu/source/i18n/numparse_stringsegment.cpp",
+      "../externals/icu/source/i18n/numparse_stringsegment.h",
+      "../externals/icu/source/i18n/numparse_symbols.cpp",
+      "../externals/icu/source/i18n/numparse_symbols.h",
+      "../externals/icu/source/i18n/numparse_types.h",
+      "../externals/icu/source/i18n/numparse_utils.h",
+      "../externals/icu/source/i18n/numparse_validators.cpp",
+      "../externals/icu/source/i18n/numparse_validators.h",
+      "../externals/icu/source/i18n/numrange_fluent.cpp",
+      "../externals/icu/source/i18n/numrange_impl.cpp",
+      "../externals/icu/source/i18n/numrange_impl.h",
+      "../externals/icu/source/i18n/numsys.cpp",
+      "../externals/icu/source/i18n/numsys_impl.h",
+      "../externals/icu/source/i18n/olsontz.cpp",
+      "../externals/icu/source/i18n/olsontz.h",
+      "../externals/icu/source/i18n/persncal.cpp",
+      "../externals/icu/source/i18n/persncal.h",
+      "../externals/icu/source/i18n/plurfmt.cpp",
+      "../externals/icu/source/i18n/plurrule.cpp",
+      "../externals/icu/source/i18n/plurrule_impl.h",
+      "../externals/icu/source/i18n/quant.cpp",
+      "../externals/icu/source/i18n/quant.h",
+      "../externals/icu/source/i18n/quantityformatter.cpp",
+      "../externals/icu/source/i18n/quantityformatter.h",
+      "../externals/icu/source/i18n/rbnf.cpp",
+      "../externals/icu/source/i18n/rbt.cpp",
+      "../externals/icu/source/i18n/rbt.h",
+      "../externals/icu/source/i18n/rbt_data.cpp",
+      "../externals/icu/source/i18n/rbt_data.h",
+      "../externals/icu/source/i18n/rbt_pars.cpp",
+      "../externals/icu/source/i18n/rbt_pars.h",
+      "../externals/icu/source/i18n/rbt_rule.cpp",
+      "../externals/icu/source/i18n/rbt_rule.h",
+      "../externals/icu/source/i18n/rbt_set.cpp",
+      "../externals/icu/source/i18n/rbt_set.h",
+      "../externals/icu/source/i18n/rbtz.cpp",
+      "../externals/icu/source/i18n/regexcmp.cpp",
+      "../externals/icu/source/i18n/regexcmp.h",
+      "../externals/icu/source/i18n/regexcst.h",
+      "../externals/icu/source/i18n/regeximp.cpp",
+      "../externals/icu/source/i18n/regeximp.h",
+      "../externals/icu/source/i18n/regexst.cpp",
+      "../externals/icu/source/i18n/regexst.h",
+      "../externals/icu/source/i18n/regextxt.cpp",
+      "../externals/icu/source/i18n/regextxt.h",
+      "../externals/icu/source/i18n/region.cpp",
+      "../externals/icu/source/i18n/region_impl.h",
+      "../externals/icu/source/i18n/reldatefmt.cpp",
+      "../externals/icu/source/i18n/reldtfmt.cpp",
+      "../externals/icu/source/i18n/reldtfmt.h",
+      "../externals/icu/source/i18n/rematch.cpp",
+      "../externals/icu/source/i18n/remtrans.cpp",
+      "../externals/icu/source/i18n/remtrans.h",
+      "../externals/icu/source/i18n/repattrn.cpp",
+      "../externals/icu/source/i18n/rulebasedcollator.cpp",
+      "../externals/icu/source/i18n/scientificnumberformatter.cpp",
+      "../externals/icu/source/i18n/scriptset.cpp",
+      "../externals/icu/source/i18n/scriptset.h",
+      "../externals/icu/source/i18n/search.cpp",
+      "../externals/icu/source/i18n/selfmt.cpp",
+      "../externals/icu/source/i18n/selfmtimpl.h",
+      "../externals/icu/source/i18n/sharedbreakiterator.cpp",
+      "../externals/icu/source/i18n/sharedbreakiterator.h",
+      "../externals/icu/source/i18n/sharedcalendar.h",
+      "../externals/icu/source/i18n/shareddateformatsymbols.h",
+      "../externals/icu/source/i18n/sharednumberformat.h",
+      "../externals/icu/source/i18n/sharedpluralrules.h",
+      "../externals/icu/source/i18n/simpletz.cpp",
+      "../externals/icu/source/i18n/smpdtfmt.cpp",
+      "../externals/icu/source/i18n/smpdtfst.cpp",
+      "../externals/icu/source/i18n/smpdtfst.h",
+      "../externals/icu/source/i18n/sortkey.cpp",
+      "../externals/icu/source/i18n/standardplural.cpp",
+      "../externals/icu/source/i18n/standardplural.h",
+      "../externals/icu/source/i18n/strmatch.cpp",
+      "../externals/icu/source/i18n/strmatch.h",
+      "../externals/icu/source/i18n/strrepl.cpp",
+      "../externals/icu/source/i18n/strrepl.h",
+      "../externals/icu/source/i18n/stsearch.cpp",
+      "../externals/icu/source/i18n/taiwncal.cpp",
+      "../externals/icu/source/i18n/taiwncal.h",
+      "../externals/icu/source/i18n/timezone.cpp",
+      "../externals/icu/source/i18n/titletrn.cpp",
+      "../externals/icu/source/i18n/titletrn.h",
+      "../externals/icu/source/i18n/tmunit.cpp",
+      "../externals/icu/source/i18n/tmutamt.cpp",
+      "../externals/icu/source/i18n/tmutfmt.cpp",
+      "../externals/icu/source/i18n/tolowtrn.cpp",
+      "../externals/icu/source/i18n/tolowtrn.h",
+      "../externals/icu/source/i18n/toupptrn.cpp",
+      "../externals/icu/source/i18n/toupptrn.h",
+      "../externals/icu/source/i18n/translit.cpp",
+      "../externals/icu/source/i18n/transreg.cpp",
+      "../externals/icu/source/i18n/transreg.h",
+      "../externals/icu/source/i18n/tridpars.cpp",
+      "../externals/icu/source/i18n/tridpars.h",
+      "../externals/icu/source/i18n/tzfmt.cpp",
+      "../externals/icu/source/i18n/tzgnames.cpp",
+      "../externals/icu/source/i18n/tzgnames.h",
+      "../externals/icu/source/i18n/tznames.cpp",
+      "../externals/icu/source/i18n/tznames_impl.cpp",
+      "../externals/icu/source/i18n/tznames_impl.h",
+      "../externals/icu/source/i18n/tzrule.cpp",
+      "../externals/icu/source/i18n/tztrans.cpp",
+      "../externals/icu/source/i18n/ucal.cpp",
+      "../externals/icu/source/i18n/ucln_in.cpp",
+      "../externals/icu/source/i18n/ucln_in.h",
+      "../externals/icu/source/i18n/ucol.cpp",
+      "../externals/icu/source/i18n/ucol_imp.h",
+      "../externals/icu/source/i18n/ucol_res.cpp",
+      "../externals/icu/source/i18n/ucol_sit.cpp",
+      "../externals/icu/source/i18n/ucoleitr.cpp",
+      "../externals/icu/source/i18n/ucsdet.cpp",
+      "../externals/icu/source/i18n/udat.cpp",
+      "../externals/icu/source/i18n/udateintervalformat.cpp",
+      "../externals/icu/source/i18n/udatpg.cpp",
+      "../externals/icu/source/i18n/ufieldpositer.cpp",
+      "../externals/icu/source/i18n/uitercollationiterator.cpp",
+      "../externals/icu/source/i18n/uitercollationiterator.h",
+      "../externals/icu/source/i18n/ulistformatter.cpp",
+      "../externals/icu/source/i18n/ulocdata.cpp",
+      "../externals/icu/source/i18n/umsg.cpp",
+      "../externals/icu/source/i18n/umsg_imp.h",
+      "../externals/icu/source/i18n/unesctrn.cpp",
+      "../externals/icu/source/i18n/unesctrn.h",
+      "../externals/icu/source/i18n/uni2name.cpp",
+      "../externals/icu/source/i18n/uni2name.h",
+      "../externals/icu/source/i18n/unum.cpp",
+      "../externals/icu/source/i18n/unumsys.cpp",
+      "../externals/icu/source/i18n/upluralrules.cpp",
+      "../externals/icu/source/i18n/uregex.cpp",
+      "../externals/icu/source/i18n/uregexc.cpp",
+      "../externals/icu/source/i18n/uregion.cpp",
+      "../externals/icu/source/i18n/usearch.cpp",
+      "../externals/icu/source/i18n/uspoof.cpp",
+      "../externals/icu/source/i18n/uspoof_build.cpp",
+      "../externals/icu/source/i18n/uspoof_conf.cpp",
+      "../externals/icu/source/i18n/uspoof_conf.h",
+      "../externals/icu/source/i18n/uspoof_impl.cpp",
+      "../externals/icu/source/i18n/uspoof_impl.h",
+      "../externals/icu/source/i18n/usrchimp.h",
+      "../externals/icu/source/i18n/utf16collationiterator.cpp",
+      "../externals/icu/source/i18n/utf16collationiterator.h",
+      "../externals/icu/source/i18n/utf8collationiterator.cpp",
+      "../externals/icu/source/i18n/utf8collationiterator.h",
+      "../externals/icu/source/i18n/utmscale.cpp",
+      "../externals/icu/source/i18n/utrans.cpp",
+      "../externals/icu/source/i18n/vtzone.cpp",
+      "../externals/icu/source/i18n/vzone.cpp",
+      "../externals/icu/source/i18n/vzone.h",
+      "../externals/icu/source/i18n/windtfmt.cpp",
+      "../externals/icu/source/i18n/windtfmt.h",
+      "../externals/icu/source/i18n/winnmfmt.cpp",
+      "../externals/icu/source/i18n/winnmfmt.h",
+      "../externals/icu/source/i18n/wintzimpl.cpp",
+      "../externals/icu/source/i18n/wintzimpl.h",
+      "../externals/icu/source/i18n/zonemeta.cpp",
+      "../externals/icu/source/i18n/zonemeta.h",
+      "../externals/icu/source/i18n/zrule.cpp",
+      "../externals/icu/source/i18n/zrule.h",
+      "../externals/icu/source/i18n/ztrans.cpp",
+      "../externals/icu/source/i18n/ztrans.h",
     ]
     if (is_win) {
       deps = [
@@ -219,14 +756,12 @@
         "U_STATIC_IMPLEMENTATION",
       ]
       libs = [ "Advapi32.lib" ]
-      sources += [ "../externals/icu/source/stubdata/stubdata.c" ]
+      sources += [ "../externals/icu/source/stubdata/stubdata.cpp" ]
     } else {
-      if (is_ios) {
-        sources += [ "../externals/icu/mac/icudtl_dat.S" ]
-      } else {
-        sources += [ "../externals/icu/$current_os/icudtl_dat.S" ]
-      }
-      libs = [ "dl" ]
+      sources += [ "$data_assembly" ]
+      deps = [
+        ":make_data_assembly",
+      ]
     }
   }
   if (is_win) {
diff --git a/third_party/libjpeg-turbo/BUILD.gn b/third_party/libjpeg-turbo/BUILD.gn
index 1f9b486..218505b 100644
--- a/third_party/libjpeg-turbo/BUILD.gn
+++ b/third_party/libjpeg-turbo/BUILD.gn
@@ -70,12 +70,12 @@
       "../externals/libjpeg-turbo/jutils.c",
     ]
 
-    if (current_cpu == "arm" && !is_ios) {
+    if (current_cpu == "arm" && !is_ios && !is_win) {
       sources += [
         "../externals/libjpeg-turbo/simd/arm/jsimd.c",
         "../externals/libjpeg-turbo/simd/arm/jsimd_neon.S",
       ]
-    } else if (current_cpu == "arm64" && !is_ios) {
+    } else if (current_cpu == "arm64" && !is_ios && !is_win) {
       sources += [
         "../externals/libjpeg-turbo/simd/arm64/jsimd.c",
         "../externals/libjpeg-turbo/simd/arm64/jsimd_neon.S",
diff --git a/third_party/skcms/skcms.cc b/third_party/skcms/skcms.cc
index 4231527..19ea5a2 100644
--- a/third_party/skcms/skcms.cc
+++ b/third_party/skcms/skcms.cc
@@ -1403,80 +1403,69 @@
                              : powf_(tf->a * x + tf->b, tf->g) + tf->e);
 }
 
-// TODO: Adjust logic here? This still assumes that purely linear inputs will have D > 1, which
-// we never generate. It also emits inverted linear using the same formulation. Standardize on
-// G == 1 here, too?
+#if defined(__clang__)
+    [[clang::no_sanitize("float-divide-by-zero")]]  // Checked for by tf_is_valid() on the way out.
+#endif
 bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
-    // Original equation is:       y = (ax + b)^g + e   for x >= d
-    //                             y = cx + f           otherwise
-    //
-    // so 1st inverse is:          (y - e)^(1/g) = ax + b
-    //                             x = ((y - e)^(1/g) - b) / a
-    //
-    // which can be re-written as: x = (1/a)(y - e)^(1/g) - b/a
-    //                             x = ((1/a)^g)^(1/g) * (y - e)^(1/g) - b/a
-    //                             x = ([(1/a)^g]y + [-((1/a)^g)e]) ^ [1/g] + [-b/a]
-    //
-    // and 2nd inverse is:         x = (y - f) / c
-    // which can be re-written as: x = [1/c]y + [-f/c]
-    //
-    // and now both can be expressed in terms of the same parametric form as the
-    // original - parameters are enclosed in square brackets.
-    skcms_TransferFunction tf_inv = { 0, 0, 0, 0, 0, 0, 0 };
-
-    // This rejects obviously malformed inputs, as well as decreasing functions
     if (!tf_is_valid(src)) {
         return false;
     }
 
-    // There are additional constraints to be invertible
-    bool has_nonlinear = (src->d <= 1);
-    bool has_linear = (src->d > 0);
+    // We're inverting this function, solving for x in terms of y.
+    //   y = (cx + f)         x < d
+    //       (ax + b)^g + e   x ≥ d
+    // The inverse of this function can be expressed in the same piecewise form.
+    skcms_TransferFunction inv = {0,0,0,0,0,0,0};
 
-    // Is the linear section not invertible?
-    if (has_linear && src->c == 0) {
+    // We'll start by finding the new threshold inv.d.
+    // In principle we should be able to find that by solving for y at x=d from either side.
+    // (If those two d values aren't the same, it's a discontinuous transfer function.)
+    float d_l =       src->c * src->d + src->f,
+          d_r = powf_(src->a * src->d + src->b, src->g) + src->e;
+    if (fabsf_(d_l - d_r) > 1/512.0f) {
         return false;
     }
+    inv.d = d_l;  // TODO(mtklein): better in practice to choose d_r?
 
-    // Is the nonlinear section not invertible?
-    if (has_nonlinear && (src->a == 0 || src->g == 0)) {
-        return false;
+    // When d=0, the linear section collapses to a point.  We leave c,d,f all zero in that case.
+    if (inv.d > 0) {
+        // Inverting the linear section is pretty straightfoward:
+        //        y       = cx + f
+        //        y - f   = cx
+        //   (1/c)y - f/c = x
+        inv.c =    1.0f/src->c;
+        inv.f = -src->f/src->c;
     }
 
-    // If both segments are present, they need to line up
-    if (has_linear && has_nonlinear) {
-        float l_at_d = src->c * src->d + src->f;
-        float n_at_d = powf_(src->a * src->d + src->b, src->g) + src->e;
-        if (fabsf_(l_at_d - n_at_d) > (1 / 512.0f)) {
-            return false;
-        }
-    }
+    // The interesting part is inverting the nonlinear section:
+    //         y                = (ax + b)^g + e.
+    //         y - e            = (ax + b)^g
+    //        (y - e)^1/g       =  ax + b
+    //        (y - e)^1/g - b   =  ax
+    //   (1/a)(y - e)^1/g - b/a =   x
+    //
+    // To make that fit our form, we need to move the (1/a) term inside the exponentiation:
+    //   let k = (1/a)^g
+    //   (1/a)( y -  e)^1/g - b/a = x
+    //        (ky - ke)^1/g - b/a = x
 
-    // Invert linear segment
-    if (has_linear) {
-        tf_inv.c = 1.0f / src->c;
-        tf_inv.f = -src->f / src->c;
-    }
+#ifndef SKCMS_LEGACY_TF_INVERT
+    float k = powf_(src->a, -src->g);  // (1/a)^g == a^-g
+#else
+    float k = powf_(1.0f / src->a, src->g);
+#endif
+    inv.g = 1.0f / src->g;
+    inv.a = k;
+    inv.b = -k * src->e;
+    inv.e = -src->b / src->a;
 
-    // Invert nonlinear segment
-    if (has_nonlinear) {
-        tf_inv.g = 1.0f / src->g;
-        tf_inv.a = powf_(1.0f / src->a, src->g);
-        tf_inv.b = -tf_inv.a * src->e;
-        tf_inv.e = -src->b / src->a;
-    }
+    // TODO(mtklein): we'd like to guarantee the edge cases more strongly:
+    //    inv(src(0)) = 0
+    //    inv(src(d)) = d
+    //    inv(src(1)) = 1
 
-    if (!has_linear) {
-        tf_inv.d = 0;
-    } else if (!has_nonlinear) {
-        // Any value larger than 1 works
-        tf_inv.d = 2.0f;
-    } else {
-        tf_inv.d = src->c * src->d + src->f;
-    }
-
-    *dst = tf_inv;
-    return true;
+    *dst = inv;
+    return tf_is_valid(dst);
 }
 
 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //
diff --git a/third_party/skcms/version.sha1 b/third_party/skcms/version.sha1
index 02872f4..c935f62 100755
--- a/third_party/skcms/version.sha1
+++ b/third_party/skcms/version.sha1
@@ -1 +1 @@
-ee555769341602bc7b28a4eefe2c966eb5c63a3a
\ No newline at end of file
+1747706b4afed3f41e76e4ef136dc45beebaee9f
\ No newline at end of file
diff --git a/third_party/vulkanmemoryallocator/BUILD.gn b/third_party/vulkanmemoryallocator/BUILD.gn
index b865ca0..8b5115b 100644
--- a/third_party/vulkanmemoryallocator/BUILD.gn
+++ b/third_party/vulkanmemoryallocator/BUILD.gn
@@ -10,7 +10,7 @@
 source_set("vulkanmemoryallocator") {
   public_configs = [ ":vulkanmemoryallocator_public" ]
 
-  include_dirs = [ "../vulkan" ]
+  include_dirs = [ "../../include/third_party/vulkan" ]
 
   # Need to add this so when we include GrVkDefines.h it internally can find SkTypes.h which is
   # needed in case the user set defines in SkUserConfig.h.
diff --git a/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.cpp b/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.cpp
index 5888463..00a20c2 100644
--- a/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.cpp
+++ b/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.cpp
@@ -12,5 +12,6 @@
 #define VMA_STATIC_VULKAN_FUNCTIONS 0
 
 #define VMA_IMPLEMENTATION
+#include <vulkan/vulkan_core.h>
 #include "GrVulkanMemoryAllocator.h"
 
diff --git a/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.h b/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.h
index 419eb55..518923a 100644
--- a/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.h
+++ b/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.h
@@ -12,7 +12,10 @@
 #ifndef GrVulkanMemoryAllocator_DEFINED
 #define GrVulkanMemoryAllocator_DEFINED
 
-#include <vulkan/vulkan_core.h>
+// We only ever include this from src files which have already included vulkan.
+#ifndef VULKAN_CORE_H_
+#error "vulkan_core.h has not been included before trying to include the GrVulkanMemoryAllocator"
+#endif
 #include "include/vk_mem_alloc.h"
 
 #endif
diff --git a/third_party/wuffs/BUILD.gn b/third_party/wuffs/BUILD.gn
index 377b609..e0aa730 100644
--- a/third_party/wuffs/BUILD.gn
+++ b/third_party/wuffs/BUILD.gn
@@ -7,7 +7,45 @@
 
 third_party("wuffs") {
   public_include_dirs = [ "../externals/wuffs/release/c" ]
+
+  defines = [
+    # Copy/pasting from "../externals/wuffs/release/c/wuffs-*.c":
+    #
+    # ----
+    #
+    # Wuffs ships as a "single file C library" or "header file library" as per
+    # https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
+    #
+    # To use that single file as a "foo.c"-like implementation, instead of a
+    # "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
+    # compiling it.
+    #
+    # ----
+    "WUFFS_IMPLEMENTATION",
+
+    # Continuing to copy/paste:
+    #
+    # ----
+    #
+    # Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users
+    # of Wuffs' .c file whitelist which parts of Wuffs to build. That file
+    # contains the entire Wuffs standard library, implementing a variety of
+    # codecs and file formats. Without this macro definition, an optimizing
+    # compiler or linker may very well discard Wuffs code for unused codecs,
+    # but listing the Wuffs modules we use makes that process explicit.
+    # Preprocessing means that such code simply isn't compiled.
+    #
+    # ----
+    #
+    # For Skia, we're only interested in particular image codes (e.g. GIF) and
+    # their dependencies (e.g. BASE, LZW).
+    "WUFFS_CONFIG__MODULES",
+    "WUFFS_CONFIG__MODULE__BASE",
+    "WUFFS_CONFIG__MODULE__GIF",
+    "WUFFS_CONFIG__MODULE__LZW",
+  ]
+
   sources = [
-    "wuffs.c",
+    "../externals/wuffs/release/c/wuffs-v0.2.c",
   ]
 }
diff --git a/third_party/wuffs/wuffs.c b/third_party/wuffs/wuffs.c
deleted file mode 100644
index 9c6eb9d..0000000
--- a/third_party/wuffs/wuffs.c
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-// The Wuffs library ships as a single file - a .h file - which GN does not
-// recognize as something to be compiled, because it ends in .h and not .c or
-// .cpp. Instead, this trivial file is a placeholder .c file that is a BUILD.gn
-// target for the third party Wuffs library.
-//
-// Copy/pasting from the Wuffs .h file's comments:
-//
-// ----
-//
-// Wuffs ships as a "single file C library" or "header file library" as per
-// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt
-//
-// To use that single file as a "foo.c"-like implementation, instead of a
-// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or
-// compiling it.
-//
-// ----
-#define WUFFS_IMPLEMENTATION
-
-// Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of
-// Wuffs' .h file whitelist which parts of Wuffs to build. That file contains
-// the entire Wuffs standard library, implementing a variety of codecs and file
-// formats. Without this macro definition, an optimizing compiler or linker may
-// very well discard Wuffs code for unused codecs, but listing the Wuffs
-// modules we use makes that process explicit. Preprocessing means that such
-// code simply isn't compiled.
-//
-// For Skia, we're only interested in particular image codes (e.g. GIF) and
-// their dependencies (e.g. BASE, LZW).
-#define WUFFS_CONFIG__MODULES
-#define WUFFS_CONFIG__MODULE__BASE
-#define WUFFS_CONFIG__MODULE__GIF
-#define WUFFS_CONFIG__MODULE__LZW
-
-#include "wuffs-v0.2.h"
diff --git a/tools/DDLPromiseImageHelper.cpp b/tools/DDLPromiseImageHelper.cpp
index 53fcd1c..2586835 100644
--- a/tools/DDLPromiseImageHelper.cpp
+++ b/tools/DDLPromiseImageHelper.cpp
@@ -24,10 +24,6 @@
     }
 }
 
-const GrCaps* DDLPromiseImageHelper::PromiseImageCallbackContext::caps() const {
-    return fContext->contextPriv().caps();
-}
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 DDLPromiseImageHelper::~DDLPromiseImageHelper() {}
@@ -51,6 +47,51 @@
     return inputPicture->serialize(&procs);
 }
 
+// needed until we have SkRG_88_ColorType;
+static GrBackendTexture create_yuva_texture(GrGpu* gpu, const SkPixmap& pm,
+                                            const SkYUVAIndex yuvaIndices[4], int texIndex) {
+    SkASSERT(texIndex >= 0 && texIndex <= 3);
+    int channelCount = 0;
+    for (int i = 0; i < SkYUVAIndex::kIndexCount; ++i) {
+        if (yuvaIndices[i].fIndex == texIndex) {
+            ++channelCount;
+        }
+    }
+    // Need to create an RG texture for two-channel planes
+    GrBackendTexture tex;
+    if (2 == channelCount) {
+        SkASSERT(kRGBA_8888_SkColorType == pm.colorType());
+        SkAutoTMalloc<char> pixels(2 * pm.width()*pm.height());
+        char* currPixel = pixels;
+        for (int y = 0; y < pm.height(); ++y) {
+            for (int x = 0; x < pm.width(); ++x) {
+                SkColor color = pm.getColor(x, y);
+                currPixel[0] = SkColorGetR(color);
+                currPixel[1] = SkColorGetG(color);
+                currPixel += 2;
+            }
+        }
+        tex = gpu->createTestingOnlyBackendTexture(
+            pixels,
+            pm.width(),
+            pm.height(),
+            GrColorType::kRG_88,
+            false,
+            GrMipMapped::kNo,
+            2 * pm.width());
+    } else {
+        tex = gpu->createTestingOnlyBackendTexture(
+            pm.addr(),
+            pm.width(),
+            pm.height(),
+            pm.colorType(),
+            false,
+            GrMipMapped::kNo,
+            pm.rowBytes());
+    }
+    return tex;
+}
+
 void DDLPromiseImageHelper::uploadAllToGPU(GrContext* context) {
     GrGpu* gpu = context->contextPriv().getGpu();
     SkASSERT(gpu);
@@ -67,13 +108,9 @@
 
                 sk_sp<PromiseImageCallbackContext> callbackContext(
                                                         new PromiseImageCallbackContext(context));
-                callbackContext->setBackendTexture(gpu->createTestingOnlyBackendTexture(
-                                                            yuvPixmap.addr(),
-                                                            yuvPixmap.width(),
-                                                            yuvPixmap.height(),
-                                                            yuvPixmap.colorType(),
-                                                            false, GrMipMapped::kNo,
-                                                            yuvPixmap.rowBytes()));
+
+                callbackContext->setBackendTexture(create_yuva_texture(gpu, yuvPixmap,
+                                                                       info.yuvaIndices(), j));
                 SkAssertResult(callbackContext->backendTexture().isValid());
 
                 fImageInfo[i].setCallbackContext(j, std::move(callbackContext));
@@ -138,8 +175,6 @@
     }
     SkASSERT(curImage.index() == *indexPtr);
 
-    const GrCaps* caps = curImage.caps();
-
     sk_sp<SkImage> image;
     if (curImage.isYUV()) {
         GrBackendFormat backendFormats[SkYUVASizeInfo::kMaxCount];
@@ -150,8 +185,8 @@
         SkAssertResult(SkYUVAIndex::AreValidIndices(curImage.yuvaIndices(), &textureCount));
         for (int i = 0; i < textureCount; ++i) {
             const GrBackendTexture& backendTex = curImage.backendTexture(i);
-            backendFormats[i] = caps->createFormatFromBackendTexture(backendTex);
-
+            backendFormats[i] = backendTex.getBackendFormat();
+            SkASSERT(backendFormats[i].isValid());
             contexts[i] = curImage.refCallbackContext(i).release();
             sizes[i].set(curImage.yuvPixmap(i).width(), curImage.yuvPixmap(i).height());
         }
@@ -174,7 +209,8 @@
 
     } else {
         const GrBackendTexture& backendTex = curImage.backendTexture(0);
-        GrBackendFormat backendFormat = caps->createFormatFromBackendTexture(backendTex);
+        GrBackendFormat backendFormat = backendTex.getBackendFormat();
+        SkASSERT(backendFormat.isValid());
 
         // Each DDL recorder gets its own ref on the promise callback context for the
         // promise images it creates.
diff --git a/tools/DDLPromiseImageHelper.h b/tools/DDLPromiseImageHelper.h
index 583f3fd..7a636ce 100644
--- a/tools/DDLPromiseImageHelper.h
+++ b/tools/DDLPromiseImageHelper.h
@@ -83,8 +83,6 @@
 
         const GrBackendTexture& backendTexture() const { return fBackendTexture; }
 
-        const GrCaps* caps() const;
-
     private:
         GrContext*       fContext;
         GrBackendTexture fBackendTexture;
@@ -145,8 +143,6 @@
             return fCallbackContexts[index];
         }
 
-        const GrCaps* caps() const { return fCallbackContexts[0]->caps(); }
-
         const GrBackendTexture& backendTexture(int index) const {
             SkASSERT(index >= 0 && index < (this->isYUV() ? SkYUVASizeInfo::kMaxCount : 1));
             return fCallbackContexts[index]->backendTexture();
diff --git a/tools/LsanSuppressions.cpp b/tools/LsanSuppressions.cpp
index d0b349e..06db901 100644
--- a/tools/LsanSuppressions.cpp
+++ b/tools/LsanSuppressions.cpp
@@ -21,6 +21,7 @@
                "leak:libGL.so\n"            // For NVidia driver.
                "leak:libGLX_nvidia.so\n"    // For NVidia driver.
                "leak:libnvidia-glcore.so\n" // For NVidia driver.
+               "leak:libnvidia-tls.so\n"    // For NVidia driver.
                "leak:__strdup\n"            // An eternal mystery, skia:2916.
                ;
     }
diff --git a/tools/bookmaker/includeParser.cpp b/tools/bookmaker/includeParser.cpp
index 7fb5938..73a8015 100644
--- a/tools/bookmaker/includeParser.cpp
+++ b/tools/bookmaker/includeParser.cpp
@@ -310,11 +310,18 @@
     if ('{' == i.peek()) {
         if (looks_like_method(i)) {
             fCheck.fState = CheckCode::State::kMethod;
-            if (!i.skipToBalancedEndBracket('{', '}')) {
-                i.reportError("unbalanced open brace");
+            bool inBalance = false;
+            TextParser paren(i.fFileName, i.fStart, i.fEnd, i.fLineCount);
+            paren.skipToEndBracket('(');
+            paren.skipToBalancedEndBracket('(', ')');
+            inBalance = i.fChar < paren.fChar;
+            if (!inBalance) {
+                if (!i.skipToBalancedEndBracket('{', '}')) {
+                    i.reportError("unbalanced open brace");
+                }
+                i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
+                return false;
             }
-            i.skipToLineStart(&fCheck.fIndent, &fCheck.fWriteReturn);
-            return false;
         } else if (looks_like_class_decl(i)) {
             fCheck.fState = CheckCode::State::kClassDeclaration;
             fCheck.fPrivateBrace = fCheck.fBraceCount + 1;
@@ -434,9 +441,18 @@
         i.fEnd = loc;
     }
     if (i.contains("{", i.fEnd, &loc)) {
-        i.fEnd = loc + 1;
-        while (i.fEnd < iDef.fContentEnd && ' ' >= i.fEnd[0]) {
-            ++i.fEnd;
+        bool inBalance = false;
+        if (MarkType::kMethod == iDef.fMarkType) {
+            TextParser paren(&iDef);
+            paren.skipToEndBracket('(');
+            paren.skipToBalancedEndBracket('(', ')');
+            inBalance = loc < paren.fChar;
+        }
+        if (!inBalance) {
+            i.fEnd = loc + 1;
+            while (i.fEnd < iDef.fContentEnd && ' ' >= i.fEnd[0]) {
+                ++i.fEnd;
+            }
         }
     }
     while (i.fEnd > i.fStart && ' ' == i.fEnd[-1]) {
@@ -1828,7 +1844,9 @@
         className = className.substr(subClassPos + 2);
     }
     // match may be constructor; compare strings to see if this is so
-    SkASSERT(string::npos != methodName.find('('));
+    if (string::npos == methodName.find('(')) {
+        return nullptr;
+    }
     auto stripper = [](string s) -> string {
         bool last = false;
         string result;
diff --git a/tools/bookmaker/includeWriter.cpp b/tools/bookmaker/includeWriter.cpp
index 209d424..34f6f08 100644
--- a/tools/bookmaker/includeWriter.cpp
+++ b/tools/bookmaker/includeWriter.cpp
@@ -1197,6 +1197,8 @@
                     inMethod = false;
                 } else if (Punctuation::kSemicolon == token.fPunctuation) {
                     inMethod = false;
+                } else if (Punctuation::kAsterisk == token.fPunctuation) {
+                    inMethod = false;
                 } else {
                     SkASSERT(0);  // incomplete
                 }
diff --git a/tools/bookmaker/mdOut.cpp b/tools/bookmaker/mdOut.cpp
index abea5cc..3e16b06 100644
--- a/tools/bookmaker/mdOut.cpp
+++ b/tools/bookmaker/mdOut.cpp
@@ -318,6 +318,7 @@
         parser.skipToEndBracket('(');
         const char* parenStart = parser.fChar;
         parser.skipToBalancedEndBracket('(', ')');
+        (void) parser.skipExact(" const");
         string methodName = fPriorWord + fSeparator
                 + string(parenStart + 1, parser.fChar - parenStart - 1);
         string testLink;
@@ -446,7 +447,8 @@
     }
     // look in parent fNames and above for match
     if (fNames) {
-        if (this->findLink(fWord, &fLink, Resolvable::kClone == fResolvable && fAddParens)) {
+        if (this->findLink(fWord, &fLink, (Resolvable::kClone == fResolvable && fAddParens)
+                || (Resolvable::kCode == fResolvable && '(' == fEnd[0]))) {
             return;
         }
     }
@@ -1666,7 +1668,10 @@
                 SkASSERT(MarkType::kMethod == parent->fMarkType);
                 // retrieve parameters, return, description from include
                 Definition* iMethod = fIncludeParser.findMethod(*parent);
-                SkASSERT(iMethod);  // deprecated or 'in progress' functions should not include populate
+                if (!iMethod) {  // deprecated or 'in progress' functions should not include populate
+                    SkDebugf("#Populate found in deprecated or missing method %s\n", def->fName.c_str());
+                    def->fParent->reportError<void>("Remove #Method");
+                }
                 bool wroteParam = false;
                 SkASSERT(fMethod == iMethod);
                 for (auto& entry : iMethod->fTokens) {
diff --git a/tools/check-headers-self-sufficient b/tools/check-headers-self-sufficient
index 59c4d79..fe8a8ac 100755
--- a/tools/check-headers-self-sufficient
+++ b/tools/check-headers-self-sufficient
@@ -30,10 +30,10 @@
     '-Iinclude/ports',
     '-Iinclude/private',
     '-Iinclude/svg',
+    '-Iinclude/third_party/vulkan',
     '-Iinclude/utils',
     '-Iinclude/utils/mac',
     '-Iinclude/views',
-    '-Ithird_party/vulkan',
 ]
 
 all_header_args = [
@@ -49,6 +49,7 @@
     '-Iinclude/ports',
     '-Iinclude/private',
     '-Iinclude/svg',
+    '-Iinclude/third_party/vulkan',
     '-Iinclude/utils',
     '-Iinclude/utils/mac',
     '-Iinclude/views',
@@ -82,7 +83,6 @@
     '-Ithird_party/externals/sfntly/cpp/src',
     '-Ithird_party/externals/zlib',
     '-Ithird_party/gif',
-    '-Ithird_party/vulkan',
 ]
 
 ignore = [
diff --git a/tools/clang-tidy.sh b/tools/clang-tidy.sh
new file mode 100755
index 0000000..addbee3
--- /dev/null
+++ b/tools/clang-tidy.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# Copyright 2018 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -e
+
+args=""
+src=""
+
+while [ "$1" ]; do
+    arg=$1
+
+    args="$args $1"
+    shift
+
+    if [ "$arg" == "-c" ]; then
+        src=$1
+
+        args="$args $1"
+        shift
+    fi
+done
+
+if [ "$src" ]; then
+    clang-tidy -quiet -header-filter='.*' -warnings-as-errors='*' $src -- $args
+fi
+exec clang++ $args
+
diff --git a/tools/debugger/SkDebugCanvas.cpp b/tools/debugger/SkDebugCanvas.cpp
index 22c36ed..7f5c544 100644
--- a/tools/debugger/SkDebugCanvas.cpp
+++ b/tools/debugger/SkDebugCanvas.cpp
@@ -397,17 +397,6 @@
     this->addDrawCommand(new SkDrawPointsCommand(mode, count, pts, paint));
 }
 
-void SkDebugCanvas::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                                  const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawPosTextCommand(text, byteLength, pos, paint));
-}
-
-void SkDebugCanvas::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                                   SkScalar constY, const SkPaint& paint) {
-    this->addDrawCommand(
-        new SkDrawPosTextHCommand(text, byteLength, xpos, constY, paint));
-}
-
 void SkDebugCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) {
     // NOTE(chudy): Messing up when renamed to DrawRect... Why?
     addDrawCommand(new SkDrawRectCommand(rect, paint));
@@ -422,16 +411,6 @@
     this->addDrawCommand(new SkDrawDRRectCommand(outer, inner, paint));
 }
 
-void SkDebugCanvas::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                               const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawTextCommand(text, byteLength, x, y, paint));
-}
-
-void SkDebugCanvas::onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform xform[],
-                                      const SkRect* cull, const SkPaint& paint) {
-    this->addDrawCommand(new SkDrawTextRSXformCommand(text, byteLength, xform, cull, paint));
-}
-
 void SkDebugCanvas::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                                    const SkPaint& paint) {
     this->addDrawCommand(new SkDrawTextBlobCommand(sk_ref_sp(const_cast<SkTextBlob*>(blob)),
@@ -483,6 +462,11 @@
     return kNoLayer_SaveLayerStrategy;
 }
 
+bool SkDebugCanvas::onDoSaveBehind(const SkRect* subset) {
+    // TODO
+    return false;
+}
+
 void SkDebugCanvas::didSetMatrix(const SkMatrix& matrix) {
     this->addDrawCommand(new SkSetMatrixCommand(matrix));
     this->INHERITED::didSetMatrix(matrix);
diff --git a/tools/debugger/SkDebugCanvas.h b/tools/debugger/SkDebugCanvas.h
index e9263d9..43598ca 100644
--- a/tools/debugger/SkDebugCanvas.h
+++ b/tools/debugger/SkDebugCanvas.h
@@ -113,9 +113,8 @@
 
 protected:
     void willSave() override;
-
     SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec &) override;
-
+    bool onDoSaveBehind(const SkRect*) override;
     void willRestore() override;
 
     void didConcat(const SkMatrix &) override;
@@ -124,14 +123,6 @@
 
     void onDrawAnnotation(const SkRect&, const char[], SkData*) override;
     void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override;
-    void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                    const SkPaint&) override;
-    void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[],
-                       const SkPaint&) override;
-    void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[],
-                        SkScalar constY, const SkPaint&) override;
-    void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform[], const SkRect*,
-                           const SkPaint&) override;
     void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y,
                         const SkPaint& paint) override;
 
diff --git a/tools/debugger/SkDrawCommand.cpp b/tools/debugger/SkDrawCommand.cpp
index a159bd8..a77c764 100644
--- a/tools/debugger/SkDrawCommand.cpp
+++ b/tools/debugger/SkDrawCommand.cpp
@@ -34,6 +34,7 @@
 #define SKDEBUGCANVAS_ATTRIBUTE_MATRIX            "matrix"
 #define SKDEBUGCANVAS_ATTRIBUTE_DRAWDEPTHTRANS    "drawDepthTranslation"
 #define SKDEBUGCANVAS_ATTRIBUTE_COORDS            "coords"
+#define SKDEBUGCANVAS_ATTRIBUTE_EDGING            "edging"
 #define SKDEBUGCANVAS_ATTRIBUTE_HINTING           "hinting"
 #define SKDEBUGCANVAS_ATTRIBUTE_BOUNDS            "bounds"
 #define SKDEBUGCANVAS_ATTRIBUTE_PAINT             "paint"
@@ -190,6 +191,10 @@
 #define SKDEBUGCANVAS_HINTING_NORMAL              "normal"
 #define SKDEBUGCANVAS_HINTING_FULL                "full"
 
+#define SKDEBUGCANVAS_EDGING_ALIAS                "alias"
+#define SKDEBUGCANVAS_EDGING_ANTIALIAS            "antialias"
+#define SKDEBUGCANVAS_EDGING_SUBPIXELANTIALIAS    "subpixelantialias"
+
 #define SKDEBUGCANVAS_SHADOWFLAG_TRANSPARENT_OCC  "transparentOccluder"
 #define SKDEBUGCANVAS_SHADOWFLAG_GEOMETRIC_ONLY   "geometricOnly"
 
@@ -229,15 +234,11 @@
         case kDrawPath_OpType: return "DrawPath";
         case kDrawArc_OpType: return "DrawArc";
         case kDrawPoints_OpType: return "DrawPoints";
-        case kDrawPosText_OpType: return "DrawPosText";
-        case kDrawPosTextH_OpType: return "DrawPosTextH";
         case kDrawRect_OpType: return "DrawRect";
         case kDrawRRect_OpType: return "DrawRRect";
         case kDrawRegion_OpType: return "DrawRegion";
         case kDrawShadow_OpType: return "DrawShadow";
-        case kDrawText_OpType: return "DrawText";
         case kDrawTextBlob_OpType: return "DrawTextBlob";
-        case kDrawTextRSXform_OpType: return "DrawTextRSXform";
         case kDrawVertices_OpType: return "DrawVertices";
         case kDrawAtlas_OpType: return "DrawAtlas";
         case kDrawDrawable_OpType: return "DrawDrawable";
@@ -790,8 +791,8 @@
     return success;
 }
 
-static void apply_paint_hinting(const SkPaint& paint, Json::Value* target) {
-    SkFontHinting hinting = (SkFontHinting)paint.getHinting();
+static void apply_font_hinting(const SkFont& font, Json::Value* target) {
+    SkFontHinting hinting = font.getHinting();
     if (hinting != SkPaintDefaults_Hinting) {
         switch (hinting) {
             case kNo_SkFontHinting:
@@ -810,6 +811,20 @@
     }
 }
 
+static void apply_font_edging(const SkFont& font, Json::Value* target) {
+    switch (font.getEdging()) {
+        case SkFont::Edging::kAlias:
+            (*target)[SKDEBUGCANVAS_ATTRIBUTE_EDGING] = SKDEBUGCANVAS_EDGING_ALIAS;
+            break;
+        case SkFont::Edging::kAntiAlias:
+            (*target)[SKDEBUGCANVAS_ATTRIBUTE_EDGING] = SKDEBUGCANVAS_EDGING_ANTIALIAS;
+            break;
+        case SkFont::Edging::kSubpixelAntiAlias:
+            (*target)[SKDEBUGCANVAS_ATTRIBUTE_EDGING] = SKDEBUGCANVAS_EDGING_SUBPIXELANTIALIAS;
+            break;
+    }
+}
+
 static void apply_paint_color(const SkPaint& paint, Json::Value* target) {
     SkColor color = paint.getColor();
     if (color != SK_ColorBLACK) {
@@ -963,9 +978,9 @@
     }
 }
 
-static void apply_paint_typeface(const SkPaint& paint, Json::Value* target,
+static void apply_font_typeface(const SkFont& font, Json::Value* target,
                                  UrlDataManager& urlDataManager) {
-    SkTypeface* typeface = paint.getTypeface();
+    SkTypeface* typeface = font.getTypeface();
     if (typeface != nullptr) {
         Json::Value jsonTypeface;
         SkDynamicMemoryWStream buffer;
@@ -1028,19 +1043,7 @@
                  SkPaintDefaults_MiterLimit);
     store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_ANTIALIAS, paint.isAntiAlias(), false);
     store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_DITHER, paint.isDither(), false);
-    store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_FAKEBOLDTEXT, paint.isFakeBoldText(), false);
-    store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_LINEARTEXT, paint.isLinearText(), false);
-    store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_SUBPIXELTEXT, paint.isSubpixelText(), false);
-    store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_LCDRENDERTEXT, paint.isLCDRenderText(), false);
-    store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_EMBEDDEDBITMAPTEXT, paint.isEmbeddedBitmapText(), false);
-    store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_AUTOHINTING, paint.isAutohinted(), false);
-    //kGenA8FromLCD_Flag
 
-    store_scalar(&result, SKDEBUGCANVAS_ATTRIBUTE_TEXTSIZE, paint.getTextSize(),
-                 SkPaintDefaults_TextSize);
-    store_scalar(&result, SKDEBUGCANVAS_ATTRIBUTE_TEXTSCALEX, paint.getTextScaleX(), SK_Scalar1);
-    store_scalar(&result, SKDEBUGCANVAS_ATTRIBUTE_TEXTSCALEX, paint.getTextSkewX(), 0.0f);
-    apply_paint_hinting(paint, &result);
     apply_paint_color(paint, &result);
     apply_paint_style(paint, &result);
     apply_paint_blend_mode(paint, &result);
@@ -1053,7 +1056,24 @@
     apply_paint_looper(paint, &result, urlDataManager);
     apply_paint_imagefilter(paint, &result, urlDataManager);
     apply_paint_colorfilter(paint, &result, urlDataManager);
-    apply_paint_typeface(paint, &result, urlDataManager);
+    return result;
+}
+
+static Json::Value MakeJsonFont(const SkFont& font, UrlDataManager& urlDataManager) {
+    Json::Value result(Json::objectValue);
+    store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_FAKEBOLDTEXT, font.isEmbolden(), false);
+    store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_LINEARTEXT, font.isLinearMetrics(), false);
+    store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_SUBPIXELTEXT, font.isSubpixel(), false);
+    store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_EMBEDDEDBITMAPTEXT, font.isEmbeddedBitmaps(), false);
+    store_bool(&result, SKDEBUGCANVAS_ATTRIBUTE_AUTOHINTING, font.isForceAutoHinting(), false);
+
+    store_scalar(&result, SKDEBUGCANVAS_ATTRIBUTE_TEXTSIZE, font.getSize(),
+                 SkPaintDefaults_TextSize);
+    store_scalar(&result, SKDEBUGCANVAS_ATTRIBUTE_TEXTSCALEX, font.getScaleX(), SK_Scalar1);
+    store_scalar(&result, SKDEBUGCANVAS_ATTRIBUTE_TEXTSCALEX, font.getSkewX(), 0.0f);
+    apply_font_edging(font, &result);
+    apply_font_hinting(font, &result);
+    apply_font_typeface(font, &result, urlDataManager);
     return result;
 }
 
@@ -1810,61 +1830,6 @@
     return result;
 }
 
-static Json::Value make_json_text(sk_sp<SkData> text) {
-    return Json::Value((const char*)text->data(), (const char*)text->data() + text->size());
-}
-
-SkDrawPosTextCommand::SkDrawPosTextCommand(const void* text, size_t byteLength,
-                                           const SkPoint pos[], const SkPaint& paint)
-    : INHERITED(kDrawPosText_OpType)
-    , fText(SkData::MakeWithCopy(text, byteLength))
-    , fPos(pos, paint.countText(text, byteLength))
-    , fPaint(paint) {}
-
-void SkDrawPosTextCommand::execute(SkCanvas* canvas) const {
-    canvas->drawPosText(fText->data(), fText->size(), fPos.begin(), fPaint);
-}
-
-Json::Value SkDrawPosTextCommand::toJSON(UrlDataManager& urlDataManager) const {
-    Json::Value result = INHERITED::toJSON(urlDataManager);
-    result[SKDEBUGCANVAS_ATTRIBUTE_TEXT] = make_json_text(fText);
-    Json::Value coords(Json::arrayValue);
-    size_t numCoords = fPaint.textToGlyphs(fText->data(), fText->size(), nullptr);
-    for (size_t i = 0; i < numCoords; i++) {
-        coords.append(MakeJsonPoint(fPos[i]));
-    }
-    result[SKDEBUGCANVAS_ATTRIBUTE_COORDS] = coords;
-    result[SKDEBUGCANVAS_ATTRIBUTE_PAINT] = MakeJsonPaint(fPaint, urlDataManager);
-    return result;
-}
-
-SkDrawPosTextHCommand::SkDrawPosTextHCommand(const void* text, size_t byteLength,
-                                             const SkScalar xpos[], SkScalar constY,
-                                             const SkPaint& paint)
-    : INHERITED(kDrawPosTextH_OpType)
-    , fText(SkData::MakeWithCopy(text, byteLength))
-    , fXpos(xpos, paint.countText(text, byteLength))
-    , fConstY(constY)
-    , fPaint(paint) {}
-
-void SkDrawPosTextHCommand::execute(SkCanvas* canvas) const {
-    canvas->drawPosTextH(fText->data(), fText->size(), fXpos.begin(), fConstY, fPaint);
-}
-
-Json::Value SkDrawPosTextHCommand::toJSON(UrlDataManager& urlDataManager) const {
-    Json::Value result = INHERITED::toJSON(urlDataManager);
-    result[SKDEBUGCANVAS_ATTRIBUTE_TEXT] = make_json_text(fText);
-    result[SKDEBUGCANVAS_ATTRIBUTE_Y] = Json::Value(fConstY);
-    Json::Value xpos(Json::arrayValue);
-    size_t numXpos = fPaint.textToGlyphs(fText->data(), fText->size(), nullptr);
-    for (size_t i = 0; i < numXpos; i++) {
-        xpos.append(Json::Value(fXpos[i]));
-    }
-    result[SKDEBUGCANVAS_ATTRIBUTE_POSITIONS] = xpos;
-    result[SKDEBUGCANVAS_ATTRIBUTE_PAINT] = MakeJsonPaint(fPaint, urlDataManager);
-    return result;
-}
-
 SkDrawTextBlobCommand::SkDrawTextBlobCommand(sk_sp<SkTextBlob> blob, SkScalar x, SkScalar y,
                                              const SkPaint& paint)
     : INHERITED(kDrawTextBlob_OpType)
@@ -1912,6 +1877,9 @@
                     break;
                 case SkTextBlobRunIterator::kDefault_Positioning:
                     break;
+                case SkTextBlobRunIterator::kRSXform_Positioning:
+                    // TODO_RSXFORM_BLOB
+                    break;
             }
             jsonGlyphs.append(Json::Value(iterGlyphs[i]));
         }
@@ -1920,8 +1888,7 @@
         }
         run[SKDEBUGCANVAS_ATTRIBUTE_GLYPHS] = jsonGlyphs;
         SkPaint fontPaint;
-        iter.applyFontToPaint(&fontPaint);
-        run[SKDEBUGCANVAS_ATTRIBUTE_FONT] = MakeJsonPaint(fontPaint, urlDataManager);
+        run[SKDEBUGCANVAS_ATTRIBUTE_FONT] = MakeJsonFont(iter.font(), urlDataManager);
         run[SKDEBUGCANVAS_ATTRIBUTE_COORDS] = MakeJsonPoint(iter.offset());
         runs.append(run);
         iter.next();
@@ -2108,52 +2075,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-SkDrawTextCommand::SkDrawTextCommand(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                                     const SkPaint& paint)
-    : INHERITED(kDrawText_OpType)
-    , fText(SkData::MakeWithCopy(text, byteLength))
-    , fX(x)
-    , fY(y)
-    , fPaint(paint) {}
-
-void SkDrawTextCommand::execute(SkCanvas* canvas) const {
-    canvas->drawText(fText->data(), fText->size(), fX, fY, fPaint);
-}
-
-Json::Value SkDrawTextCommand::toJSON(UrlDataManager& urlDataManager) const {
-    Json::Value result = INHERITED::toJSON(urlDataManager);
-    result[SKDEBUGCANVAS_ATTRIBUTE_TEXT] = make_json_text(fText);
-    Json::Value coords(Json::arrayValue);
-    result[SKDEBUGCANVAS_ATTRIBUTE_COORDS] = MakeJsonPoint(fX, fY);
-    result[SKDEBUGCANVAS_ATTRIBUTE_PAINT] = MakeJsonPaint(fPaint, urlDataManager);
-    return result;
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-SkDrawTextRSXformCommand::SkDrawTextRSXformCommand(const void* text, size_t byteLength,
-                                                   const SkRSXform xform[], const SkRect* cull,
-                                                   const SkPaint& paint)
-    : INHERITED(kDrawTextRSXform_OpType)
-    , fText(SkData::MakeWithCopy(text, byteLength))
-    , fXform(xform, paint.countText(text, byteLength))
-    , fCull(cull)
-    , fPaint(paint) {}
-
-void SkDrawTextRSXformCommand::execute(SkCanvas* canvas) const {
-    canvas->drawTextRSXform(fText->data(), fText->size(), fXform.begin(), fCull.getMaybeNull(),
-                            fPaint);
-}
-
-Json::Value SkDrawTextRSXformCommand::toJSON(UrlDataManager& urlDataManager) const {
-    Json::Value result = INHERITED::toJSON(urlDataManager);
-    result[SKDEBUGCANVAS_ATTRIBUTE_TEXT] = make_json_text(fText);
-    result[SKDEBUGCANVAS_ATTRIBUTE_PAINT] = MakeJsonPaint(fPaint, urlDataManager);
-    return result;
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
 SkDrawVerticesCommand::SkDrawVerticesCommand(sk_sp<SkVertices> vertices, SkBlendMode bmode,
                                              const SkPaint& paint)
     : INHERITED(kDrawVertices_OpType)
diff --git a/tools/debugger/SkDrawCommand.h b/tools/debugger/SkDrawCommand.h
index 3244673..057001c 100644
--- a/tools/debugger/SkDrawCommand.h
+++ b/tools/debugger/SkDrawCommand.h
@@ -50,15 +50,11 @@
         kDrawPatch_OpType,
         kDrawPath_OpType,
         kDrawPoints_OpType,
-        kDrawPosText_OpType,
-        kDrawPosTextH_OpType,
         kDrawRect_OpType,
         kDrawRRect_OpType,
         kDrawRegion_OpType,
         kDrawShadow_OpType,
-        kDrawText_OpType,
         kDrawTextBlob_OpType,
-        kDrawTextRSXform_OpType,
         kDrawVertices_OpType,
         kDrawAtlas_OpType,
         kDrawDrawable_OpType,
@@ -496,69 +492,6 @@
     typedef SkDrawCommand INHERITED;
 };
 
-class SkDrawTextCommand : public SkDrawCommand {
-public:
-    SkDrawTextCommand(const void* text, size_t byteLength, SkScalar x, SkScalar y,
-                      const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    Json::Value toJSON(UrlDataManager& urlDataManager) const override;
-
-private:
-    sk_sp<SkData> fText;
-    SkScalar      fX;
-    SkScalar      fY;
-    SkPaint       fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawPosTextCommand : public SkDrawCommand {
-public:
-    SkDrawPosTextCommand(const void* text, size_t byteLength, const SkPoint pos[],
-                         const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    Json::Value toJSON(UrlDataManager& urlDataManager) const override;
-
-private:
-    sk_sp<SkData>      fText;
-    SkTDArray<SkPoint> fPos;
-    SkPaint            fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawTextRSXformCommand : public SkDrawCommand {
-public:
-    SkDrawTextRSXformCommand(const void* text, size_t byteLength, const SkRSXform[],
-                             const SkRect*, const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    Json::Value toJSON(UrlDataManager& urlDataManager) const override;
-
-private:
-    sk_sp<SkData>        fText;
-    SkTDArray<SkRSXform> fXform;
-    SkTLazy<SkRect>      fCull;
-    SkPaint              fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
-class SkDrawPosTextHCommand : public SkDrawCommand {
-public:
-    SkDrawPosTextHCommand(const void* text, size_t byteLength, const SkScalar xpos[],
-                          SkScalar constY, const SkPaint& paint);
-    void execute(SkCanvas* canvas) const override;
-    Json::Value toJSON(UrlDataManager& urlDataManager) const override;
-
-private:
-    sk_sp<SkData>       fText;
-    SkTDArray<SkScalar> fXpos;
-    SkScalar            fConstY;
-    SkPaint             fPaint;
-
-    typedef SkDrawCommand INHERITED;
-};
-
 class SkDrawTextBlobCommand : public SkDrawCommand {
 public:
     SkDrawTextBlobCommand(sk_sp<SkTextBlob> blob, SkScalar x, SkScalar y, const SkPaint& paint);
diff --git a/tools/embed_resources.py b/tools/embed_resources.py
index 56d5982..98b6c96 100644
--- a/tools/embed_resources.py
+++ b/tools/embed_resources.py
@@ -44,7 +44,7 @@
   # Write the resources.
   index = 0
   for f in args.input:
-    out('static const uint8_t resource{0:d}[] SK_STRUCT_ALIGN({1:d}) = {{\n'
+    out('alignas({1:d}) static const uint8_t resource{0:d}[] = {{\n'
         .format(index, args.align))
     bytes_written = 0
     bytes_on_line = 0
diff --git a/tools/flags/SkCommonFlagsConfig.cpp b/tools/flags/SkCommonFlagsConfig.cpp
index f7878b8..44e79ac 100644
--- a/tools/flags/SkCommonFlagsConfig.cpp
+++ b/tools/flags/SkCommonFlagsConfig.cpp
@@ -83,26 +83,27 @@
     { "angle_gl_es2_msaa8",    "gpu", "api=angle_gl_es2,samples=8" },
     { "angle_gl_es3_msaa8",    "gpu", "api=angle_gl_es3,samples=8" },
     { "commandbuffer",         "gpu", "api=commandbuffer" },
-    { "mock",                  "gpu", "api=mock" }
+    { "mock",                  "gpu", "api=mock" },
 #ifdef SK_VULKAN
-    ,{ "vk",                   "gpu", "api=vulkan" }
-    ,{ "vknostencils",         "gpu", "api=vulkan,stencils=false" }
-    ,{ "vk1010102",            "gpu", "api=vulkan,color=1010102" }
-    ,{ "vksrgb",               "gpu", "api=vulkan,color=srgb" }
-    ,{ "vkesrgb",              "gpu", "api=vulkan,color=esrgb" }
-    ,{ "vknarrow",             "gpu", "api=vulkan,color=narrow" }
-    ,{ "vkenarrow",            "gpu", "api=vulkan,color=enarrow" }
-    ,{ "vkf16",                "gpu", "api=vulkan,color=f16" }
-    ,{ "vkmsaa4",              "gpu", "api=vulkan,samples=4" }
-    ,{ "vkmsaa8",              "gpu", "api=vulkan,samples=8" }
-    ,{ "vkbetex",              "gpu", "api=vulkan,surf=betex" }
-    ,{ "vkbert",               "gpu", "api=vulkan,surf=bert" }
+    { "vk",                    "gpu", "api=vulkan" },
+    { "vknostencils",          "gpu", "api=vulkan,stencils=false" },
+    { "vk1010102",             "gpu", "api=vulkan,color=1010102" },
+    { "vksrgb",                "gpu", "api=vulkan,color=srgb" },
+    { "vkesrgb",               "gpu", "api=vulkan,color=esrgb" },
+    { "vknarrow",              "gpu", "api=vulkan,color=narrow" },
+    { "vkenarrow",             "gpu", "api=vulkan,color=enarrow" },
+    { "vkf16",                 "gpu", "api=vulkan,color=f16" },
+    { "vkmsaa4",               "gpu", "api=vulkan,samples=4" },
+    { "vkmsaa8",               "gpu", "api=vulkan,samples=8" },
+    { "vkbetex",               "gpu", "api=vulkan,surf=betex" },
+    { "vkbert",                "gpu", "api=vulkan,surf=bert" },
+    { "vktestpersistentcache", "gpu", "api=vulkan,testPersistentCache=true" },
 #endif
 #ifdef SK_METAL
-    ,{ "mtl",                   "gpu", "api=metal" }
-    ,{ "mtl1010102",            "gpu", "api=metal,color=1010102" }
-    ,{ "mtlmsaa4",              "gpu", "api=metal,samples=4" }
-    ,{ "mtlmsaa8",              "gpu", "api=metal,samples=8" }
+    { "mtl",                   "gpu", "api=metal" },
+    { "mtl1010102",            "gpu", "api=metal,color=1010102" },
+    { "mtlmsaa4",              "gpu", "api=metal,samples=4" },
+    { "mtlmsaa8",              "gpu", "api=metal,samples=8" },
 #endif
 };
 // clang-format on
@@ -308,16 +309,13 @@
         *outColorSpace = SkColorSpace::MakeSRGB();
     } else if (value.equals("p3")) {
         *outColorType = kRGBA_8888_SkColorType;
-        *outColorSpace = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma,
-                                               SkColorSpace::kDCIP3_D65_Gamut);
+        *outColorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3);
     } else if (value.equals("esrgb")) {
         *outColorType = kRGBA_F16_SkColorType;
         *outColorSpace = SkColorSpace::MakeSRGB();
     } else if (value.equals("narrow") || value.equals("enarrow")) {
-        SkMatrix44 narrow_gamut;
-        narrow_gamut.set3x3RowMajorf(gNarrow_toXYZD50);
         *outColorType = value.equals("narrow") ? kRGBA_8888_SkColorType : kRGBA_F16_SkColorType;
-        *outColorSpace = SkColorSpace::MakeRGB(k2Dot2Curve_SkGammaNamed, narrow_gamut);
+        *outColorSpace = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, gNarrow_toXYZD50);
     } else if (value.equals("f16")) {
         *outColorType = kRGBA_F16_SkColorType;
         *outColorSpace = SkColorSpace::MakeSRGBLinear();
diff --git a/tools/fonts/SkTestFontMgr.cpp b/tools/fonts/SkTestFontMgr.cpp
index cfd519f..f902e12 100644
--- a/tools/fonts/SkTestFontMgr.cpp
+++ b/tools/fonts/SkTestFontMgr.cpp
@@ -79,10 +79,12 @@
                     if (defaultFamily) {
                         fDefaultFamily = family;
                     }
+                    break;
                 }
             }
             if (!found) {
                 fFamilies.emplace_back(sk_make_sp<FontStyleSet>(sub.fFamilyName));
+                // NOLINTNEXTLINE(bugprone-use-after-move)
                 fFamilies.back()->fTypefaces.emplace_back(std::move(typeface), sub.fStyle, sub.fStyleName);
                 if (defaultFamily) {
                     fDefaultFamily = fFamilies.back();
diff --git a/tools/fonts/SkTestSVGTypeface.cpp b/tools/fonts/SkTestSVGTypeface.cpp
index 2dd7058..78ccc07 100644
--- a/tools/fonts/SkTestSVGTypeface.cpp
+++ b/tools/fonts/SkTestSVGTypeface.cpp
@@ -628,13 +628,13 @@
     int strikeSizes[3] = { 16, 64, 128 };
 
     SkPaint paint;
-    paint.setTypeface(sk_ref_sp(const_cast<SkTestSVGTypeface*>(this)));
-    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+    SkFont font;
+    font.setTypeface(sk_ref_sp(const_cast<SkTestSVGTypeface*>(this)));
 
     out->writeText("  <CBDT>\n");
     out->writeText("    <header version=\"2.0\"/>\n");
     for (size_t strikeIndex = 0; strikeIndex < SK_ARRAY_COUNT(strikeSizes); ++strikeIndex) {
-        paint.setTextSize(strikeSizes[strikeIndex]);
+        font.setSize(strikeSizes[strikeIndex]);
         out->writeText("    <strikedata index=\"");
             out->writeDecAsText(strikeIndex);
             out->writeText("\">\n");
@@ -642,7 +642,7 @@
             SkGlyphID gid = i;
             SkScalar advance;
             SkRect bounds;
-            paint.getTextWidths(&gid, sizeof(gid), &advance, &bounds);
+            font.getWidthsBounds(&gid, 1, &advance, &bounds, nullptr);
             SkIRect ibounds = bounds.roundOut();
             if (ibounds.isEmpty()) {
                 continue;
@@ -654,7 +654,8 @@
             canvas->clear(0);
             SkPixmap pix;
             surface->peekPixels(&pix);
-            canvas->drawText(&gid, sizeof(gid), -bounds.fLeft, -bounds.fTop, paint);
+            canvas->drawSimpleText(&gid, sizeof(gid), kGlyphID_SkTextEncoding,
+                                   -bounds.fLeft, -bounds.fTop, font, paint);
             canvas->flush();
             sk_sp<SkImage> image = surface->makeImageSnapshot();
             sk_sp<SkData> data = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
@@ -701,8 +702,8 @@
     out->writeText("  <CBLC>\n");
     out->writeText("    <header version=\"2.0\"/>\n");
     for (size_t strikeIndex = 0; strikeIndex < SK_ARRAY_COUNT(strikeSizes); ++strikeIndex) {
-        paint.setTextSize(strikeSizes[strikeIndex]);
-        paint.getFontMetrics(&fm);
+        font.setSize(strikeSizes[strikeIndex]);
+        font.getMetrics(&fm);
         out->writeText("    <strike index=\"");
             out->writeDecAsText(strikeIndex);
             out->writeText("\">\n");
@@ -763,7 +764,7 @@
         for (int i = 0; i < fGlyphCount; ++i) {
             SkGlyphID gid = i;
             SkRect bounds;
-            paint.getTextWidths(&gid, sizeof(gid), nullptr, &bounds);
+            font.getBounds(&gid, 1, &bounds, nullptr);
             if (bounds.isEmpty()) {
                 continue;
             }
@@ -796,8 +797,8 @@
     this->exportTtxCommon(out, "sbix");
 
     SkPaint paint;
-    paint.setTypeface(sk_ref_sp(const_cast<SkTestSVGTypeface*>(this)));
-    paint.setTextEncoding(kGlyphID_SkTextEncoding);
+    SkFont font;
+    font.setTypeface(sk_ref_sp(const_cast<SkTestSVGTypeface*>(this)));
 
     out->writeText("  <glyf>\n");
     for (int i = 0; i < fGlyphCount; ++i) {
@@ -847,7 +848,7 @@
     out->writeText("    <version value=\"1\"/>\n");
     out->writeText("    <flags value=\"00000000 00000001\"/>\n");
     for (size_t strikeIndex = 0; strikeIndex < SK_ARRAY_COUNT(strikeSizes); ++strikeIndex) {
-        paint.setTextSize(strikeSizes[strikeIndex]);
+        font.setSize(strikeSizes[strikeIndex]);
         out->writeText("    <strike>\n");
         out->writeText("      <ppem value=\"");
             out->writeDecAsText(strikeSizes[strikeIndex]);
@@ -857,7 +858,7 @@
             SkGlyphID gid = i;
             SkScalar advance;
             SkRect bounds;
-            paint.getTextWidths(&gid, sizeof(gid), &advance, &bounds);
+            font.getWidthsBounds(&gid, 1, &advance, &bounds, nullptr);
             SkIRect ibounds = bounds.roundOut();
             if (ibounds.isEmpty()) {
                 continue;
@@ -869,7 +870,8 @@
             canvas->clear(0);
             SkPixmap pix;
             surface->peekPixels(&pix);
-            canvas->drawText(&gid, sizeof(gid), -bounds.fLeft, -bounds.fTop, paint);
+            canvas->drawSimpleText(&gid, sizeof(gid), kGlyphID_SkTextEncoding,
+                                   -bounds.fLeft, -bounds.fTop, font, paint);
             canvas->flush();
             sk_sp<SkImage> image = surface->makeImageSnapshot();
             sk_sp<SkData> data = image->encodeToData(SkEncodedImageFormat::kPNG, 100);
diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp
index cd44ec9..43b9b38 100644
--- a/tools/gpu/GrTest.cpp
+++ b/tools/gpu/GrTest.cpp
@@ -27,6 +27,7 @@
 #include "SkString.h"
 #include "SkTo.h"
 #include "ccpr/GrCoverageCountingPathRenderer.h"
+#include "ccpr/GrCCPathCache.h"
 #include "ops/GrMeshDrawOp.h"
 #include "text/GrGlyphCache.h"
 #include "text/GrTextBlobCache.h"
@@ -109,8 +110,7 @@
 
     SkASSERT(proxies[index]->priv().isExact());
     sk_sp<SkImage> image(new SkImage_Gpu(sk_ref_sp(fContext), kNeedNewImageUniqueID,
-                                         kPremul_SkAlphaType, proxies[index], nullptr,
-                                         SkBudgeted::kNo));
+                                         kPremul_SkAlphaType, proxies[index], nullptr));
     return image;
 }
 
@@ -279,10 +279,55 @@
     this->onDrawPath(args);
 }
 
-const GrUniqueKey& GrCoverageCountingPathRenderer::testingOnly_getStashedAtlasKey() const {
-    return fStashedAtlasKey;
+const GrCCPerFlushResources*
+GrCoverageCountingPathRenderer::testingOnly_getCurrentFlushResources() {
+    SkASSERT(fFlushing);
+    if (fFlushingPaths.empty()) {
+        return nullptr;
+    }
+    // All pending paths should share the same resources.
+    const GrCCPerFlushResources* resources = fFlushingPaths.front()->fFlushResources.get();
+#ifdef SK_DEBUG
+    for (const auto& flushingPaths : fFlushingPaths) {
+        SkASSERT(flushingPaths->fFlushResources.get() == resources);
+    }
+#endif
+    return resources;
 }
 
+const GrCCPathCache* GrCoverageCountingPathRenderer::testingOnly_getPathCache() const {
+    return fPathCache.get();
+}
+
+const GrTexture* GrCCPerFlushResources::testingOnly_frontCopyAtlasTexture() const {
+    if (fCopyAtlasStack.empty()) {
+        return nullptr;
+    }
+    const GrTextureProxy* proxy = fCopyAtlasStack.front().textureProxy();
+    return (proxy) ? proxy->peekTexture() : nullptr;
+}
+
+const GrTexture* GrCCPerFlushResources::testingOnly_frontRenderedAtlasTexture() const {
+    if (fRenderedAtlasStack.empty()) {
+        return nullptr;
+    }
+    const GrTextureProxy* proxy = fRenderedAtlasStack.front().textureProxy();
+    return (proxy) ? proxy->peekTexture() : nullptr;
+}
+
+const SkTHashTable<GrCCPathCache::HashNode, const GrCCPathCache::Key&>&
+GrCCPathCache::testingOnly_getHashTable() const {
+    return fHashTable;
+}
+
+const SkTInternalLList<GrCCPathCacheEntry>& GrCCPathCache::testingOnly_getLRU() const {
+    return fLRU;
+}
+
+int GrCCPathCacheEntry::testingOnly_peekOnFlushRefCnt() const { return fOnFlushRefCnt; }
+
+int GrCCCachedAtlas::testingOnly_peekOnFlushRefCnt() const { return fOnFlushRefCnt; }
+
 //////////////////////////////////////////////////////////////////////////////
 
 #define DRAW_OP_TEST_EXTERN(Op) \
@@ -290,7 +335,6 @@
 #define DRAW_OP_TEST_ENTRY(Op) Op##__Test
 
 DRAW_OP_TEST_EXTERN(AAConvexPathOp);
-DRAW_OP_TEST_EXTERN(AAFillRectOp);
 DRAW_OP_TEST_EXTERN(AAFlatteningConvexPathOp);
 DRAW_OP_TEST_EXTERN(AAHairlineOp);
 DRAW_OP_TEST_EXTERN(AAStrokeRectOp);
@@ -303,7 +347,6 @@
 DRAW_OP_TEST_EXTERN(GrAtlasTextOp);
 DRAW_OP_TEST_EXTERN(GrDrawAtlasOp);
 DRAW_OP_TEST_EXTERN(GrDrawVerticesOp);
-DRAW_OP_TEST_EXTERN(NonAAFillRectOp);
 DRAW_OP_TEST_EXTERN(NonAALatticeOp);
 DRAW_OP_TEST_EXTERN(NonAAStrokeRectOp);
 DRAW_OP_TEST_EXTERN(ShadowRRectOp);
@@ -318,7 +361,6 @@
     using MakeDrawOpFn = std::unique_ptr<GrDrawOp>(GrPaint&&, SkRandom*, GrContext*, GrFSAAType);
     static constexpr MakeDrawOpFn* gFactories[] = {
             DRAW_OP_TEST_ENTRY(AAConvexPathOp),
-            DRAW_OP_TEST_ENTRY(AAFillRectOp),
             DRAW_OP_TEST_ENTRY(AAFlatteningConvexPathOp),
             DRAW_OP_TEST_ENTRY(AAHairlineOp),
             DRAW_OP_TEST_ENTRY(AAStrokeRectOp),
@@ -331,7 +373,6 @@
             DRAW_OP_TEST_ENTRY(GrAtlasTextOp),
             DRAW_OP_TEST_ENTRY(GrDrawAtlasOp),
             DRAW_OP_TEST_ENTRY(GrDrawVerticesOp),
-            DRAW_OP_TEST_ENTRY(NonAAFillRectOp),
             DRAW_OP_TEST_ENTRY(NonAALatticeOp),
             DRAW_OP_TEST_ENTRY(NonAAStrokeRectOp),
             DRAW_OP_TEST_ENTRY(ShadowRRectOp),
diff --git a/tools/gpu/ProxyUtils.cpp b/tools/gpu/ProxyUtils.cpp
index fc167d8..a1f4df6 100644
--- a/tools/gpu/ProxyUtils.cpp
+++ b/tools/gpu/ProxyUtils.cpp
@@ -36,7 +36,7 @@
                     backendTex, origin, 1, kAdopt_GrWrapOwnership);
         } else {
             proxy = context->contextPriv().proxyProvider()->wrapBackendTexture(
-                    backendTex, origin, kAdopt_GrWrapOwnership);
+                    backendTex, origin, kAdopt_GrWrapOwnership, kRW_GrIOType);
         }
 
         if (!proxy) {
diff --git a/tools/gpu/gl/angle/GLTestContext_angle.cpp b/tools/gpu/gl/angle/GLTestContext_angle.cpp
index 50d7129..303b8ea 100644
--- a/tools/gpu/gl/angle/GLTestContext_angle.cpp
+++ b/tools/gpu/gl/angle/GLTestContext_angle.cpp
@@ -7,6 +7,8 @@
 
 #include "GLTestContext_angle.h"
 
+#define EGL_EGL_PROTOTYPES 1
+
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 
diff --git a/tools/gpu/gl/iOS/CreatePlatformGLTestContext_iOS.mm b/tools/gpu/gl/iOS/CreatePlatformGLTestContext_iOS.mm
index c62a330..2b0ad81 100644
--- a/tools/gpu/gl/iOS/CreatePlatformGLTestContext_iOS.mm
+++ b/tools/gpu/gl/iOS/CreatePlatformGLTestContext_iOS.mm
@@ -42,10 +42,17 @@
 
     if (shareContext) {
         EAGLContext* iosShareContext = shareContext->fEAGLContext;
-        fEAGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2
-                                            sharegroup: [iosShareContext sharegroup]];
+        fEAGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3
+                                            sharegroup:[iosShareContext sharegroup]];
+        if (fEAGLContext == nil) {
+            fEAGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2
+                                                sharegroup:[iosShareContext sharegroup]];
+        }
     } else {
-        fEAGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+        fEAGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
+        if (fEAGLContext == nil) {
+            fEAGLContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+        }
     }
     SkScopeExit restorer(context_restorer());
     [EAGLContext setCurrentContext:fEAGLContext];
diff --git a/tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp b/tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp
index c09f0b3..5d496e4 100644
--- a/tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp
+++ b/tools/gpu/gl/win/CreatePlatformGLTestContext_win.cpp
@@ -8,6 +8,16 @@
 
 #include "gl/GLTestContext.h"
 
+#if defined(_M_ARM64)
+
+namespace sk_gpu_test {
+
+GLTestContext* CreatePlatformGLTestContext(GrGLStandard, GLTestContext*) { return nullptr; }
+
+}  // namespace sk_gpu_test
+
+#else
+
 #include <windows.h>
 #include <GL/GL.h>
 #include "win/SkWGL.h"
@@ -220,3 +230,4 @@
 }
 }  // namespace sk_gpu_test
 
+#endif
diff --git a/tools/imgcvt.cpp b/tools/imgcvt.cpp
index afc9498..a1b96e2 100644
--- a/tools/imgcvt.cpp
+++ b/tools/imgcvt.cpp
@@ -28,9 +28,10 @@
 
     const char* dst_profile_path = argc > 2 ? argv[2] : nullptr;
     skcms_ICCProfile dst_profile = *skcms_sRGB_profile();
+    sk_sp<SkData> dst_blob;
     if (dst_profile_path) {
-        sk_sp<SkData> blob = SkData::MakeFromFileName(dst_profile_path);
-        if (!skcms_Parse(blob->data(), blob->size(), &dst_profile)) {
+        dst_blob = SkData::MakeFromFileName(dst_profile_path);
+        if (!skcms_Parse(dst_blob->data(), dst_blob->size(), &dst_profile)) {
             SkDebugf("Can't parse %s as an ICC profile.\n", dst_profile_path);
             return 1;
         }
@@ -90,8 +91,14 @@
 
     sk_sp<SkColorSpace> dst_cs = SkColorSpace::Make(dst_profile);
     if (!dst_cs) {
-        SkDebugf("We can't convert to this destination profile.\n");
-        return 1;
+        SkDebugf("We can't convert to this destination profile as-is. Coercing it.\n");
+        if (skcms_MakeUsableAsDestinationWithSingleCurve(&dst_profile)) {
+            dst_cs = SkColorSpace::Make(dst_profile);
+        }
+        if (!dst_cs) {
+            SkDebugf("We can't convert to this destination profile at all.\n");
+            return 1;
+        }
     }
 
     { // transform with skcms
@@ -109,7 +116,7 @@
                 return 1;
         }
 
-        if (pixmap.alphaType() != kPremul_SkAlphaType) {
+        if (pixmap.alphaType() == kUnpremul_SkAlphaType) {
             SkDebugf("not premul, that's weird.\n");
             return 1;
         }
diff --git a/tools/pathops_sorter.htm b/tools/pathops_sorter.htm
index 017d916..c2c5103 100644
--- a/tools/pathops_sorter.htm
+++ b/tools/pathops_sorter.htm
@@ -7,8 +7,9 @@
 <div style="height:0">
 
     <div id="cubics">

-        {{{0.00000000000000000, 0.00000000000000000 }, {0.00022939755581319332, 0.00022927834652364254 },{0.00022930106206331402, 0.00022929999977350235 }, {0.00022930069826543331, 0.00022913678549230099}}},

-        {{{0.00022930069826543331, 0.00022930069826543331 }, {0.00011465034913271666, 0.00011465034913271666 },{0.00011465061106719077, 0.00011460937093943357 }, {0.00014331332931760699, 0.00014325146912597120}}},

+{{{fX=124.70011901855469 fY=9.3718261718750000 } {fX=124.66775026544929 fY=9.3744316215161234 } {fX=124.63530969619751 fY=9.3770743012428284 }{fX=124.60282897949219 fY=9.3797206878662109 } id=10      

+{{{fX=124.70011901855469 fY=9.3718004226684570 } {fX=124.66775026544929 fY=9.3744058723095804 } {fX=124.63530969619751 fY=9.3770485520362854 } {fX=124.60282897949219 fY=9.3796949386596680 } id=1

+{{{fX=124.70011901855469 fY=9.3718004226684570 } {fX=124.66786243087600 fY=9.3743968522034287 } {fX=124.63553249625420 fY=9.3770303056986286 } {fX=124.60316467285156 fY=9.3796672821044922 } id=2

     </div>
 
     </div>
diff --git a/tools/pathops_visualizer.htm b/tools/pathops_visualizer.htm
index 2d411d1..c8afb44 100644
--- a/tools/pathops_visualizer.htm
+++ b/tools/pathops_visualizer.htm
@@ -1,1026 +1,538 @@
 <html>
 <head>
 <div height="0" hidden="true">

-<div id="crbug_526025">

+<div id="bug8380">

 SkDCubic::ComplexBreak

-{{{360, -2147483648}, {593011648, -2147483520}, {1073742208, -1666752896}, {1073742080, -1073741568}}},

-maxCurvature[0]=0.500000161 {{{-406603233.5424470901, -2998846434.998814106}, {1925104612.000926018, -667136804.5425114632}}},

-seg=1 {{{360, -2147483648}, {593011648, -2147483520}, {1073742208, -1666752896}, {1073742080, -1073741568}}}

-seg=2 {{{1073742080, -1073741568}, {1073742080, -480730560}, {593011840, -135.508026f}, {905.953125f, 255.999786f}}}

-seg=3 {{{905.953125f, 255.999786f}, {815.80835f, 897.304565f}}}

-seg=4 {{{815.80835f, 897.304565f}, {213.229446f, 572.949036f}}}

-seg=5 {{{213.229446f, 572.949036f}, {32, 16}}}

-seg=6 {{{32, 16}, {100, 512}}}

-seg=7 {{{100, 512}, {213.229446f, 572.949036f}}}

-seg=8 {{{213.229446f, 572.949036f}, {360, 1024}}}

-seg=9 {{{360, 1024}, {360, 4140}}}

-seg=10 {{{360, 4140}, {360, -2147483648}}}

-op union

-seg=11 {{{127, 321}, {6840, 270}}}

-seg=12 {{{6840, 270}, {-21474836480, 100000000}}}

-seg=13 {{{-21474836480, 100000000}, {2551, 64}}}

-seg=14 {{{2551, 64}, {127, 321}}}

-debugShowCubicIntersection wtTs[0]=1 {{{360,-2.14748365e+09}, {593011648,-2.14748352e+09}, {1.07374221e+09,-1.6667529e+09}, {1.07374208e+09,-1.07374157e+09}}} {{1.07374208e+09,-1.07374157e+09}} wnTs[0]=0 {{{1.07374208e+09,-1.07374157e+09}, {1.07374208e+09,-480730560}, {593011840,-135.508026}, {905.953125,255.999786}}}

-debugShowCubicLineIntersection wtTs[0]=0 {{{360,-2.14748365e+09}, {593011648,-2.14748352e+09}, {1.07374221e+09,-1.6667529e+09}, {1.07374208e+09,-1.07374157e+09}}} {{360,-2.14748365e+09}} wnTs[0]=1 {{{360,4140}, {360,-2.14748365e+09}}}

-debugShowCubicLineIntersection wtTs[0]=1 {{{1.07374208e+09,-1.07374157e+09}, {1.07374208e+09,-480730560}, {593011840,-135.508026}, {905.953125,255.999786}}} {{905.953125,255.999786}} wnTs[0]=0 {{{905.953125,255.999786}, {815.80835,897.304565}}}

-debugShowLineIntersection wtTs[0]=1 {{{905.953125,255.999786}, {815.80835,897.304565}}} {{815.80835,897.304565}} wnTs[0]=0 {{{815.80835,897.304565}, {213.229446,572.949036}}}

-debugShowLineIntersection wtTs[0]=1 {{{815.80835,897.304565}, {213.229446,572.949036}}} {{213.229446,572.949036}} wnTs[0]=0 {{{213.229446,572.949036}, {32,16}}}

-debugShowLineIntersection wtTs[0]=1 {{{815.80835,897.304565}, {213.229446,572.949036}}} {{213.229446,572.949036}} wnTs[0]=1 {{{100,512}, {213.229446,572.949036}}}

-debugShowLineIntersection wtTs[0]=1 {{{815.80835,897.304565}, {213.229446,572.949036}}} {{213.229446,572.949036}} wnTs[0]=0 {{{213.229446,572.949036}, {360,1024}}}

-debugShowLineIntersection wtTs[0]=0.756429319 {{{815.80835,897.304565}, {213.229446,572.949036}}} {{360,651.952515}} wnTs[0]=1.62425e-06 {{{360,4140}, {360,-2.14748365e+09}}}

-SkOpSegment::addT insert t=0.756429319 segID=4 spanID=29

-SkOpSegment::addT insert t=1.62424554e-06 segID=10 spanID=30

-debugShowLineIntersection wtTs[0]=1 {{{213.229446,572.949036}, {32,16}}} {{32,16}} wnTs[0]=0 {{{32,16}, {100,512}}}

-debugShowLineIntersection wtTs[0]=0 {{{213.229446,572.949036}, {32,16}}} {{213.229446,572.949036}} wnTs[0]=1 {{{100,512}, {213.229446,572.949036}}}

-debugShowLineIntersection wtTs[0]=0 {{{213.229446,572.949036}, {32,16}}} {{213.229446,572.949036}} wnTs[0]=0 {{{213.229446,572.949036}, {360,1024}}}

-debugShowLineIntersection wtTs[0]=1 {{{32,16}, {100,512}}} {{100,512}} wnTs[0]=0 {{{100,512}, {213.229446,572.949036}}}

-debugShowLineIntersection wtTs[0]=1 {{{100,512}, {213.229446,572.949036}}} {{213.229446,572.949036}} wnTs[0]=0 {{{213.229446,572.949036}, {360,1024}}}

-debugShowLineIntersection wtTs[0]=1 {{{213.229446,572.949036}, {360,1024}}} {{360,1024}} wnTs[0]=0 {{{360,1024}, {360,4140}}}

-debugShowLineIntersection wtTs[0]=1 {{{213.229446,572.949036}, {360,1024}}} {{360,1024}} wnTs[0]=1.451e-06 {{{360,4140}, {360,-2.14748365e+09}}}

-SkOpSegment::addT insert t=1.45099777e-06 segID=10 spanID=31

-debugShowLineIntersection wtTs[0]=0 {{{360,4140}, {360,-2.14748365e+09}}} {{360,4140}} wtTs[1]=1.45099777e-06 {{360,1024}} wnTs[0]=1 {{{360,1024}, {360,4140}}} wnTs[1]=0

-debugShowCubicLineIntersection wtTs[0]=1 {{{1.07374208e+09,-1.07374157e+09}, {1.07374208e+09,-480730560}, {593011840,-135.508026}, {905.953125,255.999786}}} {{905.953125,255.999786}} wnTs[0]=1 {{{-2.14748365e+10,100000000}, {2551,64}}}

-SkOpSegment::addT insert t=0.999999923 segID=13 spanID=32

-debugShowCubicLineIntersection no intersect {{{1.07374208e+09,-1.07374157e+09}, {1.07374208e+09,-480730560}, {593011840,-135.508026}, {905.953125,255.999786}}} {{{2551,64}, {127,321}}}

-debugShowLineIntersection wtTs[0]=0.0922268392 {{{905.953125,255.999786}, {815.80835,897.304565}}} {{897.639343,315.145294}} wnTs[0]=0.114798 {{{127,321}, {6840,270}}}

-SkOpSegment::addT insert t=0.0922268392 segID=3 spanID=33

-SkOpSegment::addT insert t=0.114798057 segID=11 spanID=34

-debugShowLineIntersection wtTs[0]=0.0649612467 {{{905.953125,255.999786}, {815.80835,897.304565}}} {{900.097229,297.65976}} wtTs[1]=1 {{815.80835,897.304565}} wnTs[0]=2.76598e-07 {{{6840,270}, {-2.14748365e+10,100000000}}} wnTs[1]=2.80653133e-07

-SkOpSegment::addT insert t=0.0649612467 segID=3 spanID=35

-SkOpSegment::addT insert t=2.7659819e-07 segID=12 spanID=36

-SkOpSegment::addT insert t=2.80653133e-07 segID=12 spanID=37

-debugShowLineIntersection wtTs[0]=0 {{{905.953125,255.999786}, {815.80835,897.304565}}} {{905.953125,255.999786}} wtTs[1]=1 {{815.80835,897.304565}} wnTs[0]=1 {{{-2.14748365e+10,100000000}, {2551,64}}} wnTs[1]=0.999999919

-SkOpSegment::addT insert t=0.999999919 segID=13 spanID=38

-debugShowLineIntersection no intersect {{{905.953125,255.999786}, {815.80835,897.304565}}} {{{2551,64}, {127,321}}}

-debugShowLineIntersection wtTs[0]=0 {{{815.80835,897.304565}, {213.229446,572.949036}}} {{815.80835,897.304565}} wtTs[1]=1 {{213.229446,572.949036}} wnTs[0]=2.80653e-07 {{{6840,270}, {-2.14748365e+10,100000000}}} wnTs[1]=3.08641951e-07

-SkOpSegment::addT insert t=3.08641951e-07 segID=12 spanID=39

-debugShowLineIntersection wtTs[0]=0 {{{815.80835,897.304565}, {213.229446,572.949036}}} {{815.80835,897.304565}} wtTs[1]=1 {{213.229446,572.949036}} wnTs[0]=1 {{{-2.14748365e+10,100000000}, {2551,64}}} wnTs[1]=0.999999891

-SkOpSegment::addT insert t=0.999999891 segID=13 spanID=40

-debugShowLineIntersection wtTs[0]=0.452431368 {{{213.229446,572.949036}, {32,16}}} {{131.235565,320.967834}} wnTs[0]=0.000630949 {{{127,321}, {6840,270}}}

-SkOpSegment::addT insert t=0.452431368 segID=5 spanID=41

-SkOpSegment::addT insert t=0.000630948916 segID=11 spanID=42

-debugShowLineIntersection wtTs[0]=0 {{{213.229446,572.949036}, {32,16}}} {{213.229446,572.949036}} wtTs[1]=0.487798966 {{124.825912,301.269867}} wnTs[0]=3.08642e-07 {{{6840,270}, {-2.14748365e+10,100000000}}} wnTs[1]=3.12699562e-07

-SkOpSegment::addT insert t=0.487798966 segID=5 spanID=43

-SkOpSegment::addT insert t=3.12699562e-07 segID=12 spanID=44

-debugShowLineIntersection wtTs[0]=0 {{{213.229446,572.949036}, {32,16}}} {{213.229446,572.949036}} wtTs[1]=0.892917257 {{51.4065475,75.6396332}} wnTs[0]=1 {{{-2.14748365e+10,100000000}, {2551,64}}} wnTs[1]=0.999999884

-SkOpSegment::addT insert t=0.892917257 segID=5 spanID=45

-SkOpSegment::addT insert t=0.999999884 segID=13 spanID=46

-debugShowLineIntersection wtTs[0]=0.453154928 {{{213.229446,572.949036}, {32,16}}} {{131.104431,320.56485}} wnTs[0]=0.998307 {{{2551,64}, {127,321}}}

-SkOpSegment::addT insert t=0.453154928 segID=5 spanID=47

-SkOpSegment::addT insert t=0.998306753 segID=14 spanID=48

-debugShowLineIntersection wtTs[0]=0.575644854 {{{32,16}, {100,512}}} {{71.1438522,301.519836}} wtTs[1]=1 {{100,512}} wnTs[0]=3.15199e-07 {{{6840,270}, {-2.14748365e+10,100000000}}} wnTs[1]=3.13901276e-07

-SkOpSegment::addT insert t=0.575644854 segID=6 spanID=49

-SkOpSegment::addT insert t=3.15199326e-07 segID=12 spanID=50

-SkOpSegment::addT insert t=3.13901276e-07 segID=12 spanID=51

-debugShowLineIntersection wtTs[0]=0.120346555 {{{32,16}, {100,512}}} {{40.183567,75.6918945}} wtTs[1]=1 {{100,512}} wnTs[0]=1 {{{-2.14748365e+10,100000000}, {2551,64}}} wnTs[1]=0.999999886

-SkOpSegment::addT insert t=0.120346555 segID=6 spanID=52

-SkOpSegment::addT insert t=0.999999883 segID=13 spanID=53

-SkOpSegment::addT insert t=0.999999886 segID=13 spanID=54

-debugShowLineIntersection wtTs[0]=0 {{{100,512}, {213.229446,572.949036}}} {{100,512}} wtTs[1]=1 {{213.229446,572.949036}} wnTs[0]=3.13901e-07 {{{6840,270}, {-2.14748365e+10,100000000}}} wnTs[1]=3.08641951e-07

-debugShowLineIntersection wtTs[0]=0 {{{100,512}, {213.229446,572.949036}}} {{100,512}} wtTs[1]=1 {{213.229446,572.949036}} wnTs[0]=1 {{{-2.14748365e+10,100000000}, {2551,64}}} wnTs[1]=0.999999891

-debugShowLineIntersection wtTs[0]=0 {{{213.229446,572.949036}, {360,1024}}} {{213.229446,572.949036}} wtTs[1]=1 {{360,1024}} wnTs[0]=3.08642e-07 {{{6840,270}, {-2.14748365e+10,100000000}}} wnTs[1]=3.01905369e-07

-SkOpSegment::addT insert t=3.01905369e-07 segID=12 spanID=55

-debugShowLineIntersection wtTs[0]=0 {{{213.229446,572.949036}, {360,1024}}} {{213.229446,572.949036}} wtTs[1]=1 {{360,1024}} wnTs[0]=1 {{{-2.14748365e+10,100000000}, {2551,64}}} wnTs[1]=0.999999898

-SkOpSegment::addT insert t=0.999999898 segID=13 spanID=56

-debugShowLineIntersection wtTs[0]=3.01905369e-07 {{{6840,270}, {-2.14748365e+10,100000000}}} {{360,1024}} wnTs[0]=0 {{{360,1024}, {360,4140}}}

-debugShowLineIntersection wtTs[0]=0.999999897 {{{-2.14748365e+10,100000000}, {2551,64}}} {{360,4140}} wnTs[0]=1 {{{360,1024}, {360,4140}}}

-SkOpSegment::addT insert t=0.999999897 segID=13 spanID=57

-debugShowLineIntersection wtTs[0]=0.034708774 {{{127,321}, {6840,270}}} {{360,319.229858}} wnTs[0]=1.77918e-06 {{{360,4140}, {360,-2.14748365e+09}}}

-SkOpSegment::addT insert t=1.77918132e-06 segID=10 spanID=58

-SkOpSegment::addT insert t=0.034708774 segID=11 spanID=59

-debugShowLineIntersection wtTs[0]=3.02581027e-07 {{{6840,270}, {-2.14748365e+10,100000000}}} {{360,4140}} wnTs[0]=0 {{{360,4140}, {360,-2.14748365e+09}}}

-SkOpSegment::addT insert t=3.02581027e-07 segID=12 spanID=60

-debugShowLineIntersection wtTs[0]=0.999999897 {{{-2.14748365e+10,100000000}, {2551,64}}} {{360,4140}} wnTs[0]=0 {{{360,4140}, {360,-2.14748365e+09}}}

-debugShowLineIntersection wtTs[0]=0.903877888 {{{2551,64}, {127,321}}} {{360,296.296631}} wnTs[0]=1.78986e-06 {{{360,4140}, {360,-2.14748365e+09}}}

-SkOpSegment::addT insert t=1.78986041e-06 segID=10 spanID=61

-SkOpSegment::addT insert t=0.903877888 segID=14 spanID=62

-debugShowLineIntersection wtTs[0]=0 {{{127,321}, {6840,270}}} {{127,321}} wtTs[1]=1 {{6840,270}} wnTs[0]=3.12603e-07 {{{6840,270}, {-2.14748365e+10,100000000}}} wnTs[1]=0

-SkOpSegment::addT insert t=3.12602603e-07 segID=12 spanID=63

-debugShowLineIntersection wtTs[0]=0 {{{127,321}, {6840,270}}} {{127,321}} wnTs[0]=1 {{{-2.14748365e+10,100000000}, {2551,64}}}

-SkOpSegment::addT insert t=0.999999887 segID=13 spanID=64

-debugShowLineIntersection wtTs[0]=0 {{{127,321}, {6840,270}}} {{127,321}} wnTs[0]=1 {{{2551,64}, {127,321}}}

-debugShowLineIntersection wtTs[0]=1 {{{6840,270}, {-2.14748365e+10,100000000}}} {{-2.14748365e+10,100000000}} wnTs[0]=0 {{{-2.14748365e+10,100000000}, {2551,64}}}

-debugShowLineIntersection wtTs[0]=3.03529974e-07 {{{6840,270}, {-2.14748365e+10,100000000}}} {{321.741364,300.352905}} wnTs[0]=0.919661 {{{2551,64}, {127,321}}}

-SkOpSegment::addT insert t=3.03529974e-07 segID=12 spanID=65

-SkOpSegment::addT insert t=0.919661149 segID=14 spanID=66

-debugShowLineIntersection wtTs[0]=1 {{{-2.14748365e+10,100000000}, {2551,64}}} {{2551,64}} wnTs[0]=0 {{{2551,64}, {127,321}}}

---x---x----------xx-xxxx----------x--- addExpanded

-00:  seg/base=13/38 coinSeg/Span/PtT=13/54/54 MergeMatches

-01:  seg/base=12/37 coinSeg/Span/PtT=12/51/51 MergeMatches

-02:  seg/base=13/38 coinSeg/Span/PtT=13/54/54 MergeMatches

-03:  seg/base=12/37 coinSeg/Span/PtT=12/51/51 MergeMatches

-04:  seg/base=13/38 coinSeg/Span/PtT=13/54/54 MergeMatches

-05:  seg/base=12/37 coinSeg/Span/PtT=12/51/51 MergeMatches

-06:  seg/base=13/38 coinSeg/Span/PtT=13/54/54 MergeMatches

-07:  seg/base=12/37 coinSeg/Span/PtT=12/51/51 MergeMatches

-08:  seg/base=13/40 coinSeg/Span/PtT=13/46/46 MergeMatches

-09:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-10:  seg/base=13/40 coinSeg/Span/PtT=13/46/46 MergeMatches

-11:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-12:  seg/base=13/40 coinSeg/Span/PtT=13/54/54 MergeMatches

-13:  seg/base=12/39 coinSeg/Span/PtT=12/51/51 MergeMatches

-14:  segment=7 MergeMatches

-15:  seg/base=13/40 coinSeg/Span/PtT=13/54/54 MergeMatches

-16:  seg/base=12/39 coinSeg/Span/PtT=12/51/51 MergeMatches

-17:  segment=7 MergeMatches

-18:  seg/base=13/54 coinSeg/Span/PtT=13/64/64 MergeMatches

-19:  seg/base=12/51 coinSeg/Span/PtT=12/63/63 MergeMatches

-20:  seg/base=13/54 coinSeg/Span/PtT=13/64/64 MergeMatches

-21:  seg/base=12/51 coinSeg/Span/PtT=12/63/63 MergeMatches

-22:  seg/base=13/54 coinSeg/Span/PtT=13/53/53 MergeMatches

-23:  seg/base=6/52 coinSeg/Span/PtT=6/12/12 MergeMatches

-24:  seg/base=13/54 coinSeg/Span/PtT=13/53/53 MergeMatches

-25:  seg/base=6/52 coinSeg/Span/PtT=6/12/12 MergeMatches

-26:  seg/base=13/40 coinSeg/Span/PtT=13/46/46 MergeMatches

-27:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-28:  seg/base=13/40 coinSeg/Span/PtT=13/46/46 MergeMatches

-29:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-30:  seg/base=13/40 coinSeg/Span/PtT=13/46/46 MergeMatches

-31:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-32:  seg/base=13/40 coinSeg/Span/PtT=13/46/46 MergeMatches

-33:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-34:  seg/base=13/56 coinSeg/Span/PtT=13/54/54 MergeMatches

-35:  seg/base=12/55 coinSeg/Span/PtT=12/51/51 MergeMatches

-36:  seg/base=13/56 coinSeg/Span/PtT=13/54/54 MergeMatches

-37:  seg/base=12/55 coinSeg/Span/PtT=12/51/51 MergeMatches

-38:  seg/base=13/56 coinSeg/Span/PtT=13/54/54 MergeMatches

-39:  seg/base=12/55 coinSeg/Span/PtT=12/51/51 MergeMatches

-40:  seg/base=13/56 coinSeg/Span/PtT=13/54/54 MergeMatches

-41:  seg/base=12/55 coinSeg/Span/PtT=12/51/51 MergeMatches

-42:  seg/base=12/60 coinSeg/Span/PtT=12/55/55 MergeMatches

-43:  seg/base=10/31 coinSeg/Span/PtT=10/19/19 MergeMatches

-44:  segment=9 MergeMatches

-45:  seg/base=13/57 coinSeg/Span/PtT=13/56/56 MergeMatches

-46:  seg/base=12/60 coinSeg/Span/PtT=12/55/55 MergeMatches

-47:  seg/base=10/31 coinSeg/Span/PtT=10/19/19 MergeMatches

-48:  segment=9 MergeMatches

-49:  seg/base=13/57 coinSeg/Span/PtT=13/56/56 MergeMatches

-50:  coinSeg/Span/PtT=13/57/57 endSpan=56 oppSeg/Span/PtT=9/18/18 oppEndSpan=17 MissingCoin

-51:  coinSeg/Span/PtT=12/55/55 endSpan=60 oppSeg/Span/PtT=9/17/17 oppEndSpan=18 MissingCoin

-52:  seg/base=13/57 coinSeg/Span/PtT=13/54/54 MergeMatches

-53:  seg/base=12/60 coinSeg/Span/PtT=12/51/51 MergeMatches

-54:  seg/base=13/57 coinSeg/Span/PtT=13/54/54 MergeMatches

-55:  seg/base=12/60 coinSeg/Span/PtT=12/51/51 MergeMatches

-56:  seg/base=13/56 coinSeg/Span/PtT=13/54/54 MergeMatches

-57:  seg/base=12/55 coinSeg/Span/PtT=12/51/51 MergeMatches

-58:  seg/base=13/56 coinSeg/Span/PtT=13/54/54 MergeMatches

-59:  seg/base=12/55 coinSeg/Span/PtT=12/51/51 MergeMatches

-60:  coinSeg/Span/PtT=13/57/57 endSpan=56 oppSeg/Span/PtT=10/19/19 oppEndSpan=31 MissingCoin

-61:  coinSeg/Span/PtT=12/55/55 endSpan=60 oppSeg/Span/PtT=10/31/31 oppEndSpan=19 MissingCoin

-62:  coinSeg/Span/PtT=13/57/57 endSpan=56 oppSeg/Span/PtT=9/18/18 oppEndSpan=17 MissingCoin

-63:  coinSeg/Span/PtT=12/55/55 endSpan=60 oppSeg/Span/PtT=9/17/17 oppEndSpan=18 MissingCoin

-64:  coinSeg/Span/PtT=13/57/57 endSpan=56 oppSeg/Span/PtT=10/19/19 oppEndSpan=31 MissingCoin

-65:  coinSeg/Span/PtT=12/55/55 endSpan=60 oppSeg/Span/PtT=10/31/31 oppEndSpan=19 MissingCoin

-66:  seg/base=13/64 coinSeg/Span/PtT=13/54/54 MergeMatches

-67:  seg/base=12/63 coinSeg/Span/PtT=12/51/51 MergeMatches

-68:  seg/base=13/64 coinSeg/Span/PtT=13/54/54 MergeMatches

-69:  seg/base=12/63 coinSeg/Span/PtT=12/51/51 MergeMatches

-70:  seg/base=13/56 coinSeg/Span/PtT=13/57/57 MergeMatches

-71:  seg/base=10/31 coinSeg/Span/PtT=10/19/19 MergeMatches

-72:  seg/base=12/55 coinSeg/Span/PtT=12/60/60 MergeMatches

-73:  segment=9 MergeMatches

-74:  seg/base=13/56 coinSeg/Span/PtT=13/57/57 MergeMatches

-75:  seg/base=10/31 coinSeg/Span/PtT=10/19/19 MergeMatches

-76:  seg/base=12/55 coinSeg/Span/PtT=12/60/60 MergeMatches

-77:  segment=9 MergeMatches

-78:  coinSeg/Span/PtT=13/25/25 endSpan=54 oppSeg/Span/PtT=12/24/24 oppEndSpan=51 MissingCoin

-79:  seg/base=10/31 coinSeg/Span/PtT=10/19/19 MergeMatches

-80:  segment=9 MergeMatches

-81:  seg/base=13/57 coinSeg/Span/PtT=13/56/56 MergeMatches

-82:  seg/base=12/60 coinSeg/Span/PtT=12/55/55 MergeMatches

-83:  seg/base=10/31 coinSeg/Span/PtT=10/19/19 MergeMatches

-84:  segment=9 MergeMatches

-85:  seg/base=13/57 coinSeg/Span/PtT=13/56/56 MergeMatches

-86:  seg/base=12/60 coinSeg/Span/PtT=12/55/55 MergeMatches

-87:  coinSeg/Span/PtT=12/51/51 endSpan=24 oppSeg/Span/PtT=13/54/54 oppEndSpan=25 MissingCoin

-88:  coinSeg/Span/PtT=13/64/64 endSpan=26 oppSeg/Span/PtT=14/28/28 oppEndSpan=27 MissingCoin

-89:  coinSeg/Span/PtT=12/65/65 endSpan=63 oppSeg/Span/PtT=14/66/66 oppEndSpan=28 MissingCoin

-90:  coinSeg/Span/PtT=13/25/25 endSpan=54 oppSeg/Span/PtT=12/24/24 oppEndSpan=51 MissingCoin

-91:  coinSeg/Span/PtT=12/51/51 endSpan=24 oppSeg/Span/PtT=13/54/54 oppEndSpan=25 MissingCoin

-92:  coinSeg/Span/PtT=13/64/64 endSpan=26 oppSeg/Span/PtT=14/28/28 oppEndSpan=27 MissingCoin

-93:  coinSeg/Span/PtT=12/65/65 endSpan=63 oppSeg/Span/PtT=14/66/66 oppEndSpan=28 MissingCoin

-94:  segment=11 ReturnFalse

-95:  seg/base=11/21 Fail

-96:  seg/base=11/42 Fail

-97:  seg/base=11/21 Fail

-98:  seg/base=11/42 Fail

-99:  seg/base=8/15 Fail

-100:  segment=11 ReturnFalse

-101:  segment=11 startT=0.0126699 endT=0.0342199 segment=8 oppStartT=0 oppEndT=1 AddMissingCoin

-102:  segment=11 ReturnFalse

-103:  seg/base=7/13 Fail

-104:  seg/base=11/21 Fail

-105:  seg/base=11/42 Fail

-106:  seg/base=7/13 Fail

-107:  segment=11 ReturnFalse

-108:  segment=11 startT=0 endT=0.0126699 segment=7 oppStartT=0.246928 oppEndT=1 AddMissingCoin

-109:  segment=11 ReturnFalse

-110:  seg/base=5/9 Fail

-111:  seg/base=5/41 Fail

-112:  seg/base=5/47 Fail

-113:  seg/base=11/21 Fail

-114:  seg/base=11/42 Fail

-115:  segment=11 ReturnFalse

-116:  segment=11 startT=0 endT=0.0126699 segment=5 oppStartT=0.476143 oppEndT=0 AddMissingCoin

-117:  segment=11 ReturnFalse

-118:  seg/base=11/21 Fail

-119:  seg/base=11/42 Fail

-120:  seg/base=4/7 Fail

-121:  seg/base=4/29 Fail

-122:  seg/base=11/21 Fail

-123:  seg/base=11/42 Fail

-124:  seg/base=11/59 Fail

-125:  segment=11 ReturnFalse

-126:  segment=11 startT=0.0126699 endT=0.102205 segment=4 oppStartT=1 oppEndT=0 AddMissingCoin

-127:  segment=11 ReturnFalse

-128:  seg/base=11/21 Fail

-129:  seg/base=11/42 Fail

-130:  seg/base=11/59 Fail

-131:  seg/base=3/5 Fail

-132:  seg/base=3/35 Fail

-133:  seg/base=3/33 Fail

-134:  seg/base=11/21 Fail

-135:  seg/base=11/42 Fail

-136:  seg/base=11/59 Fail

-137:  seg/base=11/34 Fail

-138:  seg/base=3/5 Fail

-139:  segment=11 ReturnFalse

-140:  segment=11 startT=0.102205 endT=0.115176 segment=3 oppStartT=1 oppEndT=0.0649612 AddMissingCoin

-141:  segment=13 ReturnFalse

-142:  segment=13 startT=1 endT=1 segment=12 oppStartT=3.08642e-07 oppEndT=3.01905e-07 AddMissingCoin

-143:  segment=8 ReturnFalse

-144:  seg/base=8/15 Fail

-145:  seg/base=4/7 Fail

-146:  seg/base=4/29 Fail

-147:  segment=8 ReturnFalse

-148:  segment=8 startT=0 endT=1 segment=4 oppStartT=1 oppEndT=0.759312 AddMissingCoin

-149:  segment=8 ReturnFalse

-150:  seg/base=8/15 Fail

-151:  seg/base=4/7 Fail

-152:  seg/base=4/29 Fail

-153:  segment=8 ReturnFalse

-154:  segment=8 startT=0 endT=1 segment=4 oppStartT=1 oppEndT=0.759312 AddMissingCoin

-155:  segment=13 ReturnFalse

-156:  segment=13 startT=1 endT=1 segment=12 oppStartT=3.13901e-07 oppEndT=3.08642e-07 AddMissingCoin

-157:  segment=7 ReturnFalse

-158:  seg/base=5/9 Fail

-159:  seg/base=5/41 Fail

-160:  seg/base=5/47 Fail

-161:  seg/base=5/43 Fail

-162:  segment=7 ReturnFalse

-163:  segment=7 startT=0 endT=1 segment=5 oppStartT=0.632267 oppEndT=0 AddMissingCoin

-164:  segment=7 ReturnFalse

-165:  seg/base=7/13 Fail

-166:  seg/base=5/9 Fail

-167:  seg/base=5/41 Fail

-168:  seg/base=5/47 Fail

-169:  segment=7 ReturnFalse

-170:  segment=7 startT=0.228492 endT=1 segment=5 oppStartT=0.487799 oppEndT=0 AddMissingCoin

-171:  segment=13 ReturnFalse

-172:  seg/base=13/25 Fail

-173:  seg/base=13/53 Fail

-174:  seg/base=13/46 Fail

-175:  seg/base=12/23 Fail

-176:  seg/base=12/36 Fail

-177:  seg/base=12/37 Fail

-178:  seg/base=12/55 Fail

-179:  seg/base=12/60 Fail

-180:  seg/base=12/65 Fail

-181:  seg/base=12/39 Fail

-182:  seg/base=12/63 Fail

-183:  seg/base=12/44 Fail

-184:  seg/base=12/51 Fail

-185:  segment=13 ReturnFalse

-186:  segment=13 startT=1 endT=1 segment=12 oppStartT=3.15199e-07 oppEndT=3.13901e-07 AddMissingCoin

-187:  segment=6 ReturnFalse

-188:  seg/base=6/11 Fail

-189:  seg/base=6/52 Fail

-190:  seg/base=5/9 Fail

-191:  seg/base=5/41 Fail

-192:  seg/base=5/47 Fail

-193:  seg/base=5/43 Fail

-194:  seg/base=6/11 Fail

-195:  seg/base=6/52 Fail

-196:  seg/base=6/49 Fail

-197:  seg/base=5/9 Fail

-198:  seg/base=5/41 Fail

-199:  seg/base=5/47 Fail

-200:  seg/base=5/43 Fail

-201:  segment=6 ReturnFalse

-202:  segment=6 startT=0.291197 endT=1 segment=5 oppStartT=0.892917 oppEndT=0.632267 AddMissingCoin

-203:  segment=13 ReturnFalse

-204:  seg/base=13/25 Fail

-205:  seg/base=13/53 Fail

-206:  seg/base=13/46 Fail

-207:  seg/base=13/54 Fail

-208:  seg/base=12/23 Fail

-209:  seg/base=12/36 Fail

-210:  seg/base=12/37 Fail

-211:  seg/base=12/55 Fail

-212:  seg/base=12/60 Fail

-213:  seg/base=12/65 Fail

-214:  seg/base=12/39 Fail

-215:  seg/base=12/63 Fail

-216:  segment=13 ReturnFalse

-217:  segment=13 startT=1 endT=1 segment=12 oppStartT=3.127e-07 oppEndT=3.08642e-07 AddMissingCoin

-218:  segment=13 ReturnFalse

-219:  segment=13 startT=1 endT=1 segment=12 oppStartT=3.08642e-07 oppEndT=2.80653e-07 AddMissingCoin

-220:  segment=13 ReturnFalse

-221:  seg/base=13/25 Fail

-222:  seg/base=13/53 Fail

-223:  seg/base=13/46 Fail

-224:  seg/base=13/54 Fail

-225:  seg/base=13/64 Fail

-226:  seg/base=13/40 Fail

-227:  seg/base=13/57 Fail

-228:  seg/base=13/56 Fail

-229:  seg/base=13/38 Fail

-230:  seg/base=12/23 Fail

-231:  segment=13 ReturnFalse

-232:  segment=13 startT=1 endT=1 segment=12 oppStartT=2.80653e-07 oppEndT=2.76598e-07 AddMissingCoin

-233:  seg/base=11/42 startT=3.12405e-07 AddExpandedCoin

-234:  seg/base=12/65 startT=0.0300077 AddExpandedCoin

-235:  seg/base=12/60 startT=0.0665796 AddExpandedCoin

-236:  seg/base=12/55 startT=0.146093 AddExpandedCoin

-237:  seg/base=12/37 startT=0.208476 AddExpandedCoin

-238:  seg/base=12/36 startT=0.228359 AddExpandedCoin

-239:  seg/base=13/57 startT=0.899703 AddExpandedCoin

-240:  seg/base=12/65 startT=0.758838 AddExpandedCoin

-241:  seg/base=12/60 startT=3.73071 AddExpandedCoin

-242:  seg/base=13/64 startT=0.246928 AddExpandedCoin

-243:  seg/base=12/44 startT=0.228492 AddExpandedCoin

-244:  seg/base=12/63 startT=0.320059 AddExpandedCoin

-245:  seg/base=13/46 startT=0.291197 AddExpandedCoin

-246:  seg/base=13/54 startT=0.632267 AddExpandedCoin

-247:  seg/base=13/64 startT=0.367348 AddExpandedCoin

-248:  seg/base=5/41 startT=1 AddExpandedCoin

-249:  seg/base=5/41 startT=3.12405e-07 AddExpandedCoin

-250:  seg/base=5/47 startT=3.12605e-07 AddExpandedCoin

-251:  seg/base=13/57 startT=0.783452 AddExpandedCoin

-252:  seg/base=13/56 startT=0.733122 AddExpandedCoin

-253:  seg/base=4/29 startT=3.01825e-07 AddExpandedCoin

-254:  seg/base=12/60 startT=1.54926 AddExpandedCoin

-255:  seg/base=12/65 startT=1.67578 AddExpandedCoin

-256:  seg/base=3/33 startT=1 AddExpandedCoin

-257:  seg/base=3/35 startT=1 AddExpandedCoin

-258:  seg/base=3/33 startT=2.76716e-07 AddExpandedCoin

-259:  seg/base=11/21 seg/base=12/23 MarkCoinStart

-260:  seg/base=11/22 seg/base=12/63 MarkCoinEnd

-261:  coinSeg/Span/PtT=11/21/21 endSpan=22 Fail

-262:  seg/base=11/42 segment=12 MarkCoinMissing

-263:  seg/base=11/59 segment=12 MarkCoinMissing

-264:  seg/base=11/34 segment=12 MarkCoinMissing

-265:  seg/base=12/36 segment=11 MarkCoinMissing

-266:  seg/base=12/37 segment=11 MarkCoinMissing

-267:  seg/base=12/55 segment=11 MarkCoinMissing

-268:  seg/base=12/60 segment=11 MarkCoinMissing

-269:  seg/base=12/65 segment=11 MarkCoinMissing

-270:  seg/base=12/39 segment=11 MarkCoinMissing

-271:  seg/base=13/40 seg/base=8/15 MarkCoinStart

-272:  seg/base=13/56 seg/base=8/16 MarkCoinEnd

-273:  coinSeg/Span/PtT=13/40/40 endSpan=56 Fail

-274:  seg/base=13/57 segment=8 MarkCoinMissing

-275:  seg/base=8/15 seg/base=12/55 MarkCoinStart

-276:  seg/base=8/16 seg/base=12/39 MarkCoinEnd

-277:  seg/base=12/60 segment=8 MarkCoinMissing

-278:  seg/base=12/65 segment=8 MarkCoinMissing

-279:  seg/base=13/54 seg/base=7/13 MarkCoinStart

-280:  seg/base=13/40 seg/base=7/14 MarkCoinEnd

-281:  coinSeg/Span/PtT=13/54/54 endSpan=40 Fail

-282:  seg/base=13/64 segment=7 MarkCoinMissing

-283:  seg/base=7/13 seg/base=12/39 MarkCoinStart

-284:  seg/base=7/14 seg/base=12/51 MarkCoinEnd

-285:  seg/base=12/63 segment=7 MarkCoinMissing

-286:  seg/base=12/44 segment=7 MarkCoinMissing

-287:  seg/base=13/53 seg/base=6/52 MarkCoinStart

-288:  seg/base=13/54 seg/base=6/12 MarkCoinEnd

-289:  coinSeg/Span/PtT=13/53/53 endSpan=54 Fail

-290:  seg/base=13/46 segment=6 MarkCoinMissing

-291:  seg/base=6/49 segment=13 MarkCoinMissing

-292:  seg/base=6/49 seg/base=12/51 MarkCoinStart

-293:  seg/base=6/12 seg/base=12/50 MarkCoinEnd

-294:  seg/base=13/46 seg/base=5/9 MarkCoinStart

-295:  seg/base=13/40 seg/base=5/45 MarkCoinEnd

-296:  coinSeg/Span/PtT=13/46/46 endSpan=40 Fail

-297:  seg/base=13/54 segment=5 MarkCoinMissing

-298:  seg/base=13/64 segment=5 MarkCoinMissing

-299:  seg/base=5/41 segment=13 MarkCoinMissing

-300:  seg/base=5/47 segment=13 MarkCoinMissing

-301:  seg/base=5/43 segment=13 MarkCoinMissing

-302:  seg/base=5/9 seg/base=12/39 MarkCoinStart

-303:  seg/base=5/43 seg/base=12/44 MarkCoinEnd

-304:  coinSeg/Span/PtT=5/9/9 endSpan=43 Fail

-305:  seg/base=5/41 segment=12 MarkCoinMissing

-306:  seg/base=5/47 segment=12 MarkCoinMissing

-307:  seg/base=12/63 segment=5 MarkCoinMissing

-308:  seg/base=13/40 seg/base=4/7 MarkCoinStart

-309:  seg/base=13/38 seg/base=4/8 MarkCoinEnd

-310:  coinSeg/Span/PtT=13/40/40 endSpan=38 Fail

-311:  seg/base=13/57 segment=4 MarkCoinMissing

-312:  seg/base=13/56 segment=4 MarkCoinMissing

-313:  seg/base=4/29 segment=13 MarkCoinMissing

-314:  seg/base=4/7 seg/base=12/37 MarkCoinStart

-315:  seg/base=4/8 seg/base=12/39 MarkCoinEnd

-316:  coinSeg/Span/PtT=4/7/7 endSpan=8 Fail

-317:  seg/base=4/29 segment=12 MarkCoinMissing

-318:  seg/base=12/55 segment=4 MarkCoinMissing

-319:  seg/base=12/60 segment=4 MarkCoinMissing

-320:  seg/base=12/65 segment=4 MarkCoinMissing

-321:  seg/base=13/38 seg/base=3/5 MarkCoinStart

-322:  seg/base=13/32 seg/base=3/6 MarkCoinEnd

-323:  seg/base=3/35 segment=13 MarkCoinMissing

-324:  seg/base=3/33 segment=13 MarkCoinMissing

-325:  seg/base=3/35 seg/base=12/36 MarkCoinStart

-326:  seg/base=3/6 seg/base=12/37 MarkCoinEnd

-327:  coinSeg/Span/PtT=3/35/35 endSpan=6 Fail

-328:  seg/base=3/33 segment=12 MarkCoinMissing

-329:  seg/base=9/17 seg/base=10/19 MarkCoinStart

-330:  seg/base=9/18 seg/base=10/31 MarkCoinEnd

-SkOpSegment::debugShowActiveSpans id=1 (360,-2.14748365e+09 593011648,-2.14748352e+09 1.07374221e+09,-1.6667529e+09 1.07374208e+09,-1.07374157e+09) t=0 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=2 (1.07374208e+09,-1.07374157e+09 1.07374208e+09,-480730560 593011840,-135.508026 905.953125,255.999786) t=0 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=3 (905.953125,255.999786 900.097229,297.65976) t=0 tEnd=0.0649612467 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=3 (900.097229,297.65976 897.639343,315.145294) t=0.0649612467 tEnd=0.0922268392 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=3 (897.639343,315.145294 815.80835,897.304565) t=0.0922268392 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=4 (815.80835,897.304565 360,651.952515) t=0 tEnd=0.756429319 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=4 (360,651.952515 213.229446,572.949036) t=0.756429319 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (213.229446,572.949036 131.235565,320.967834) t=0 tEnd=0.452431368 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (131.235565,320.967834 131.104431,320.56485) t=0.452431368 tEnd=0.453154928 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (131.104431,320.56485 124.825912,301.269867) t=0.453154928 tEnd=0.487798966 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (124.825912,301.269867 51.4065475,75.6396332) t=0.487798966 tEnd=0.892917257 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (51.4065475,75.6396332 32,16) t=0.892917257 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=6 (32,16 40.183567,75.6918945) t=0 tEnd=0.120346555 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=6 (40.183567,75.6918945 71.1438522,301.519836) t=0.120346555 tEnd=0.575644854 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=6 (71.1438522,301.519836 100,512) t=0.575644854 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=7 (100,512 213.229446,572.949036) t=0 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=8 (213.229446,572.949036 360,1024) t=0 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=9 (360,1024 360,4140) t=0 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,4140 360,1024) t=0 tEnd=1.45099777e-06 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,1024 360,651.952515) t=1.45099777e-06 tEnd=1.62424554e-06 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,651.952515 360,319.229858) t=1.62424554e-06 tEnd=1.77918132e-06 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,319.229858 360,296.296631) t=1.77918132e-06 tEnd=1.78986041e-06 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,296.296631 360,-2.14748365e+09) t=1.78986041e-06 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (127,321 131.235565,320.967834) t=0 tEnd=0.000630948916 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (131.235565,320.967834 360,319.229858) t=0.000630948916 tEnd=0.034708774 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (360,319.229858 897.639343,315.145294) t=0.034708774 tEnd=0.114798057 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (897.639343,315.145294 6840,270) t=0.114798057 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (6840,270 900.097229,297.65976) t=0 tEnd=2.7659819e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (900.097229,297.65976 813.017944,298.065247) t=2.7659819e-07 tEnd=2.80653133e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (813.017944,298.065247 360,1024) t=2.80653133e-07 tEnd=3.01905369e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (360,1024 360,4140) t=3.01905369e-07 tEnd=3.02581027e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (360,4140 321.741364,300.352905) t=3.02581027e-07 tEnd=3.03529974e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (321.741364,300.352905 211.962463,300.864105) t=3.03529974e-07 tEnd=3.08641951e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (211.962463,300.864105 127,321) t=3.08641951e-07 tEnd=3.12602603e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (127,321 124.825912,301.269867) t=3.12602603e-07 tEnd=3.12699562e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (124.825912,301.269867 100,512) t=3.12699562e-07 tEnd=3.13901276e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (100,512 71.1438522,301.519836) t=3.13901276e-07 tEnd=3.15199326e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (71.1438522,301.519836 -2.14748365e+10,100000000) t=3.15199326e-07 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (-2.14748365e+10,100000000 40.183567,75.6918945) t=0 tEnd=0.999999883 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (40.183567,75.6918945 51.4065437,75.6396332) t=0.999999883 tEnd=0.999999884 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (51.4065437,75.6396332 100,512) t=0.999999884 tEnd=0.999999886 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (100,512 127,321) t=0.999999886 tEnd=0.999999887 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (127,321 210.910217,74.8968811) t=0.999999887 tEnd=0.999999891 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (210.910217,74.8968811 360,4140) t=0.999999891 tEnd=0.999999897 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (360,4140 360,1024) t=0.999999897 tEnd=0.999999898 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (360,1024 811.965698,72.0980072) t=0.999999898 tEnd=0.999999919 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (811.965698,72.0980072 905.094727,71.6643372) t=0.999999919 tEnd=0.999999923 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (905.094727,71.6643372 2551,64) t=0.999999923 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=14 (2551,64 360,296.296631) t=0 tEnd=0.903877888 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=14 (360,296.296631 321.741364,300.352905) t=0.903877888 tEnd=0.919661149 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=14 (321.741364,300.352905 131.104431,320.56485) t=0.919661149 tEnd=0.998306753 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=14 (131.104431,320.56485 127,321) t=0.998306753 tEnd=1 windSum=? windValue=1

-SkOpSegment::addT insert t=3.12405367e-07 segID=12 spanID=67

-SkOpSegment::addT insert t=0.0126699258 segID=11 spanID=68

-SkOpSegment::addT insert t=0.0290228849 segID=11 spanID=69

-SkOpSegment::addT insert t=0.0320585186 segID=11 spanID=70

-SkOpSegment::addT insert t=0.0342199142 segID=11 spanID=71

-SkOpSegment::addT insert t=3.0175255e-07 segID=12 spanID=72

-SkOpSegment::addT insert t=0.102204746 segID=11 spanID=73

-SkOpSegment::addT insert t=2.76716432e-07 segID=12 spanID=74

-SkOpSegment::addT insert t=0.115176306 segID=11 spanID=75

-SkOpSegment::addT insert t=0.899702926 segID=8 spanID=76

-SkOpSegment::addT insert t=0.758838213 segID=8 spanID=77

-SkOpSegment::addT insert t=0.246927782 segID=7 spanID=78

-SkOpSegment::addT insert t=0.228492228 segID=7 spanID=79

-SkOpSegment::addT insert t=0.284429982 segID=7 spanID=80

-SkOpSegment::addT insert t=0.291197031 segID=6 spanID=81

-SkOpSegment::addT insert t=0.999999884 segID=13 spanID=82

-SkOpSegment::addT insert t=0.788316424 segID=5 spanID=83

-SkOpSegment::addT insert t=0.632267074 segID=5 spanID=84

-SkOpSegment::addT insert t=0.999999887 segID=13 spanID=85

-SkOpSegment::addT insert t=0.476142764 segID=5 spanID=86

-SkOpSegment::addT insert t=0.999999887 segID=13 spanID=87

-SkOpSegment::addT insert t=0.999999887 segID=13 spanID=88

-SkOpSegment::addT insert t=3.12411385e-07 segID=12 spanID=89

-SkOpSegment::addT insert t=0.783451987 segID=4 spanID=90

-SkOpSegment::addT insert t=0.759311649 segID=4 spanID=91

-SkOpSegment::addT insert t=0.999999898 segID=13 spanID=92

-SkOpSegment::addT insert t=0.753851653 segID=4 spanID=93

-SkOpSegment::addT insert t=3.01824696e-07 segID=12 spanID=94

-SkOpSegment::addT insert t=0.817356482 segID=4 spanID=95

-SkOpSegment::addT insert t=0.999999923 segID=13 spanID=96

-SkOpSegment::addT insert t=0.999999923 segID=13 spanID=97

---x---x----------xxxxxxx----------x--- move_multiples

-00:  seg/base=13/38 coinSeg/Span/PtT=13/85/85 MergeMatches

-01:  seg/base=12/37 coinSeg/Span/PtT=12/44/44 MergeMatches

-02:  seg/base=13/38 coinSeg/Span/PtT=13/85/85 MergeMatches

-03:  seg/base=12/37 coinSeg/Span/PtT=12/44/44 MergeMatches

-04:  seg/base=13/38 coinSeg/Span/PtT=13/85/85 MergeMatches

-05:  seg/base=12/37 coinSeg/Span/PtT=12/44/44 MergeMatches

-06:  seg/base=13/38 coinSeg/Span/PtT=13/85/85 MergeMatches

-07:  seg/base=12/37 coinSeg/Span/PtT=12/44/44 MergeMatches

-08:  seg/base=13/40 coinSeg/Span/PtT=13/82/82 MergeMatches

-09:  seg/base=5/83 coinSeg/Span/PtT=5/9/9 MergeMatches

-10:  seg/base=12/39 coinSeg/Span/PtT=12/50/50 MergeMatches

-11:  seg/base=13/40 coinSeg/Span/PtT=13/82/82 MergeMatches

-12:  seg/base=5/83 coinSeg/Span/PtT=5/9/9 MergeMatches

-13:  seg/base=12/39 coinSeg/Span/PtT=12/50/50 MergeMatches

-14:  seg/base=13/40 coinSeg/Span/PtT=13/85/85 MergeMatches

-15:  seg/base=12/39 coinSeg/Span/PtT=12/44/44 MergeMatches

-16:  seg/base=7/79 coinSeg/Span/PtT=7/14/14 MergeMatches

-17:  seg/base=5/43 coinSeg/Span/PtT=5/9/9 MergeMatches

-18:  seg/base=13/40 coinSeg/Span/PtT=13/85/85 MergeMatches

-19:  seg/base=12/39 coinSeg/Span/PtT=12/44/44 MergeMatches

-20:  seg/base=7/79 coinSeg/Span/PtT=7/14/14 MergeMatches

-21:  seg/base=5/43 coinSeg/Span/PtT=5/9/9 MergeMatches

-22:  seg/base=13/54 coinSeg/Span/PtT=13/82/82 MergeMatches

-23:  seg/base=5/84 coinSeg/Span/PtT=5/83/83 MergeMatches

-24:  seg/base=12/51 coinSeg/Span/PtT=12/50/50 MergeMatches

-25:  seg/base=6/49 coinSeg/Span/PtT=6/12/12 MergeMatches

-26:  seg/base=13/54 coinSeg/Span/PtT=13/82/82 MergeMatches

-27:  seg/base=5/84 coinSeg/Span/PtT=5/83/83 MergeMatches

-28:  seg/base=12/51 coinSeg/Span/PtT=12/50/50 MergeMatches

-29:  seg/base=6/49 coinSeg/Span/PtT=6/12/12 MergeMatches

-30:  seg/base=13/54 coinSeg/Span/PtT=13/53/53 MergeMatches

-31:  seg/base=6/52 coinSeg/Span/PtT=6/12/12 MergeMatches

-32:  seg/base=13/54 coinSeg/Span/PtT=13/53/53 MergeMatches

-33:  seg/base=6/52 coinSeg/Span/PtT=6/12/12 MergeMatches

-34:  seg/base=13/40 coinSeg/Span/PtT=13/82/82 MergeMatches

-35:  seg/base=5/83 coinSeg/Span/PtT=5/9/9 MergeMatches

-36:  seg/base=12/39 coinSeg/Span/PtT=12/50/50 MergeMatches

-37:  seg/base=13/40 coinSeg/Span/PtT=13/82/82 MergeMatches

-38:  seg/base=5/83 coinSeg/Span/PtT=5/9/9 MergeMatches

-39:  seg/base=12/39 coinSeg/Span/PtT=12/50/50 MergeMatches

-40:  seg/base=13/40 coinSeg/Span/PtT=13/82/82 MergeMatches

-41:  seg/base=5/83 coinSeg/Span/PtT=5/9/9 MergeMatches

-42:  seg/base=12/39 coinSeg/Span/PtT=12/50/50 MergeMatches

-43:  seg/base=13/40 coinSeg/Span/PtT=13/82/82 MergeMatches

-44:  seg/base=5/83 coinSeg/Span/PtT=5/9/9 MergeMatches

-45:  seg/base=12/39 coinSeg/Span/PtT=12/50/50 MergeMatches

-46:  seg/base=13/56 coinSeg/Span/PtT=13/85/85 MergeMatches

-47:  seg/base=12/55 coinSeg/Span/PtT=12/44/44 MergeMatches

-48:  seg/base=13/56 coinSeg/Span/PtT=13/85/85 MergeMatches

-49:  seg/base=12/55 coinSeg/Span/PtT=12/44/44 MergeMatches

-50:  seg/base=13/56 coinSeg/Span/PtT=13/85/85 MergeMatches

-51:  seg/base=12/55 coinSeg/Span/PtT=12/44/44 MergeMatches

-52:  seg/base=13/56 coinSeg/Span/PtT=13/85/85 MergeMatches

-53:  seg/base=12/55 coinSeg/Span/PtT=12/44/44 MergeMatches

-54:  seg/base=10/30 coinSeg/Span/PtT=10/19/19 MergeMatches

-55:  seg/base=4/90 coinSeg/Span/PtT=4/29/29 MergeMatches

-56:  seg/base=12/60 coinSeg/Span/PtT=12/94/94 MergeMatches

-57:  seg/base=13/57 coinSeg/Span/PtT=13/92/92 MergeMatches

-58:  seg/base=10/30 coinSeg/Span/PtT=10/19/19 MergeMatches

-59:  seg/base=4/90 coinSeg/Span/PtT=4/29/29 MergeMatches

-60:  seg/base=12/60 coinSeg/Span/PtT=12/94/94 MergeMatches

-61:  seg/base=13/57 coinSeg/Span/PtT=13/92/92 MergeMatches

-62:  coinSeg/Span/PtT=13/57/57 endSpan=56 oppSeg/Span/PtT=9/18/18 oppEndSpan=17 MissingCoin

-63:  coinSeg/Span/PtT=12/55/55 endSpan=60 oppSeg/Span/PtT=9/17/17 oppEndSpan=18 MissingCoin

-64:  seg/base=12/60 coinSeg/Span/PtT=12/23/23 MergeMatches

-65:  seg/base=11/70 coinSeg/Span/PtT=11/22/22 MergeMatches

-66:  seg/base=12/60 coinSeg/Span/PtT=12/23/23 MergeMatches

-67:  seg/base=11/70 coinSeg/Span/PtT=11/22/22 MergeMatches

-68:  seg/base=13/56 coinSeg/Span/PtT=13/85/85 MergeMatches

-69:  seg/base=12/55 coinSeg/Span/PtT=12/44/44 MergeMatches

-70:  seg/base=13/56 coinSeg/Span/PtT=13/85/85 MergeMatches

-71:  seg/base=12/55 coinSeg/Span/PtT=12/44/44 MergeMatches

-72:  coinSeg/Span/PtT=13/57/57 endSpan=56 oppSeg/Span/PtT=10/19/19 oppEndSpan=31 MissingCoin

-73:  coinSeg/Span/PtT=12/55/55 endSpan=60 oppSeg/Span/PtT=10/31/31 oppEndSpan=19 MissingCoin

-74:  coinSeg/Span/PtT=13/57/57 endSpan=56 oppSeg/Span/PtT=9/18/18 oppEndSpan=17 MissingCoin

-75:  coinSeg/Span/PtT=12/55/55 endSpan=60 oppSeg/Span/PtT=9/17/17 oppEndSpan=18 MissingCoin

-76:  coinSeg/Span/PtT=13/57/57 endSpan=56 oppSeg/Span/PtT=10/19/19 oppEndSpan=31 MissingCoin

-77:  coinSeg/Span/PtT=12/55/55 endSpan=60 oppSeg/Span/PtT=10/31/31 oppEndSpan=19 MissingCoin

-78:  seg/base=13/64 coinSeg/Span/PtT=13/82/82 MergeMatches

-79:  seg/base=5/86 coinSeg/Span/PtT=5/83/83 MergeMatches

-80:  seg/base=12/63 coinSeg/Span/PtT=12/50/50 MergeMatches

-81:  seg/base=13/64 coinSeg/Span/PtT=13/82/82 MergeMatches

-82:  seg/base=5/86 coinSeg/Span/PtT=5/83/83 MergeMatches

-83:  seg/base=12/63 coinSeg/Span/PtT=12/50/50 MergeMatches

-84:  seg/base=13/56 coinSeg/Span/PtT=13/88/88 MergeMatches

-85:  seg/base=11/71 coinSeg/Span/PtT=11/42/42 MergeMatches

-86:  seg/base=12/55 coinSeg/Span/PtT=12/67/67 MergeMatches

-87:  seg/base=13/56 coinSeg/Span/PtT=13/88/88 MergeMatches

-88:  seg/base=11/71 coinSeg/Span/PtT=11/42/42 MergeMatches

-89:  seg/base=12/55 coinSeg/Span/PtT=12/67/67 MergeMatches

-90:  coinSeg/Span/PtT=13/25/25 endSpan=82 oppSeg/Span/PtT=12/24/24 oppEndSpan=50 MissingCoin

-91:  seg/base=10/31 coinSeg/Span/PtT=10/19/19 MergeMatches

-92:  segment=9 MergeMatches

-93:  seg/base=8/76 coinSeg/Span/PtT=8/16/16 MergeMatches

-94:  seg/base=13/57 coinSeg/Span/PtT=13/56/56 MergeMatches

-95:  seg/base=4/90 coinSeg/Span/PtT=4/91/91 MergeMatches

-96:  seg/base=12/60 coinSeg/Span/PtT=12/55/55 MergeMatches

-97:  seg/base=11/70 coinSeg/Span/PtT=11/71/71 MergeMatches

-98:  seg/base=10/31 coinSeg/Span/PtT=10/19/19 MergeMatches

-99:  segment=9 MergeMatches

-100:  seg/base=8/76 coinSeg/Span/PtT=8/16/16 MergeMatches

-101:  seg/base=13/57 coinSeg/Span/PtT=13/56/56 MergeMatches

-102:  seg/base=4/90 coinSeg/Span/PtT=4/91/91 MergeMatches

-103:  seg/base=12/60 coinSeg/Span/PtT=12/55/55 MergeMatches

-104:  seg/base=11/70 coinSeg/Span/PtT=11/71/71 MergeMatches

-105:  coinSeg/Span/PtT=12/50/50 endSpan=24 oppSeg/Span/PtT=13/82/82 oppEndSpan=25 MissingCoin

-106:  coinSeg/Span/PtT=13/25/25 endSpan=82 oppSeg/Span/PtT=12/24/24 oppEndSpan=50 MissingCoin

-107:  coinSeg/Span/PtT=12/50/50 endSpan=24 oppSeg/Span/PtT=13/82/82 oppEndSpan=25 MissingCoin

-108:  segment=11 ReturnFalse

-109:  segment=11 startT=0.0126699 endT=0.0342199 segment=8 oppStartT=0 oppEndT=1 AddMissingCoin

-110:  segment=11 ReturnFalse

-111:  segment=11 startT=0 endT=0.0126699 segment=7 oppStartT=0.246928 oppEndT=1 AddMissingCoin

-112:  segment=11 ReturnFalse

-113:  segment=11 startT=0 endT=0.0126699 segment=5 oppStartT=0.476143 oppEndT=0 AddMissingCoin

-114:  segment=11 ReturnFalse

-115:  segment=11 startT=0.0126699 endT=0.102205 segment=4 oppStartT=1 oppEndT=0 AddMissingCoin

-116:  segment=11 ReturnFalse

-117:  segment=11 startT=0.102205 endT=0.115176 segment=3 oppStartT=1 oppEndT=0.0649612 AddMissingCoin

-118:  segment=13 ReturnFalse

-119:  segment=13 startT=1 endT=1 segment=12 oppStartT=3.08642e-07 oppEndT=3.01905e-07 AddMissingCoin

-120:  segment=8 ReturnFalse

-121:  segment=8 startT=0 endT=1 segment=4 oppStartT=1 oppEndT=0.759312 AddMissingCoin

-122:  segment=8 ReturnFalse

-123:  segment=8 startT=0 endT=1 segment=4 oppStartT=1 oppEndT=0.759312 AddMissingCoin

-124:  segment=13 ReturnFalse

-125:  segment=13 startT=1 endT=1 segment=12 oppStartT=3.13901e-07 oppEndT=3.08642e-07 AddMissingCoin

-126:  segment=7 ReturnFalse

-127:  segment=7 startT=0 endT=1 segment=5 oppStartT=0.632267 oppEndT=0 AddMissingCoin

-128:  segment=7 ReturnFalse

-129:  segment=7 startT=0.228492 endT=1 segment=5 oppStartT=0.487799 oppEndT=0 AddMissingCoin

-130:  segment=13 ReturnFalse

-131:  segment=13 startT=1 endT=1 segment=12 oppStartT=3.15199e-07 oppEndT=3.13901e-07 AddMissingCoin

-132:  segment=6 ReturnFalse

-133:  segment=6 startT=0.291197 endT=1 segment=5 oppStartT=0.892917 oppEndT=0.632267 AddMissingCoin

-134:  segment=13 ReturnFalse

-135:  segment=13 startT=1 endT=1 segment=12 oppStartT=3.127e-07 oppEndT=3.08642e-07 AddMissingCoin

-136:  segment=13 ReturnFalse

-137:  segment=13 startT=1 endT=1 segment=12 oppStartT=3.08642e-07 oppEndT=2.80653e-07 AddMissingCoin

-138:  segment=13 ReturnFalse

-139:  segment=13 startT=1 endT=1 segment=12 oppStartT=2.80653e-07 oppEndT=2.76598e-07 AddMissingCoin

-140:  seg/base=12/89 startT=0.000611695 AddExpandedCoin

-141:  seg/base=12/94 startT=0.0689388 AddExpandedCoin

-142:  seg/base=8/77 startT=1 AddExpandedCoin

-143:  seg/base=13/87 startT=0.283286 AddExpandedCoin

-144:  seg/base=12/89 startT=0.530213 AddExpandedCoin

-145:  seg/base=4/95 startT=1 AddExpandedCoin

-146:  seg/base=4/93 startT=1 AddExpandedCoin

-147:  seg/base=11/21 seg/base=12/23 MarkCoinStart

-148:  seg/base=11/22 seg/base=12/63 MarkCoinEnd

-149:  seg/base=12/39 MarkCoinInsert

-150:  seg/base=12/65 MarkCoinInsert

-151:  seg/base=12/60 MarkCoinInsert

-152:  seg/base=12/55 MarkCoinInsert

-153:  seg/base=12/94 MarkCoinInsert

-154:  seg/base=12/37 MarkCoinInsert

-155:  seg/base=12/74 MarkCoinInsert

-156:  seg/base=12/36 MarkCoinInsert

-157:  seg/base=12/23 MarkCoinInsert

-158:  seg/base=11/34 MarkCoinInsert

-159:  seg/base=11/73 MarkCoinInsert

-160:  seg/base=11/59 MarkCoinInsert

-161:  seg/base=11/71 MarkCoinInsert

-162:  seg/base=12/94 segment=11 MarkCoinMissing

-163:  seg/base=11/70 MarkCoinInsert

-164:  seg/base=11/69 MarkCoinInsert

-165:  seg/base=11/68 MarkCoinInsert

-166:  seg/base=11/42 MarkCoinInsert

-167:  seg/base=11/21 MarkCoinInsert

-168:  seg/base=12/89 segment=11 MarkCoinMissing

-169:  seg/base=13/40 seg/base=8/15 MarkCoinStart

-170:  seg/base=13/56 seg/base=8/16 MarkCoinEnd

-171:  seg/base=8/76 MarkCoinInsert

-172:  seg/base=8/77 segment=13 MarkCoinMissing

-173:  seg/base=13/57 MarkCoinInsert

-174:  seg/base=8/15 seg/base=12/55 MarkCoinStart

-175:  seg/base=8/16 seg/base=12/39 MarkCoinEnd

-176:  seg/base=12/60 MarkCoinInsert

-177:  seg/base=12/55 MarkCoinInsert

-178:  seg/base=8/77 MarkCoinInsert

-179:  seg/base=8/15 MarkCoinInsert

-180:  seg/base=13/54 seg/base=7/13 MarkCoinStart

-181:  seg/base=13/40 seg/base=7/14 MarkCoinEnd

-182:  coinSeg/Span/PtT=13/54/54 endSpan=40 Fail

-183:  seg/base=7/79 MarkCoinInsert

-184:  seg/base=7/78 MarkCoinInsert

-185:  seg/base=13/87 segment=7 MarkCoinMissing

-186:  seg/base=7/80 MarkCoinInsert

-187:  seg/base=13/85 MarkCoinInsert

-188:  seg/base=13/64 MarkCoinInsert

-189:  seg/base=13/88 MarkCoinInsert

-190:  seg/base=7/13 seg/base=12/39 MarkCoinStart

-191:  seg/base=7/14 seg/base=12/51 MarkCoinEnd

-192:  seg/base=12/63 MarkCoinInsert

-193:  seg/base=12/89 MarkCoinInsert

-194:  seg/base=12/39 MarkCoinInsert

-195:  seg/base=7/78 MarkCoinInsert

-196:  seg/base=12/89 segment=7 MarkCoinMissing

-197:  seg/base=7/79 MarkCoinInsert

-198:  seg/base=7/13 MarkCoinInsert

-199:  seg/base=13/53 seg/base=6/52 MarkCoinStart

-200:  seg/base=13/54 seg/base=6/12 MarkCoinEnd

-201:  seg/base=6/81 MarkCoinInsert

-202:  seg/base=6/49 MarkCoinInsert

-203:  seg/base=13/46 MarkCoinInsert

-204:  seg/base=13/82 MarkCoinInsert

-205:  seg/base=6/49 seg/base=12/51 MarkCoinStart

-206:  seg/base=6/12 seg/base=12/50 MarkCoinEnd

-207:  seg/base=13/46 seg/base=5/9 MarkCoinStart

-208:  seg/base=13/40 seg/base=5/45 MarkCoinEnd

-209:  seg/base=5/84 MarkCoinInsert

-210:  seg/base=5/43 MarkCoinInsert

-211:  seg/base=5/86 MarkCoinInsert

-212:  seg/base=5/47 MarkCoinInsert

-213:  seg/base=5/41 MarkCoinInsert

-214:  seg/base=5/9 MarkCoinInsert

-215:  seg/base=13/87 MarkCoinInsert

-216:  seg/base=13/64 MarkCoinInsert

-217:  seg/base=13/85 MarkCoinInsert

-218:  seg/base=13/54 MarkCoinInsert

-219:  seg/base=13/82 MarkCoinInsert

-220:  seg/base=13/46 MarkCoinInsert

-221:  seg/base=5/9 seg/base=12/39 MarkCoinStart

-222:  seg/base=5/43 seg/base=12/44 MarkCoinEnd

-223:  seg/base=12/67 MarkCoinInsert

-224:  seg/base=12/89 MarkCoinInsert

-225:  seg/base=12/63 MarkCoinInsert

-226:  seg/base=5/41 MarkCoinInsert

-227:  seg/base=5/47 MarkCoinInsert

-228:  seg/base=5/86 MarkCoinInsert

-229:  seg/base=13/40 seg/base=4/7 MarkCoinStart

-230:  seg/base=13/38 seg/base=4/8 MarkCoinEnd

-231:  seg/base=4/91 MarkCoinInsert

-232:  seg/base=4/29 MarkCoinInsert

-233:  seg/base=4/93 MarkCoinInsert

-234:  seg/base=4/93 segment=13 MarkCoinMissing

-235:  seg/base=13/56 MarkCoinInsert

-236:  seg/base=13/57 MarkCoinInsert

-237:  seg/base=13/40 MarkCoinInsert

-238:  seg/base=4/95 segment=13 MarkCoinMissing

-239:  seg/base=4/7 seg/base=12/37 MarkCoinStart

-240:  seg/base=4/8 seg/base=12/39 MarkCoinEnd

-241:  seg/base=12/72 MarkCoinInsert

-242:  seg/base=12/94 MarkCoinInsert

-243:  seg/base=12/55 MarkCoinInsert

-244:  seg/base=12/60 MarkCoinInsert

-245:  seg/base=12/65 MarkCoinInsert

-246:  seg/base=4/93 MarkCoinInsert

-247:  seg/base=4/29 MarkCoinInsert

-248:  seg/base=4/91 MarkCoinInsert

-249:  seg/base=4/90 MarkCoinInsert

-250:  seg/base=4/95 MarkCoinInsert

-251:  seg/base=13/38 seg/base=3/5 MarkCoinStart

-252:  seg/base=13/32 seg/base=3/6 MarkCoinEnd

-253:  seg/base=3/35 MarkCoinInsert

-254:  seg/base=3/5 MarkCoinInsert

-255:  seg/base=13/96 MarkCoinInsert

-256:  seg/base=13/38 MarkCoinInsert

-257:  seg/base=3/35 seg/base=12/36 MarkCoinStart

-258:  seg/base=3/6 seg/base=12/37 MarkCoinEnd

-259:  seg/base=12/74 MarkCoinInsert

-260:  seg/base=3/33 MarkCoinInsert

-261:  seg/base=9/17 seg/base=10/19 MarkCoinStart

-262:  seg/base=9/18 seg/base=10/31 MarkCoinEnd

-SkOpSegment::debugShowActiveSpans id=1 (360,-2.14748365e+09 593011648,-2.14748352e+09 1.07374221e+09,-1.6667529e+09 1.07374208e+09,-1.07374157e+09) t=0 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=2 (1.07374208e+09,-1.07374157e+09 1.07374208e+09,-480730560 593011840,-135.508026 905.953125,255.999786) t=0 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=3 (905.953125,255.999786 900.097229,297.65976) t=0 tEnd=0.0649612467 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=3 (900.097229,297.65976 897.639343,315.145294) t=0.0649612467 tEnd=0.0922268392 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=3 (897.639343,315.145294 815.80835,897.304565) t=0.0922268392 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=4 (815.80835,897.304565 361.553253,652.788635) t=0 tEnd=0.753851653 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=4 (361.553253,652.788635 360,651.952515) t=0.753851653 tEnd=0.756429319 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=4 (360,651.952515 358.263184,651.017639) t=0.756429319 tEnd=0.759311649 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=4 (358.263184,651.017639 343.716705,643.187561) t=0.759311649 tEnd=0.783451987 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=4 (343.716705,643.187561 323.286591,632.190491) t=0.783451987 tEnd=0.817356482 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=4 (323.286591,632.190491 213.229446,572.949036) t=0.817356482 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (213.229446,572.949036 131.235565,320.967834) t=0 tEnd=0.452431368 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (131.235565,320.967834 131.104431,320.56485) t=0.452431368 tEnd=0.453154928 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (131.104431,320.56485 126.938354,307.76178) t=0.453154928 tEnd=0.476142764 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (126.938354,307.76178 124.825912,301.269867) t=0.476142764 tEnd=0.487798966 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (124.825912,301.269867 98.6440353,220.808502) t=0.487798966 tEnd=0.632267074 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (98.6440353,220.808502 70.3632965,133.896957) t=0.632267074 tEnd=0.788316424 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (70.3632965,133.896957 51.4065475,75.6396332) t=0.788316424 tEnd=0.892917257 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (51.4065475,75.6396332 32,16) t=0.892917257 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=6 (32,16 40.183567,75.6918945) t=0 tEnd=0.120346555 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=6 (40.183567,75.6918945 51.8013992,160.433731) t=0.120346555 tEnd=0.291197031 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=6 (51.8013992,160.433731 71.1438522,301.519836) t=0.291197031 tEnd=0.575644854 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=6 (71.1438522,301.519836 100,512) t=0.575644854 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=7 (100,512 125.872047,525.926392) t=0 tEnd=0.228492228 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=7 (125.872047,525.926392 127.959496,527.049988) t=0.228492228 tEnd=0.246927782 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=7 (127.959496,527.049988 132.205856,529.335754) t=0.246927782 tEnd=0.284429982 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=7 (132.205856,529.335754 213.229446,572.949036) t=0.284429982 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=8 (213.229446,572.949036 324.604553,915.223755) t=0 tEnd=0.758838213 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=8 (324.604553,915.223755 345.279358,978.760925) t=0.758838213 tEnd=0.899702926 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=8 (345.279358,978.760925 360,1024) t=0.899702926 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=9 (360,1024 360,4140) t=0 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,4140 360,1024) t=0 tEnd=1.45099777e-06 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,1024 360,651.952515) t=1.45099777e-06 tEnd=1.62424554e-06 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,651.952515 360,319.229858) t=1.62424554e-06 tEnd=1.77918132e-06 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,319.229858 360,296.296631) t=1.77918132e-06 tEnd=1.78986041e-06 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,296.296631 360,-2.14748365e+09) t=1.78986041e-06 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (127,321 131.235565,320.967834) t=0 tEnd=0.000630948916 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (131.235565,320.967834 212.053207,320.353821) t=0.000630948916 tEnd=0.0126699258 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (212.053207,320.353821 321.830627,319.519836) t=0.0126699258 tEnd=0.0290228849 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (321.830627,319.519836 342.208832,319.365021) t=0.0290228849 tEnd=0.0320585186 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (342.208832,319.365021 356.718292,319.254791) t=0.0320585186 tEnd=0.0342199142 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (356.718292,319.254791 360,319.229858) t=0.0342199142 tEnd=0.034708774 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (360,319.229858 813.100464,315.787567) t=0.034708774 tEnd=0.102204746 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (813.100464,315.787567 897.639343,315.145294) t=0.102204746 tEnd=0.114798057 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (897.639343,315.145294 900.178528,315.126007) t=0.114798057 tEnd=0.115176306 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (900.178528,315.126007 6840,270) t=0.115176306 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (6840,270 900.097229,297.65976) t=0 tEnd=2.7659819e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (900.097229,297.65976 897.557983,297.67157) t=2.7659819e-07 tEnd=2.76716432e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (897.557983,297.67157 813.017944,298.065247) t=2.76716432e-07 tEnd=2.80653133e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (813.017944,298.065247 359.911255,300.175171) t=2.80653133e-07 tEnd=3.0175255e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (359.911255,300.175171 358.361938,300.182373) t=3.0175255e-07 tEnd=3.01824696e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (358.361938,300.182373 360,1024) t=3.01824696e-07 tEnd=3.01905369e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (360,1024 360,4140) t=3.01905369e-07 tEnd=3.02581027e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (360,4140 321.741364,300.352905) t=3.02581027e-07 tEnd=3.03529974e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (321.741364,300.352905 211.962463,300.864105) t=3.03529974e-07 tEnd=3.08641951e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (211.962463,300.864105 131.143692,301.240448) t=3.08641951e-07 tEnd=3.12405367e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (131.143692,301.240448 131.01445,301.241058) t=3.12405367e-07 tEnd=3.12411385e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (131.01445,301.241058 127,321) t=3.12411385e-07 tEnd=3.12602603e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (127,321 124.825912,301.269867) t=3.12602603e-07 tEnd=3.12699562e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (124.825912,301.269867 100,512) t=3.12699562e-07 tEnd=3.13901276e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (100,512 71.1438522,301.519836) t=3.13901276e-07 tEnd=3.15199326e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (71.1438522,301.519836 -2.14748365e+10,100000000) t=3.15199326e-07 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (-2.14748365e+10,100000000 40.183567,75.6918945) t=0 tEnd=0.999999883 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (40.183567,75.6918945 51.4065437,75.6396332) t=0.999999883 tEnd=0.999999884 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (51.4065437,75.6396332 70.0916061,75.5526199) t=0.999999884 tEnd=0.999999884 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (70.0916061,75.5526199 100,512) t=0.999999884 tEnd=0.999999886 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (100,512 123.773666,75.3026428) t=0.999999886 tEnd=0.999999887 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (123.773666,75.3026428 127,321) t=0.999999887 tEnd=0.999999887 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (127,321 129.962204,75.2738266) t=0.999999887 tEnd=0.999999887 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (129.962204,75.2738266 130.091461,75.2732239) t=0.999999887 tEnd=0.999999887 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (130.091461,75.2732239 210.910217,74.8968811) t=0.999999887 tEnd=0.999999891 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (210.910217,74.8968811 360,4140) t=0.999999891 tEnd=0.999999897 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (360,4140 360,1024) t=0.999999897 tEnd=0.999999898 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (360,1024 357.309692,74.2151566) t=0.999999898 tEnd=0.999999898 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (357.309692,74.2151566 811.965698,72.0980072) t=0.999999898 tEnd=0.999999919 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (811.965698,72.0980072 896.505737,71.7043304) t=0.999999919 tEnd=0.999999923 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (896.505737,71.7043304 899.044983,71.6925125) t=0.999999923 tEnd=0.999999923 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (899.044983,71.6925125 905.094727,71.6643372) t=0.999999923 tEnd=0.999999923 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (905.094727,71.6643372 2551,64) t=0.999999923 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=14 (2551,64 360,296.296631) t=0 tEnd=0.903877888 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=14 (360,296.296631 321.741364,300.352905) t=0.903877888 tEnd=0.919661149 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=14 (321.741364,300.352905 131.104431,320.56485) t=0.919661149 tEnd=0.998306753 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=14 (131.104431,320.56485 127,321) t=0.998306753 tEnd=1 windSum=? windValue=1

-SkOpSegment::markDone id=4 (815.80835,897.304565 213.229446,572.949036) t=0 [7] (815.80835,897.304565) tEnd=0.753851653 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=4 (815.80835,897.304565 213.229446,572.949036) t=0.753851653 [93] (361.553253,652.788635) tEnd=0.756429319 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=4 (815.80835,897.304565 213.229446,572.949036) t=0.756429319 [29] (360,651.952515) tEnd=0.759311649 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=4 (815.80835,897.304565 213.229446,572.949036) t=0.759311649 [91] (358.263184,651.017639) tEnd=0.783451987 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=4 (815.80835,897.304565 213.229446,572.949036) t=0.783451987 [90] (343.716705,643.187561) tEnd=0.817356482 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=4 (815.80835,897.304565 213.229446,572.949036) t=0.817356482 [95] (323.286591,632.190491) tEnd=1 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=7 (100,512 213.229446,572.949036) t=0 [13] (100,512) tEnd=0.246927782 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=7 (100,512 213.229446,572.949036) t=0.246927782 [78] (127.959496,527.049988) tEnd=0.284429982 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=7 (100,512 213.229446,572.949036) t=0.284429982 [80] (132.205856,529.335754) tEnd=1 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=8 (213.229446,572.949036 360,1024) t=0 [15] (213.229446,572.949036) tEnd=0.758838213 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=8 (213.229446,572.949036 360,1024) t=0.758838213 [77] (324.604553,915.223755) tEnd=0.899702926 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=8 (213.229446,572.949036 360,1024) t=0.899702926 [76] (345.279358,978.760925) tEnd=1 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-SkOpSegment::markDone id=9 (360,1024 360,4140) t=0 [17] (360,1024) tEnd=1 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=1 oppValue=0

-------------------xx-xxx----x--x------ move_nearby

-00:  seg/base=13/96 coinSeg/Span/PtT=13/46/46 MergeMatches

-01:  seg/base=6/81 coinSeg/Span/PtT=6/12/12 MergeMatches

-02:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-03:  seg/base=13/96 coinSeg/Span/PtT=13/46/46 MergeMatches

-04:  seg/base=6/81 coinSeg/Span/PtT=6/12/12 MergeMatches

-05:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-06:  seg/base=13/96 coinSeg/Span/PtT=13/46/46 MergeMatches

-07:  seg/base=6/81 coinSeg/Span/PtT=6/12/12 MergeMatches

-08:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-09:  seg/base=13/96 coinSeg/Span/PtT=13/46/46 MergeMatches

-10:  seg/base=6/81 coinSeg/Span/PtT=6/12/12 MergeMatches

-11:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-12:  seg/base=12/89 coinSeg/Span/PtT=12/23/23 MergeMatches

-13:  seg/base=13/96 coinSeg/Span/PtT=13/87/87 MergeMatches

-14:  segment=11 MergeMatches

-15:  seg/base=5/47 coinSeg/Span/PtT=5/9/9 MergeMatches

-16:  seg/base=12/89 coinSeg/Span/PtT=12/23/23 MergeMatches

-17:  seg/base=13/96 coinSeg/Span/PtT=13/87/87 MergeMatches

-18:  segment=11 MergeMatches

-19:  seg/base=5/47 coinSeg/Span/PtT=5/9/9 MergeMatches

-20:  seg/base=4/90 coinSeg/Span/PtT=4/7/7 MoveNearbyRelease

-21:  seg/base=4/29 coinSeg/Span/PtT=4/7/7 MoveNearbyRelease

-22:  seg/base=4/91 coinSeg/Span/PtT=4/7/7 MoveNearbyRelease

-23:  seg/base=4/91 coinSeg/Span/PtT=4/29/29 MoveNearbyRelease

-24:  seg/base=4/90 coinSeg/Span/PtT=4/29/29 MoveNearbyRelease

-25:  seg/base=4/90 coinSeg/Span/PtT=4/91/91 MoveNearbyRelease

-26:  seg/base=4/29 coinSeg/Span/PtT=4/91/91 MoveNearbyRelease

-27:  seg/base=4/29 coinSeg/Span/PtT=4/90/90 MoveNearbyRelease

-28:  seg/base=4/91 coinSeg/Span/PtT=4/90/90 MoveNearbyRelease

-29:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-30:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-31:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-32:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-33:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-34:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-35:  seg/base=13/96 coinSeg/Span/PtT=13/87/87 MergeMatches

-36:  segment=11 MergeMatches

-37:  seg/base=5/47 coinSeg/Span/PtT=5/9/9 MergeMatches

-38:  seg/base=12/89 coinSeg/Span/PtT=12/23/23 MergeMatches

-39:  seg/base=13/96 coinSeg/Span/PtT=13/87/87 MergeMatches

-40:  segment=11 MergeMatches

-41:  seg/base=5/47 coinSeg/Span/PtT=5/9/9 MergeMatches

-42:  seg/base=12/89 coinSeg/Span/PtT=12/23/23 MergeMatches

-43:  seg/base=13/96 coinSeg/Span/PtT=13/46/46 MergeMatches

-44:  seg/base=6/81 coinSeg/Span/PtT=6/12/12 MergeMatches

-45:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-46:  seg/base=13/96 coinSeg/Span/PtT=13/46/46 MergeMatches

-47:  seg/base=6/81 coinSeg/Span/PtT=6/12/12 MergeMatches

-48:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-49:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-50:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-51:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-52:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-53:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-54:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-55:  seg/base=7/80 coinSeg/Span/PtT=7/13/13 MoveNearbyRelease

-56:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-57:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-58:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-59:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-60:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-61:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-62:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-63:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-64:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-65:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-66:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-67:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-68:  seg/base=8/76 coinSeg/Span/PtT=8/15/15 MoveNearbyRelease

-69:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-70:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-71:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-72:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-73:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-74:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-75:  seg/base=13/96 coinSeg/Span/PtT=13/46/46 MergeMatches

-76:  seg/base=6/81 coinSeg/Span/PtT=6/12/12 MergeMatches

-77:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-78:  seg/base=13/96 coinSeg/Span/PtT=13/46/46 MergeMatches

-79:  seg/base=6/81 coinSeg/Span/PtT=6/12/12 MergeMatches

-80:  seg/base=5/45 coinSeg/Span/PtT=5/9/9 MergeMatches

-81:  segment=9 MoveNearbyClearAll2

-82:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-83:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-84:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-85:  seg/base=12/72 coinSeg/Span/PtT=12/23/23 MergeMatches

-86:  seg/base=10/58 coinSeg/Span/PtT=10/19/19 MergeMatches

-87:  seg/base=11/59 coinSeg/Span/PtT=11/22/22 MergeMatches

-88:  seg/base=12/89 coinSeg/Span/PtT=12/23/23 MergeMatches

-89:  seg/base=5/47 coinSeg/Span/PtT=5/9/9 MergeMatches

-90:  segment=11 MergeMatches

-91:  seg/base=13/87 coinSeg/Span/PtT=13/96/96 MergeMatches

-92:  seg/base=12/89 coinSeg/Span/PtT=12/23/23 MergeMatches

-93:  seg/base=5/47 coinSeg/Span/PtT=5/9/9 MergeMatches

-94:  segment=11 MergeMatches

-95:  seg/base=13/87 coinSeg/Span/PtT=13/96/96 MergeMatches

-96:  coinSeg/Span/PtT=13/25/25 endSpan=87 oppSeg/Span/PtT=12/24/24 oppEndSpan=89 MissingCoin

-97:  coinSeg/Span/PtT=12/89/89 endSpan=24 oppSeg/Span/PtT=13/87/87 oppEndSpan=25 MissingCoin

-98:  coinSeg/Span/PtT=13/25/25 endSpan=87 oppSeg/Span/PtT=12/24/24 oppEndSpan=89 MissingCoin

-99:  coinSeg/Span/PtT=12/89/89 endSpan=24 oppSeg/Span/PtT=13/87/87 oppEndSpan=25 MissingCoin

-100:  seg/base=11/21 seg/base=12/23 MarkCoinStart

-101:  seg/base=11/22 seg/base=12/89 MarkCoinEnd

-102:  seg/base=12/72 MarkCoinInsert

-103:  seg/base=12/23 MarkCoinInsert

-104:  seg/base=11/69 MarkCoinInsert

-105:  seg/base=11/21 MarkCoinInsert

-106:  seg/base=13/46 seg/base=5/9 MarkCoinStart

-107:  seg/base=13/96 seg/base=5/45 MarkCoinEnd

-108:  seg/base=5/9 MarkCoinInsert

-109:  seg/base=13/46 MarkCoinInsert

-110:  seg/base=13/96 seg/base=3/5 MarkCoinStart

-111:  seg/base=13/32 seg/base=3/6 MarkCoinEnd

-SkOpSegment::debugShowActiveSpans id=1 (360,-2.14748365e+09 593011648,-2.14748352e+09 1.07374221e+09,-1.6667529e+09 1.07374208e+09,-1.07374157e+09) t=0 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=2 (1.07374208e+09,-1.07374157e+09 1.07374208e+09,-480730560 593011840,-135.508026 905.953125,255.999786) t=0 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=3 (905.953125,255.999786 815.80835,897.304565) t=0 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (213.229446,572.949036 131.104431,320.56485) t=0 tEnd=0.453154928 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (131.104431,320.56485 51.4065475,75.6396332) t=0.453154928 tEnd=0.892917257 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=5 (51.4065475,75.6396332 32,16) t=0.892917257 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=6 (32,16 51.8013992,160.433731) t=0 tEnd=0.291197031 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=6 (51.8013992,160.433731 100,512) t=0.291197031 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,4140 360,319.229858) t=0 tEnd=1.77918132e-06 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,319.229858 360,296.296631) t=1.77918132e-06 tEnd=1.78986041e-06 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=10 (360,296.296631 360,-2.14748365e+09) t=1.78986041e-06 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (127,321 321.830627,319.519836) t=0 tEnd=0.0290228849 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (321.830627,319.519836 360,319.229858) t=0.0290228849 tEnd=0.034708774 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=11 (360,319.229858 6840,270) t=0.034708774 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (6840,270 359.911255,300.175171) t=0 tEnd=3.0175255e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (359.911255,300.175171 321.741364,300.352905) t=3.0175255e-07 tEnd=3.03529974e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (321.741364,300.352905 131.01445,301.241058) t=3.03529974e-07 tEnd=3.12411385e-07 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=12 (131.01445,301.241058 -2.14748365e+10,100000000) t=3.12411385e-07 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (-2.14748365e+10,100000000 51.4065437,75.6396332) t=0 tEnd=0.999999884 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (51.4065437,75.6396332 129.962204,75.2738266) t=0.999999884 tEnd=0.999999887 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (129.962204,75.2738266 896.505737,71.7043304) t=0.999999887 tEnd=0.999999923 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (896.505737,71.7043304 905.094727,71.6643372) t=0.999999923 tEnd=0.999999923 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=13 (905.094727,71.6643372 2551,64) t=0.999999923 tEnd=1 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=14 (2551,64 360,296.296631) t=0 tEnd=0.903877888 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=14 (360,296.296631 321.741364,300.352905) t=0.903877888 tEnd=0.919661149 windSum=? windValue=1

-SkOpSegment::debugShowActiveSpans id=14 (321.741364,300.352905 127,321) t=0.919661149 tEnd=1 windSum=? windValue=1

-c:\skia\src\pathops\skopsegment.cpp(1358): fatal error: "assert((this->globalState() && (this->globalState()->debugCheckHealth() || this->globalState()->debugSkipAssert())) || (!SkDPoint::ApproximatelyEqual(dBugRef->fPt, dBugCheck->fPt)))"

-pathops_unittest.exe has triggered a breakpoint.

-        </div>

+{{{126, 9.396100044250488281}, {125.6320571899414063, 9.295844078063964844}, {125.1227340698242188, 9.337338447570800781}, {124.6031646728515625, 9.379667282104492188}}},

+inflectionsTs[0]=0.999997776 {{{126.1618761931709827, 9.252680507258252973}, {123.04446008475567, 9.506653492188940291}}},

+SkDCubic::ComplexBreak

+{{{124.6031646728515625, 9.379667282104492188}, {124.1427383422851563, 9.417178153991699219}, {123.6742630004882813, 9.45534515380859375}, {123.28900146484375, 9.396100044250488281}}},

+inflectionsTs[0]=4.14921088e-06 {{{125.984438133710114, 9.267135117035042668}, {123.2218797495565639, 9.492200381017113386}}},

+maxCurvature[0]=0.0817322831 {{{125.8735553329735666, 9.277935394706121386}, {123.1067608854740314, 9.499713475347833835}}},

+SkDCubic::ComplexBreak

+{{{126, 9.396200180053710938}, {125.305999755859375, 9.206999778747558594}, {124.1090011596679688, 9.522199630737304688}, {123.28900146484375, 9.396200180053710938}}},

+inflectionsTs[0]=0.530286644 {{{127.5428560571536707, 9.140180090182106198}, {121.6628069847287179, 9.619260454379688241}}},

+maxCurvature[0]=0.568563182 {{{127.4346914043237859, 9.15278490094694952}, {121.5456828946143446, 9.624913158409162506}}},

+seg=1 {{{0, 353.891998f}, {126, 9.39610004f}}}

+seg=2 {{{126, 9.39610004f}, {125.632057f, 9.29584408f}, {125.122734f, 9.33733845f}, {124.603165f, 9.37966728f}}}

+seg=3 {{{124.603165f, 9.37966728f}, {124.142731f, 9.41717815f}, {123.674263f, 9.45534515f}, {123.289001f, 9.39610004f}}}

+seg=4 {{{123.289001f, 9.39610004f}, {118.119003f, 8.07219982f}}}

+seg=5 {{{118.119003f, 8.07219982f}, {8.17210007f, 104.212997f}}}

+seg=6 {{{8.17210007f, 104.212997f}, {0, 259.298737f}}}

+seg=7 {{{0, 259.298737f}, {0, 353.891998f}}}

+op sect

+seg=8 {{{8.17210007f, 104.212997f}, {-5.82350016f, 369.813995f}}}

+seg=9 {{{-5.82350016f, 369.813995f}, {126, 9.39620018f}}}

+seg=10 {{{126, 9.39620018f}, {125.631981f, 9.29586983f}, {125.12252f, 9.3373785f}, {124.602829f, 9.37972069f}}}

+seg=11 {{{124.602829f, 9.37972069f}, {124.142509f, 9.41722488f}, {123.674164f, 9.45538425f}, {123.289001f, 9.39620018f}}}

+seg=12 {{{123.289001f, 9.39620018f}, {118.119003f, 8.07219982f}}}

+seg=13 {{{118.119003f, 8.07219982f}, {8.17210007f, 104.212997f}}}

+debugShowLineIntersection wtTs[0]=1 {{{8.17210007,104.212997}, {-5.82350016,369.813995}}} {{-5.82350016,369.813995}} wnTs[0]=0 {{{-5.82350016,369.813995}, {126,9.39620018}}}

+debugShowLineIntersection wtTs[0]=0 {{{8.17210007,104.212997}, {-5.82350016,369.813995}}} {{8.17210007,104.212997}} wnTs[0]=1 {{{118.119003,8.07219982}, {8.17210007,104.212997}}}

+debugShowCubicLineIntersection wtTs[0]=0 {{{126,9.39620018}, {125.631981,9.29586983}, {125.12252,9.3373785}, {124.602829,9.37972069}}} {{126,9.39620018}} wnTs[0]=1 {{{-5.82350016,369.813995}, {126,9.39620018}}}

+debugShowCubicLineIntersection no intersect {{{124.602829,9.37972069}, {124.142509,9.41722488}, {123.674164,9.45538425}, {123.289001,9.39620018}}} {{{-5.82350016,369.813995}, {126,9.39620018}}}

+debugShowLineIntersection no intersect {{{-5.82350016,369.813995}, {126,9.39620018}}} {{{123.289001,9.39620018}, {118.119003,8.07219982}}}

+debugShowLineIntersection no intersect {{{-5.82350016,369.813995}, {126,9.39620018}}} {{{118.119003,8.07219982}, {8.17210007,104.212997}}}

+debugShowCubicIntersection wtTs[0]=1 {{{126,9.39620018}, {125.631981,9.29586983}, {125.12252,9.3373785}, {124.602829,9.37972069}}} {{124.602829,9.37972069}} wnTs[0]=0 {{{124.602829,9.37972069}, {124.142509,9.41722488}, {123.674164,9.45538425}, {123.289001,9.39620018}}}

+debugShowCubicLineIntersection wtTs[0]=1 {{{124.602829,9.37972069}, {124.142509,9.41722488}, {123.674164,9.45538425}, {123.289001,9.39620018}}} {{123.289001,9.39620018}} wnTs[0]=0 {{{123.289001,9.39620018}, {118.119003,8.07219982}}}

+debugShowLineIntersection wtTs[0]=1 {{{123.289001,9.39620018}, {118.119003,8.07219982}}} {{118.119003,8.07219982}} wnTs[0]=0 {{{118.119003,8.07219982}, {8.17210007,104.212997}}}

+debugShowLineIntersection no intersect {{{8.17210007,104.212997}, {-5.82350016,369.813995}}} {{{0,353.891998}, {126,9.39610004}}}

+debugShowLineIntersection wtTs[0]=0 {{{8.17210007,104.212997}, {-5.82350016,369.813995}}} {{8.17210007,104.212997}} wnTs[0]=1 {{{118.119003,8.07219982}, {8.17210007,104.212997}}}

+debugShowLineIntersection wtTs[0]=0 {{{8.17210007,104.212997}, {-5.82350016,369.813995}}} {{8.17210007,104.212997}} wtTs[1]=0.583904956 {{0,259.298737}} wnTs[0]=0 {{{8.17210007,104.212997}, {0,259.298737}}} wnTs[1]=1

+SkOpSegment::addT insert t=0.583904956 segID=8 spanID=27

+debugShowLineIntersection wtTs[0]=0.583904956 {{{8.17210007,104.212997}, {-5.82350016,369.813995}}} {{0,259.298737}} wnTs[0]=0 {{{0,259.298737}, {0,353.891998}}}

+debugShowLineIntersection wtTs[0]=0.0441765002 {{{-5.82350016,369.813995}, {126,9.39620018}}} {{0,353.891998}} wtTs[1]=1 {{126,9.39620018}} wnTs[0]=0 {{{0,353.891998}, {126,9.39610004}}} wnTs[1]=0.999999744

+SkOpSegment::addT insert t=0.0441765002 segID=9 spanID=28

+debugShowCubicLineIntersection no intersect {{{124.603165,9.37966728}, {124.142731,9.41717815}, {123.674263,9.45534515}, {123.289001,9.39610004}}} {{{-5.82350016,369.813995}, {126,9.39620018}}}

+debugShowLineIntersection no intersect {{{-5.82350016,369.813995}, {126,9.39620018}}} {{{118.119003,8.07219982}, {8.17210007,104.212997}}}

+debugShowLineIntersection no intersect {{{-5.82350016,369.813995}, {126,9.39620018}}} {{{8.17210007,104.212997}, {0,259.298737}}}

+debugShowLineIntersection wtTs[0]=0.0441765002 {{{-5.82350016,369.813995}, {126,9.39620018}}} {{0,353.891998}} wnTs[0]=1 {{{0,259.298737}, {0,353.891998}}}

+debugShowCubicLineIntersection wtTs[0]=0 {{{126,9.39620018}, {125.631981,9.29586983}, {125.12252,9.3373785}, {124.602829,9.37972069}}} {{126,9.39620018}} wnTs[0]=1 {{{0,353.891998}, {126,9.39610004}}}

+-1=(0.375,0.5) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+-1=(0.5,0.625) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+-1=(0.625,0.75) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+-1=(0.75,0.8125) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+-1=(0.8125,0.875) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+-1=(0.875,0.9375) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+SkTSect::addForPerp priorSpan=-1 t=0.937702598 opp=-1

+-1=(0.9375,1) []

+SkTSect::addForPerp addBounded span=-1 opp=-1

+debugShowCubicIntersection wtTs[0]=0.307128906 {{{126,9.39620018}, {125.631981,9.29586983}, {125.12252,9.3373785}, {124.602829,9.37972069}}} {{125.624687,9.33981037}} wtTs[1]=0.9375 {{124.700119,9.37182617}} wnTs[0]=0.307191 {{{126,9.39610004}, {125.632057,9.29584408}, {125.122734,9.33733845}, {124.603165,9.37966728}}} wnTs[1]=0.937702598

+SkOpSegment::addT insert t=0.307128906 segID=10 spanID=29

+SkOpSegment::addT insert t=0.307190555 segID=2 spanID=30

+SkOpSegment::addT insert t=0.9375 segID=10 spanID=31

+SkOpSegment::addT insert t=0.937702598 segID=2 spanID=32

+debugShowCubicIntersection no intersect {{{126,9.39620018}, {125.631981,9.29586983}, {125.12252,9.3373785}, {124.602829,9.37972069}}} {{{124.603165,9.37966728}, {124.142731,9.41717815}, {123.674263,9.45534515}, {123.289001,9.39610004}}}

+debugShowCubicLineIntersection no intersect {{{124.602829,9.37972069}, {124.142509,9.41722488}, {123.674164,9.45538425}, {123.289001,9.39620018}}} {{{0,353.891998}, {126,9.39610004}}}

+-1=(0.125,0.1875) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+-1=(0.1875,0.25) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+-1=(0.25,0.375) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+-1=(0.375,0.5) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+-1=(0.5,0.625) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+-1=(0.625,0.75) [-1]

+SkTSect::addForPerp addBounded span=-1 opp=-1

+debugShowCubicIntersection wtTs[0]=0.0625 {{{124.602829,9.37972069}, {124.142509,9.41722488}, {123.674164,9.45538425}, {123.289001,9.39620018}}} {{124.516449,9.38673687}} wtTs[1]=0.625 {{123.752594,9.4268837}} wnTs[0]=0.0627287 {{{124.603165,9.37966728}, {124.142731,9.41717815}, {123.674263,9.45534515}, {123.289001,9.39610004}}} wnTs[1]=0.625091708

+SkOpSegment::addT insert t=0.0625 segID=11 spanID=33

+SkOpSegment::addT insert t=0.0627286673 segID=3 spanID=34

+SkOpSegment::addT insert t=0.625 segID=11 spanID=35

+SkOpSegment::addT insert t=0.625091708 segID=3 spanID=36

+debugShowCubicLineIntersection no intersect {{{124.602829,9.37972069}, {124.142509,9.41722488}, {123.674164,9.45538425}, {123.289001,9.39620018}}} {{{123.289001,9.39610004}, {118.119003,8.07219982}}}

+debugShowLineIntersection no intersect {{{123.289001,9.39620018}, {118.119003,8.07219982}}} {{{0,353.891998}, {126,9.39610004}}}

+debugShowCubicLineIntersection wtTs[0]=0.999978653 {{{124.603165,9.37966728}, {124.142731,9.41717815}, {123.674263,9.45534515}, {123.289001,9.39610004}}} {{123.289001,9.39620018}} wnTs[0]=0 {{{123.289001,9.39620018}, {118.119003,8.07219982}}}

+debugShowLineIntersection wtTs[0]=4.65488731e-06 {{{123.289001,9.39620018}, {118.119003,8.07219982}}} {{123.289001,9.39610004}} wtTs[1]=1 {{118.119003,8.07219982}} wnTs[0]=0 {{{123.289001,9.39610004}, {118.119003,8.07219982}}} wnTs[1]=1

+debugShowLineIntersection wtTs[0]=1 {{{123.289001,9.39620018}, {118.119003,8.07219982}}} {{118.119003,8.07219982}} wnTs[0]=0 {{{118.119003,8.07219982}, {8.17210007,104.212997}}}

+debugShowLineIntersection no intersect {{{118.119003,8.07219982}, {8.17210007,104.212997}}} {{{0,353.891998}, {126,9.39610004}}}

+debugShowLineIntersection wtTs[0]=0 {{{118.119003,8.07219982}, {8.17210007,104.212997}}} {{118.119003,8.07219982}} wnTs[0]=1 {{{123.289001,9.39610004}, {118.119003,8.07219982}}}

+debugShowLineIntersection wtTs[0]=0 {{{118.119003,8.07219982}, {8.17210007,104.212997}}} {{118.119003,8.07219982}} wtTs[1]=1 {{8.17210007,104.212997}} wnTs[0]=0 {{{118.119003,8.07219982}, {8.17210007,104.212997}}} wnTs[1]=1

+debugShowLineIntersection wtTs[0]=1 {{{118.119003,8.07219982}, {8.17210007,104.212997}}} {{8.17210007,104.212997}} wnTs[0]=0 {{{8.17210007,104.212997}, {0,259.298737}}}

+debugShowCubicLineIntersection wtTs[0]=0 {{{126,9.39610004}, {125.632057,9.29584408}, {125.122734,9.33733845}, {124.603165,9.37966728}}} {{126,9.39610004}} wnTs[0]=1 {{{0,353.891998}, {126,9.39610004}}}

+debugShowCubicLineIntersection no intersect {{{124.603165,9.37966728}, {124.142731,9.41717815}, {123.674263,9.45534515}, {123.289001,9.39610004}}} {{{0,353.891998}, {126,9.39610004}}}

+debugShowLineIntersection no intersect {{{0,353.891998}, {126,9.39610004}}} {{{123.289001,9.39610004}, {118.119003,8.07219982}}}

+debugShowLineIntersection no intersect {{{0,353.891998}, {126,9.39610004}}} {{{118.119003,8.07219982}, {8.17210007,104.212997}}}

+debugShowLineIntersection no intersect {{{0,353.891998}, {126,9.39610004}}} {{{8.17210007,104.212997}, {0,259.298737}}}

+debugShowLineIntersection wtTs[0]=0 {{{0,353.891998}, {126,9.39610004}}} {{0,353.891998}} wnTs[0]=1 {{{0,259.298737}, {0,353.891998}}}

+debugShowCubicIntersection wtTs[0]=1 {{{126,9.39610004}, {125.632057,9.29584408}, {125.122734,9.33733845}, {124.603165,9.37966728}}} {{124.603165,9.37966728}} wnTs[0]=0 {{{124.603165,9.37966728}, {124.142731,9.41717815}, {123.674263,9.45534515}, {123.289001,9.39610004}}}

+debugShowCubicLineIntersection wtTs[0]=1 {{{124.603165,9.37966728}, {124.142731,9.41717815}, {123.674263,9.45534515}, {123.289001,9.39610004}}} {{123.289001,9.39610004}} wnTs[0]=0 {{{123.289001,9.39610004}, {118.119003,8.07219982}}}

+debugShowLineIntersection wtTs[0]=1 {{{123.289001,9.39610004}, {118.119003,8.07219982}}} {{118.119003,8.07219982}} wnTs[0]=0 {{{118.119003,8.07219982}, {8.17210007,104.212997}}}

+debugShowLineIntersection wtTs[0]=1 {{{118.119003,8.07219982}, {8.17210007,104.212997}}} {{8.17210007,104.212997}} wnTs[0]=0 {{{8.17210007,104.212997}, {0,259.298737}}}

+debugShowLineIntersection wtTs[0]=1 {{{8.17210007,104.212997}, {0,259.298737}}} {{0,259.298737}} wnTs[0]=0 {{{0,259.298737}, {0,353.891998}}}

+----------------x-x--x-x-------------- addExpanded

+00:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+01:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+02:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+03:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+04:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+05:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+06:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+07:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+08:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+09:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+10:  coinSeg/Span/PtT=11/33/33 endSpan=6 oppSeg/Span/PtT=11/22/22 oppEndSpan=6 ExpandCoin

+11:  coinSeg/Span/PtT=2/30/30 endSpan=19 oppSeg/Span/PtT=2/3/3 oppEndSpan=19 ExpandCoin

+12:  seg/base=13/25 seg/base=5/9 MarkCoinStart

+13:  seg/base=13/26 seg/base=5/10 MarkCoinEnd

+14:  seg/base=4/7 seg/base=12/23 MarkCoinStart

+15:  seg/base=4/8 seg/base=12/24 MarkCoinEnd

+16:  seg/base=11/33 seg/base=3/34 MarkCoinStart

+17:  seg/base=11/35 seg/base=3/36 MarkCoinEnd

+18:  seg/base=2/30 seg/base=10/29 MarkCoinStart

+19:  seg/base=2/32 seg/base=10/31 MarkCoinEnd

+20:  seg/base=9/28 seg/base=1/1 MarkCoinStart

+21:  seg/base=9/18 seg/base=1/2 MarkCoinEnd

+22:  seg/base=8/15 seg/base=6/11 MarkCoinStart

+23:  seg/base=8/27 seg/base=6/12 MarkCoinEnd

+SkOpSegment::debugShowActiveSpans id=8 (8.17210007,104.212997 -2.71619996e-07,259.298737) t=0 tEnd=0.583904956 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=8 (-2.71619996e-07,259.298737 -5.82350016,369.813995) t=0.583904956 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=9 (-5.82350016,369.813995 7.26031715e-07,353.891998) t=0 tEnd=0.0441765002 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=9 (7.26031715e-07,353.891998 126,9.39620018) t=0.0441765002 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=10 (126,9.39620018 125.886971,9.36538583 125.760599,9.34795095 125.624687,9.33981037) t=0 tEnd=0.307128906 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=10 (125.624687,9.33981037 125.345733,9.32310212 125.026588,9.34554777 124.700119,9.37182617) t=0.307128906 tEnd=0.9375 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=10 (124.700119,9.37182617 124.66775,9.37443162 124.63531,9.3770743 124.602829,9.37972069) t=0.9375 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=11 (124.602829,9.37972069 124.574059,9.3820647 124.545259,9.38441166 124.516449,9.38673687) t=0 tEnd=0.0625 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=11 (124.516449,9.38673687 124.257155,9.40766372 123.997126,9.42685982 123.752594,9.4268837) t=0.0625 tEnd=0.625 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=11 (123.752594,9.4268837 123.589573,9.42689962 123.433437,9.41839421 123.289001,9.39620018) t=0.625 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=12 (123.289001,9.39620018 118.119003,8.07219982) t=0 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=13 (118.119003,8.07219982 8.17210007,104.212997) t=0 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=1 (0,353.891998 126,9.39610004) t=0 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=2 (126,9.39610004 125.886971,9.36530236 125.760605,9.34788101 125.624695,9.33975124) t=0 tEnd=0.307190555 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=2 (125.624695,9.33975124 125.345738,9.3230648 125.026588,9.34552196 124.700119,9.37180042) t=0.307190555 tEnd=0.937702598 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=2 (124.700119,9.37180042 124.667862,9.37439685 124.635532,9.37703031 124.603165,9.37966728) t=0.937702598 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=3 (124.603165,9.37966728 124.574282,9.38202029 124.545364,9.3843762 124.516441,9.38671017) t=0 tEnd=0.0627286673 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=3 (124.516441,9.38671017 124.257145,9.40763418 123.997124,9.42681973 123.752594,9.42682648) t=0.0627286673 tEnd=0.625091708 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=3 (123.752594,9.42682648 123.589574,9.42683098 123.433439,9.41831153 123.289001,9.39610004) t=0.625091708 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=4 (123.289001,9.39610004 118.119003,8.07219982) t=0 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=5 (118.119003,8.07219982 8.17210007,104.212997) t=0 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=6 (8.17210007,104.212997 0,259.298737) t=0 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=7 (0,259.298737 0,353.891998) t=0 tEnd=1 windSum=? windValue=1

+----------------x-x--x-x-------------- move_multiples

+00:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+01:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+02:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+03:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+04:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+05:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+06:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+07:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+08:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+09:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+10:  coinSeg/Span/PtT=11/33/33 endSpan=6 oppSeg/Span/PtT=11/22/22 oppEndSpan=6 ExpandCoin

+11:  coinSeg/Span/PtT=2/30/30 endSpan=19 oppSeg/Span/PtT=2/3/3 oppEndSpan=19 ExpandCoin

+12:  seg/base=13/25 seg/base=5/9 MarkCoinStart

+13:  seg/base=13/26 seg/base=5/10 MarkCoinEnd

+14:  seg/base=4/7 seg/base=12/23 MarkCoinStart

+15:  seg/base=4/8 seg/base=12/24 MarkCoinEnd

+16:  seg/base=11/33 seg/base=3/34 MarkCoinStart

+17:  seg/base=11/35 seg/base=3/36 MarkCoinEnd

+18:  seg/base=2/30 seg/base=10/29 MarkCoinStart

+19:  seg/base=2/32 seg/base=10/31 MarkCoinEnd

+20:  seg/base=9/28 seg/base=1/1 MarkCoinStart

+21:  seg/base=9/18 seg/base=1/2 MarkCoinEnd

+22:  seg/base=8/15 seg/base=6/11 MarkCoinStart

+23:  seg/base=8/27 seg/base=6/12 MarkCoinEnd

+----------------x-x--x-x-------------- move_nearby

+00:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+01:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+02:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+03:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+04:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+05:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+06:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+07:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+08:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+09:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+10:  coinSeg/Span/PtT=11/33/33 endSpan=6 oppSeg/Span/PtT=11/22/22 oppEndSpan=6 ExpandCoin

+11:  coinSeg/Span/PtT=2/30/30 endSpan=19 oppSeg/Span/PtT=2/3/3 oppEndSpan=19 ExpandCoin

+12:  seg/base=13/25 seg/base=5/9 MarkCoinStart

+13:  seg/base=13/26 seg/base=5/10 MarkCoinEnd

+14:  seg/base=4/7 seg/base=12/23 MarkCoinStart

+15:  seg/base=4/8 seg/base=12/24 MarkCoinEnd

+16:  seg/base=11/33 seg/base=3/34 MarkCoinStart

+17:  seg/base=11/35 seg/base=3/36 MarkCoinEnd

+18:  seg/base=2/30 seg/base=10/29 MarkCoinStart

+19:  seg/base=2/32 seg/base=10/31 MarkCoinEnd

+20:  seg/base=9/28 seg/base=1/1 MarkCoinStart

+21:  seg/base=9/18 seg/base=1/2 MarkCoinEnd

+22:  seg/base=8/15 seg/base=6/11 MarkCoinStart

+23:  seg/base=8/27 seg/base=6/12 MarkCoinEnd

+----------------x-x--x-x-------------- correctEnds

+00:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+01:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+02:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+03:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+04:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+05:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+06:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+07:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+08:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+09:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+10:  coinSeg/Span/PtT=11/33/33 endSpan=6 oppSeg/Span/PtT=11/22/22 oppEndSpan=6 ExpandCoin

+11:  coinSeg/Span/PtT=2/30/30 endSpan=19 oppSeg/Span/PtT=2/3/3 oppEndSpan=19 ExpandCoin

+12:  seg/base=13/25 seg/base=5/9 MarkCoinStart

+13:  seg/base=13/26 seg/base=5/10 MarkCoinEnd

+14:  seg/base=4/7 seg/base=12/23 MarkCoinStart

+15:  seg/base=4/8 seg/base=12/24 MarkCoinEnd

+16:  seg/base=11/33 seg/base=3/34 MarkCoinStart

+17:  seg/base=11/35 seg/base=3/36 MarkCoinEnd

+18:  seg/base=2/30 seg/base=10/29 MarkCoinStart

+19:  seg/base=2/32 seg/base=10/31 MarkCoinEnd

+20:  seg/base=9/28 seg/base=1/1 MarkCoinStart

+21:  seg/base=9/18 seg/base=1/2 MarkCoinEnd

+22:  seg/base=8/15 seg/base=6/11 MarkCoinStart

+23:  seg/base=8/27 seg/base=6/12 MarkCoinEnd

+----------------x-x--x-x-------------- addEndMovedSpans

+00:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+01:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+02:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+03:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+04:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+05:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+06:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+07:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+08:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+09:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+10:  coinSeg/Span/PtT=11/33/33 endSpan=6 oppSeg/Span/PtT=11/22/22 oppEndSpan=6 ExpandCoin

+11:  coinSeg/Span/PtT=2/30/30 endSpan=19 oppSeg/Span/PtT=2/3/3 oppEndSpan=19 ExpandCoin

+12:  seg/base=13/25 seg/base=5/9 MarkCoinStart

+13:  seg/base=13/26 seg/base=5/10 MarkCoinEnd

+14:  seg/base=4/7 seg/base=12/23 MarkCoinStart

+15:  seg/base=4/8 seg/base=12/24 MarkCoinEnd

+16:  seg/base=11/33 seg/base=3/34 MarkCoinStart

+17:  seg/base=11/35 seg/base=3/36 MarkCoinEnd

+18:  seg/base=2/30 seg/base=10/29 MarkCoinStart

+19:  seg/base=2/32 seg/base=10/31 MarkCoinEnd

+20:  seg/base=9/28 seg/base=1/1 MarkCoinStart

+21:  seg/base=9/18 seg/base=1/2 MarkCoinEnd

+22:  seg/base=8/15 seg/base=6/11 MarkCoinStart

+23:  seg/base=8/27 seg/base=6/12 MarkCoinEnd

+----------------x-x--x-x-------------- expand

+00:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+01:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+02:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+03:  coinSeg/Span/PtT=2/3/3 endSpan=30 oppSeg/Span/PtT=10/19/19 oppEndSpan=29 MissingCoin

+04:  coinSeg/Span/PtT=3/36/36 endSpan=6 oppSeg/Span/PtT=11/35/35 oppEndSpan=22 MissingCoin

+05:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+06:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+07:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+08:  coinSeg/Span/PtT=10/19/19 endSpan=29 oppSeg/Span/PtT=2/3/3 oppEndSpan=30 MissingCoin

+09:  coinSeg/Span/PtT=11/35/35 endSpan=22 oppSeg/Span/PtT=3/36/36 oppEndSpan=6 MissingCoin

+10:  coinSeg/Span/PtT=11/33/33 endSpan=6 oppSeg/Span/PtT=11/22/22 oppEndSpan=6 ExpandCoin

+11:  coinSeg/Span/PtT=2/30/30 endSpan=19 oppSeg/Span/PtT=2/3/3 oppEndSpan=19 ExpandCoin

+12:  seg/base=13/25 seg/base=5/9 MarkCoinStart

+13:  seg/base=13/26 seg/base=5/10 MarkCoinEnd

+14:  seg/base=4/7 seg/base=12/23 MarkCoinStart

+15:  seg/base=4/8 seg/base=12/24 MarkCoinEnd

+16:  seg/base=11/33 seg/base=3/34 MarkCoinStart

+17:  seg/base=11/35 seg/base=3/36 MarkCoinEnd

+18:  seg/base=2/30 seg/base=10/29 MarkCoinStart

+19:  seg/base=2/32 seg/base=10/31 MarkCoinEnd

+20:  seg/base=9/28 seg/base=1/1 MarkCoinStart

+21:  seg/base=9/18 seg/base=1/2 MarkCoinEnd

+22:  seg/base=8/15 seg/base=6/11 MarkCoinStart

+23:  seg/base=8/27 seg/base=6/12 MarkCoinEnd

+------------------xx-x-x-------------- addExpanded

+00:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+01:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+02:  seg/base=13/25 seg/base=5/9 MarkCoinStart

+03:  seg/base=13/26 seg/base=5/10 MarkCoinEnd

+04:  seg/base=4/7 seg/base=12/23 MarkCoinStart

+05:  seg/base=4/8 seg/base=12/24 MarkCoinEnd

+06:  seg/base=11/33 seg/base=3/34 MarkCoinStart

+07:  seg/base=11/22 seg/base=3/6 MarkCoinEnd

+08:  seg/base=3/36 MarkCoinInsert

+09:  seg/base=11/35 MarkCoinInsert

+10:  seg/base=2/3 seg/base=10/19 MarkCoinStart

+11:  seg/base=2/32 seg/base=10/31 MarkCoinEnd

+12:  seg/base=10/29 MarkCoinInsert

+13:  seg/base=2/30 MarkCoinInsert

+14:  seg/base=9/28 seg/base=1/1 MarkCoinStart

+15:  seg/base=9/18 seg/base=1/2 MarkCoinEnd

+16:  seg/base=8/15 seg/base=6/11 MarkCoinStart

+17:  seg/base=8/27 seg/base=6/12 MarkCoinEnd

+------------------xx-x-x-------------- move_multiples

+00:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+01:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+02:  seg/base=13/25 seg/base=5/9 MarkCoinStart

+03:  seg/base=13/26 seg/base=5/10 MarkCoinEnd

+04:  seg/base=4/7 seg/base=12/23 MarkCoinStart

+05:  seg/base=4/8 seg/base=12/24 MarkCoinEnd

+06:  seg/base=11/33 seg/base=3/34 MarkCoinStart

+07:  seg/base=11/22 seg/base=3/6 MarkCoinEnd

+08:  seg/base=3/36 MarkCoinInsert

+09:  seg/base=11/35 MarkCoinInsert

+10:  seg/base=2/3 seg/base=10/19 MarkCoinStart

+11:  seg/base=2/32 seg/base=10/31 MarkCoinEnd

+12:  seg/base=10/29 MarkCoinInsert

+13:  seg/base=2/30 MarkCoinInsert

+14:  seg/base=9/28 seg/base=1/1 MarkCoinStart

+15:  seg/base=9/18 seg/base=1/2 MarkCoinEnd

+16:  seg/base=8/15 seg/base=6/11 MarkCoinStart

+17:  seg/base=8/27 seg/base=6/12 MarkCoinEnd

+------------------xx-x-x-------------- move_nearby

+00:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+01:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+02:  seg/base=13/25 seg/base=5/9 MarkCoinStart

+03:  seg/base=13/26 seg/base=5/10 MarkCoinEnd

+04:  seg/base=4/7 seg/base=12/23 MarkCoinStart

+05:  seg/base=4/8 seg/base=12/24 MarkCoinEnd

+06:  seg/base=11/33 seg/base=3/34 MarkCoinStart

+07:  seg/base=11/22 seg/base=3/6 MarkCoinEnd

+08:  seg/base=3/36 MarkCoinInsert

+09:  seg/base=11/35 MarkCoinInsert

+10:  seg/base=2/3 seg/base=10/19 MarkCoinStart

+11:  seg/base=2/32 seg/base=10/31 MarkCoinEnd

+12:  seg/base=10/29 MarkCoinInsert

+13:  seg/base=2/30 MarkCoinInsert

+14:  seg/base=9/28 seg/base=1/1 MarkCoinStart

+15:  seg/base=9/18 seg/base=1/2 MarkCoinEnd

+16:  seg/base=8/15 seg/base=6/11 MarkCoinStart

+17:  seg/base=8/27 seg/base=6/12 MarkCoinEnd

+------------------xx-x-x-------------- addExpanded

+00:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+01:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+02:  seg/base=13/25 seg/base=5/9 MarkCoinStart

+03:  seg/base=13/26 seg/base=5/10 MarkCoinEnd

+04:  seg/base=4/7 seg/base=12/23 MarkCoinStart

+05:  seg/base=4/8 seg/base=12/24 MarkCoinEnd

+06:  seg/base=11/33 seg/base=3/34 MarkCoinStart

+07:  seg/base=11/22 seg/base=3/6 MarkCoinEnd

+08:  seg/base=3/36 MarkCoinInsert

+09:  seg/base=11/35 MarkCoinInsert

+10:  seg/base=2/3 seg/base=10/19 MarkCoinStart

+11:  seg/base=2/32 seg/base=10/31 MarkCoinEnd

+12:  seg/base=10/29 MarkCoinInsert

+13:  seg/base=2/30 MarkCoinInsert

+14:  seg/base=9/28 seg/base=1/1 MarkCoinStart

+15:  seg/base=9/18 seg/base=1/2 MarkCoinEnd

+16:  seg/base=8/15 seg/base=6/11 MarkCoinStart

+17:  seg/base=8/27 seg/base=6/12 MarkCoinEnd

+------------------xx-x-x-------------- mark

+00:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+01:  coinSeg/Span/PtT=5/9/9 endSpan=10 oppSeg/Span/PtT=13/25/25 oppEndSpan=26 MissingCoin

+02:  seg/base=13/25 seg/base=5/9 MarkCoinStart

+03:  seg/base=13/26 seg/base=5/10 MarkCoinEnd

+04:  seg/base=4/7 seg/base=12/23 MarkCoinStart

+05:  seg/base=4/8 seg/base=12/24 MarkCoinEnd

+06:  seg/base=11/33 seg/base=3/34 MarkCoinStart

+07:  seg/base=11/22 seg/base=3/6 MarkCoinEnd

+08:  seg/base=3/36 MarkCoinInsert

+09:  seg/base=11/35 MarkCoinInsert

+10:  seg/base=2/3 seg/base=10/19 MarkCoinStart

+11:  seg/base=2/32 seg/base=10/31 MarkCoinEnd

+12:  seg/base=10/29 MarkCoinInsert

+13:  seg/base=2/30 MarkCoinInsert

+14:  seg/base=9/28 seg/base=1/1 MarkCoinStart

+15:  seg/base=9/18 seg/base=1/2 MarkCoinEnd

+16:  seg/base=8/15 seg/base=6/11 MarkCoinStart

+17:  seg/base=8/27 seg/base=6/12 MarkCoinEnd

+-------------------------------------- missing_coincidence

+-------------------------------------- expand

+-------------------------------------- expand

+-------------------------------------- apply

+SkOpSegment::markDone id=5 (118.119003,8.07219982 8.17210007,104.212997) t=0 [9] (118.119003,8.07219982) tEnd=1 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=0 oppValue=0

+SkOpSegment::markDone id=12 (123.289001,9.39620018 118.119003,8.07219982) t=0 [23] (123.289001,9.39620018) tEnd=1 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=0 oppValue=0

+SkOpSegment::markDone id=3 (124.603165,9.37966728 124.142731,9.41717815 123.674263,9.45534515 123.289001,9.39610004) t=0.0627286673 [34] (124.516441,9.38671017) tEnd=0.625091708 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=0 oppValue=0

+SkOpSegment::markDone id=3 (124.603165,9.37966728 124.142731,9.41717815 123.674263,9.45534515 123.289001,9.39610004) t=0.625091708 [36] (123.752594,9.42682648) tEnd=1 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=0 oppValue=0

+SkOpSegment::markDone id=10 (126,9.39620018 125.631981,9.29586983 125.12252,9.3373785 124.602829,9.37972069) t=0 [19] (126,9.39620018) tEnd=0.307128906 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=0 oppValue=0

+SkOpSegment::markDone id=10 (126,9.39620018 125.631981,9.29586983 125.12252,9.3373785 124.602829,9.37972069) t=0.307128906 [29] (125.624687,9.33981037) tEnd=0.9375 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=0 oppValue=0

+SkOpSegment::markDone id=1 (0,353.891998 126,9.39610004) t=0 [1] (0,353.891998) tEnd=1 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=0 oppValue=0

+SkOpSegment::markDone id=6 (8.17210007,104.212997 0,259.298737) t=0 [11] (8.17210007,104.212997) tEnd=1 newWindSum=? newOppSum=? oppSum=? windSum=? windValue=0 oppValue=0

+-------------------------------------- findOverlaps

+SkOpSegment::debugShowActiveSpans id=8 (8.17210007,104.212997 -2.71619996e-07,259.298737) t=0 tEnd=0.583904956 windSum=? oppSum=? windValue=1 oppValue=1

+SkOpSegment::debugShowActiveSpans id=8 (-2.71619996e-07,259.298737 -5.82350016,369.813995) t=0.583904956 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=9 (-5.82350016,369.813995 7.26031715e-07,353.891998) t=0 tEnd=0.0441765002 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=9 (7.26031715e-07,353.891998 126,9.39620018) t=0.0441765002 tEnd=1 windSum=? oppSum=? windValue=1 oppValue=1

+SkOpSegment::debugShowActiveSpans id=10 (124.700119,9.37182617 124.66775,9.37443162 124.63531,9.3770743 124.602829,9.37972069) t=0.9375 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=11 (124.602829,9.37972069 124.574059,9.3820647 124.545259,9.38441166 124.516449,9.38673687) t=0 tEnd=0.0625 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=11 (124.516449,9.38673687 124.257155,9.40766372 123.997126,9.42685982 123.752594,9.4268837) t=0.0625 tEnd=0.625 windSum=? oppSum=? windValue=1 oppValue=1

+SkOpSegment::debugShowActiveSpans id=11 (123.752594,9.4268837 123.589573,9.42689962 123.433437,9.41839421 123.289001,9.39620018) t=0.625 tEnd=1 windSum=? oppSum=? windValue=1 oppValue=1

+SkOpSegment::debugShowActiveSpans id=13 (118.119003,8.07219982 8.17210007,104.212997) t=0 tEnd=1 windSum=? oppSum=? windValue=1 oppValue=1

+SkOpSegment::debugShowActiveSpans id=2 (126,9.39610004 125.886971,9.36530236 125.760605,9.34788101 125.624695,9.33975124) t=0 tEnd=0.307190555 windSum=? oppSum=? windValue=1 oppValue=1

+SkOpSegment::debugShowActiveSpans id=2 (125.624695,9.33975124 125.345738,9.3230648 125.026588,9.34552196 124.700119,9.37180042) t=0.307190555 tEnd=0.937702598 windSum=? oppSum=? windValue=1 oppValue=1

+SkOpSegment::debugShowActiveSpans id=2 (124.700119,9.37180042 124.667862,9.37439685 124.635532,9.37703031 124.603165,9.37966728) t=0.937702598 tEnd=1 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=3 (124.603165,9.37966728 124.574282,9.38202029 124.545364,9.3843762 124.516441,9.38671017) t=0 tEnd=0.0627286673 windSum=? windValue=1

+SkOpSegment::debugShowActiveSpans id=4 (123.289001,9.39610004 118.119003,8.07219982) t=0 tEnd=1 windSum=? oppSum=? windValue=1 oppValue=1

+SkOpSegment::debugShowActiveSpans id=7 (0,259.298737 0,353.891998) t=0 tEnd=1 windSum=? windValue=1

+-------------------------------------- calc_angles

+SkOpSegment::sortAngles [8] tStart=0 [15]

+SkOpSegment::sortAngles [8] tStart=0.583904956 [27]

+SkOpAngle::after [8/2] 5/5 tStart=0.583904956 tEnd=0 < [7/23] 23/23 tStart=0 tEnd=1 < [8/3] 21/21 tStart=0.583904956 tEnd=1  F 4

+SkOpAngle::afterPart {{{0,259.298737}, {8.17210034,104.212997}}} id=8

+SkOpAngle::afterPart {{{0,259.298737}, {0,353.891998}}} id=7

+SkOpAngle::afterPart {{{0,259.298737}, {-5.82349988,369.813995}}} id=8

+SkOpSegment::sortAngles [9] tStart=0.0441765002 [28]

+SkOpAngle::after [9/4] 21/21 tStart=0.0441765002 tEnd=0 < [7/24] 7/7 tStart=1 tEnd=0 < [9/5] 5/5 tStart=0.0441765002 tEnd=1  F 4

+SkOpAngle::afterPart {{{0,353.891998}, {-5.82350088,369.813995}}} id=9

+SkOpAngle::afterPart {{{0,353.891998}, {0,259.298737}}} id=7

+SkOpAngle::afterPart {{{0,353.891998}, {125.999999,9.39620018}}} id=9

+SkOpSegment::sortAngles [9] tStart=1 [18]

+SkOpSegment::sortAngles [10] tStart=0.9375 [31]

+SkOpAngle::after [10/7] 17/17 tStart=0.9375 tEnd=1 < [2/19] 17/17 tStart=0.937702598 tEnd=1 < [2/18] 1/1 tStart=0.937702598 tEnd=0.307190555  T 12

+SkOpAngle::afterPart {{{124.700119,9.37180042}, {124.66775,9.37440587}, {124.63531,9.37704855}, {124.602829,9.37969494}}} id=10

+SkOpAngle::afterPart {{{124.700119,9.37180042}, {124.667862,9.37439685}, {124.635532,9.37703031}, {124.603165,9.37966728}}} id=2

+SkOpAngle::afterPart {{{124.700119,9.37180042}, {125.026588,9.34552196}, {125.345738,9.3230648}, {125.624695,9.33975124}}} id=2

+SkOpSegment::sortAngles [11] tStart=0.0625 [33]

+SkOpAngle::after [11/8] 1/1 tStart=0.0625 tEnd=0 < [3/20] 1/1 tStart=0.0627286673 tEnd=0 < [11/9] 17/17 tStart=0.0625 tEnd=0.625  T 12

+SkOpAngle::afterPart {{{124.516441,9.38671017}, {124.545252,9.38438496}, {124.574051,9.382038}, {124.602821,9.37969398}}} id=11

+SkOpAngle::afterPart {{{124.516441,9.38671017}, {124.545364,9.3843762}, {124.574282,9.38202029}, {124.603165,9.37966728}}} id=3

+SkOpAngle::afterPart {{{124.516441,9.38671017}, {124.257148,9.40763702}, {123.997118,9.42683311}, {123.752586,9.42685699}}} id=11

+SkOpSegment::sortAngles [11] tStart=0.625 [35]

+SkOpSegment::sortAngles [11] tStart=1 [22]

+SkOpSegment::sortAngles [13] tStart=0 [25]

+SkOpSegment::sortAngles [13] tStart=1 [26]

+SkOpSegment::sortAngles [2] tStart=0 [3]

+SkOpSegment::sortAngles [2] tStart=0.307190555 [30]

+SkOpSegment::sortAngles [2] tStart=0.937702598 [32]

+SkOpSegment::sortAngles [3] tStart=0.0627286673 [34]

+SkOpSegment::sortAngles [4] tStart=0 [7]

+SkOpSegment::sortAngles [4] tStart=1 [8]

+SkOpSegment::sortAngles [7] tStart=0 [13]

+SkOpSegment::sortAngles [7] tStart=1 [14]

+coinSpan - id=13 t=0 tEnd=1

+coinSpan + id=5 t=0 tEnd=1

+coinSpan - id=4 t=0 tEnd=1

+coinSpan + id=12 t=0 tEnd=1

+coinSpan - id=11 t=0.0625 tEnd=1

+coinSpan + id=3 t=0.0627286673 tEnd=1

+coinSpan - id=2 t=0 tEnd=0.937702598

+coinSpan + id=10 t=0 tEnd=0.9375

+coinSpan - id=9 t=0.0441765002 tEnd=1

+coinSpan + id=1 t=0 tEnd=1

+coinSpan - id=8 t=0 tEnd=0.583904956

+coinSpan + id=6 t=0 tEnd=1

+SkOpSpan::sortableTop dir=kLeft seg=8 t=0.291952478 pt=(4.08605003,181.755859)

+SkOpSpan::sortableTop [0] valid=1 operand=1 span=15 ccw=0 seg=8 {{{8.17210007f, 104.212997f}, {-5.82350016f, 369.813995f}}} t=0.291952478 pt=(4.08605003,181.755859) slope=(-13.9956002,265.600998)

+SkOpSegment::markWinding id=8 (8.17210007,104.212997 -5.82350016,369.813995) t=0 [15] (8.17210007,104.212997) tEnd=0.583904956 newWindSum=1 newOppSum=1 oppSum=1 windSum=1 windValue=1 oppValue=1

+SkOpSegment::markWinding id=8 (8.17210007,104.212997 -5.82350016,369.813995) t=0 [15] (8.17210007,104.212997) tEnd=0.583904956 newWindSum=1 newOppSum=1 oppSum=1 windSum=1 windValue=1 oppValue=1

+SkOpSegment::markWinding id=13 (118.119003,8.07219982 8.17210007,104.212997) t=0 [25] (118.119003,8.07219982) tEnd=1 newWindSum=1 newOppSum=1 oppSum=? windSum=? windValue=1 oppValue=1

+SkOpSegment::markWinding id=4 (123.289001,9.39610004 118.119003,8.07219982) t=0 [7] (123.289001,9.39610004) tEnd=1 newWindSum=1 newOppSum=1 oppSum=? windSum=? windValue=1 oppValue=1

+SkOpSegment::markWinding id=11 (124.602829,9.37972069 124.142509,9.41722488 123.674164,9.45538425 123.289001,9.39620018) t=0.625 [35] (123.752594,9.4268837) tEnd=1 newWindSum=1 newOppSum=1 oppSum=? windSum=? windValue=1 oppValue=1

+SkOpSegment::markWinding id=11 (124.602829,9.37972069 124.142509,9.41722488 123.674164,9.45538425 123.289001,9.39620018) t=0.0625 [33] (124.516449,9.38673687) tEnd=0.625 newWindSum=1 newOppSum=1 oppSum=? windSum=? windValue=1 oppValue=1

+SkOpSegment::activeOp id=8 t=0.583904956 tEnd=0 op=sect miFrom=1 miTo=0 suFrom=1 suTo=0 result=1

+SkOpSegment::findNextOp simple

+SkOpSegment::markDone id=8 (8.17210007,104.212997 -5.82350016,369.813995) t=0 [15] (8.17210007,104.212997) tEnd=0.583904956 newWindSum=1 newOppSum=1 oppSum=1 windSum=1 windValue=1 oppValue=1

+bridgeOp current id=8 from=(-2.71619996e-07,259.298737) to=(8.17210007,104.212997)

+SkOpSegment::findNextOp simple

+SkOpSegment::markDone id=13 (118.119003,8.07219982 8.17210007,104.212997) t=0 [25] (118.119003,8.07219982) tEnd=1 newWindSum=1 newOppSum=1 oppSum=1 windSum=1 windValue=1 oppValue=1

+bridgeOp current id=13 from=(8.17210007,104.212997) to=(118.119003,8.07219982)

+path.moveTo(-2.71619996e-07,259.298737);

+path.lineTo(8.17210007,104.212997);

+SkOpSegment::findNextOp simple

+SkOpSegment::markDone id=4 (123.289001,9.39610004 118.119003,8.07219982) t=0 [7] (123.289001,9.39610004) tEnd=1 newWindSum=1 newOppSum=1 oppSum=1 windSum=1 windValue=1 oppValue=1

+bridgeOp current id=4 from=(118.119003,8.07219982) to=(123.289001,9.39610004)

+path.lineTo(118.119003,8.07219982);

+SkOpSegment::findNextOp simple

+SkOpSegment::markDone id=11 (124.602829,9.37972069 124.142509,9.41722488 123.674164,9.45538425 123.289001,9.39620018) t=0.625 [35] (123.752594,9.4268837) tEnd=1 newWindSum=1 newOppSum=1 oppSum=1 windSum=1 windValue=1 oppValue=1

+bridgeOp current id=11 from=(123.289001,9.39620018) to=(123.752594,9.4268837)

+path.lineTo(123.289001,9.39610004);

+path.cubicTo(123.433441,9.41839409, 123.589569,9.42689991, 123.752594,9.4268837);

+SkOpSegment::markWinding id=11 (124.602829,9.37972069 124.142509,9.41722488 123.674164,9.45538425 123.289001,9.39620018) t=0 [21] (124.602829,9.37972069) tEnd=0.0625 newWindSum=1 newOppSum=1 oppSum=? windSum=? windValue=1 oppValue=0

+SkOpSegment::markWinding id=10 (126,9.39620018 125.631981,9.29586983 125.12252,9.3373785 124.602829,9.37972069) t=0.9375 [31] (124.700119,9.37182617) tEnd=1 newWindSum=1 newOppSum=1 oppSum=? windSum=? windValue=1 oppValue=0

+SkOpSegment::markAngle last segment=10 span=31 windSum=1

+SkOpSegment::markWinding id=3 (124.603165,9.37966728 124.142731,9.41717815 123.674263,9.45534515 123.289001,9.39610004) t=0 [5] (124.603165,9.37966728) tEnd=0.0627286673 newWindSum=1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0

+SkOpSegment::markWinding id=2 (126,9.39610004 125.632057,9.29584408 125.122734,9.33733845 124.603165,9.37966728) t=0.937702598 [32] (124.700119,9.37180042) tEnd=1 newWindSum=1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0

+SkOpSegment::markAngle last segment=2 span=32 windSum=1

+SkOpSegment::findNextOp

+SkOpAngle::dumpOne [11/9] next=11/8 sect=17/17  s=0.0625 [33] e=0.625 [35] sgn=-1 windVal=1 windSum=1 oppVal=1 oppSum=1 operand

+SkOpAngle::dumpOne [11/8] next=3/20 sect=1/1  s=0.0625 [33] e=0 [21] sgn=1 windVal=1 windSum=1 oppVal=0 oppSum=1 operand

+SkOpAngle::dumpOne [3/20] next=11/9 sect=1/1  s=0.0627286673 [34] e=0 [5] sgn=1 windVal=1 windSum=1 oppVal=0 oppSum=0

+SkOpSegment::activeOp id=11 t=0.0625 tEnd=0 op=sect miFrom=1 miTo=1 suFrom=1 suTo=0 result=1

+SkOpSegment::findNextOp chase.append segment=10 span=31 windSum=1

+SkOpSegment::activeOp id=3 t=0.0627286673 tEnd=0 op=sect miFrom=1 miTo=0 suFrom=0 suTo=0 result=0

+SkOpSegment::markDone id=3 (124.603165,9.37966728 124.142731,9.41717815 123.674263,9.45534515 123.289001,9.39610004) t=0 [5] (124.603165,9.37966728) tEnd=0.0627286673 newWindSum=1 newOppSum=0 oppSum=0 windSum=1 windValue=1 oppValue=0

+SkOpSegment::markDone id=2 (126,9.39610004 125.632057,9.29584408 125.122734,9.33733845 124.603165,9.37966728) t=0.937702598 [32] (124.700119,9.37180042) tEnd=1 newWindSum=1 newOppSum=0 oppSum=0 windSum=1 windValue=1 oppValue=0

+SkOpSegment::findNextOp chase.append segment=2 span=32 windSum=1

+SkOpSegment::markDone id=11 (124.602829,9.37972069 124.142509,9.41722488 123.674164,9.45538425 123.289001,9.39620018) t=0.0625 [33] (124.516449,9.38673687) tEnd=0.625 newWindSum=1 newOppSum=1 oppSum=1 windSum=1 windValue=1 oppValue=1

+SkOpSegment::findNextOp from:[11] to:[11] start=-1132576784 end=-1353716568

+bridgeOp current id=11 from=(123.752594,9.4268837) to=(124.516449,9.38673687)

+path.cubicTo(123.997124,9.42685986, 124.257156,9.40766335, 124.516449,9.38673687);

+SkOpSegment::findNextOp simple

+SkOpSegment::markDone id=11 (124.602829,9.37972069 124.142509,9.41722488 123.674164,9.45538425 123.289001,9.39620018) t=0 [21] (124.602829,9.37972069) tEnd=0.0625 newWindSum=1 newOppSum=1 oppSum=1 windSum=1 windValue=1 oppValue=0

+bridgeOp current id=11 from=(124.516449,9.38673687) to=(124.602829,9.37972069)

+path.cubicTo(124.545258,9.38441181, 124.574059,9.38206482, 124.602829,9.37972069);

+SkOpSegment::markWinding id=2 (126,9.39610004 125.632057,9.29584408 125.122734,9.33733845 124.603165,9.37966728) t=0.307190555 [30] (125.624695,9.33975124) tEnd=0.937702598 newWindSum=1 newOppSum=-1 oppSum=? windSum=? windValue=1 oppValue=1

+SkOpSegment::markWinding id=2 (126,9.39610004 125.632057,9.29584408 125.122734,9.33733845 124.603165,9.37966728) t=0 [3] (126,9.39610004) tEnd=0.307190555 newWindSum=1 newOppSum=-1 oppSum=? windSum=? windValue=1 oppValue=1

+SkOpSegment::markWinding id=9 (-5.82350016,369.813995 126,9.39620018) t=0.0441765002 [28] (7.26031715e-07,353.891998) tEnd=1 newWindSum=-1 newOppSum=1 oppSum=? windSum=? windValue=1 oppValue=1

+SkOpSegment::markAngle last segment=9 span=28 windSum=-1

+SkOpSegment::findNextOp

+SkOpAngle::dumpOne [10/7] next=2/19 sect=17/17  s=0.9375 [31] e=1 [20] sgn=-1 windVal=1 windSum=1 oppVal=0 oppSum=1 operand

+SkOpAngle::dumpOne [2/19] next=2/18 sect=17/17  s=0.937702598 [32] e=1 [4] sgn=-1 windVal=1 windSum=1 oppVal=0 oppSum=0 done

+SkOpAngle::dumpOne [2/18] next=10/7 sect=1/1  s=0.937702598 [32] e=0.307190555 [30] sgn=1 windVal=1 windSum=1 oppVal=1 oppSum=-1

+SkOpSegment::activeOp id=2 t=0.937702598 tEnd=1 op=sect = result=1

+SkOpSegment::activeOp id=2 t=0.937702598 tEnd=0.307190555 op=sect miFrom=0 miTo=1 suFrom=1 suTo=0 result=0

+SkOpSegment::markDone id=2 (126,9.39610004 125.632057,9.29584408 125.122734,9.33733845 124.603165,9.37966728) t=0.307190555 [30] (125.624695,9.33975124) tEnd=0.937702598 newWindSum=1 newOppSum=-1 oppSum=-1 windSum=1 windValue=1 oppValue=1

+SkOpSegment::markDone id=2 (126,9.39610004 125.632057,9.29584408 125.122734,9.33733845 124.603165,9.37966728) t=0 [3] (126,9.39610004) tEnd=0.307190555 newWindSum=1 newOppSum=-1 oppSum=-1 windSum=1 windValue=1 oppValue=1

+SkOpSegment::markDone id=9 (-5.82350016,369.813995 126,9.39620018) t=0.0441765002 [28] (7.26031715e-07,353.891998) tEnd=1 newWindSum=-1 newOppSum=1 oppSum=1 windSum=-1 windValue=1 oppValue=1

+SkOpSegment::findNextOp chase.append segment=9 span=28 windSum=-1

+SkOpSegment::markDone id=10 (126,9.39620018 125.631981,9.29586983 125.12252,9.3373785 124.602829,9.37972069) t=0.9375 [31] (124.700119,9.37182617) tEnd=1 newWindSum=1 newOppSum=1 oppSum=1 windSum=1 windValue=1 oppValue=0

+SkOpSegment::findNextOp from:[10] to:[2] start=-1132576976 end=-1353719496

+bridgeOp current id=10 from=(124.602829,9.37972069) to=(124.700119,9.37182617)

+path.cubicTo(124.635307,9.37707424, 124.667747,9.37443161, 124.700119,9.37182617);

+SkOpSegment::markWinding id=7 (0,259.298737 0,353.891998) t=0 [13] (0,259.298737) tEnd=1 newWindSum=1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0

+SkOpSegment::markWinding id=9 (-5.82350016,369.813995 126,9.39620018) t=0 [17] (-5.82350016,369.813995) tEnd=0.0441765002 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0

+SkOpSegment::markWinding id=8 (8.17210007,104.212997 -5.82350016,369.813995) t=0.583904956 [27] (-2.71619996e-07,259.298737) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=? windSum=? windValue=1 oppValue=0

+SkOpSegment::debugShowActiveSpans id=8 (-2.71619996e-07,259.298737 -5.82350016,369.813995) t=0.583904956 tEnd=1 windSum=-1 oppSum=0 windValue=1 oppValue=0

+SkOpSegment::debugShowActiveSpans id=9 (-5.82350016,369.813995 7.26031715e-07,353.891998) t=0 tEnd=0.0441765002 windSum=-1 oppSum=0 windValue=1 oppValue=0

+SkOpSegment::debugShowActiveSpans id=7 (0,259.298737 0,353.891998) t=0 tEnd=1 windSum=1 oppSum=0 windValue=1 oppValue=0

+SkOpSegment::activeOp id=7 t=1 tEnd=0 op=sect miFrom=1 miTo=0 suFrom=0 suTo=0 result=0

+SkOpSegment::markDone id=7 (0,259.298737 0,353.891998) t=0 [13] (0,259.298737) tEnd=1 newWindSum=1 newOppSum=0 oppSum=0 windSum=1 windValue=1 oppValue=0

+bridgeOp chase.append id=7 windSum=1

+SkOpSegment::debugShowActiveSpans id=8 (-2.71619996e-07,259.298737 -5.82350016,369.813995) t=0.583904956 tEnd=1 windSum=-1 oppSum=0 windValue=1 oppValue=0

+SkOpSegment::debugShowActiveSpans id=9 (-5.82350016,369.813995 7.26031715e-07,353.891998) t=0 tEnd=0.0441765002 windSum=-1 oppSum=0 windValue=1 oppValue=0

+SkOpSegment::activeOp id=8 t=0.583904956 tEnd=1 op=sect miFrom=0 miTo=0 suFrom=1 suTo=0 result=0

+SkOpSegment::markDone id=8 (8.17210007,104.212997 -5.82350016,369.813995) t=0.583904956 [27] (-2.71619996e-07,259.298737) tEnd=1 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 oppValue=0

+SkOpSegment::markDone id=9 (-5.82350016,369.813995 126,9.39620018) t=0 [17] (-5.82350016,369.813995) tEnd=0.0441765002 newWindSum=-1 newOppSum=0 oppSum=0 windSum=-1 windValue=1 oppValue=0

+</div>

 

 

 

@@ -1029,7 +541,7 @@
 <script type="text/javascript">
 
     var testDivs = [
-        crbug_526025,
+        bug8380,
     ];
 
 var decimal_places = 3; // make this 3 to show more precision
diff --git a/tools/sk_app/CommandSet.cpp b/tools/sk_app/CommandSet.cpp
index 9684ef2..52d956c 100644
--- a/tools/sk_app/CommandSet.cpp
+++ b/tools/sk_app/CommandSet.cpp
@@ -8,6 +8,7 @@
 #include "CommandSet.h"
 
 #include "SkCanvas.h"
+#include "SkFont.h"
 #include "SkTSort.h"
 
 namespace sk_app {
@@ -101,17 +102,21 @@
     SkTQSort(fCommands.begin(), fCommands.end() - 1,
              kAlphabetical_HelpMode == fHelpMode ? compareCommandKey : compareCommandGroup);
 
+    SkFont font;
+    font.setSize(16);
+
+    SkFont groupFont;
+    groupFont.setSize(18);
+
     SkPaint bgPaint;
     bgPaint.setColor(0xC0000000);
     canvas->drawPaint(bgPaint);
 
     SkPaint paint;
-    paint.setTextSize(16);
     paint.setAntiAlias(true);
     paint.setColor(0xFFFFFFFF);
 
     SkPaint groupPaint;
-    groupPaint.setTextSize(18);
     groupPaint.setAntiAlias(true);
     groupPaint.setColor(0xFFFFFFFF);
 
@@ -122,14 +127,15 @@
     SkScalar keyWidth = 0;
     for (Command& cmd : fCommands) {
         keyWidth = SkMaxScalar(keyWidth,
-                               paint.measureText(cmd.fKeyName.c_str(), cmd.fKeyName.size()));
+                               font.measureText(cmd.fKeyName.c_str(), cmd.fKeyName.size(),
+                                                kUTF8_SkTextEncoding));
     }
-    keyWidth += paint.measureText(" ", 1);
+    keyWidth += font.measureText(" ", 1, kUTF8_SkTextEncoding);
 
     // If we're grouping by category, we'll be adding text height on every new group (including the
     // first), so no need to do that here. Otherwise, skip down so the first line is where we want.
     if (kGrouped_HelpMode != fHelpMode) {
-        y += paint.getTextSize();
+        y += font.getSize();
     }
 
     // Print everything:
@@ -137,16 +143,18 @@
     for (Command& cmd : fCommands) {
         if (kGrouped_HelpMode == fHelpMode && lastGroup != cmd.fGroup) {
             // Group change. Advance and print header:
-            y += paint.getTextSize();
-            canvas->drawString(cmd.fGroup, x, y, groupPaint);
-            y += groupPaint.getTextSize() + 2;
+            y += font.getSize();
+            canvas->drawSimpleText(cmd.fGroup.c_str(), cmd.fGroup.size(), kUTF8_SkTextEncoding,
+                                   x, y, groupFont, groupPaint);
+            y += groupFont.getSize() + 2;
             lastGroup = cmd.fGroup;
         }
 
-        canvas->drawString(cmd.fKeyName, x, y, paint);
+        canvas->drawSimpleText(cmd.fKeyName.c_str(), cmd.fKeyName.size(), kUTF8_SkTextEncoding,
+                               x, y, font, paint);
         SkString text = SkStringPrintf(": %s", cmd.fDescription.c_str());
-        canvas->drawString(text, x + keyWidth, y, paint);
-        y += paint.getTextSize() + 2;
+        canvas->drawString(text, x + keyWidth, y, font, paint);
+        y += font.getSize() + 2;
     }
 }
 
diff --git a/tools/sk_app/win/ANGLEWindowContext_win.cpp b/tools/sk_app/win/ANGLEWindowContext_win.cpp
index 123b2ae..eb68dff 100644
--- a/tools/sk_app/win/ANGLEWindowContext_win.cpp
+++ b/tools/sk_app/win/ANGLEWindowContext_win.cpp
@@ -6,14 +6,15 @@
  * found in the LICENSE file.
  */
 
+#define EGL_EGL_PROTOTYPES 1
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
 #include "../GLWindowContext.h"
 #include "WindowContextFactory_win.h"
 #include "gl/GrGLAssembleInterface.h"
 #include "gl/GrGLDefines.h"
 
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-
 using sk_app::GLWindowContext;
 using sk_app::DisplayParams;
 
diff --git a/tools/sk_app/win/GLWindowContext_win.cpp b/tools/sk_app/win/GLWindowContext_win.cpp
index 8e0eeba..2fcd065 100644
--- a/tools/sk_app/win/GLWindowContext_win.cpp
+++ b/tools/sk_app/win/GLWindowContext_win.cpp
@@ -17,6 +17,18 @@
 using sk_app::GLWindowContext;
 using sk_app::DisplayParams;
 
+#if defined(_M_ARM64)
+
+namespace sk_app {
+namespace window_context_factory {
+
+WindowContext* NewGLForWin(HWND, const DisplayParams&) { return nullptr; }
+
+}  // namespace window_context_factory
+}  // namespace sk_app
+
+#else
+
 namespace {
 
 class GLWindowContext_win : public GLWindowContext {
@@ -141,3 +153,5 @@
 
 }  // namespace window_context_factory
 }  // namespace sk_app
+
+#endif
diff --git a/tools/sk_app/win/Window_win.cpp b/tools/sk_app/win/Window_win.cpp
index 86c772b..9420428 100644
--- a/tools/sk_app/win/Window_win.cpp
+++ b/tools/sk_app/win/Window_win.cpp
@@ -74,11 +74,11 @@
         wcex.cbWndExtra = 0;
         wcex.hInstance = fHInstance;
         wcex.hIcon = LoadIcon(fHInstance, (LPCTSTR)IDI_WINLOGO);
-        wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);;
+        wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
         wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
         wcex.lpszMenuName = nullptr;
         wcex.lpszClassName = gSZWindowClass;
-        wcex.hIconSm = LoadIcon(fHInstance, (LPCTSTR)IDI_WINLOGO);;
+        wcex.hIconSm = LoadIcon(fHInstance, (LPCTSTR)IDI_WINLOGO);
 
         if (!RegisterClassEx(&wcex)) {
             return false;
diff --git a/tools/sk_tool_utils.cpp b/tools/sk_tool_utils.cpp
index 35c059f..a50dffc 100644
--- a/tools/sk_tool_utils.cpp
+++ b/tools/sk_tool_utils.cpp
@@ -10,6 +10,7 @@
 #include "SkCanvas.h"
 #include "SkColorData.h"
 #include "SkColorPriv.h"
+#include "SkFontPriv.h"
 #include "SkFloatingPoint.h"
 #include "SkImage.h"
 #include "SkMatrix.h"
@@ -119,13 +120,13 @@
     SkCanvas canvas(bitmap);
 
     SkPaint paint;
-    paint.setAntiAlias(true);
-    sk_tool_utils::set_portable_typeface(&paint);
     paint.setColor(c);
-    paint.setTextSize(SkIntToScalar(textSize));
+
+    SkFont font(sk_tool_utils::create_portable_typeface(), textSize);
 
     canvas.clear(0x00000000);
-    canvas.drawString(str, SkIntToScalar(x), SkIntToScalar(y), paint);
+    canvas.drawSimpleText(str, strlen(str), kUTF8_SkTextEncoding,
+                          SkIntToScalar(x), SkIntToScalar(y), font, paint);
 
     // Tag data as sRGB (without doing any color space conversion). Color-space aware configs
     // will process this correctly but legacy configs will render as if this returned N32.
@@ -136,29 +137,50 @@
 }
 
 void add_to_text_blob_w_len(SkTextBlobBuilder* builder, const char* text, size_t len,
-                            const SkPaint& paint, SkScalar x, SkScalar y) {
-    SkFont font = SkFont::LEGACY_ExtractFromPaint(paint);
-    SkTDArray<uint16_t> glyphs;
-
-    glyphs.append(font.countText(text, len, (SkTextEncoding)paint.getTextEncoding()));
-    font.textToGlyphs(text, len, (SkTextEncoding)paint.getTextEncoding(), glyphs.begin(), glyphs.count());
-
-    const SkTextBlobBuilder::RunBuffer& run = builder->allocRun(font, glyphs.count(), x, y,
-                                                                nullptr);
-    memcpy(run.glyphs, glyphs.begin(), glyphs.count() * sizeof(uint16_t));
+                            SkTextEncoding encoding, const SkFont& font, SkScalar x, SkScalar y) {
+    int count = font.countText(text, len, encoding);
+    auto run = builder->allocRun(font, count, x, y);
+    font.textToGlyphs(text, len, encoding, run.glyphs, count);
 }
 
-void add_to_text_blob(SkTextBlobBuilder* builder, const char* text,
-                      const SkPaint& origPaint, SkScalar x, SkScalar y) {
-    add_to_text_blob_w_len(builder, text, strlen(text), origPaint, x, y);
+void add_to_text_blob(SkTextBlobBuilder* builder, const char* text, const SkFont& font,
+                      SkScalar x, SkScalar y) {
+    add_to_text_blob_w_len(builder, text, strlen(text), kUTF8_SkTextEncoding, font, x, y);
+}
+
+void get_text_path(const SkFont& font, const void* text, size_t length, SkTextEncoding encoding,
+                   SkPath* dst, const SkPoint pos[]) {
+    SkAutoToGlyphs atg(font, text, length, encoding);
+    const int count = atg.count();
+    SkAutoTArray<SkPoint> computedPos;
+    if (pos == nullptr) {
+        computedPos.reset(count);
+        font.getPos(atg.glyphs(), count, &computedPos[0]);
+        pos = computedPos.get();
+    }
+
+    struct Rec {
+        SkPath* fDst;
+        const SkPoint* fPos;
+    } rec = { dst, pos };
+    font.getPaths(atg.glyphs(), atg.count(), [](const SkPath* src, const SkMatrix& mx, void* ctx) {
+        Rec* rec = (Rec*)ctx;
+        if (src) {
+            SkMatrix tmp(mx);
+            tmp.postTranslate(rec->fPos->fX, rec->fPos->fY);
+            rec->fDst->addPath(*src, tmp);
+        }
+        rec->fPos += 1;
+    }, &rec);
 }
 
 SkPath make_star(const SkRect& bounds, int numPts, int step) {
+    SkASSERT(numPts != step);
     SkPath path;
     path.setFillType(SkPath::kEvenOdd_FillType);
     path.moveTo(0,-1);
     for (int i = 1; i < numPts; ++i) {
-        int idx = i*step;
+        int idx = i*step % numPts;
         SkScalar theta = idx * 2*SK_ScalarPI/numPts + SK_ScalarPI/2;
         SkScalar x = SkScalarCos(theta);
         SkScalar y = -SkScalarSin(theta);
diff --git a/tools/sk_tool_utils.h b/tools/sk_tool_utils.h
index b3aae80..e3fbd1c 100644
--- a/tools/sk_tool_utils.h
+++ b/tools/sk_tool_utils.h
@@ -11,7 +11,9 @@
 #include "SkColor.h"
 #include "SkData.h"
 #include "SkEncodedImageFormat.h"
+#include "SkFont.h"
 #include "SkFontStyle.h"
+#include "SkFontTypes.h"
 #include "SkImageEncoder.h"
 #include "SkImageInfo.h"
 #include "SkRandom.h"
@@ -78,6 +80,9 @@
         return create_portable_typeface(nullptr, SkFontStyle());
     }
 
+    void get_text_path(const SkFont&, const void* text, size_t length, SkTextEncoding, SkPath*,
+                       const SkPoint* positions = nullptr);
+
     /**
      *  Call writePixels() by using the pixels from bitmap, but with an info that claims
      *  the pixels are colorType + alphaType
@@ -121,11 +126,11 @@
     sk_sp<SkSurface> makeSurface(SkCanvas*, const SkImageInfo&, const SkSurfaceProps* = nullptr);
 
     // A helper for inserting a drawtext call into a SkTextBlobBuilder
-    void add_to_text_blob_w_len(SkTextBlobBuilder* builder, const char* text, size_t len,
-                                const SkPaint& origPaint, SkScalar x, SkScalar y);
+    void add_to_text_blob_w_len(SkTextBlobBuilder*, const char* text, size_t len, SkTextEncoding,
+                                const SkFont&, SkScalar x, SkScalar y);
 
-    void add_to_text_blob(SkTextBlobBuilder* builder, const char* text,
-                          const SkPaint& origPaint, SkScalar x, SkScalar y);
+    void add_to_text_blob(SkTextBlobBuilder*, const char* text, const SkFont&,
+                          SkScalar x, SkScalar y);
 
     // Constructs a star by walking a 'numPts'-sided regular polygon with even/odd fill:
     //
diff --git a/tools/skqp/README.md b/tools/skqp/README.md
index 232920a..e652795 100644
--- a/tools/skqp/README.md
+++ b/tools/skqp/README.md
@@ -44,6 +44,7 @@
 
 3.  Build the APK:
 
+        tools/git-sync-deps
         tools/skqp/make_universal_apk
 
 4.  Build, install, and run.
@@ -65,7 +66,7 @@
 
         OUTPUT_LOCATION="/storage/emulated/0/Android/data/org.skia.skqp/files/output"
         adb pull $OUTPUT_LOCATION /tmp/
-        tools/skqp/sysopen.py /tmp/output/skqp_report/report.html
+        bin/sysopen /tmp/output/skqp_report/report.html
 
 Running a single test
 ---------------------
diff --git a/tools/skqp/README_GENERATING_MODELS.md b/tools/skqp/README_GENERATING_MODELS.md
index 337ff96..a4b5b0b 100644
--- a/tools/skqp/README_GENERATING_MODELS.md
+++ b/tools/skqp/README_GENERATING_MODELS.md
@@ -13,7 +13,7 @@
 
     Open the resulting URL in a browser and download the resulting `meta.json` file.
 
-        tools/skqp/sysopen.py $(tools/skqp/get_gold_export_url.py HEAD~10 HEAD)
+        bin/sysopen $(tools/skqp/get_gold_export_url.py HEAD~10 HEAD)
 
 2.  From a checkout of Skia's master branch, execute:
 
@@ -31,18 +31,13 @@
     `origin/skqp/dev` a parent of this commit (without merging it in), and
     push this new commit to `origin/skqp/dev`:
 
-        git merge -s ours origin/skqp/dev -m "Cut SkQP $(date +%Y-%m-%d)"
-        git add \
-          platform_tools/android/apps/skqp/src/main/assets/files.checksum \
-          platform_tools/android/apps/skqp/src/main/assets/skqp/rendertests.txt \
-          platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt
-        git commit --amend --reuse-message=HEAD
-        git push origin HEAD:refs/for/skqp/dev
+        tools/skqp/branch_skqp_dev.sh
 
     Review and submit the change:
 
-        NUM=$(experimental/tools/gerrit-change-id-to-number HEAD)
-        tools/skqp/sysopen.py https://review.skia.org/$NUM
+        git push origin HEAD:refs/for/skqp/dev
+        bin/sysopen https://review.skia.org/$(bin/gerrit-number HEAD)
+
 
 `tools/skqp/cut_release`
 ------------------------
diff --git a/tools/skqp/branch_skqp_dev.sh b/tools/skqp/branch_skqp_dev.sh
new file mode 100755
index 0000000..2662c1e
--- /dev/null
+++ b/tools/skqp/branch_skqp_dev.sh
@@ -0,0 +1,13 @@
+#! /bin/sh
+# Copyright 2018 Google LLC.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+set -xe
+set -- platform_tools/android/apps/skqp/src/main/assets/files.checksum       \
+       platform_tools/android/apps/skqp/src/main/assets/skqp/rendertests.txt \
+       platform_tools/android/apps/skqp/src/main/assets/skqp/unittests.txt
+for arg; do [ -f "$arg" ] || exit 1; done
+MSG="$(printf 'Cut SkQP %s\n\nNo-Try: true' "$(date +%Y-%m-%d)")"
+git merge -s ours origin/skqp/dev -m "$MSG"
+git add "$@"
+git commit --amend --reuse-message=HEAD
diff --git a/tools/skqp/docker_build_universal_apk.sh b/tools/skqp/docker_build_universal_apk.sh
new file mode 100755
index 0000000..7503268
--- /dev/null
+++ b/tools/skqp/docker_build_universal_apk.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+# Copyright 2018 Google LLC.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Notes:
+#
+#    You may need to run as root for docker permissions.
+#
+#    You *must* run `tools/git-sync-deps` first.
+
+OUT="$(mktemp -d "${TMPDIR:-/tmp}/skqp_apk.XXXXXXXXXX")"
+BUILD="$(mktemp -d "${TMPDIR:-/tmp}/skqp_apk_build.XXXXXXXXXX")"
+SKIA_ROOT="$(cd "$(dirname "$0")/../.."; pwd)"
+
+cd "${SKIA_ROOT}/infra/skqp/docker"
+
+docker build -t android-skqp ./android-skqp/
+
+docker run --rm -d --name android_em \
+        --env=DEVICE="Samsung Galaxy S6" \
+        --volume="$SKIA_ROOT":/SRC \
+        --volume="$OUT":/OUT \
+        --volume="$BUILD":/BUILD \
+        android-skqp
+
+docker exec \
+    --env=SKQP_OUTPUT_DIR=/OUT \
+    --env=SKQP_BUILD_DIR=/BUILD \
+    android_em /SRC/tools/skqp/make_universal_apk.py
+
+docker exec android_em find '/BUILD/.' '!' -name '.' -prune -exec rm -rf '{}' '+'
+
+if [ -f "$OUT"/skqp-universal-debug.apk ]; then
+    docker exec android_em find /OUT -type f -exec chmod 0666 '{}' '+'
+fi
+
+docker kill android_em
+
+rmdir "$BUILD"
+
+ls -l "$OUT"/*.apk 2> /dev/null
diff --git a/tools/skqp/generate_gn_args b/tools/skqp/generate_gn_args
index 1eb98fc..4dc1aa1 100755
--- a/tools/skqp/generate_gn_args
+++ b/tools/skqp/generate_gn_args
@@ -23,7 +23,7 @@
 skia_use_lua = false
 skia_use_piex = false
 skia_tools_require_resources = true
-extra_cflags = [ "-DSK_ENABLE_DUMP_GPU" ]
+extra_cflags = [ "-DSK_ENABLE_DUMP_GPU", "-DSK_BUILD_FOR_SKQP" ]
 '''
 
 def parse_args():
diff --git a/tools/skqp/make_universal_apk.py b/tools/skqp/make_universal_apk.py
index 1578ecb..d2a7a17 100755
--- a/tools/skqp/make_universal_apk.py
+++ b/tools/skqp/make_universal_apk.py
@@ -166,11 +166,11 @@
     for path in build_paths:
         remove(path)
 
-    if len(architectures) == 1:
-        arch = architectures[0]
-        copy = os.path.join(final_output_dir, "%s-%s-debug.apk" % (app, arch))
-        shutil.copyfile(out, copy)
-        sys.stdout.write(copy + '\n')
+    arches = '_'.join(sorted(architectures))
+    copy = os.path.join(final_output_dir, "%s-%s-debug.apk" % (app, arches))
+    shutil.copyfile(out, copy)
+    sys.stdout.write(copy + '\n')
+
     sys.stdout.write('* * * COMPLETE * * *\n\n')
 
 def main():
diff --git a/tools/skqp/test_apk.sh b/tools/skqp/test_apk.sh
index 1938f88..4a155d7 100755
--- a/tools/skqp/test_apk.sh
+++ b/tools/skqp/test_apk.sh
@@ -69,17 +69,10 @@
 
 REPORT="${TDIR}/${odir_basename}/report.html"
 
-open_file() {
-    case "$(uname)" in
-        Linux) [ "$DISPLAY" ] && xdg-open "$@" > /dev/null 2>&1 & ;;
-        Darwin) open "$@" & ;;
-    esac
-}
-
 if [ -f "$REPORT" ]; then
     grep 'f(.*;' "$REPORT"
     echo "$REPORT"
-    open_file "$REPORT"
+    "$(dirname "$0")"/../../bin/sysopen "$REPORT"
 else
     echo "$TDIR"
 fi
diff --git a/tools/using_skia_and_harfbuzz.cpp b/tools/using_skia_and_harfbuzz.cpp
index 2e3f715..6f378e2 100644
--- a/tools/using_skia_and_harfbuzz.cpp
+++ b/tools/using_skia_and_harfbuzz.cpp
@@ -139,11 +139,11 @@
     }
 
     void WriteLine(const SkShaper& shaper, const char *text, size_t textBytes) {
-        SkTextBlobBuilder textBlobBuilder;
+        SkTextBlobBuilderLineHandler textBlobBuilder;
         SkPoint endPoint = shaper.shape(&textBlobBuilder, font, text, textBytes, true,
                                         SkPoint{0, 0},
                                         config->page_width.value - 2*config->left_margin.value);
-        sk_sp<const SkTextBlob> blob = textBlobBuilder.make();
+        sk_sp<const SkTextBlob> blob = textBlobBuilder.makeBlob();
         // If we don't have a page, or if we're not at the start of the page and the blob won't fit
         if (!pageCanvas ||
               (current_y > config->line_spacing_ratio.value * config->font_size.value &&
diff --git a/tools/viewer/Viewer.cpp b/tools/viewer/Viewer.cpp
index 96491d7..7ec001b 100644
--- a/tools/viewer/Viewer.cpp
+++ b/tools/viewer/Viewer.cpp
@@ -185,7 +185,7 @@
     , fColorMode(ColorMode::kLegacy)
     , fColorSpacePrimaries(gSrgbPrimaries)
     // Our UI can only tweak gamma (currently), so start out gamma-only
-    , fColorSpaceTransferFn(g2Dot2_TransferFn)
+    , fColorSpaceTransferFn(SkNamedTransferFn::k2Dot2)
     , fZoomLevel(0.0f)
     , fRotation(0.0f)
     , fOffset{0.5f, 0.5f}
@@ -356,23 +356,23 @@
         fWindow->inval();
     });
     fCommands.addCommand('H', "Paint", "Hinting mode", [this]() {
-        if (!fPaintOverrides.fHinting) {
-            fPaintOverrides.fHinting = true;
-            fPaint.setHinting(kNo_SkFontHinting);
+        if (!fFontOverrides.fHinting) {
+            fFontOverrides.fHinting = true;
+            fFont.setHinting(kNo_SkFontHinting);
         } else {
-            switch ((SkFontHinting)fPaint.getHinting()) {
+            switch (fFont.getHinting()) {
                 case kNo_SkFontHinting:
-                    fPaint.setHinting(kSlight_SkFontHinting);
+                    fFont.setHinting(kSlight_SkFontHinting);
                     break;
                 case kSlight_SkFontHinting:
-                    fPaint.setHinting(kNormal_SkFontHinting);
+                    fFont.setHinting(kNormal_SkFontHinting);
                     break;
                 case kNormal_SkFontHinting:
-                    fPaint.setHinting(kFull_SkFontHinting);
+                    fFont.setHinting(kFull_SkFontHinting);
                     break;
                 case kFull_SkFontHinting:
-                    fPaint.setHinting(kNo_SkFontHinting);
-                    fPaintOverrides.fHinting = false;
+                    fFont.setHinting(kNo_SkFontHinting);
+                    fFontOverrides.fHinting = false;
                     break;
             }
         }
@@ -631,7 +631,7 @@
                 fSlides.push_back(
                     sk_make_sp<SlideDir>(SkStringPrintf("%s[%s]", info.fDirName, flag.c_str()),
                                          std::move(dirSlides)));
-                dirSlides.reset();
+                dirSlides.reset();  // NOLINT(bugprone-use-after-move)
             }
         }
     }
@@ -708,8 +708,8 @@
     paintFlag(SkPaint::kAutoHinting_Flag, &SkPaint::isAutohinted,
               "Force Autohint", "No Force Autohint");
 
-    if (fPaintOverrides.fHinting) {
-        switch ((SkFontHinting)fPaint.getHinting()) {
+    if (fFontOverrides.fHinting) {
+        switch (fFont.getHinting()) {
             case kNo_SkFontHinting:
                 paintTitle.append("No Hinting");
                 break;
@@ -748,7 +748,7 @@
         }
         title.appendf(" %s Gamma %f",
                       curPrimaries >= 0 ? gNamedPrimaries[curPrimaries].fName : "Custom",
-                      fColorSpaceTransferFn.fG);
+                      fColorSpaceTransferFn.g);
     }
 
     const DisplayParams& params = fWindow->getRequestedDisplayParams();
@@ -962,18 +962,17 @@
 
 class OveridePaintFilterCanvas : public SkPaintFilterCanvas {
 public:
-    OveridePaintFilterCanvas(SkCanvas* canvas, SkPaint* paint, Viewer::SkPaintFields* fields)
-        : SkPaintFilterCanvas(canvas), fPaint(paint), fPaintOverrides(fields)
+    OveridePaintFilterCanvas(SkCanvas* canvas, SkPaint* paint, Viewer::SkPaintFields* pfields,
+            SkFont* font, Viewer::SkFontFields* ffields)
+        : SkPaintFilterCanvas(canvas), fPaint(paint), fPaintOverrides(pfields), fFont(font), fFontOverrides(ffields)
     { }
     const SkTextBlob* filterTextBlob(const SkPaint& paint, const SkTextBlob* blob,
                                      sk_sp<SkTextBlob>* cache) {
         bool blobWillChange = false;
         for (SkTextBlobRunIterator it(blob); !it.done(); it.next()) {
-            SkPaint blobPaint = paint;
-            it.applyFontToPaint(&blobPaint);
-            SkTCopyOnFirstWrite<SkPaint> filteredPaint(blobPaint);
-            bool shouldDraw = this->onFilter(&filteredPaint, kTextBlob_Type);
-            if (blobPaint != *filteredPaint || !shouldDraw) {
+            SkTCopyOnFirstWrite<SkFont> filteredFont(it.font());
+            bool shouldDraw = this->filterFont(&filteredFont);
+            if (it.font() != *filteredFont || !shouldDraw) {
                 blobWillChange = true;
                 break;
             }
@@ -984,23 +983,23 @@
 
         SkTextBlobBuilder builder;
         for (SkTextBlobRunIterator it(blob); !it.done(); it.next()) {
-            SkPaint blobPaint = paint;
-            it.applyFontToPaint(&blobPaint);
-            SkTCopyOnFirstWrite<SkPaint> filteredPaint(blobPaint);
-            bool shouldDraw = this->onFilter(&filteredPaint, kTextBlob_Type);
+            SkTCopyOnFirstWrite<SkFont> filteredFont(it.font());
+            bool shouldDraw = this->filterFont(&filteredFont);
             if (!shouldDraw) {
                 continue;
             }
 
+            SkFont font = *filteredFont;
+
             const SkTextBlobBuilder::RunBuffer& runBuffer
                 = it.positioning() == SkTextBlobRunIterator::kDefault_Positioning
-                    ? SkTextBlobBuilderPriv::AllocRunText(&builder, *filteredPaint,
+                    ? SkTextBlobBuilderPriv::AllocRunText(&builder, font,
                         it.offset().x(),it.offset().y(), it.glyphCount(), it.textSize(), SkString())
                 : it.positioning() == SkTextBlobRunIterator::kHorizontal_Positioning
-                    ? SkTextBlobBuilderPriv::AllocRunTextPosH(&builder, *filteredPaint,
+                    ? SkTextBlobBuilderPriv::AllocRunTextPosH(&builder, font,
                         it.offset().y(), it.glyphCount(), it.textSize(), SkString())
                 : it.positioning() == SkTextBlobRunIterator::kFull_Positioning
-                    ? SkTextBlobBuilderPriv::AllocRunTextPos(&builder, *filteredPaint,
+                    ? SkTextBlobBuilderPriv::AllocRunTextPos(&builder, font,
                         it.glyphCount(), it.textSize(), SkString())
                 : (SkASSERT_RELEASE(false), SkTextBlobBuilder::RunBuffer());
             uint32_t glyphCount = it.glyphCount();
@@ -1032,46 +1031,54 @@
         this->SkPaintFilterCanvas::onDrawTextBlob(
             this->filterTextBlob(paint, blob, &cache), x, y, paint);
     }
+    bool filterFont(SkTCopyOnFirstWrite<SkFont>* font) const {
+        if (fFontOverrides->fTextSize) {
+            font->writable()->setSize(fFont->getSize());
+        }
+        if (fFontOverrides->fHinting) {
+            font->writable()->setHinting(fFont->getHinting());
+        }
+#if 0
+        if (fFontOverrides->fFlags & SkPaint::kAntiAlias_Flag) {
+            paint->writable()->setAntiAlias(fPaint->isAntiAlias());
+        }
+        if (fFontOverrides->fFlags & SkPaint::kFakeBoldText_Flag) {
+            paint->writable()->setFakeBoldText(fPaint->isFakeBoldText());
+        }
+        if (fFontOverrides->fFlags & SkPaint::kLinearText_Flag) {
+            paint->writable()->setLinearText(fPaint->isLinearText());
+        }
+        if (fFontOverrides->fFlags & SkPaint::kSubpixelText_Flag) {
+            paint->writable()->setSubpixelText(fPaint->isSubpixelText());
+        }
+        if (fFontOverrides->fFlags & SkPaint::kLCDRenderText_Flag) {
+            paint->writable()->setLCDRenderText(fPaint->isLCDRenderText());
+        }
+        if (fFontOverrides->fFlags & SkPaint::kEmbeddedBitmapText_Flag) {
+            paint->writable()->setEmbeddedBitmapText(fPaint->isEmbeddedBitmapText());
+        }
+        if (fFontOverrides->fFlags & SkPaint::kAutoHinting_Flag) {
+            paint->writable()->setAutohinted(fPaint->isAutohinted());
+        }
+#endif
+        return true;
+    }
     bool onFilter(SkTCopyOnFirstWrite<SkPaint>* paint, Type) const override {
         if (*paint == nullptr) {
             return true;
         }
-        if (fPaintOverrides->fTextSize) {
-            paint->writable()->setTextSize(fPaint->getTextSize());
-        }
-        if (fPaintOverrides->fHinting) {
-            paint->writable()->setHinting(fPaint->getHinting());
-        }
-
         if (fPaintOverrides->fFlags & SkPaint::kAntiAlias_Flag) {
             paint->writable()->setAntiAlias(fPaint->isAntiAlias());
         }
         if (fPaintOverrides->fFlags & SkPaint::kDither_Flag) {
             paint->writable()->setDither(fPaint->isDither());
         }
-        if (fPaintOverrides->fFlags & SkPaint::kFakeBoldText_Flag) {
-            paint->writable()->setFakeBoldText(fPaint->isFakeBoldText());
-        }
-        if (fPaintOverrides->fFlags & SkPaint::kLinearText_Flag) {
-            paint->writable()->setLinearText(fPaint->isLinearText());
-        }
-        if (fPaintOverrides->fFlags & SkPaint::kSubpixelText_Flag) {
-            paint->writable()->setSubpixelText(fPaint->isSubpixelText());
-        }
-        if (fPaintOverrides->fFlags & SkPaint::kLCDRenderText_Flag) {
-            paint->writable()->setLCDRenderText(fPaint->isLCDRenderText());
-        }
-        if (fPaintOverrides->fFlags & SkPaint::kEmbeddedBitmapText_Flag) {
-            paint->writable()->setEmbeddedBitmapText(fPaint->isEmbeddedBitmapText());
-        }
-        if (fPaintOverrides->fFlags & SkPaint::kAutoHinting_Flag) {
-            paint->writable()->setAutohinted(fPaint->isAutohinted());
-        }
-
         return true;
     }
     SkPaint* fPaint;
     Viewer::SkPaintFields* fPaintOverrides;
+    SkFont* fFont;
+    Viewer::SkFontFields* fFontOverrides;
 };
 
 void Viewer::drawSlide(SkCanvas* canvas) {
@@ -1084,7 +1091,7 @@
     // If we're in any of the color managed modes, construct the color space we're going to use
     sk_sp<SkColorSpace> colorSpace = nullptr;
     if (ColorMode::kLegacy != fColorMode) {
-        SkMatrix44 toXYZ;
+        skcms_Matrix3x3 toXYZ;
         SkAssertResult(fColorSpacePrimaries.toXYZD50(&toXYZ));
         colorSpace = SkColorSpace::MakeRGB(fColorSpaceTransferFn, toXYZ);
     }
@@ -1143,7 +1150,8 @@
                 tileCanvas->translate(-x, -y);
                 tileCanvas->clear(SK_ColorTRANSPARENT);
                 tileCanvas->concat(m);
-                OveridePaintFilterCanvas filterCanvas(tileCanvas, &fPaint, &fPaintOverrides);
+                OveridePaintFilterCanvas filterCanvas(tileCanvas, &fPaint, &fPaintOverrides,
+                                                      &fFont, &fFontOverrides);
                 fSlides[fCurrentSlide]->draw(&filterCanvas);
                 tileSurface->draw(slideCanvas, x, y, nullptr);
             }
@@ -1165,7 +1173,7 @@
         if (kPerspective_Real == fPerspectiveMode) {
             slideCanvas->clipRect(SkRect::MakeWH(fWindow->width(), fWindow->height()));
         }
-        OveridePaintFilterCanvas filterCanvas(slideCanvas, &fPaint, &fPaintOverrides);
+        OveridePaintFilterCanvas filterCanvas(slideCanvas, &fPaint, &fPaintOverrides, &fFont, &fFontOverrides);
         fSlides[fCurrentSlide]->draw(&filterCanvas);
     }
     fStatsLayer.endTiming(fPaintTimer);
@@ -1608,19 +1616,19 @@
 
             if (ImGui::CollapsingHeader("Paint")) {
                 int hintingIdx = 0;
-                if (fPaintOverrides.fHinting) {
-                    hintingIdx = static_cast<unsigned>(fPaint.getHinting()) + 1;
+                if (fFontOverrides.fHinting) {
+                    hintingIdx = static_cast<unsigned>(fFont.getHinting()) + 1;
                 }
                 if (ImGui::Combo("Hinting", &hintingIdx,
                                  "Default\0None\0Slight\0Normal\0Full\0\0"))
                 {
                     if (hintingIdx == 0) {
-                        fPaintOverrides.fHinting = false;
-                        fPaint.setHinting(kNo_SkFontHinting);
+                        fFontOverrides.fHinting = false;
+                        fFont.setHinting(kNo_SkFontHinting);
                     } else {
-                        fPaintOverrides.fHinting = true;
+                        fFontOverrides.fHinting = true;
                         SkFontHinting hinting = SkTo<SkFontHinting>(hintingIdx - 1);
-                        fPaint.setHinting(hinting);
+                        fFont.setHinting(hinting);
                     }
                     paramsChanged = true;
                 }
@@ -1696,7 +1704,7 @@
                           "Default\0No Dither\0Dither\0\0",
                           SkPaint::kDither_Flag,
                           &SkPaint::isDither, &SkPaint::setDither);
-
+#if 0
                 paintFlag("Fake Bold Glyphs",
                           "Default\0No Fake Bold\0Fake Bold\0\0",
                           SkPaint::kFakeBoldText_Flag,
@@ -1726,18 +1734,18 @@
                           "Default\0No Force Auto-Hinting\0Force Auto-Hinting\0\0",
                           SkPaint::kAutoHinting_Flag,
                           &SkPaint::isAutohinted, &SkPaint::setAutohinted);
-
-                ImGui::Checkbox("Override TextSize", &fPaintOverrides.fTextSize);
-                if (fPaintOverrides.fTextSize) {
-                    ImGui::DragFloat2("TextRange", fPaintOverrides.fTextSizeRange,
+#endif
+                ImGui::Checkbox("Override TextSize", &fFontOverrides.fTextSize);
+                if (fFontOverrides.fTextSize) {
+                    ImGui::DragFloat2("TextRange", fFontOverrides.fTextSizeRange,
                                       0.001f, -10.0f, 300.0f, "%.6f", 2.0f);
-                    float textSize = fPaint.getTextSize();
+                    float textSize = fFont.getSize();
                     if (ImGui::DragFloat("TextSize", &textSize, 0.001f,
-                                         fPaintOverrides.fTextSizeRange[0],
-                                         fPaintOverrides.fTextSizeRange[1],
+                                         fFontOverrides.fTextSizeRange[0],
+                                         fFontOverrides.fTextSizeRange[1],
                                          "%.6f", 2.0f))
                     {
-                        fPaint.setTextSize(textSize);
+                        fFont.setSize(textSize);
                         this->preTouchMatrixChanged();
                         paramsChanged = true;
                     }
@@ -1827,7 +1835,7 @@
                 }
 
                 // Let user adjust the gamma
-                ImGui::SliderFloat("Gamma", &fColorSpaceTransferFn.fG, 0.5f, 3.5f);
+                ImGui::SliderFloat("Gamma", &fColorSpaceTransferFn.g, 0.5f, 3.5f);
 
                 if (ImGui::Combo("Primaries", &primariesIdx,
                                  "sRGB\0AdobeRGB\0P3\0Rec. 2020\0Custom\0\0")) {
diff --git a/tools/viewer/Viewer.h b/tools/viewer/Viewer.h
index eace2e9..2cff486 100644
--- a/tools/viewer/Viewer.h
+++ b/tools/viewer/Viewer.h
@@ -38,8 +38,15 @@
     bool onKey(sk_app::Window::Key key, sk_app::Window::InputState state, uint32_t modifiers) override;
     bool onChar(SkUnichar c, uint32_t modifiers) override;
 
-    struct SkPaintFields {
+    struct SkFontFields {
         bool fTypeface = false;
+        bool fTextSize = false;
+        SkScalar fTextSizeRange[2] = { 0, 20 };
+        bool fTextScaleX = false;
+        bool fTextSkewX = false;
+        bool fHinting = false;
+    };
+    struct SkPaintFields {
         bool fPathEffect = false;
         bool fShader = false;
         bool fMaskFilter = false;
@@ -47,10 +54,6 @@
         bool fDrawLooper = false;
         bool fImageFilter = false;
 
-        bool fTextSize = false;
-        SkScalar fTextSizeRange[2] = { 0, 20 };
-        bool fTextScaleX = false;
-        bool fTextSkewX = false;
         bool fColor = false;
         bool fWidth = false;
         bool fMiterLimit = false;
@@ -70,12 +73,9 @@
         const bool fOriginalSkUseDeltaAA = gSkUseDeltaAA;
         const bool fOriginalSkForceDeltaAA = gSkForceDeltaAA;
 
-        bool fTextAlign = false;
         bool fCapType = false;
         bool fJoinType = false;
         bool fStyle = false;
-        bool fTextEncoding = false;
-        bool fHinting = false;
         bool fFilterQuality = false;
     };
 private:
@@ -138,7 +138,7 @@
     // Color properties for slide rendering
     ColorMode              fColorMode;
     SkColorSpacePrimaries  fColorSpacePrimaries;
-    SkColorSpaceTransferFn fColorSpaceTransferFn;
+    skcms_TransferFunction fColorSpaceTransferFn;
 
     // transform data
     SkScalar               fZoomLevel;
@@ -175,6 +175,8 @@
 
     SkPaint fPaint;
     SkPaintFields fPaintOverrides;
+    SkFont fFont;
+    SkFontFields fFontOverrides;
     bool fPixelGeometryOverrides = false;
 };